I'm using Webpack and I would like to split my client code into several pieces and load them once user needs them.
Here's my file structure:
app.js <-- Entry point as introduced to Webpack
Module.js <-- To be loaded dynamically
There's no direct connection between app.js
and Module.js
, instead the second file is loaded by the first like this:
require.ensure([], (require) => {
let path = "Module";
let module = require("./" + path).default;
});
I used "./" + path
just to make sure Webpack won't go smart on me and try to resolve the path statically. Anyways, this code works in development mode with webpack-dev-server. By that I mean the Module.js
is not downloaded until I trigger the event to run the above code. And after that, it is loaded and executed perfectly.
But when I pack the project for production, it stops working. It gives out the following error (in browser when I trigger the download event) without even trying to send the request:
Uncaught Error: Cannot find module './Module'.
Furthermore, when I compose the path dynamically (like the above code), building process gives out the following warning:
WARNING in ./src/app/app.js Critical dependencies: 74:34-47 the request of a dependency is an expression
What's the right way to configure Webpack for the production so it supports code splitting to be downloaded dynamically?
I've tested the solution given by @wollnyst and here are the results. When I implement it like this, it works:
require.ensure(["./Module"], (require) => {
let path = "Module";
let module = require("./" + path).default;
});
But that's not how I want it, I want it like this:
let path = "Module";
require.ensure(["./" + path], (require) => {
let module = require("./" + path).default;
});
Now it gives out a runtime error in browser:
Uncaught TypeError: webpack_require(...).ensure is not a function
Still no luck!
Putting the path you want to require dynamically in the require.ensure
array first argument is wrong and should not be done. This array is intended to be used for the dependencies of the module you want to load dynamically and that are needed for the async code in your callback to run safely.
Critical part in understanding how webpack can handle the code splitting is that you can't do fully dynamic statements, as webpack needs some location information to resolve the module you want to require, thus the dependency is an expression
warning. Even if you can go smart like you did by preprending the ./
, it is preferable to actually repeat the full ensure
statement with the static module path string even if you have a lot of modules to dynamically load and it's a bit obnoxious, so that you won't hit any issue.
Another way to do it would be to create a folder dedicated to that purpose, splits
for example, and resolve all your modules from here. But that would mean that every module you require in this directory would be in the same split point, not really what would anyone want except for specific use-cases.
Regarding the configs, you will need to use the namedModulesPlugins
and the CommonsChunkPlugin
one. What I personally do is having a main.js
that contains all the common sources, a vendor.js
, containing all the common node_modules
, and a runtime.js
(required by webpack). Then, the chunks are separated and if one depends on a specific vendor dependency, it will be added to this specific chunk rather than the common vendor.js
.
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.indexOf('node_modules'),
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
}),
],
You might also make sure to have something like the following output.filename
:
output: {
filename: '[name]-[chunkhash].js',
}
Otherwise the hash of your vendor.js
will change everytime your sources change, even if you didn't modified your deps, which is bad if you're concerned about caching.
The recent webpack visualizer is a very good tool to inspect what your bundle and chunks look like and to verify things looks okay or detect dependencies that could be chunked better. You might need to change a bit your config if using the StatsWriterPlugin
to collect the stats so that it handles your chunks:
new StatsWriterPlugin({
fields: null,
transform: (data, opts) => {
const stats = opts.compiler.getStats().toJson({ chunkModules: true })
return JSON.stringify(stats, null, 2)
},
}),
One thing to note too, is that require.ensure
is specific to webpack and is being superseded by the import()
that it now also handles. Since this question is from 2016 and it might not have the same elements we have now with webpack 2, I'm gonna expand a bit on it.
Now, the dynamic import proposal is on its way into ES, and you can make use of it in webpack. You'll require a Promise polyfill and something like babel syntax-dynamic-import plugin (and that should now be in the stage-3 preset) or the dynamic-import-webpack if that doesn't work for you yet (which basically transform import()
to ensures
).
You'll still need to specify the full path like with ensure if you want to get a chunk per module, but that's a better syntax and more oriented in the future.
There's a lot of other resources you could look into on the new webpack documentation page about code splitting.
You need to pass the module you want to require in the first argument of require.ensure
:
require.ensure(['./Module'], function(require) {
const module = require('./Module');
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With