How to separate out webpack-dev-server from my splitChunks in Webpack? - javascript

I am trying to get multiple entrypoints working with webpackDevServer.
One entrypoint requires my entire node_modules folder. The other requires only a single file, with a single console.log in it (the entrypoint file).
For some reason, my single file with a single console.log won't run. See this question as well.
I was testing this setup in WebpackDevServer, so I suspected that all files needed at least WebpackDevServer to function, maybe. So, I changed my optimization.splitChunks to look like this, based off the example on the webpack docs:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
},
vendor: {
test: /[\\/]node_modules[\\/](webpack|webpack-dev-server)[\\/]/,
name: 'webpack',
chunks: 'all',
}
}
},
},
I expect there to be a "vendor" bundle and a "webpack" bundle. There is only "vendor" (and my entrypoints):
app.js 6.92 MiB app [emitted] app
resetPassword.js 35.2 KiB resetPassword [emitted] resetPassword
vendor.js 14.4 MiB vendor [emitted] vendor
How can I get webpack-dev-server into its own bundle, which I can then include into HtmlWebpackPlugin, to test to see if that (or other node_modules) are what's needed to run my console.log?
Webpack config
module.exports = {
entry: {
app: './public/js/ide.js',
resetPassword: './public/js/reset_password.js'
},
output: {
path: path.resolve(__dirname, '../build'),
filename: '[name].js',
publicPath: '/'
},
...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
},
vendor: {
test: /[\\/]node_modules[\\/](webpack|webpack-dev-server)[\\/]/,
name: 'webpack',
chunks: 'all',
}
}
},
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/html/ide.html',
inject: true,
chunks: ['app', 'vendor']
}),
new HtmlWebpackPlugin({
filename: 'reset_password.html',
template: 'public/html/reset_password.html',
inject: true,
chunks: ['resetPassword'] // this does not work
//chunks: ['resetPassword', 'vendor'] //this works
}),
],
}
reset_password.js
console.log('hello')
webpack dev server config
devServer: {
clientLogLevel: 'warning',
historyApiFallback: true,
hot: true,
compress: true,
host: HOST,
port: PORT,
open: config.dev.autoOpenBrowser,
overlay: false,
publicPath: '/',
contentBase: [
path.join(__dirname, "../../public"),
path.join(__dirname, "../../public/js")],
watchOptions: {
poll: config.dev.poll,
},
disableHostCheck: true,
https: true,
noInfo: false,
},

Add a priority attribute to each of the chunks. From the docs.
splitChunks.cacheGroups.priority
number
A module can belong to multiple cache groups. The optimization will prefer the cache group with a higher priority. The default groups have a negative priority to allow custom groups to take higher priority (default value is 0 for custom groups).
So your code would be something like this. Note that the priority is the highest number value, not ranking value.
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
priority: 1
},
vendor: {
test: /[\\/]node_modules[\\/](webpack|webpack-dev-server)[\\/]/,
name: 'webpack',
chunks: 'all',
priority: 2
}
}
},
},

Related

Prevent Webpack from modifying javascript files

I have webpack in my plain HTML, CSS, and Javascript application. I use webpack to convert scss to CSS. I want my javascript to be the same untouched in my dist folder, as I want to edit it later my wordpress projects. Webpack is adding a lot of code, which makes the JS files hard to edit later. Here is my config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { HotModuleReplacementPlugin } = require("webpack");
const HtmlWebpackPartialsPlugin = require("html-webpack-partials-plugin");
module.exports = {
mode: "development",
entry: {
index: "./src/js/index.js",
about: "./src/js/about.js",
courses: "./src/js/courses.js",
contactUs: "./src/js/contact-us.js",
},
output: {
filename: "js/[name].bundle.js",
path: path.resolve(__dirname, "dist"),
assetModuleFilename: "images/[name][ext]",
},
devServer: {
static: { directory: path.join(__dirname, "dist") },
port: 9000,
hot: true,
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
}),
new HtmlWebpackPlugin({
title: "Leads",
filename: "index.html",
template: "./src/pages/index.html",
chunks: ["index"],
}),
new HtmlWebpackPlugin({
title: "Leads About",
filename: "about-us.html",
template: "./src/pages/about-us.html",
chunks: ["about"],
}),
new HtmlWebpackPlugin({
title: "Courses",
filename: "courses.html",
template: "./src/pages/courses.html",
chunks: ["courses"],
}),
new HtmlWebpackPlugin({
title: "Contact Us",
filename: "contact-us.html",
template: "./src/pages/contact-us.html",
chunks: ["contactUs"],
}),
new HtmlWebpackPartialsPlugin({
path: path.join(__dirname, "./src/partials/footer.html"),
location: "partialfooter",
template_filename: [
"index.html",
"about-us.html",
"courses.html",
"contact-us.html",
],
}),
new HtmlWebpackPartialsPlugin({
path: path.join(
__dirname,
"./src/partials/components/infrastructure.html"
),
location: "infrastructure",
template_filename: [
"index.html",
"about-us.html",
"courses.html",
"contact-us.html",
],
}),
new HotModuleReplacementPlugin(),
],
module: {
rules: [
{
test: /\.js$/,
exclude: "/node_modules",
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"],
},
},
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
// Creates `style` nodes from JS strings
// "style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource",
},
],
},
optimization: {
minimize: false,
},
};
How can I get webpack to convert my sass files, but simply copy my JS files?
Perhaps you could use copy-webpack-plugin https://webpack.js.org/plugins/copy-webpack-plugin/ to copy files from your src static folder to your build destination folder. Roughly like this -
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{ from: "source", to: "dest" },
{ from: "other", to: "public" },
],
}),
],
};
The code that Webpack adds can allow many features for JavaScript like transpiling ES6 JavaScript to ES5, bundling, code-splitting and dynamic imports. But if you don't need them a straight copy might do.

Webpack 5: Create vendor chunk(s) from .js files

Simply I want to insert vendor scripts in my HTML template.
These scripts are collected in the src/vendor directory.
I want to import them in my template HTML file as <script>.
For example:
<script src="../vendor/luxon.min.js"></script>
When I run webpack, it stores luxon in the dist/assets directory with the name ba9f5c2186e41fc21fa3.js. The HTML file cannot import the script because the name`- which is created by webpack - is wrong:
<script src="assets/8939b829f8da59dc2687.js"></script>
How can I insert my vendor js files to my HTML?
My webpack config:
mode: "production",
entry: {
base: "./src/base.ts",
material: "./src/ts/material.ts",
},
output: {
filename: "[name]-[contenthash].js",
path: path.resolve(__dirname, "dist"),
clean: true,
assetModuleFilename: 'assets/[hash][ext][query]',
chunkFilename: "[id].chunk.js",
library: pkg.name,
libraryTarget: 'umd',
},
module: {
rules: [
// Resolves urls in templates from <img>
{
test: /\.html$/i,
use: ["html-loader",],
},
// Support typescript
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
// Supports importing graphics in js/ts and html.
{
test: /\.(svg|png|jpg|jpeg|gif)$/i,
type: "asset/resource",
generator: {
filename: 'assets/images/[hash][ext][query]'
}
},
// Support importing fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'assets/fonts/[hash][ext][query]'
}
},
// SASS support.
{
test: /\.scss$/i,
use: [
MiniCssExtractPliugin.loader, // Extract css into files
"css-loader", // Turns css into commonjs
"postcss-loader", // Cross browser support
"sass-loader", // Turns sass into css
],
},
]
},
plugins: [
new MiniCssExtractPliugin({
filename: "css/[name]-[contenthash].css",
}),
new HtmlWebpackPlugin({
title: 'index',
filename: 'index.html',
template: "./src/templates/index.html",
chunks: ['base'],
}),
new HtmlWebpackPlugin({
title: 'about',
filename: 'about.html',
template: './src/templates/about.html',
chunks: ['material'],
}),
],
optimization: {
minimize: true, // Minimize css
},
alias: {
Style: path.resolve(__dirname, 'src/sass'),
Fonts: path.resolve(__dirname, 'src/fonts'),
Images: path.resolve(__dirname, 'src/images'),
},
extensions: ['.ts'],
// External libraries which are ignored.
externals: {
luxon: 'luxon',
},
I tried to add a rule so that the name of the imported js files do not change,
but then my loaders throw many errors.
{
test: /\.js$/i,
type: "asset/resource",
generator: {
filename: 'vendor/[name][ext][query]'
}
},
Edit:
Tried the code-splitting example from webpack documentation (Prevent Duplication). Now a new bundle is created using the luxon.min.js. Unfortunately it is now complete useless because you cannot use any function of luxon anymore (typing luxon.DateTime in the browser console returns luxon is not defined).
entry: {
base: {
import: "./src/base.ts"
},
material: {
import: "./src/ts/material.ts",
dependOn: 'shared',
},
shared: './src/vendor/luxon.min.js',
},
...
optimization: {
minimize: true, // Minimize css
runtimeChunk: 'single',
},

Multiple HtmlWebpackPlugins with webpack DevServer

You can use multiple HtmlWebpackPlugin plugins to create more than one HTML file in production, but only one of the HTML files will be used by the DevServer. Is there any way to use all the HtmlWebpackPlugin plugins in development as well?
module.exports = {
entry: {
main: './src/main.js',
anotherEntry: './src/anotherEntry.js'
},
// This only serves the index.html file on 404 responses
devServer: {
contentBase: './dist',
historyApiFallback: true,
port: 3000,
},
// ...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/main.html',
chunks: ['main'],
}),
new HtmlWebpackPlugin({
filename: 'anotherEntry.html',
template: './src/anotherEntry.html',
chunks: ['anotherEntry'],
}),
]
};
historyApiFallback can be given manual rewrites to control in a more fine-grained manner what the DevServer should fallback to on 404 responses. This way we can serve the other files in development as well.
module.exports = {
entry: {
main: './src/main.js',
anotherEntry: './src/anotherEntry.js'
},
devServer: {
contentBase: './dist',
historyApiFallback: {
rewrites: [
{ from: /^\/anotherEntry/, to: '/anotherEntry.html' },
{ to: '/index.html' },
],
},
port: 3000,
},
// ...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/main.html',
chunks: ['main'],
}),
new HtmlWebpackPlugin({
filename: 'anotherEntry.html',
template: './src/anotherEntry.html',
chunks: ['anotherEntry'],
}),
]
};

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 Tree-Shaking Dynamic Imports seems not to be working

History:
I recently discovered an odd behaviour with using Webpack and dynamic imports. First I thought it might be the 3rd-party library 'Loadable Components' I used, so I opened a bug issue (https://github.com/gregberge/loadable-components/issues/517) on their end. The author replied telling me that the behaviour is coming from Webpack and the dynamic imports themselves.
I can stand the fact that it does not tree-shake the dynamic import, for me it is more important to understand why that is the case.
Demo repository to demonstrate the behaviour can be found here: https://github.com/dazlious/treeshaking-dynamic-imports
Short description of the problem: From my perspective, an imported named export should not force all the exported code to be bundled within it.
In the demo case we have a component (./lib/index.jsx) that has two sub components called module1 (./lib/module1/module1.jsx) and module2 (./lib/module1/module2.jsx). Module1 exports a constant called FOO_BAR that is then imported by Module2 as a named import.
When looking at the build output, you'll find Module2 containing Module1 in whole and not only the string that is specifically imported.
Is anyone with deep knowledge of Webpack and/or dynamic imports around here? Would be happy to learn more about the behaviour.
I edited the webpack.config to be:
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const baseDir = path.resolve(__dirname);
const config = {
mode: process.env.NODE_ENV,
stats: 'minimal',
resolve: {
extensions: ['.js', '.jsx'],
symlinks: false,
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'analyze.html',
}),
],
target: 'web',
devtool: 'hidden-source-map',
entry: {
bundle: [path.resolve(baseDir, 'lib')],
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
mangleWasmImports: true,
splitChunks: {
cacheGroups: {
default: false,
vendors: false,
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/,
priority: 20
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 10,
reuseExistingChunk: true,
enforce: true
}
}
},
},
output: {
chunkFilename: '[name].[chunkhash].js',
publicPath: '/',
path: path.join(baseDir, 'dist'),
filename: '[name].[hash].js',
},
module: {
rules: [
{
test: /^.*\.jsx?$/,
include: [path.resolve(baseDir, 'lib')],
loader: 'babel-loader?cacheDirectory',
},
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
},
],
},
};
module.exports = config;
I think this has the result you are looking for?
image of bunde analyzer showing modules in their own bundles
I think it requires the splitChunks option to actually tree-shake the components properly.
I have spend a lot of time trying to figure webpack out, but I'm still guessing here.

Categories

Resources