Webpack, html-webpack-plugin, Error: Child compilation failed - javascript

I 've got a problem with my webpack configuration. After implementing html-webpack-plugin I got an Error, there's whole error stack from generated index.html.
Error Stack:
Html Webpack Plugin:
Error: Child compilation failed:
Conflict: Multiple assets emit to the same filename index.html:
Error: Conflict: Multiple assets emit to the same filename index.html
compiler.js:76
[Pre-build]/[html-webpack-plugin]/lib/compiler.js:76:16
Compiler.js:291 Compiler.
[Pre-build]/[webpack]/lib/Compiler.js:291:10
Compiler.js:494
[Pre-build]/[webpack]/lib/Compiler.js:494:13
Tapable.js:138 next
[Pre-build]/[tapable]/lib/Tapable.js:138:11
CachePlugin.js:62 Compiler.
[Pre-build]/[webpack]/lib/CachePlugin.js:62:5
Tapable.js:142 Compiler.applyPluginsAsyncSeries
[Pre-build]/[tapable]/lib/Tapable.js:142:13
Compiler.js:491
[Pre-build]/[webpack]/lib/Compiler.js:491:10
Tapable.js:131 Compilation.applyPluginsAsyncSeries
[Pre-build]/[tapable]/lib/Tapable.js:131:46
Compilation.js:645 self.applyPluginsAsync.err
[Pre-build]/[webpack]/lib/Compilation.js:645:19
Tapable.js:131 Compilation.applyPluginsAsyncSeries
[Pre-build]/[tapable]/lib/Tapable.js:131:46
Compilation.js:636 self.applyPluginsAsync.err
[Pre-build]/[webpack]/lib/Compilation.js:636:11
Tapable.js:131 Compilation.applyPluginsAsyncSeries
[Pre-build]/[tapable]/lib/Tapable.js:131:46
Compilation.js:631 self.applyPluginsAsync.err
[Pre-build]/[webpack]/lib/Compilation.js:631:10
Tapable.js:131 Compilation.applyPluginsAsyncSeries
[Pre-build]/[tapable]/lib/Tapable.js:131:46
Compilation.js:627 sealPart2
[Pre-build]/[webpack]/lib/Compilation.js:627:9
Tapable.js:131 Compilation.applyPluginsAsyncSeries
[Pre-build]/[tapable]/lib/Tapable.js:131:46
Compilation.js:575 Compilation.seal
[Pre-build]/[webpack]/lib/Compilation.js:575:8
Compiler.js:488
[Pre-build]/[webpack]/lib/Compiler.js:488:16
Tapable.js:225
[Pre-build]/[tapable]/lib/Tapable.js:225:11
Compilation.js:477 _addModuleChain
[Pre-build]/[webpack]/lib/Compilation.js:477:11
Compilation.js:448 processModuleDependencies.err
[Pre-build]/[webpack]/lib/Compilation.js:448:13
next_tick.js:73 _combinedTickCallback
internal/process/next_tick.js:73:7
next_tick.js:104 process._tickCallback
internal/process/next_tick.js:104:9
My webpack configuration code:
var webpack = require('webpack'),
path = require('path');
var CopyWebpackPlugin = require('copy-webpack-plugin'),
ExtractTextWebpackPlugin = require('extract-text-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
const sourcePath = path.resolve(__dirname, './src');
const staticPath = path.resolve(__dirname, './static');
module.exports = function (env) {
const nodeEnv = env && env.prod ? 'production' : 'development';
const isProd = nodeEnv === 'production';
const postcssLoader = {
loader: 'postcss-loader',
options: {
plugins: function () {
return [
require('autoprefixer')
];
}
}
}
const plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: 'vendor.bundle.js'
}),
new webpack.EnvironmentPlugin({
NODE_ENV: nodeEnv,
}),
new HtmlWebpackPlugin({
template: 'index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})
];
if(isProd) {
plugins.push(
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
if_return: true,
join_vars: true,
},
output: {
comments: false,
},
})
);
} else {
plugins.push(
new webpack.HotModuleReplacementPlugin()
);
}
return {
devtool: isProd? 'source-map' : 'eval',
context: sourcePath,
entry: {
app: './app/entry.ts',
vendor: './app/vendor.ts'
},
output: {
path: staticPath,
filename: '[name].bundle.js',
},
module: {
rules: [
{
test: /\.html$/,
exclude: /node_modules/,
use: {
loader: 'file-loader',
query: {
name: '[name].[ext]'
},
},
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
'ts-loader'
],
},
],
},
resolve: {
alias: {
Public: path.resolve(__dirname,'src/public'),
Style: path.resolve(__dirname,'src/styles')
},
extensions: ['.ts','.js', '.html'],
modules: [
path.resolve(__dirname, 'node_modules'),
sourcePath
]
},
plugins,
performance: isProd && {
maxAssetSize: 100,
maxEntrypointSize: 300,
hints: 'warning'
},
stats: {
colors: {
green: '\u001b[32m'
}
},
devServer: {
contentBase: './src',
historyApiFallback: true,
port: 3000,
compress: isProd,
inline: !isProd,
hot: !isProd,
stats: {
assets: true,
children: false,
chunks: false,
hash: false,
modules: false,
publicPath: false,
timings: true,
version: false,
warnings: true,
color: {
green: '\u001b[32m'
}
},
}
};
};
I couldn't find any source of that Error, maybe I am little bit tired, but I would like to finish it up, so I hope for your help guys.
Maybe should I use some raw-loader to load .html(?), which does not make me happy.

The problem is indeed the file-loader, because it simply copies the file over. By the time html-webpack-plugin tries to write index.html it has already been written by file-loader, hence resulting in a conflict.
There are several ways to resolve that issue, depending on what your needs are.
You could use html-loader for your HTML, although if you expect your imported HTML to simply be copied, it isn't the correct choice. To be clear, by the imported HTML I don't mean the template used by the html-webpack-plugin.
If you want to keep using the file-loader for your other HTML files, you can exclude the index.html so html-webpack-plugin falls back to its default loader. require.resolve works like require but gives you the full path of the module instead of its content.
{
test: /\.html$/,
exclude: [/node_modules/, require.resolve('./index.html')],
use: {
loader: 'file-loader',
query: {
name: '[name].[ext]'
},
},
},
When no loader matches the template, the html-webpack-plugin uses an ejs loader as a fallback. If you don't need any loader for .html files, you could remove the rule entirely and it would work just fine. That is rather unlikely, otherwise you wouldn't have a .html rule in the first place, but this also means you can use the .ejs extension to not apply the .html rule, as all HTML is valid EJS. You would rename index.html to index.ejs and change your plugin configuration accordingly:
new HtmlWebpackPlugin({
template: 'index.ejs',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})

I had to upgrade my node version. then the issue is fixed.
upgrade to latest node version(ubuntu)
sudo npm cache clean -f
sudo npm install -g n
sudo n stable
sudo n latest
to check the version
node -v
you might need to restart your terminal to see the latest version.

For my case I had the template file name misspelled.

Solution to my problem was updating node version.

This is may be not ordinary case, but for me, problem was caused by symbol "!" in the path. After I moved all content to the folder without "!" then the error disappeared.

delete node modules package then run npm install all package install then npm start the same problem are face but this process to run solve the problem so this solution is useful

I had the same issue few minutes ago and I switched to a stable version of node to solve it. It worked
sudo npm cache clean -f
sudo npm install -g n
sudo n stable

Simple Updated Node JS Version. And use
npm i
this will update your all libraries
This method works for me you can also try

Related

Webpack code splitting impacts web performance

I have a React/Node + SSR application, I am trying to create a production build, I have managed to do that but the problem is that the files that I have in the build are too large.
I use latest version of react + webpack 4.
Here is my webpack configuration:
clientConfig.js
const path = require('path');
const common = require('./webpack.common-config');
const clientConfig = {
...common,
mode: 'production',
name: 'client',
target: 'web',
devtool: false,
entry: {
client: [
'#babel/polyfill',
'./src/client.js'
],
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'all',
name: 'vendor',
test: module => /node_modules/.test(module.resource),
enforce: true
},
common: {
chunks: 'all',
name: 'client'
}
},
},
},
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
}
};
module.exports = clientConfig;
serverConfig.js
const nodeExternals = require('webpack-node-externals');
const path = require('path');
const common = require('./webpack.common-config');
const serverConfig = {
...common,
mode: 'production',
name: 'server',
target: 'node',
devtool: false,
externals: [nodeExternals()],
entry: {
server: ['#babel/polyfill', path.resolve(__dirname, 'src', 'server.js')],
},
optimization: {
splitChunks: {
chunks: 'all',
}
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js',
chunkFilename: "[id].chunk.js"
},
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
};
module.exports = serverConfig;
commonConfig.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const common = {
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: [path.resolve(__dirname, 'src')],
query: {
presets: [
['#babel/preset-env', {loose: true, modules: false}],
"#babel/preset-react"
],
plugins: [
"#babel/plugin-proposal-class-properties"
]
},
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader'
]
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
}
]
},
plugins: [
new OptimizeCSSAssetsPlugin(),
new MiniCssExtractPlugin({
filename: "styles.css",
})
],
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
},
};
module.exports = common;
And another file which basically merges the client and the server config.
I run npm run build after that I run webpack -p --mode=production --optimize-minimize && node ./build/server.js
I get the following warning:
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
vendor.js (667 KiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
client (928 KiB)
vendor.js
styles.css
client.js
Any advice or idea for the above size warning would be great! Thank you!
I recommend that you try core-js instead of babel/pollyfill this can help you to reduce your bundle size.
Also, I recommend that you try dynamic importing with react-loadable which supports SSR. there are different strategies for splitting your code. you can read more here(most important one)
In case you are using CSS frameworks such as bootstrap you should only use parts that you need and avoid importing all of them. there is a tool for purging unused CSS named purgecss but use it with caution and you have to know what are you doing.
In case you are using libraries such as lodash or material-ui you should import your module specifically to avoid importing all the packages into your bundle.
exp:
import debounce from 'lodash/debounce'
npm dedup or yarn dedup can help to remove duplicate dependencies inside bundle.
You may be able to get some reduction through adjusting your babel configuration. For instance, specifying some options for the "preset-env", such as bugfixes, targets if you don't have support older browsers, and using corejs polyfills instead of babel/polyfill.
"#babel/preset-env", {
"targets": {
"browsers": [
"last 2 years"
]
},
"bugfixes": true,
"useBuiltIns": "entry", // experiment with "entry" vs. "usage"
"corejs": 3
...
Depending on your codebase, the babel-plugin-transform-runtime may help too. I had some projects where it made a substantial difference, but other where it hasn't.
"#babel/plugin-transform-runtime",
{
"corejs": 3,
"version": "7.9.2"
}
Additional options for Webpack include using the webpack-cdn-plugin. This can greatly reduce your vendor bundle size. Of course, users will still have to download those same libraries, but they will be cached and don't need to re-downloaded every time your bundle changes or updates.
A little additional savings can be had by specifying runtimeChunk: true, and optionally inlining that chunk in your index.html with
new HTMLWebpackPlugin({
inlineSource: 'runtime~.+[.]js',
...
and the html-webpack-inline-source-plugin or similar.
You can split files to lower size to remove warning using splitChunks:
optimization: {
splitChunks: {
chunks: 'all',
minSize: 10000,
maxSize: 250000,
}
}
Check more for minSize and maxSize.
If you want to disable the warning, you can do it using performance
performance: {
hints: false
}
or remove warning for certain size limit like 1 MB:
performance: {
maxAssetSize: 1000000
}
I managed to switch to Next.js so all of this is not necessary anymore.

Webpack style-loader / css-loader: url() path resolution not working

There are a few SO posts about style-loader and css-loader, but despite this I have not been able to find a solution to my problem.
In short summary, when I #import css files in other css files, and the imported css contains url()s with relative paths, the paths are not resolved correctly.
Basically, the error message shows that Webpack ends up thinking the url() paths in the imported css are relative to src (main entry point), rather than being relative to the css file it it is imported into:
// css-one.scss
#import "./assets/open-iconic-master/font/css/open-iconic-bootstrap.css";
// open-iconic-bootstrap.css
#font-face {
src: url('../fonts/open-iconic.eot');
}
Error:
ERROR in ./src/main.scss
(./node_modules/css-loader??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/sass-loader/lib/loader.js??ref--5-3!./src/main.scss)
Module not found: Error: Can't resolve '../fonts/open-iconic.eot' in
'C:\Users\...\src'
# ./src/main.scss
(./node_modules/css-loader??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/sass-loader/lib/loader.js??ref--5-3!./src/main.scss) 7:106-141 7:172-207 # ./src/main.scss # ./src/index.js
What I Have Tried:
I have tried to use the convertToAbsoluteUrls flag in style-loader
I have tried to turn off all source maps (mentioned in style-loader docs)
My Webpack Config File (loaders are at the bottom):
const path = require('path');
const webpack = require('webpack'); // for webpack built-in plugins
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// const WriteFilePlugin = require('write-file-webpack-plugin');
// const ManifestPlugin = require('webpack-manifest-plugin');
// const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const PATHS = {
// when using __dirname, resolve and join gives same result,
// because __dirname is absolute path to directory of this file.
// OK to use no slashes,
// both resolve and join adds platform-specific separators by default
src: path.resolve(__dirname, 'src'),
dist: path.resolve(__dirname, 'dist'),
build: path.resolve(__dirname, 'build'),
test: path.resolve(__dirname, 'test')
};
const NAMES = {
// JS FILES
index: 'index',
print: 'print',
// Chrome Extension Development
popup: 'popup',
options: 'options',
background: 'background',
contentScript: 'contentScript',
// FOLDERS
assets: 'assets',
utilities: 'utilities',
images: 'images',
fonts: 'fonts',
include: 'include'
};
const FILE_PATHS = {
// JS
indexJs: `${path.join(PATHS.src, NAMES.index)}.js`,
printJs: `${path.join(PATHS.src, NAMES.print)}.js`,
// Chrome Extension Development
popupJs: `${path.join(PATHS.src, NAMES.popup)}.js`,
optionsJs: `${path.join(PATHS.src, NAMES.options)}.js`,
backgroundJs: `${path.join(PATHS.src, NAMES.background)}.js`,
contentScriptJs: `${path.join(
PATHS.src,
NAMES.include,
NAMES.contentScript
)}.js`,
// HTML
indexHtml: `${path.join(PATHS.src, NAMES.index)}.html`,
printHtml: `${path.join(PATHS.src, NAMES.print)}.html`,
// Chrome Extension Development
popupHtml: `${path.join(PATHS.src, NAMES.popup)}.html`,
optionsHtml: `${path.join(PATHS.src, NAMES.options)}.html`,
backgroundHtml: `${path.join(PATHS.src, NAMES.background)}.html`
};
// Third-party (vendor) libraries to include
// const VENDORS = ['react', 'bootstrap', 'lodash', 'jQuery']; // Relative paths to node_modules
// Note: These are relative
const ASSETS = {
images: path.join(NAMES.assets, NAMES.images),
fonts: path.join(NAMES.assets, NAMES.fonts)
};
// CleanWebpackPlugin config
const pathsToClean = [PATHS.dist, PATHS.build];
const cleanOptions = {
root: __dirname,
exclude: ['shared.js'],
verbose: true,
dry: false
};
// CopyWebpackPlugin config
const copyPattern = [
// {
// from: NAMES.assets,
// to: NAMES.assets
// },
// {
// from: path.join(NAMES.include, 'contentScript.css')
// },
// {
// from: 'manifest.json',
// transform(content, copyPath) {
// // generates the manifest file using the package.json informations
// return Buffer.from(
// JSON.stringify({
// ...JSON.parse(content.toString())
// // description: env.npm_package_description,
// // version: env.npm_package_version
// })
// );
// }
// }
];
const copyOptions = {
// ignore: ['*.js'],
context: PATHS.src
};
module.exports = (env = {}) => {
// webpack injects env variable, into webpack config.
// perfect to check for production.
// remember to specify --env.production in command
// (if in production mode).
const isProduction = env.production === true;
return {
entry: {
index: FILE_PATHS.indexJs
// Chrome Extension Development
// popup: FILE_PATHS.popupJs,
// contentScript: FILE_PATHS.contentScriptJs
// options: FILE_PATHS.optionsJs,
// background: FILE_PATHS.backgroundJs,
// vendor: VENDORS
},
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'inline-source-map',
optimization: {
splitChunks: {
chunks: 'all'
}
},
output: {
filename: isProduction ? '[name].[chunkhash:8].js' : '[name].js',
// chunkFilename determine name of non-entry chunk files,
// for example dynamic imports in the app
chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].js',
path: PATHS.dist
},
plugins: [
// new webpack.SourceMapDevToolPlugin({
// filename: '[file].map',
// exclude: ['vendor', 'runtime']
// }),
new webpack.DefinePlugin({
// specifies environment variable for dependencies.
// does not apply to browser runtime environment
// (process.env is provisioned by Node)
'process.env.NODE_ENV': isProduction ?
JSON.stringify('production') :
JSON.stringify('development')
}),
// new BundleAnalyzerPlugin(),
new CleanWebpackPlugin(pathsToClean, cleanOptions),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// does not work with Hot Module Replacement (HMR)
// allows HMR in development (will only use this plugin in production)
filename: isProduction ? '[name].[contenthash].css' : '[name].css',
chunkFilename: isProduction ? '[id].[contenthash].css' : '[id].css'
}),
new webpack.HashedModuleIdsPlugin(),
isProduction ?
new UglifyJSPlugin({
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}) :
() => {},
new CopyWebpackPlugin(copyPattern, copyOptions),
// new WriteFilePlugin(),
new HtmlWebpackPlugin({
template: FILE_PATHS.indexHtml,
filename: `${NAMES.index}.html`
})
// new HtmlWebpackPlugin({
// template: FILE_PATHS.popupHtml,
// filename: `${NAMES.popup}.html`,
// excludeChunks: [NAMES.contentScript]
// In dev mode, chunks excluded vendor chunk (which holds CSS).
// Above check fixes it.
// }),
// new HtmlWebpackPlugin({
// filename: `${NAMES.contentScript}.html`,
// excludeChunks: [NAMES.popup, 'runtime'] // Runtime only needed in one HTML
// }),
// new HtmlWebpackPlugin({
// template: FILE_PATHS.optionsHtml,
// filename: `${NAMES.options}.html`,
// chunks: isProduction ? [NAMES.options] : ''
// }),
// new HtmlWebpackPlugin({
// template: FILE_PATHS.backgroundHtml,
// filename: `${NAMES.background}.html`,
// chunks: isProduction ? [NAMES.background] : ''
// }),
// no need for CSS minimization here <-- Done by PostCSS (cssnano)
// new InlineManifestWebpackPlugin(),
// new ManifestPlugin({fileName: 'webpack-manifest.json'}),
],
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.s?[ac]ss$/,
exclude: /node_modules/,
use: [
isProduction ?
MiniCssExtractPlugin.loader :
{
// creates style nodes from JS strings
loader: 'style-loader',
options: {
sourceMap: true,
convertToAbsoluteUrls: true
}
},
{
// CSS to CommonJS (resolves CSS imports into exported CSS strings)
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 2
}
},
{
loader: 'postcss-loader',
options: {
config: {
ctx: {
cssnext: {},
cssnano: {},
autoprefixer: {}
}
},
sourceMap: true
}
},
{
// compiles Sass to CSS
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[hash:4].[ext]',
outputPath: ASSETS.images
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[hash:4].[ext]',
outputPath: ASSETS.fonts
}
}]
},
{
test: /\.(csv|tsv)$/,
use: ['csv-loader']
},
{
test: /\.xml$/,
use: ['xml-loader']
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
interpolate: 'require',
minimize: true
}
}
}
// {
// test: /\.tsx?$/,
// exclude: /(node_modules|bower_components)/,
// use: 'ts-loader'
// }
]
},
devServer: {
// contentBase: path.join(__dirname, 'dist'),
contentBase: PATHS.dist,
compress: false,
port: 8080,
open: false
}
};
};
it took me around 5 days of work to understand how this webpack mess works. I have to be honest I can say that this is one of those things that I really do not understand why they are "defacto" tools of the moment. I can't understand how difficult it can be just to make the config files work as it should, in gulp took me 1 hour to do the same.
My problem was that all the url() rules (including fonts and images) were being loaded by css-loader as [object Module], and they where exported by file-loader but never loaded, so if I added ?url=false to the css-loader it never copied the files and export them. I have to say this was a totally PITA, but I got it working, and I hope it works for somebody else in the world, this was made with webpack 4.
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: "./src/index.js",
mode: "development",
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader",
options: { presets: ["#babel/env"] }
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
},
}
},
{
loader: 'file-loader',
options:{
name: '[name].[ext]',
outputPath: 'images/',
publicPath: 'images/'
}
},
'url-loader?limit=100000'
],
},
{
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader?url=false'},
{ loader: 'sass-loader', options: { sourceMap: true } }
],
},
]
},
resolve: { extensions: ["*", ".js", ".jsx"] },
output: {
path: path.resolve(__dirname, "dist/"),
publicPath: "",
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "dist/"),
port: 3000,
publicPath: "http://localhost:3000/dist/",
hotOnly: true
},
plugins: [ new MiniCssExtractPlugin(),
new CopyPlugin([{ from: 'src/images/', to: 'images/' }]),
new CopyPlugin([{ from: 'src/fonts/', to: 'fonts/' }]),
new ImageminPlugin({ test: /\.(jpe?g|png|gif|svg)$/i }),
new HtmlWebpackPlugin({
hash: true,
template: './src/index.html',
filename: './index.html' //relative to root of the application
}),
]
};
You can turn off processing of url() rules, btw. I have no idea, why this is a default behavior.
{
loader: 'css-loader',
options: {
...
url: false,
}
},
I was able to solve the problem myself. In case it could help others in the future, please find the solution below.
First of all, if you are using both postcss-loader with the postcss-import plugin, AND css-loader, turn off / delete the postcss-import plugin. You do not need more than one tool that resolves #import rules. This is not really a problem if the order of loaders is correct, but you might as well remove it.
In the sass-loader docs, you can read the following:
Since Sass/libsass does not provide url rewriting, all linked assets
must be relative to the output.
If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.
If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).
More likely you will be disrupted by this second issue. It is natural to expect relative references to be resolved against the .scss file in which they are specified (like in regular .css files). Thankfully there are two solutions to this problem:
Add the missing url rewriting using the resolve-url-loader. Place it before the sass-loader in the loader chain.
Library authors usually provide a variable to modify the asset path. bootstrap-sass for example has an $icon-font-path. Check out this working bootstrap example.
I decided to follow bullet two, and add in resolve-url-loader above sass-loader in the Webpack config. It now works as expected.
My final Webpack config (for now) looks like this:
{
test: /\.s?[ac]ss$/,
exclude: /node_modules/,
use: [
isProduction
? MiniCssExtractPlugin.loader
: {
// creates style nodes from JS strings
loader: 'style-loader',
options: {
sourceMap: true,
// convertToAbsoluteUrls: true
}
},
{
// CSS to CommonJS (resolves CSS imports into exported CSS strings)
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 2
// url: false,
// import: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
ctx: {
cssnext: {},
cssnano: {},
autoprefixer: {}
}
},
sourceMap: true
}
},
{
loader: 'resolve-url-loader',
options: {
attempts: 1,
sourceMap: true
}
},
{
// compiles Sass to CSS
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
Side Notes
I noticed that source map paths under "no domain" in Chrome's debugger are repeated. If anyone figures out why, please do share
Remember to include the below side effects in package.json, so tree shaking, which happens in production mode, does not delete the extracted css
"sideEffects": [
".css",
".scss"
],
In webpack 5, you should refrain from using raw-loader and file-loader, instead use asset/source and asset/resource.
{
test: /\.txt$/,
type: 'asset/source',
},
{
test: /\.png$/,
type: 'asset/resource',
},
You can read more about assets on: https://webpack.js.org/guides/asset-modules/. It's described how for Webpack 4 and earlier you should use file-loader, and for Webpack5 and later you should use asset modules.
If anyone is struggling with this using Webpack 5 and is trying to upgrade from css-loader 5 to css-loader 6, you might need to check this issue where the poster has a similar problem to the OP:
With css-loader 5.2.7 the images in the input stylus were embedded as data-
URL in the output CSS. With css-loader 6, the images are instead moved
to the output directory.
See Notes here for how to upgrade to css-loader 6 - the salient points are:
using ~ is deprecated when the esModules option is enabled ... and can
be removed from your code
and
file-loader and url-loader are deprecated, please migrate on asset
modules, since v6 css-loader is generating new URL(...) syntax, it
enables by default built-in assets modules, i.e. type: 'asset' for all
url()
I've therefore done the following:
removed any '~' in my .scss files
i.e.
$font-path: "~/src/fonts" !default;
becomes
$font-path: "/src/fonts" !default;
I've also removed the 'file-loader' module completely.
All NPM packages are now up to date and URLs in CSS are working correctly.
IE is not compatible with new URL()
My issue was that webpack 5 changed everything with "Asset modules" apparently and broke file-loader.
Official docs explain it well: https://webpack.js.org/guides/asset-modules/
TL;DR you can fix it by adding type: 'javascript/auto' to the rule/loader
{
test: /\.(eot|ttf|woff2?)($|\?)/i,
loader: 'file-loader',
options: {
name: '[name]-[sha1:hash:hex:10].[ext]',
esModule: false,
},
type: 'javascript/auto'
},

Webpack Dev Server Not Hot Reloading .vue files

Been working on a project and was sure HMR was working, any of my .js files if I would update them webpack would compile and the module would be replaced all hot like.
I was working on a .vue file and webpack would recompile, but there was no super fresh HMR.
Hoping someone can take a look and tell me if something is off:
The script I'm using in the cli looks like this.
webpack-dev-server --d --watch --output-path ./public --config ./_src/webpack.config.js --progress --env.dev
I'm guessing the most important bit to look at is this:
devServer: {
contentBase: 'public',
hot: true,
filename: 'main.js',
overlay: true,
stats: { colors: true },
port: 3000,
proxy: {
'/': {
target: 'http://moment.local/',
secure: false,
changeOrigin: true
}
},
historyApiFallback: true
},
But here is my whole config if it helps.
const webpack = require('webpack')
const {resolve} = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = env => {
const addPlugin = (add, plugin) => add ? plugin : undefined
const ifProd = plugin => addPlugin(env.prod, plugin)
const removeEmpty = array => array.filter(i => !!i)
// Our Sass Extraction Plugin
const extractSass = new ExtractTextPlugin({
filename: 'style.css',
disable: env.dev
})
return {
entry: {
'vendor': ['jquery', 'KlaviyoSubscribe', 'learnq', 'select2', 'slick-carousel', 'moment', 'lodash'],
'main': resolve(__dirname, './js/index.js')
},
output: {
filename: '[name].js',
path: resolve(__dirname, '../public/'),
pathinfo: !env.prod
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: 'vue-style-loader!css-loader!postcss-loader!sass-loader', // <style lang='scss'>
scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader', // <style lang='scss'>
sass: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax' // <style lang='sass'>
}
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{ test: /\.s(c|a)ss$/,
use: extractSass.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true
}
},
'postcss-loader?sourceMap',
'sass-loader?sourceMap'
]
})
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
devtool: env.prod ? 'source-map' : 'eval',
devServer: {
contentBase: 'public',
hot: true,
filename: 'main.js',
overlay: true,
stats: { colors: true },
port: 3000,
proxy: {
'/': {
target: 'http://moment.local/',
secure: false,
changeOrigin: true
}
},
historyApiFallback: true
},
bail: env.prod, // Fail out on the first error if production
resolve: {
alias: {
'vue$': 'vue/dist/vue.common.js'
}
},
plugins: removeEmpty([
extractSass,
new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.CommonsChunkPlugin({
// Let's create js for our vendor scripts
name: 'vendor',
// (with more entries, this ensures that no other module
// goes into the vendor chunk)
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
filename: 'commons.js',
// (Modules must be shared between 2 entries)
minChunks: 2
})
...
])
}
}
Really struggling here so anything would be great.
It sounds like you want the "hot" behaviour but you are missing the --hot option in the script you posted. The documentation for that option is here.
You have a lot of options already; just add --hot and that should do the trick.
UPDATE:
I do see that you have the hot: true set in the devServer property of your webpack config, but if I don't use --hot in the cli, I get the following error in the console, so that is why I am saying to use it even though you would think it would be covered by the config - its not.
Uncaught Error: [HMR] Hot Module Replacement is disabled.
Add a file called vue.config.js in your root directory.
Inside it you can enable hot reloading via:
module.exports = {
devServer: {
watchOptions: {
poll: true
}
}
};
I used this settings in a project that was set up via vue create.

Vue CLI Webpack template with vue-form-wizard not transpiling to es5

I started a project using the vue-cli webpack (simple) template.
When I try to build for release using npm run build I get my dist folder with the expected build.js, etc. and when testing the build version the app works in all browsers except IE (11).
The error in IE referenced a Promise so I looked in the dist.js file and saw the new Promise() syntax. I am not very familiar with vue-loader and babel but I thought all the es6+ code would be transpiled to es5 when I run the build command.
I have not modified the webpack.config.js from what is default with the vue-cli webpack template.
Is my expectation wrong about transpilation or am I missing something?
.babelrc
{
"presets": [
["env", { "modules": false }],
"stage-3"
]
}
webpack.config.js
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
node - 6.9.2 | npm - 3.10.2 | vue - 2.9.2
Update 1/3/18
Following the babel-polyfill answer and some more searching I was able to get this working for the production build (npm run build) by changing my 'entry' prop in the webpack.config.js file to
entry: ['babel-polyfill', './src/main.js']
and adding the babel-polyfill import statement to my main.js
import 'babel-polyfill'
after adding babel-loader and babel-polyfill npm packages to my dependencies
ES6 to ES5 transpilation only handles syntax transformation, not polyfills (these are more like runtime features).
You can either use babel-polyfill (which includes all ES6 related polyfills) or just es6-promise.

Unexpected Token error using UglifyJS with Preact, Webpack 2

I'm building a Chrome extension with Preact/ES6/Webpack. I pack using one of 2 configurations: debug uses ESLint, Babel and CSS + JSON loaders, prod adds 2 plugins: UglifyJS and Zip. Here's the webpack.config.js:
const webpack = require('webpack');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
const ZipPlugin = require('zip-webpack-plugin');
const manifest = require('./src/manifest');
let options = {
// entry file - starting point for the app
entry: {
popup: './src/scripts/popup.js',
options: './src/scripts/options.js',
background: './src/scripts/background.js'
},
// where to dump the output of a production build
output: {
path: path.join(__dirname, 'build'),
filename: 'scripts/[name].js'
},
module: {
rules: [
{
test: /\.jsx?/i,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
},
enforce: 'pre'
},
{
test: /\.jsx?/i,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
['env', {
'targets': {
'chrome': 52
}
}]
],
plugins: [
['transform-react-jsx'],
['module-resolver', {
'root': ['.'],
'alias': {
'react': 'preact-compat',
'react-dom': 'preact-compat'
}
}]
]
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.json$/,
use: 'json-loader'
}
]
},
plugins: [
new WebpackCleanupPlugin(),
new CopyWebpackPlugin([
{from: './src/_locales', to: '_locales'},
{from: './src/icons', to: 'icons'},
{from: './src/manifest.json', flatten: true},
{from: './src/*.html', flatten: true}
])
],
// enable Source Maps
devtool: 'source-map',
};
if(process.env.NODE_ENV === 'production') {
options.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: false,
screw_ie8: true,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
if_return: true,
join_vars: true,
},
output: {
comments: false,
},
}),
new ZipPlugin({
path: '../dist',
filename: `${manifest.name} ${manifest.version}.zip`
})
);
}
console.log(`This is a ${process.env.NODE_ENV === 'production' ? 'production' : 'development'} build with ${options.plugins.length} plugins`);
module.exports = options;
But when I run the prod, I get the following errors:
ERROR in scripts/popup.js from UglifyJs
Unexpected token: name (getLatestValues) [scripts/popup.js:29428,4]
ERROR in scripts/options.js from UglifyJs
Unexpected token: name (getLatestValues) [scripts/options.js:29428,4]
ERROR in scripts/background.js from UglifyJs
Unexpected token: name (getLatestValues) [scripts/background.js:28678,4]
It's worth mentioning that getLatestResults is not the only the only function in my code that has await in front of it. Also, it should only appear in background.js as the other entry points are not supposed to call it.
Also worth mentioning is that if I just comment the UglifyJS code, the resulting zipped extension works well.
What configuration piece am I missing to make these errors go away?
It turns out that currently (5/2017) the built in webpack.optimize.UglifyJsPlugin does not support ES6. When Babel transpiles await/async it turns them into generators, which causes UglifyJS to throw an error.
There are several alternatives to UglifyJS listed in this article, but I'm hoping that the Webpack guys will update the plugin, and I'd be able to leave my code intact.
TL;DR: My code is OK; UglifyJS does not support ES6; Will support in the future, or will be replaced by alternative.
I've replaced uglify-js to uglify-es for ES6 compatibility:
https://www.npmjs.com/package/uglify-es
Works fine for me!

Categories

Resources