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.
Related
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.
I have multiple entry points that share same code. What I need to do is to extract this code into one file using splitCode in webpack 4. This works fine in development mode but not in production.
Configuration file:
var path = require('path');
const ManifestPlugin = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const WebpackMd5Hash = require("webpack-md5-hash");
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.(sass|scss|css)$/,
use: [
"style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
],
},
output: {
path: path.join(__dirname, 'public'),
filename: 'js/[name]-[chunkhash].js',
chunkFilename: 'js/[name]-[chunkhash].js',
publicPath: '/'
},
externals: {
jquery: "jQuery"
},
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: "all",
priority: 1
},
utilities: {
test: /\.s?js$/,
minChunks: 2,
name: "utilities",
chunks: "all",
priority: 0
}
}
}
},
context: path.join(__dirname, 'resources/assets'),
entry: {
a: './js/a.js',
b: './js/b.js'
},
plugins: [
new CleanWebpackPlugin(['public/js/*.*', 'public/css/*.*'], {} ),
new MiniCssExtractPlugin({
filename: "css/[name]-[contenthash].css"
}),
new WebpackMd5Hash(),
new ManifestPlugin({
fileName: 'manifest.json'
}),
]
};
In development mode Webpack creates two entry points, one runtime.js, vendor.js and utilities.js which is ok.
When I change mode from development to production, webpack ignores utilities cacheGroups and appends common codebase into two entry points.
What am I missing?
Webpack version: 4.28.4
Node version: 8.15
It seems like setting enforce to true does the job (but I'm not entirely really sure why).
It should be like this:
utilities: {
test: /\.s?js$/,
minChunks: 2,
name: "utilities",
chunks: "all",
priority: 0,
enforce: true
}
From now on, utilities.js is being created not only in development mode, but also in production.
It's not a bug. It's a feature
Webpack 4 splitchunks.cacheGroups is ignored in production mode IF the new chunk is less than the size of 30kb.
solution to override this default condition:
user splitchunks.cacheGroups.runtime.enforce: true if you want to really make sure that these chunks are created
Check the documention for further details https://webpack.js.org/plugins/split-chunks-plugin/#defaults
Specifying minChunks: 2 means it will only create a split bundle if the given common imports is specified in at least 2 modules. You might want to verify but dropping it to 1.
There are few additional default rules listed here: https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693#defaults but mainly if the common codebase isn't larger than 30kb (before min+gz) then it won't get split out. You can force it by updated the key minSize as listed in the default optimization config.
Webpack SplitChunksPlugin, by default, ignores any chunk smaller than 30kb. If you run Webpack in development mode, you'll be able to see the bundle size of utilities.js and enforce the split by setting optimization.splitChunks.minSize option smaller than the size of utilities.js.
Using Webpack 4, I'm creating a bundle and a vendors bundle. Vendor bundle contains jQuery and the code bundle references jquery using 'import * from jquery'. This works perfectly.
However, I now need to use a 3rd party (already minified) javascript file. That file expects jquery to be exposed as '$' on the global window object. My bundle needs to be loaded after the new minified file, also.
So I have:
<script src='./libs/jquery-3.2.1.min.js'></script>
<script src='../vendor.js'></script>
<script src="./libs/newMinifiedFile.js"></script>
<script src="../bundle.js"></script>
as my current workaround. The static jquery file links the $ objects to the global namespace, and then the vendor.js file allows me to keep using 'import' in my bundle.
So, how do I only load jquery once, and use it in both ways? This seems to be a slightly different problem than most I've seen online because of how I'm loading things.
Here's a small example of my configs right now:
const config = (isDebug) => {
const isDevBuild = isDebug;
const extractCSS = new MiniCssExtractPlugin({filename: 'vendor.css'});
const sharedConfig = {
mode: isDevBuild ? 'development' : 'production',
stats: { modules: false },
resolve: {
extensions: [ '.js' ]
},
module: {
rules: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' },
]
},
entry: {
vendor: [
'jquery'
],
},
output: {
publicPath: 'dist/',
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, require.resolve('node-noop')) // Workaround for https://github.com/andris9/encoding/issues/16
]
};
const clientBundleConfig = merge(sharedConfig, {
output: {
path: path.join(__dirname, 'wwwroot', 'dist'),
pathinfo: false
},
module: {
rules: [
{
test: /\.css(\?|$)/, include: path.resolve(__dirname, "client"), exclude: [/webviewer/, /redux/, /helpers/],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
minimize: isDevBuild,
sourceMap: isDevBuild
}
}
]
}
]
},
plugins: [
extractCSS,
new webpack.DllPlugin({
path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
],
optimization: {
minimize: !isDevBuild,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
});
And in my normal config I use:
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(path.join(__dirname, 'wwwroot', 'dist', 'vendor-manifest.json'))
})
So, I can find jquery in the global 'vendor_hash' object, but I can't get it to map it to $ no matter what I try (Most plugins seem to see that $ is never used in any of the code I'm supplying for the bundle and thus don't seem to include it). If I add something like this in my 'entry.js' file then it still doesn't work:
window.$ = window.jQuery = require("jquery");
I have lots of code shared between web and web worker parts of my browser app.
How can I tell webpack to split my code up into common chunks so that the result is garanteed to work 100%?
The webworker code breaks (fails at runtime) after I tell webpack to generate the common chunks (which it does). Even after I fix the trivial "window not defined" error the worker just does nothing.
I believe this has to do with the webpack "target" option, which per default is set to "web". But I need "web" target because I don't have purely webworker code.
I also cannot do multiple webpack configs because I cannot do the common chunks thing with multiple configs...
What should I do?
If anybody is interested: I am trying build a minimal sized build for my app which includes the monaco editor (which provides the workers):
https://github.com/Microsoft/monaco-editor/blob/master/docs/integrate-esm.md
You can see here (at the bottom of the page) that the entry points consist of 1 main entry file + the workers.
Currently at least 6 MB is wasted because of duplicate code I am using and currently can not be split up because of this problem. That is a lot of wasted traffic.
Any ideas? :)
my webpack 4.1.1 config is basically:
module.exports = (env, options) => {
const mode = options.mode;
const isProduction = mode === 'production';
const outDir = isProduction ? 'build/release' : 'build/debug';
return {
entry: {
"app": "./src/main.tsx",
"editor.worker": 'monaco-editor/esm/vs/editor/editor.worker.js',
"ts.worker": 'monaco-editor/esm/vs/language/typescript/ts.worker.js'
},
output: {
filename: "[name].bundle.js",
path: `${__dirname}/${outDir}`,
libraryTarget: 'umd',
globalObject: 'this',
library: 'app',
umdNamedDefine: true
},
node: {
fs: 'empty'
},
devtool: isProduction ? undefined : "source-map",
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
alias: {
"#components": path.resolve(__dirname, "src/components"),
"#lib": path.resolve(__dirname, "src/lib"),
"#common": path.resolve(__dirname, "src/common"),
"#redux": path.resolve(__dirname, "src/redux"),
"#services": path.resolve(__dirname, "src/services"),
"#translations": path.resolve(__dirname, "src/translations"),
"#serverApi": path.resolve(__dirname, "src/server-api")
}
},
optimization: isProduction ? undefined : {
splitChunks: {
minSize: 30000,
minChunks: 1,
name: true,
maxAsyncRequests: 100,
maxInitialRequests: 100,
cacheGroups: {
default: {
chunks: "all",
priority: -100,
test: (module) => {
const req = module.userRequest;
if (!req) return false;
return (!/node_modules[\\/]/.test(req));
},
},
vendor: {
chunks: "all",
test: (module) => {
const req = module.userRequest;
if (!req) return false;
if (!/[\\/]node_modules[\\/]/.test(req)) return false;
return true;
},
priority: 100,
}
}
},
},
module: {
rules: [...(isProduction ? [] : [
{
enforce: "pre", test: /\.js$/, loader: "source-map-loader",
exclude: [
/node_modules[\\/]monaco-editor/
]
}
]),
{
test: require.resolve('jquery.hotkeys'),
use: 'imports-loader?jQuery=jquery'
},
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader",
options: {
configFileName: 'src/tsconfig.json',
getCustomTransformers: () => {
return {
before: [p => keysTransformer(p)]
};
}
}
},
{
test: /\.(css|sass|scss)$/,
use: extractSass.extract({
use: [
{
loader: 'css-loader',
options: {
minimize: isProduction
}
},
{
loader: "postcss-loader",
options: {
plugins: () => [autoprefixer({
browsers: [
'last 3 version',
'ie >= 10'
]
})]
}
},
{ loader: "sass-loader" }
],
fallback: "style-loader"
})
},
{
test: /node_modules[\/\\]font-awesome/,
loader: 'file-loader',
options: {
emitFile: false
}
},
{
test: { not: [{ test: /node_modules[\/\\]font-awesome/ }] },
rules: [
{
test: { or: [/icomoon\.svg$/, /fonts[\/\\]seti\.svg$/] },
rules: [
{ loader: 'file-loader?mimetype=image/svg+xml' },
]
}, {
test: { not: [/icomoon\.svg$/, /fonts[\/\\]seti\.svg$/] },
rules: [
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'svg-url-loader',
options: {}
}
},
]
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
},
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/font-woff" },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/font-woff" },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/octet-stream" },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader" },
]
},
]
},
plugins: [
new HardSourceWebpackPlugin({
cacheDirectory: '../node_modules/.cache/hard-source/[confighash]', configHash: function (webpackConfig) {
return require('node-object-hash')({ sort: false }).hash(Object.assign({}, webpackConfig, { devServer: false }));
},
environmentHash: {
root: process.cwd(),
directories: [],
files: ['../package-lock.json'],
}
}),
new webpack.ProvidePlugin({
"window.$": "jquery"
}),
new CleanWebpackPlugin(outDir),
extractSass,
new HtmlWebpackPlugin({
title: 'my title',
filename: 'index.html',
minify: isProduction ? {
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
removeComments: true,
removeRedundantAttributes: true
} : false,
template: 'index_template.html',
excludeChunks: ['ts.worker', "editor.worker"]
}),
new webpack.IgnorePlugin(/^((fs)|(path)|(os)|(crypto)|(source-map-support))$/, /vs[\\\/]language[\\\/]typescript[\\\/]lib/)
].concat(isProduction ? [new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})] : [])
}
};
EDIT: Alright I wrote a webpack plugin based on everyone's knowledge just put together.
https://www.npmjs.com/package/worker-injector-generator-plugin
You can ignore the content below, and use the plugin or if you want to understand how the plugin came to be and do it by hand yourself (so you don't have to depend on my code) you can keep reading.
=====================================================
Alright after so much researching I figured out this solution, you need to create an injection file, for a simple case you need the https://github.com/webpack-contrib/copy-webpack-plugin as it works pretty well... so let's say your setup is:
entry: {
"worker": ["./src/worker.ts"],
"app": ["./src/index.tsx"],
},
And you have setup your common plugins already let's say this example.
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
},
}
}
},
You need to now create an injection "Vanilla JS" which might look like this:
var base = location.protocol + "//" + location.host;
window = self;
self.importScripts(base + "/resources/commons.js", base + "/resources/worker.js");
Then you can add that alongside your worker, say in src/worker-injector.js
And using the copy plugin
new CopyPlugin([
{
from: "./src/worker-injector.js",
to: path.resolve(__dirname, 'dist/[name].js'),
},
]),
Make sure your output is set to umd.
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: "umd",
globalObject: "this",
}
This is nothing but a hack, but allows you to use everything as it is without having to do something as overblown.
If you need hashing (so that copy plugin doesn't work) functionality you would have to generate this file (rather than copying it), refer to this.
How to inject Webpack build hash to application code
For that you would have to create your own plugin which would generate the vanilla js file and consider the hash within itself, you would pass the urls that you want to load together, and it would attach the hash to them, this is more tricky but if you need hashes it should be straightforward to implement with your custom plugin.
Sadly so far there doesn't seem to be other way.
I could probably write the plugin myself that would do the workaround and create the injectors, but I do think this is more of a hack and shouldn't be the accepted solution.
I might later go around and write the injector plugin, it could be something as:
something like new WorkerInjectorGeneratorPlugin({name: "worker.[hash].injector.js", importScripts: ["urlToLoad.[hash].js", secondURLToLoad.[hash].js"])
refer to this issues for reference, and why it should be fixed within webpack and something as a WorkerInjectorGeneratorPlugin would be pretty much a hack plugin.
https://github.com/webpack/webpack/issues/6472
This is really bad answer, but i've managed to share chunks between workers and main thread.
The clue is that
globalObject has to be defined as above to (self || this):
output: {
globalObject: "(self || this)"
}
Webpack loads chunks with document.createElement('script') and document.head.appendChild() sequence, which is not available in worker context, but we have self.importScript. So it's just a matter of "polyfiling" it.
Here is working "polyfill" (straight from the hell):
console.log("#faking document.createElement()");
(self as any).document = {
createElement(elementName: string): any {
console.log("#fake document.createElement", elementName);
return {};
},
head: {
appendChild(element: any) {
console.log("#fake document.head.appendChild", element);
try {
console.log("loading", element.src);
importScripts(element.src);
element.onload({
target: element,
type: 'load'
})
} catch(error) {
element.onerror({
target: element,
type: 'error'
})
}
}
}
};
Ensure, that your real code is resolved after polyfill is installed, by using dynamic import, which will. Assuming, that normal "worker main" is in "./RealWorkerMain", that would be "main worker script":
// so, typescript recognizes this as module
export let dummy = 2;
// insert "polyfill from hell" from here
import("./RealWorkerMain").then(({ init }) => {
init();
});
You may need to configure dynamic import in webpack, as documented here is not easy too, this answer was very helpful.
You're looking for universal library target, aka umd.
This exposes your library under all the module definitions, allowing
it to work with CommonJS, AMD and as global variable.
To make your Webpack bundle compile to umd you should configure output property like this:
output: {
filename: '[name].bundle.js',
libraryTarget: 'umd',
library: 'yourName',
umdNamedDefine: true,
},
There is an issue with Webpack 4, but if you still want to use it, you can workaround the issue by adding globalObject: 'this' to the configuration:
output: {
filename: '[name].bundle.js',
libraryTarget: 'umd',
library: 'yourName',
umdNamedDefine: true,
globalObject: 'this'
},
Native Worker support is introduced in webpack 5. With this feature, you can share chunks between app code and webwokers with simple splitChunk options like
{
optimization: {
splitChunks: {
chunks: 'all',
minChunks: 2,
},
},
}
When combining new URL for assets with new Worker/new SharedWorker/navigator.serviceWorker.register webpack will automatically create a new entrypoint for a web worker.
new Worker(new URL("./worker.js", import.meta.url))
The syntax was chosen to allow running code without bundler too. This syntax is also available in native ECMAScript modules in the browser.
https://webpack.js.org/blog/2020-10-10-webpack-5-release/#native-worker-support
I'm updating an app to use webpack 2 from webpack 1 and the regular build works fine. The issue seems to arise when using the devServer and requiring just one of the chunks generated (it's an electron app so I have main and renderer chunks - both are included in a regular build, and with the dev server only the renderer chunk is included)
Everything worked on webpack 1, but for some reason the runtime isn't being included in my chunks? I've tried reordering them but to no avail.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
module.exports = {
context: __dirname,
entry: {
main: './main.js',
renderer: './app/index.jsx'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: "[name].js"
},
node: {
__dirname: false,
__filename: false
},
devtool: 'cheap-eval-source-map',
target: 'electron',
module: {
loaders: [
{
test: /(\.js$|\.jsx$)/,
exclude: /(node_modules|dist)/,
loader: 'babel'
},
{ test: /\.scss$/, loader: "style!css?modules!sass" },
{ test: /\.png$/, loader: "url?limit=100000" },
{ test: /\.jpg$/, loader: "file" }
]
},
resolve: {
extensions: ['.js', '.jsx', '.scss'],
modules: [path.resolve('./app'), 'node_modules']
},
plugins: [
new HtmlWebpackPlugin({
template: './app/index.html',
chunks: ['renderer'],
inject: 'body',
hash: 'true'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"dev"'
}
})
],
devServer: {
contentBase: __dirname
},
externals: {
'cheerio': 'window',
'react/addons': true,
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true
}
};
It seems one of the chunks isn't included properly:
/***/ },
/* 26 */
/***/ function(module, exports) {
module.exports = require("url");
Why is this?
So it seems the issue was with me setting the target to electron but building for web. This seemed to work in webpack 1, but after updating to webpack 2 it no longer includes the runtime in both bundles.
The solution I took was to have two configs - one for electron (my main build), and one for web (specify the target in the webpack config)