Fix dashboard (#12)

* TS replaced with js

* Livereload works for ELM part

* Fix layout issue with empty spaces

* Using pure css spinners

* Fixed services not being displayed in module page
This commit is contained in:
Pavel 2021-06-09 22:18:03 +03:00 committed by GitHub
parent ddc7cd7a57
commit d7a59b2ff0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2872 additions and 5993 deletions

View File

@ -1,5 +1,6 @@
*
**/*
!bundle/
!dist/
!Caddyfile
!nginx.conf

View File

@ -16,14 +16,40 @@ module.exports = {
'plugin:prettier/recommended',
],
plugins: ['@typescript-eslint', 'prettier'],
rules: {},
rules: {
'func-names': ['error', 'as-needed'],
'prefer-destructuring': 'off',
'object-shorthand': ['error', 'consistent-as-needed'],
'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
'import/prefer-default-export': 'off',
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
mjs: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'off',
// should be overriden for current project only
'no-param-reassign': ['error', { props: false }],
'no-console': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
settings: {
// 'import/resolver': {
// browser: {
// extensions: ['.js', '.js', '.ts', '.ts', '.css', '.elm'],
// paths: ['src'],
// },
// },
'import/extensions': ['.js', '.ts'],
'import/extensions': ['.js', '.ts', '.jsx', '.tsx'],
'import/resolver': {
typescript: {},
node: {
paths: ['src'],
},
},
},
};

View File

@ -28,7 +28,7 @@ jobs:
node-version: 14
- run: npm install
- run: npm run pack
- run: npm run build
env:
CI: true

View File

@ -1,6 +1,6 @@
FROM caddy
WORKDIR /
COPY ./bundle /bundle
COPY ./dist /bundle
COPY Caddyfile /Caddyfile
#RUN printf '\n\

View File

@ -1,15 +1,12 @@
{
"type": "application",
"source-directories": [
"src"
],
"source-directories": ["src"],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"Chadtech/unique-list": "2.1.4",
"avh4/elm-color": "1.0.0",
"ccapndave/elm-flat-map": "1.2.0",
"damienklinnert/elm-spinner": "3.0.2",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -16,7 +16,6 @@
<title>Fluence Network Dashboard</title>
</head>
<body>
<script src="/bundle.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-6ZTQKE1D4L"></script>
<script>

View File

@ -1,4 +0,0 @@
FROM nginx
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY ./bundle /usr/share/nginx/html

View File

@ -1,9 +0,0 @@
server {
listen 80;
server_name frontend;
location / {
# This would be the directory where your React app's static files are stored at
root /usr/share/nginx/html;
try_files $uri /index.html;
}
}

8074
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,12 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "npx webpack && docker build -f nginx.Dockerfile -t dashboard . && docker run --rm --name dashboard -p443:443 -p80:80 dashboard",
"serve": "webpack serve",
"pack": "webpack --mode production",
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write"
"test": "elm-test",
"start": "npm run dev",
"dev": "webpack-dev-server --hot --colors --port 3000",
"build": "webpack",
"prod": "webpack -p",
"analyse": "elm-analyse -s -p 3001 -o"
},
"repository": {
"type": "git",
@ -20,42 +22,41 @@
},
"homepage": "https://github.com/fluencelabs/fluence-admin#readme",
"dependencies": {
"@fluencelabs/fluence": "0.9.42",
"@fluencelabs/fluence-network-environment": "1.0.8",
"@fluencelabs/fluence": "0.9.47",
"@fluencelabs/fluence-network-environment": "1.0.9",
"css-spinners": "^1.0.1",
"tachyons": "^4.12.0",
"yup": "^0.32.9"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@types/yup": "^0.29.11",
"@babel/core": "7.12.9",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"clean-webpack-plugin": "3.0.0",
"copy-webpack-plugin": "6.3.2",
"create-elm-app": "5.18.0",
"css-loader": "5.0.1",
"elm-hot-webpack-loader": "1.1.7",
"elm-webpack-loader": "7.0.1",
"eslint": "^7.14.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1",
"html-webpack-plugin": "4.5.0",
"http-server": "0.12.3",
"prettier": "2.2.1",
"replace-in-file": "6.1.0",
"source-map-loader": "1.1.2",
"style-loader": "2.0.0",
"ts-loader": "8.0.11",
"typescript": "4.1.2",
"webpack": "5.7.0",
"webpack-cli": "4.2.0",
"webpack-nano": "^1.1.0",
"webpack-plugin-serve": "^1.2.0",
"webpack-serve": "3.2.0"
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"closure-webpack-plugin": "^2.3.0",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^4.3.0",
"elm": "^0.19.1-3",
"elm-analyse": "^0.16.5",
"elm-format": "^0.8.4",
"elm-hot-webpack-loader": "^1.1.7",
"elm-test": "^0.19.1-revision4",
"elm-webpack-loader": "^6.0.1",
"file-loader": "^6.1.0",
"google-closure-compiler": "^20200920.0.0",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.11.2",
"node-sass": "^4.14.1",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"resolve-url-loader": "^3.1.1",
"source-map-loader": "^1.0.0",
"sass-loader": "^10.0.2",
"style-loader": "^1.2.1",
"url-loader": "^4.1.0",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.1.4"
}
}

View File

@ -3,7 +3,7 @@ module BlueprintPage.View exposing (..)
import BlueprintPage.Model exposing (BlueprintViewInfo)
import Blueprints.Model exposing (Blueprint)
import Dict exposing (Dict)
import Html exposing (Html, a, article, div, img, span, text)
import Html exposing (Html, a, article, div, img, span, strong, text)
import Html.Attributes exposing (attribute)
import Html.Events exposing (onClick)
import Info exposing (getBlueprintDescription)
@ -81,6 +81,19 @@ blueprintToInfo model id =
Nothing
-- TODO:: do this for all possible places which could be empty
textOrBsp : String -> String
textOrBsp text =
if text == "" then
String.fromChar (Char.fromCode 0xA0)
else
text
viewInfo : BlueprintViewInfo -> Html Msg
viewInfo blueprintInfo =
let
@ -88,14 +101,19 @@ viewInfo blueprintInfo =
\id -> blueprintInfo.openedModule |> Maybe.map (\om -> om == id) |> Maybe.withDefault False
in
article [ classes "cf" ]
[ div [ classes "fl w-100 w-20-ns gray-font mv3" ] [ text "AUTHOR" ]
, div [ classes "fl w-100 w-80-ns mv3 lucida" ]
[ span [ classes "fl w-100 black b" ] [ text blueprintInfo.author ] ]
, div [ classes "fl w-100 w-20-ns gray-font mv3" ] [ text "DESCRIPTION" ]
, div [ classes "fl w-100 w-80-ns mv3" ] [ span [ classes "fl w-100 black lucida pv1" ] [ text blueprintInfo.description ] ]
, div [ classes "fl w-100 w-20-ns gray-font mv3" ] [ text "MODULES" ]
, div [ classes "fl w-100 w-80-ns mv3" ]
[ text (String.join ", " (blueprintInfo.modules |> List.map (\m -> m.name))) ]
[ div [ classes "fl w-20-ns gray-font mv3" ] [ text "AUTHOR" ]
, div [ classes "fl w-80-ns mv3 lucida" ]
[ span [ classes "fl black b" ] [ text (textOrBsp blueprintInfo.author) ] ]
, div [ classes "fl w-20-ns gray-font mv3" ] [ text "DESCRIPTION" ]
, div [ classes "fl w-80-ns mv3 cf" ]
[ span [ classes "fl black lucida pv1" ] [ text (textOrBsp blueprintInfo.description) ] ]
, div [ classes "fl w-20-ns gray-font mv3" ] [ text "MODULES" ]
, div [ classes "fl w-80-ns mv3" ]
[ text
(textOrBsp
(String.join ", " (blueprintInfo.modules |> List.map (\m -> m.name)))
)
]
--(blueprintInfo.modules
-- |> List.map (\m -> viewToggledInterface (checkToggle m.name) m.name)

View File

@ -23,7 +23,6 @@ import Dict
import Model exposing (Model)
import Msg exposing (Msg(..))
import Route
import Spinner
import Subscriptions exposing (subscriptions)
import Update exposing (update)
import Url
@ -60,7 +59,6 @@ init flags url key =
, toggledInterface = Nothing
, knownPeers = flags.knownPeers
, isInitialized = False
, spinner = Spinner.init
}
in
( emptyModel, Route.routeCommand emptyModel r )

1
src/Main.elm.d.ts vendored
View File

@ -1 +0,0 @@
export const Elm: any

View File

@ -22,7 +22,6 @@ import Dict exposing (Dict)
import Modules.Model exposing (Module)
import Nodes.Model exposing (Identify, emptyIdentify)
import Service.Model exposing (Service)
import Spinner
import Url
@ -59,5 +58,4 @@ type alias Model =
, toggledInterface : Maybe String
, knownPeers : List String
, isInitialized : Bool
, spinner : Spinner.Model
}

View File

@ -1,5 +1,6 @@
module ModulePage.View exposing (..)
import Debug exposing (toString)
import Dict exposing (Dict)
import Html exposing (Html, a, article, div, span, text)
import Html.Attributes exposing (attribute, property)
@ -12,6 +13,7 @@ import ModulePage.Model exposing (ModuleViewInfo)
import Modules.Model exposing (Module)
import Palette exposing (classes, redFont)
import SpinnerView exposing (spinner)
import Utils.Utils exposing (hashValueFromString)
view : Model -> String -> Html msg
@ -23,8 +25,11 @@ view model id =
case moduleInfo of
Just mi ->
let
hash =
mi.moduleInfo.hash
check =
Maybe.map (\bp -> bp.dependencies |> List.member id)
Maybe.map (\bp -> bp.dependencies |> List.map hashValueFromString |> List.member hash)
filter =
\s -> model.blueprints |> Dict.get s.blueprint_id |> check |> Maybe.withDefault False

View File

@ -76,9 +76,7 @@ filterByModuleName bps moduleName =
names =
\bp ->
bp.dependencies
|> List.map (\d -> String.split ":" d)
|> List.map (\p -> Maybe.withDefault [] (List.tail p))
|> List.map (\p -> Maybe.withDefault "" (List.head p))
|> List.map Utils.Utils.hashValueFromString
check =
Maybe.map (\bp -> names bp |> List.member moduleName)
@ -95,9 +93,7 @@ filterByModuleHash bps moduleHash =
hashes =
\bp ->
bp.dependencies
|> List.map (\d -> String.split ":" d)
|> List.map (\p -> Maybe.withDefault [] (List.tail p))
|> List.map (\p -> Maybe.withDefault "" (List.head p))
|> List.map Utils.Utils.hashValueFromString
check =
Maybe.map (\bp -> hashes bp |> List.member moduleHash)

View File

@ -2,7 +2,6 @@ module Msg exposing (..)
import Browser exposing (UrlRequest)
import Port
import Spinner
import Url
@ -13,5 +12,4 @@ type Msg
| AquamarineEvent Port.ReceiveEvent
| RelayChanged String
| ToggleInterface String
| SpinnerMsg Spinner.Msg
| Reload

View File

@ -1,33 +1,13 @@
module SpinnerView exposing (..)
import Color
import Html exposing (Html)
import Html.Attributes exposing (height, width)
import Model exposing (Model)
import Palette exposing (classes)
import Spinner
spinner : Model -> List (Html msg)
spinner model =
[ Html.div [ classes "p3 relative" ]
[ Spinner.view
{ lines = 11
, length = 20
, width = 9
, radius = 21
, scale = 0.5
, corners = 1
, opacity = 0.25
, rotate = 0
, direction = Spinner.Clockwise
, speed = 1
, trail = 60
, translateX = 50
, translateY = 50
, shadow = True
, hwaccel = False
, color = always <| Color.rgba 255 255 255 1
}
model.spinner
]
[ Html.div [ classes "spin" ] [] ]
]

View File

@ -19,12 +19,10 @@ limitations under the License.
import Model exposing (Model)
import Msg exposing (Msg(..))
import Port exposing (eventReceiver)
import Spinner
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ eventReceiver AquamarineEvent
, Sub.map SpinnerMsg Spinner.subscription
]

View File

@ -29,7 +29,6 @@ import Nodes.Model exposing (Identify)
import Port exposing (sendAir)
import Route exposing (getAllCmd)
import Service.Model exposing (Service)
import Spinner
import Url
@ -108,15 +107,6 @@ update msg model =
Reload ->
( model, sendAir (GetAll.air model.peerId model.relayId model.knownPeers) )
SpinnerMsg spinnerMsg ->
let
spinnerModel =
Spinner.update spinnerMsg model.spinner
in
( { model | spinner = spinnerModel }
, Cmd.none
)
updateModel : Model -> String -> Identify -> List Service -> List Module -> List Blueprint -> Model
updateModel model peer identify services modules blueprints =

View File

@ -3,6 +3,11 @@ module Utils.Utils exposing (..)
import Html exposing (Html)
hashValueFromString : String -> String
hashValueFromString x =
x |> String.split ":" |> List.tail |> Maybe.andThen List.head |> Maybe.withDefault ""
instancesText : Int -> Html msg
instancesText num =
let

View File

@ -15,21 +15,29 @@
*/
import 'tachyons/css/tachyons.min.css';
import 'css-spinners/dist/all.min.css';
import './main.css';
// eslint-disable-next-line import/no-extraneous-dependencies
import log from 'loglevel';
import { Node, dev, testNet } from '@fluencelabs/fluence-network-environment';
import { createClient, generatePeerId, Particle, sendParticle, subscribeToEvent } from '@fluencelabs/fluence';
import { dev, testNet } from '@fluencelabs/fluence-network-environment';
import {
createClient,
generatePeerId,
Particle,
sendParticle,
subscribeToEvent,
setLogLevel,
} from '@fluencelabs/fluence';
import { Elm } from './Main.elm';
import * as serviceWorker from './serviceWorker';
import {EventType, eventType} from "./types";
import { eventType } from './types';
const relayIdx = 3;
const relays: Node[] = testNet;
// const relays: Node[] = dev;
const relays = testNet;
// const relays = dev;
function genFlags(peerId: string): any {
function genFlags(peerId) {
return {
peerId,
relayId: relays[relayIdx].peerId,
@ -38,15 +46,7 @@ function genFlags(peerId: string): any {
}
/* eslint-disable */
function event(
name: string,
peer: string,
peers?: string[],
identify?: any,
services?: any[],
modules?: any[],
blueprints?: any[],
) {
function event(name, peer, peers, identify, services, modules, blueprints) {
if (!peers) {
peers = null;
}
@ -68,7 +68,8 @@ function event(
/* eslint-enable */
(async () => {
log.setLevel('silent');
setLogLevel('SILENT');
const pid = await generatePeerId();
const flags = genFlags(pid.toB58String());
console.log(`connect with client: ${pid.toB58String()}`);
@ -78,7 +79,7 @@ function event(
const app = Elm.Main.init({
node: document.getElementById('root'),
flags,
flags: flags,
});
subscribeToEvent(client, 'event', 'peers_discovered', (args, _tetraplets) => {
@ -91,29 +92,38 @@ function event(
subscribeToEvent(client, 'event', 'all_info', (args, _tetraplets) => {
try {
let peerId = args[0];
let identify = args[1];
let services = args[2];
let blueprints = args[3];
let modules = args[4];
let eventRaw = {
peerId: peerId,
identify: identify,
services: services,
blueprints: blueprints,
modules: modules,
}
const peerId = args[0];
const identify = args[1];
const services = args[2];
const blueprints = args[3];
const modules = args[4];
const eventRaw = {
peerId,
identify,
services,
blueprints,
modules,
};
const inputEventRaw: any = eventType.cast(eventRaw);
const inputEvent = inputEventRaw as EventType;
const inputEvent = eventType.cast(eventRaw);
app.ports.eventReceiver.send(event('all_info', inputEvent.peerId, undefined, inputEvent.identify, inputEvent.services, inputEvent.modules, inputEvent.blueprints));
app.ports.eventReceiver.send(
event(
'all_info',
inputEvent.peerId,
undefined,
inputEvent.identify,
inputEvent.services,
inputEvent.modules,
inputEvent.blueprints,
),
);
} catch (err) {
log.error('Elm eventreceiver failed: ', err);
}
});
app.ports.sendParticle.subscribe(async (part: { script: string; data: any }) => {
app.ports.sendParticle.subscribe(async (part) => {
const particle = new Particle(part.script, part.data, 45000);
await sendParticle(client, particle);
});
@ -123,16 +133,3 @@ function event(
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
function setLogLevel(level: any) {
log.setLevel(level);
}
declare global {
interface Window {
test: any;
setLogLevel: any;
}
}
window.setLogLevel = setLogLevel;

46
src/types.js Normal file
View File

@ -0,0 +1,46 @@
import { object, number, string, array, boolean } from 'yup';
export const service = object({
id: string().required(),
owner_id: string().required(),
blueprint_id: string().required(),
});
export const blueprint = object({
dependencies: array(string()).required(),
id: string().required(),
name: string().required(),
facade: string().nullable().notRequired(),
});
export const identify = object({
external_addresses: array(string()).required(),
});
export const wasi = object({
envs: object().notRequired().nullable(),
mapped_dirs: object().notRequired().nullable(),
preopened_files: array(string()).notRequired().nullable(),
});
export const config = object({
logger_enabled: boolean().notRequired().nullable(),
logging_mask: object().notRequired().nullable(),
mem_pages_count: number().notRequired().nullable(),
mounted_binaries: object().notRequired().nullable(),
wasi: wasi.notRequired().nullable(),
});
export const module = object({
config: config.required(),
hash: string().required(),
name: string().required(),
});
export const eventType = object({
peerId: string().required(),
identify: identify.required(),
services: array(service).required(),
blueprints: array(blueprint).required(),
modules: array(module).required(),
});

View File

@ -1,91 +0,0 @@
import { object, number, string, array, SchemaOf, boolean } from "yup";
export const service: SchemaOf<Service> = object({
id: string().required(),
owner_id: string().required(),
blueprint_id: string().required(),
});
export const blueprint: SchemaOf<Blueprint> = object({
dependencies: array(string()).required(),
id: string().required(),
name: string().required(),
facade: string().nullable().notRequired(),
});
export const identify: SchemaOf<Identify> = object({
external_addresses: array(string()).required(),
});
export const wasi: SchemaOf<Wasi> = object({
envs: object().notRequired().nullable(),
mapped_dirs: object().notRequired().nullable(),
preopened_files: array(string()).notRequired().nullable(),
})
export const config: SchemaOf<Config> = object({
logger_enabled: boolean().notRequired().nullable(),
logging_mask: object().notRequired().nullable(),
mem_pages_count: number().notRequired().nullable(),
mounted_binaries: object().notRequired().nullable(),
wasi: wasi.notRequired().nullable(),
})
export const module: SchemaOf<Module> = object({
config: config.required(),
hash: string().required(),
name: string().required(),
})
export const eventType: SchemaOf<EventType> = object({
peerId: string().required(),
identify: identify.required(),
services: array(service).required(),
blueprints: array(blueprint).required(),
modules: array(module).required(),
})
export interface Service {
blueprint_id: string;
id: string;
owner_id: string;
}
export interface Blueprint {
dependencies: string[];
facade?: any;
id: string;
name: string;
}
export interface Wasi {
envs?: any;
mapped_dirs?: any;
preopened_files: any[];
}
export interface Config {
logger_enabled?: boolean;
logging_mask?: any;
mem_pages_count?: number;
mounted_binaries?: any;
wasi?: Wasi;
}
export interface Module {
config: Config;
hash: string;
name: string;
}
export interface Identify {
external_addresses: string[]
}
export interface EventType {
peerId: string,
identify: Identify,
services: Service[],
blueprints: Blueprint[],
modules: Module[],
}

View File

@ -1,28 +0,0 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"inlineSources": true,
"noImplicitAny": true,
"strictFunctionTypes": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"pretty": true,
"target": "es2018",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"strict": true,
"strictNullChecks": false,
"esModuleInterop": true,
"declarationMap": true,
"baseUrl": ".",
"allowJs": true
},
"exclude": [
"node_modules",
"dist",
"bundle"
],
"include": ["src/**/*"]
}

View File

@ -1,82 +1,203 @@
const path = require('path');
const { merge } = require('webpack-merge');
const ClosurePlugin = require('closure-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const { WebpackPluginServe: Serve } = require('webpack-plugin-serve');
const TerserPlugin = require('terser-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = (env, argv) => {
const isDebug = argv.mode === 'development';
const isProduction = argv.mode === 'production';
// Production CSS assets - separate, minimised file
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
return {
entry: {
app: ['./src/index.ts'],
var MODE = process.env.npm_lifecycle_event === 'prod' ? 'production' : 'development';
var withDebug = !process.env['npm_config_nodebug'] && MODE === 'development';
// this may help for Yarn users
// var withDebug = !npmParams.includes("--nodebug");
console.log('\x1b[36m%s\x1b[0m', `** elm-webpack-starter: mode "${MODE}", withDebug: ${withDebug}\n`);
var common = {
mode: MODE,
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
// FIXME webpack -p automatically adds hash when building for production
filename: MODE === 'production' ? '[name]-[hash].js' : 'index.js',
},
plugins: [
new HTMLWebpackPlugin({
// Use this template to get basic responsive meta tags
template: 'index.html',
// inject details of output file at end of body
inject: 'body',
}),
],
resolve: {
extensions: ['.js', '.ts', '.elm'],
modules: [path.join(__dirname, 'src'), 'node_modules'],
extensions: ['.js', '.elm', '.scss', '.png'],
},
devServer: {
contentBase: './bundle',
hot: false,
inline: false,
},
devtool: 'eval-source-map',
module: {
rules: [
{
test: /\.html$/,
use: [{ loader: 'file-loader?name=[name].[ext]' }],
test: /\.js$/,
enforce: 'pre',
use: ['source-map-loader'],
},
{
test: [/\.elm$/],
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.scss$/,
exclude: [/elm-stuff/],
// see https://github.com/webpack-contrib/css-loader#url
use: ['style-loader', 'css-loader?url=false', 'sass-loader'],
},
{
test: /\.css$/,
exclude: [/elm-stuff/],
use: ['style-loader', 'css-loader?url=false'],
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
exclude: [/elm-stuff/, /node_modules/],
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/font-woff',
},
},
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
exclude: [/elm-stuff/, /node_modules/],
loader: 'file-loader',
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
exclude: [/elm-stuff/, /node_modules/],
loader: 'file-loader',
},
],
},
};
if (MODE === 'development') {
module.exports = merge(common, {
optimization: {
// Prevents compilation errors causing the hot loader to lose state
noEmitOnErrors: true,
},
module: {
rules: [
{
test: /\.elm$/,
exclude: [/elm-stuff/, /node_modules/],
use: [
{ loader: 'elm-hot-webpack-loader' },
{
loader: 'elm-webpack-loader',
options: {
debug: isDebug,
optimize: isProduction,
forceWatch: false,
// add Elm's debug overlay to output
debug: withDebug,
//
forceWatch: true,
},
},
],
},
{ test: /\.ts$/, loader: 'ts-loader' },
{
test: /\.(png)$/,
loader: 'file-loader',
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
mode: 'development',
watch: isDebug,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'bundle'),
publicPath: '/',
devServer: {
inline: true,
stats: 'errors-only',
contentBase: path.join(__dirname, 'src/assets'),
historyApiFallback: true,
// feel free to delete this section if you don't need anything like this
before(app) {
// on port 3000
app.get('/test', function (req, res) {
res.json({ result: 'OK' });
});
},
optimization: {
minimize: isProduction,
minimizer: [new TerserPlugin()],
},
plugins: [
new CopyWebpackPlugin({
patterns: [{ from: './*.html' }, { from: './images/*.svg' }],
}),
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
}),
new Serve({
historyFallback: true,
port: 55553,
host: 'localhost',
patterns: [{ from: './images/*.*' }],
}),
],
};
};
});
}
if (MODE === 'production') {
module.exports = merge(common, {
optimization: {
minimizer: [
new ClosurePlugin(
{ mode: 'STANDARD' },
{
// compiler flags here
//
// for debugging help, try these:
//
// formatting: 'PRETTY_PRINT',
// debug: true
// renaming: false
},
),
new OptimizeCSSAssetsPlugin({}),
],
},
plugins: [
// Delete everything from output-path (/dist) and report to user
new CleanWebpackPlugin({
root: __dirname,
exclude: [],
verbose: true,
dry: false,
}),
// Copy static assets
new CopyWebpackPlugin({
patterns: [
{
from: 'src/assets',
},
],
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name]-[hash].css',
}),
],
module: {
rules: [
{
test: /\.elm$/,
exclude: [/elm-stuff/, /node_modules/],
use: {
loader: 'elm-webpack-loader',
options: {
optimize: true,
},
},
},
{
test: /\.css$/,
exclude: [/elm-stuff/, /node_modules/],
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false'],
},
{
test: /\.scss$/,
exclude: [/elm-stuff/, /node_modules/],
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
},
],
},
});
}