I have written a simple quizz application with Elm. As it was explained in the tutorial, the only way in Elm to access external files is to use ports with Javascript. So I have included ports in my Elm file, and I now have to add them in the index.js file that I use as an entry point. I use webpack to build the complete app.
However, I don't get the webpack logic. This is my file tree:
resources
|---- images
|---- questions
|---- question_1.txt
|---- question_2.txt
|---- ...
|---- scores
|---- scores_table.json
src
|---- MyElm.elm
|---- Questions.elm
|---- index.js
|---- index.html
webpack.config.js
My JS component needs to read all possible questions in the questions folder to both determine the total number of questions and provide them to Elm through ports.
In the same way, the JS component needs to parse the scores_table.json file to send results to the Elm app.
What can I use in my index.js app to read these files? I tried with require, but I think I didn't use it correctly.
It's my first question on Stack Overflow, so if there's anything missing, please tell me.
Minimal example
This is a simplified version of what I have:
webpack.config.js
var path = require("path");
module.exports = {
entry: {
app: [
'./src/index.js'
]
},
output: {
path: path.resolve(__dirname + '/dist'),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
},
{
test: /\.(css|scss)$/,
loaders: [
'style-loader',
'css-loader',
]
},
{
test: /\.html$/,
exclude: /node_modules/,
loader: 'file-loader?name=[name].[ext]',
},
{
test: /\.elm$/,
exclude: [/elm-stuff/, /node_modules/],
loader: 'elm-webpack-loader?verbose=true&warn=true',
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff',
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
},
],
noParse: /\.elm$/,
},
devServer: {
inline: true,
stats: { colors: true },
},
};
index.js
const RESOURCES_DIR = "../resources/";
const IMAGES_DIR = RESOURCES_DIR + "images/"
const QUESTIONS_DIR = RESOURCES_DIR + "questions/"
const SCORES_DIR = RESOURCES_DIR + "scores/"
require("./index.html");
const scores_table =
require(SCORES_DIR + "scores_table.json");
var total_question_nb = 0;
var questions_table = [];
var end = false;
while (!end) {
try {
data = require(
"raw!" +
QUESTIONS_DIR +
"question_${total_question_nb}.txt");
questions_table.push(data);
total_question_nb += 1;
} catch (e) {
end = true;
}
}
console.log(questions_table[0]);
console.log(total_question_nb);
var Elm = require("./MyElm.elm");
var mountNode = document.getElementById("elm-app");
var app = Elm.MyElm.embed(mountNode);
// Need to add port gestion there
MyElm.elm
...
import Questions
...
Questions.elm
...
-- (current_question_no, ans_id)
port ask_question_score : (Int, Int) -> Cmd msg
-- next_question_no
port ask_next_question : Int -> Cmd msg
-- question_score
port receive_question_score : (List Int -> msg) -> Sub msg
-- (next_question_no, total_question_nb, next_question_text)
port receive_next_question : ((Int, Int, String) -> msg) -> Sub msg
-- ()
port receive_end_question : (() -> msg) -> Sub msg
...
And this is what I get when I load the page using Webpack:
Uncaught Error: Cannot find module "../resources/scores/scores_table.json".
at r (app.js:1)
at Object.<anonymous> (app.js:1)
at r (app.js:1)
at Object.<anonymous> (app.js:1)
at r (app.js:1)
at app.js:1
at app.js:1
TLDR You'll need to setup Webpack's code splitting with dynamic imports to enable dynamic require's
Webpack is designed to compress all of your source files into one 'build' file. This, of course, relies on identifying which files you're importing. When you pass an expression rather than a plain string to require, webpack may not correctly identify the files you want.
In order to explicitly tell webpack what to include, you can use a "dynamic" import, ie code splitting. I'd say code splitting is rather advanced, if you want to avoid it, just hardcode the files you want to import. That should be fine if the files don't change often.
Examples
If you know the filenames:
const scores = require('../resources/scores/scores_table.json')
// or
import scores from '../resources/scores/scores_table.json'
// The `import()` function could be used here, but it returns a Promise
// I believe `require()` is blocking, which is easier to deal with here
// (though not something I'd want in a production application!)
const questions = [
require('../resources/questions/question_1.txt'),
require('../resources/questions/question_2.txt'),
]
If you want to dynamically import files (like you would probably do with questions):
// Utility function
// Example:
// arrayOfLength(4) -> [0, 1, 2, 3]
const arrayOfLength = length =>
(new Array(length)).fill(0).map((val, i) => i)
const questionsCount = 100 // <- You need to know this beforehand
// This would import questions "question_0.txt" through "question_99.txt"
const questionPromises = arrayOfLength(questionsCount)
.map(i =>
import(`../resources/questions/question_${i}.txt`)
.catch(error => {
// This allows other questions to load if one fails
console.error('Failed to load question ' + i, error)
return null
})
)
Promise.all(questionPromises)
.then(questions => { ... })
With the dynamic imports, you need to handle promises. You could also use async / await to make it look a bit nicer (that isn't support in all browsers -- needs transpiling set up)
If the files do change often, that means you are frequently modifying questions and/or the score table, and you should probably be using a database instead of dynamic imports.
This I use in React project. It will allow you to change files without rebuilding with webpack:
in config/index.js
const CONFIG_FILE = '/config.json';
let loadedConfig = {};
export const loadConfig = () => {
const headers = new Headers();
headers.append('Content-type', 'application/json');
try {
return fetch(CONFIG_FILE, { headers })
.then(response => response.json())
.then((json) => { loadedConfig = json; });
} catch (error) {
console.log(error); // eslint-disable-line no-console
}
};
export default () => ({ ...loadedConfig });
In index.jsx I load it:
import React from 'react';
import ReactDOM from 'react-dom';
import { loadConfig } from 'config';
loadConfig().then(() => {
require.ensure([], (require) => {
const App = require('App').default;
ReactDOM.render(
<App />,
document.getElementById('root'),
);
});
});
And then I import it and use
import config from 'config';
console.log(config().SOME_VAR_FROM_JSON);
Related
I'm trying to load a config inside a node module but I'm not sure which is a good practice to do it, I have this scenario:
A config my.config.js:
module.exports = {
content: [
'./src/**/*.{tsx,ts}',
],
}
Then I have a module cli.mjs is supposed to load it:
import arg from 'arg'
import { readFile } from 'fs/promises'
import path from 'path'
let configPath = './my.config.js'
const args = arg({
'--config': String,
'-c': '--config',
})
if (args['--config']) {
configPath = args['--config']
}
console.log(readFile(path.resolve(configPath), { encoding: 'utf8' }))
This will just return a simple string with my config inside, not a javascript object:
`
module.exports = {
content: [
'./src/**/*.{tsx,ts}',
],
}
`
How should I load my config in the right way?
I've saw basically everyone use configs like that in TypeScript or CJS projects, but it's hard to me see how and where they parse these configs, probably I'm missing some basic information about this?
I have a react app where I wanted to import a javascript file from a third-party library but file is mark with shebang #!/usr/bin/env node.
I found (e.g. here How to Configure Webpack with Shebang Loader to Ignore Hashbang Importing Cesium React Component into Typescript React Component) I can load file by overriding webpack configuration and adding a new loader shebang-loader (I also have tried shebang-loader2) but overriding webpack in react app is recommended only with #craco/craco so I added it to package.json and tried add loader to existing webpack-config.js.
I produced this lines of code. File craco.config.js:
const throwError = (message) =>
throwUnexpectedConfigError({
packageName: 'craco',
githubRepo: 'gsoft-inc/craco',
message,
githubIssueQuery: 'webpack',
});
module.exports = {
webpack: {
configure: (webpackConfig, {paths}) => {
const shebangLoader = { test: /node_modules\/npm-groovy-lint\/lib\/groovy-lint.js$/, loader: "shebang-loader" }
const {isAdded: shebangLoaderIsAdded1} = addAfterLoader(webpackConfig, loaderByName('url-loader'), shebangLoader);
if (!shebangLoaderIsAdded1) throwError('failed to add shebang-loader');
return webpackConfig;
},
},
};
It resolves problem with shebang and it ignores #!/usr/bin/env node but now I still get error
Module parse failed: Unexpected token (14:16)
File was processed with these loaders:
* ./node_modules/shebang2-loader/index.js
You may need an additional loader to handle the result of these loaders.
| const { getSourceLines, isErrorInLogLevelScope } = require("./utils");
| class NpmGroovyLint {
> "use strict";
| options = {}; // NpmGroovyLint options
| args = []; // Command line arguments
It looks like it does not recognise "use strict" line.
Can anyone put some suggestions what should be a problem ?
After few hours of investigation, I have finally come to a resolution. Firstly I have to say that there is no option to use NpmGroovyLint in react-like applications that run in browsers because after I resolved mentioned problem up here I figured that NpmGroovyLint uses node libraries as perf_hooks which are not available in a browser enviroment.
But I can post code that resolves the problem described in my question. It was needed to add a plugin to babel-loader named 'plugin-proposal-class-properties'. Here is my snipped of craco config. You can use it as a recipe occasionally.
const {addAfterLoader, getLoaders, loaderByName, removeLoaders, throwUnexpectedConfigError} = require('#craco/craco');
const throwError = (message) =>
throwUnexpectedConfigError({
packageName: 'craco',
githubRepo: 'gsoft-inc/craco',
message,
githubIssueQuery: 'webpack',
});
module.exports = {
webpack: {
configure: (webpackConfig, {paths}) => {
const {hasFoundAny, matches} = getLoaders(webpackConfig, loaderByName('babel-loader'));
if (!hasFoundAny) throwError('failed to find babel-loader');
const {hasRemovedAny, removedCount} = removeLoaders(webpackConfig, loaderByName('babel-loader'));
if (!hasRemovedAny) throwError('no babel-loader to remove');
if (removedCount !== 2) throwError('had expected to remove 2 babel loader instances');
//add plugin proposal class properties to existing babel loader
const propClassOptions = {...matches[1].loader.options, ...{plugins: ["#babel/plugin-proposal-class-properties"]}};
const propClassLoader = {...matches[1].loader, ...{options: propClassOptions}};
const babelLoaderWithPropClassPlugin = {...matches[1], ...{loader: propClassLoader}};
const shebangLoader = {
test: /node_modules\/npm-groovy-lint\/lib\/groovy-lint.js$/,
use: [{loader: 'shebang2-loader'}, {...{loader: require.resolve('babel-loader')}, ...{options: propClassOptions}}]
}
const {isAdded: babelLoaderWithPropClassIsAdded} = addAfterLoader(webpackConfig, loaderByName('url-loader'), matches[0].loader);
if (!babelLoaderWithPropClassIsAdded) throwError('failed to add ts-loader');
const {isAdded: babelLoaderIsAdded} = addAfterLoader(webpackConfig, loaderByName('babel-loader'), babelLoaderWithPropClassPlugin.loader);
if (!babelLoaderIsAdded) throwError('failed to add back babel-loader for non-application JS');
const {isAdded: shebangLoaderIsAdded1} = addAfterLoader(webpackConfig, loaderByName('url-loader'), shebangLoader);
if (!shebangLoaderIsAdded1) throwError('failed to add shebang-loader');
return webpackConfig;
},
},
};
I have a working rollup.config.js file but need to run a separate packaging script after Rollup is done. My plan was to use Rollup's watchers via their JS API. However, I cannot get the JS API to work at all.
I am referencing this code from the Rollup site...
const loadConfigFile = require('rollup/dist/loadConfigFile');
const path = require('path');
const rollup = require('rollup');
loadConfigFile(path.resolve(__dirname, 'rollup.config.js'))
.then(async ({options, warnings}) => {
warnings.flush();
const bundle = await rollup.rollup(options);
await Promise.all(options.output.map(bundle.write));
rollup.watch(options);
})
but I keep getting an error Unknown input options: 0.......Error: You must supply options.input to rollup
My rollup.config.js is as follow...
import svelte from 'rollup-plugin-svelte';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from "rollup-plugin-terser";
import replace from '#rollup/plugin-replace';
import json from '#rollup/plugin-json';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js'
},
plugins: [
json(),
production && replace({
'eruda': ``,
exclude: 'node_modules/**',
delimiters: ['import * as eruda from \'', '\'']
}),
production && replace({
'eruda': ``,
exclude: 'node_modules/**',
delimiters: ['', '.init()']
}),
svelte({
dev: !production,
css: css => {
css.write('public/bundle.css');
}
}),
resolve({ browser: true }),
commonjs(),
!production && livereload('public'),
production && terser()
],
watch: {
clearScreen: false
}
};
Any thoughts are appreciated!
I think the example at rollupjs.org is wrong. Shouldn't it be like this instead?
const loadConfigFile = require('rollup/dist/loadConfigFile')
const path = require('path')
const rollup = require('rollup')
// load the config file next to the current script;
// the provided config object has the same effect as passing "--format es"
// on the command line and will override the format of all outputs
loadConfigFile(path.resolve(__dirname, 'rollup.config.js'), {format: 'es'})
.then(({options, warnings}) => {
// "warnings" wraps the default `onwarn` handler passed by the CLI.
// This prints all warnings up to this point:
console.log(`We currently have ${warnings.count} warnings`)
// This prints all deferred warnings
warnings.flush()
// options is an "inputOptions" object with an additional "output"
// property that contains an array of "outputOptions".
// The following will generate all outputs and write them to disk the same
// way the CLI does it:
options.map(async options => {
const bundle = await rollup.rollup(options)
await Promise.all(options.output.map(bundle.write))
// You can also pass this directly to "rollup.watch"
rollup.watch(options)
})
})
Figured it out, apparently the options returned from loadConfigFile is an array so I had to do options[0] inside the async function
I want to build a quick nodejs script to package a Typescript app as SystemJS modules, a lot like what Angular2 bundles look like.
I tried different configurations but I can't seem to put my finger on it, and haven't found clear enough documentation as of yet.
Note that for this "test", I am not using Gulp or Jspm at all, just systemjs-builder for the time being (and don't plan on using jspm at all either)
Here's what my "project" looks like:
---- Project's Root
-------- index.ts // export * from './modules/index' and eventually more
-------- modules
------------ index.ts // export * from './menu/index'
------------ menu
---------------- menu.component.ts // export class
---------------- menu.service.ts // export class
I want to package this under a single file, where I will have multiple SystemRegister modules that can be consumed in an app thereafter
I tried the following without success:
var Builder = require('systemjs-builder');
// optional constructor options
// sets the baseURL and loads the configuration file
var builder = new Builder('./modules');
builder.bundle('./modules/index.ts', {
/* SystemJS Configuration Here */
baseURL: './modules',
transpiler: 'typescript',
typescriptOptions: {
"module": "system",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
defaultExtension: 'ts',
packages: {
'modules': {
defaultExtension: 'ts'
}
}
}, 'infrastructure.js')
.then(function() {
console.log('Build complete');
})
.catch(function(err) {
console.error(err);
})
First of all, the defaultExtension options doesn't seem to work at all
So when I do import {something} from 'filePath'; (without extension), it tries to load filePath, instead of filePath.ts;
Second, if I try adding the .ts extension in my imports (which I don't want to do), it complains that the code is invalid (unexpected token #, unexpected token menuItem and so forth)
Anyone have a good example or some explanations on how this is supposed to work?
Thank you
here you have an example: angular typescript skeleton
build task looks like this:
const path = require('path');
const Builder = require('jspm').Builder;
const builder = new Builder();
const packageJson = require(path.join(config.projectDir, 'package.json'));
return beginBuild()
.then(buildSFX)
.catch((err) => console.log('Build Failed', err));
function beginBuild() {
builder.reset();
return builder.loadConfig(path.join(config.projectDir, packageJson.jspm.configFile))
}
function buildSFX() {
const appName = packageJson.name;
const distFileName = `${appName}.min.js`;
const outFile = path.join(config.distDir, distFileName);
const moduleName = 'app';
const buildConfig = {
format: 'global',
minify: true,
sourceMaps: true
};
return builder.buildStatic(moduleName, outFile, buildConfig);
}
and jspm conf looks like this:
System.config({
defaultJSExtensions: true,
transpiler: "typescript",
typescriptOptions: {
"tsconfig": "src/tsconfig.json"
},
paths: {
"github:*": "vendor/jspm_packages/github/*",
"npm:*": "vendor/jspm_packages/npm/*",
"app": "src/index"
}
/// ...
}
Why do you want to bundle typescript? Bundling is a method used for optimizing the delivery of source code to the browser. The browser doesn't know typescript, it only knows javascript (unless you do on the fly transpiling).
I'm looking for information on how to delete old webpack chunked files. Here is my current webpack configuration:
var path = require('path');
var webpack = require('webpack');
module.exports = {
debug: false,
outputPathinfo: true,
displayErrorDetails: true,
context: __dirname,
entry: {
common: ['./src/common.coffee'],
a: './src/a.cjsx',
b: './src/b.cjsx'
},
output: {
filename: '[name]-[chunkhash].js',
chunkFileName: '[name].[chunkhash].js',
path: path.join(__dirname, 'js')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('common', 'common-[chunkhash].js'),
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false }
})
],
module: {
preLoaders: [
{
test: /\.coffee$/,
exclude: /node_modules/,
loader: 'coffeelint-loader'
}
],
loaders: [
{ test: /\.coffee/, loader: 'coffee' },
{ test: /\.cjsx$/, loaders: ['coffee', 'cjsx'] },
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
}
}
If I am running $(npm bin)/webpack --config webpack.js --watch and make changes to a.cjsx, it compiles a newer version of that file with a new chunkedhash. However, the old one remains and I'd like it to be deleted right away.
How can I delete the old version of the chunked file?
Is there a way for me to hook into an after callback once watch finishes compiling?
There is a clean-webpack-plugin for those purposes, or you can write a simple bash script for npm:
"scripts": {
"build": "rm -r dist/* && webpack -p",
"clean": "rm -r dist/*"
}
Here is the webpack-clean-obsolete-chunks plugin, which do what you want. It searches for all updated chunks and deletes obsolete files after each webpack compilation.
The answer
I've decided to write an answer because others - although trying to answer the question directly - overlooked the most important part in my opinion.
And the most important part is: you shouldn't be doing it this way. Using [hash] placeholders in your development setup cause many headaches with other tooling (phpstorm's path autocomplete in symfony plugin for example). Also it's poor for webpack's incremental compilation performance and thus is not recommended by official webpack docs (reference).
So for future readers: just keep it simple for development config - define your filename as [name].js and move on.
Edit
There seems to be a confusion about what to do with the old chunk-files on the production server. Well, you don't do anything. Once a version is deployed it shouldn't be ever changed. You just keep creating new versions when deploying and keep previous as a backup. Why?
Because you want you're rollback to be reliable and for it to be possible your rollback needs to be extremely simple and atomic. If your rollback procedure is doing anything more than switching a symlink, rerouting to previous container (or similar simple operation) you're probably™ going to end up in trouble.
Rollback isn't a process of "re-deploying" the application again, but now to the previous version. It's a process of "un-doing" the deployment. So doing a git checkout to the previous version followed by a npm build --but-please-be-hurry --and-im-begging-you-dont-fail while your production app is hanging there, completely exploded doesn't cut here.
Rebuilding a previous version of the application - just like the deployment - may fail for many reasons. That's why a rollback should be switching/rerouting back to the exact same version-build that is proven to be working. Not ==-the-same, 100% ===-the-same. That's why you need to keep your previous version around, because that's the ===-same. A "regenerated" one is - in best case scenario - only ==-the-same, and so it is not proven to be working, only assumed.
And no, no amount of CI, staging environments or whatever will give you a guaranteed successful deployment. Part of doing it the right way is to be prepared for when things go wrong. And things will go wrong. Hopefully only from time to time, but still.
Of course once you have 3, 5 or <put-your-number-here> versions backed up you may start to remove the oldest ones as you probably won't ever need more than 3.
Since Webpack 5.20.0 you can use output.clean option
Take a look at this pull request:
https://github.com/johnagan/clean-webpack-plugin/pull/32/files
Open raw file view and copy it to index.js of clean webpack plugin.
Remember about config flag -> watch: true
I have solved that problem by adding below in webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
{
... your configs ...
plugins: [new CleanWebpackPlugin()]
}
You can solve the problem № 1 by using remove-files-webpack-plugin.
Use this plugin like this:
plugins: [
new RemovePlugin({
watch: {
test: [
{
folder: './js',
method: (absPath) => new RegExp(/(.*)-([^-\\\/]+)\.js/).test(absPath)
}
]
}
})
]
In "watch" mode (not normal compilation!) it grabs all files from ./js folder and tests them with this regular expression /(.*)-([^-\\\/]+)\.js/. Analyze this regular expression on regex101 (unit tests are included) if you have problems with understanding.
Note: i'm the creator of this plugin.
Looks like webpack#5.20.0+ has built-in support for this https://webpack.js.org/configuration/output/#outputclean. I use [chunkhash] in my chunk filenames and they get cleared out if I stop comment out dynamic imports and added back in if I uncomment them.
my case: webpack 5 + multipage application + themes.css via entry points
solution: https://github.com/webdiscus/webpack-remove-empty-scripts
this plugins don't work with webpack 5 entry points or with MiniCssExtractPlugin:
webpack-fix-style-only-entries,
webpack-extraneous-file-cleanup-plugin,
webpack-remove-empty-js-chunks-plugin,
webpack-delete-no-js-entries-plugin.
my webpack.config.js:
const fs = require('fs');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
const isProd = process.env.NODE_ENV === 'production';
const isDev = !isProd;
const PAGES = ['app', 'help'];
const getThemes = (themePath, alias) => {
let themes = {};
const longPath = './' + alias + '/' + themePath;
fs.readdirSync(longPath).forEach(function(fileName) {
const fileNameWithPath = path.join(themePath, fileName);
const fileNameWithLongPath = path.join(longPath, fileName);
const stat = fs.lstatSync(fileNameWithLongPath);
if (stat.isDirectory()) return;
if (!/\.scss$/.test(fileName)) return;
const nameWithoutExt = path.basename(fileName, '.scss');
themes[nameWithoutExt] = ['./' + fileNameWithPath];
});
console.log(themes);
return themes;
};
const themes = getThemes('scss/themes', 'src');
const getFilename = (filename, ext) => {
let name = filename == 'index' ? 'bundle' : filename;
const isTheme = (ext == 'css' && name.startsWith('theme')) ? true : false;
const needHash = (isDev || isTheme) ? false : true;
return needHash ? name +`.[fullhash].` + ext : name+'.'+ext;
};
const getCSSDirname = filename => {
const isTheme = filename.startsWith('theme');
return !isTheme? '/css/' : '/css/theme/';
};
const getHTMLWebpackPlugins = arr => {
// this function config multipages names and add to html-pages
// inside <head> tag our themes via tag <link rel="stylesheet" href="....css" ...>
// and return array of HTMLWebpackPlugins
};
module.exports = {
// ... //
entry: {
// mutipage:
app: ['./index.js', './scss/app.scss'],
help: ['./help.js', './scss/help.scss'],
// multitheme:
...themes,
},
optimization: {
removeEmptyChunks: true, // not work!!!
},
// ... //
plugins: [
// ... //
...getHTMLWebpackPlugins(PAGES),
new RemoveEmptyScriptsPlugin({
ignore: PAGES,
enabled: isDev === false,
}),
new MiniCssExtractPlugin({
filename: pathdata => {
return getCSSDirname(pathdata.chunk.name) + getFilename(pathdata.chunk.name, 'css');
},
chunkFilename: isDev ? '[id].css' : '[id].[contenthash].css',
}),
],
};
my src files:
[src]:
- index.js
- index.html
- help.js
- help.html
- [scss]:
- - app.scss
- - help.scss
- - [themes]:
- - - light.scss
- - - dark.scss
- - - blue.scss
after build:
[dist]:
- app.js
- index.html
- help$hash.js
- help$hash.html
- [css]:
- - app$hash.css
- - help$hash.css
- - [themes]:
- - - light.css
- - - dark.css
- - - blue.css
For Windows users
"scripts": {
"build": "npm run clean && webpack --mode production",
"clean": "del /f /s /q dist 1>nul"
}
I just had to stop my server and run yarn serve again