Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack code splitting

Im trying to set up my project with webpack, i've read about code-splitting and i am trying to make two separate bundles, one for the actual application code and the other for libraries and frameworks. So my webpack config looks like this:

entry: {
    app: './app/index.js',
    vendor: './app/vendor.js'
},
output: {
    filename: '[name].[chunkhash].js',
    path: path.resolve(__dirname, 'public/js')
},

watch: true,

module: {
    rules: [{
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
            use: 'css-loader'
        })
    }]
},

plugins: [
    new ExtractTextPlugin('styles.css'),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor'
    })
]

in my vendor.js bundle i have only one line:

 import moment from 'moment';

And when i try to use it in my app.js file it tells me, that moment is not defined. So, the thing that i don't get, do bundles have common scope or not? If not, then how can i access variables that i've exported in another bundle and if i can't, then what's even the point of having the vendor bundle like described here https://webpack.js.org/guides/code-splitting-libraries/ ?

like image 300
FancyNancy Avatar asked Apr 02 '17 14:04

FancyNancy


Video Answer


2 Answers

The bundles do not share a scope. In fact webpack respects the scope of each module, just like Node.js, so you can't use anything from another module unless you import it, even if it's in the same bundle.

You need to import moment in every module you're using it. This doesn't mean you include its source code multiple times. Webpack includes the source once, and every import will refer to that.

Code splitting, the CommonsChunkPlugin in this case, simply puts the source into the vendor bundle, and every import across the bundles will refer to the vendor bundle. This means that you don't ship the vendor dependencies with your app bundle and therefore the vendor bundle can be cached by the browser. When you publish a new version of your app without changing your vendor bundle, the browser will only need to download the app bundle, as it already has the correct vendor bundle.


Let's consider this very short example app:

import moment from 'moment';
console.log(moment().format());

Without the CommonsChunkPlugin the resulting bundles (not uglified) are:

vendor.js  470 kB       0  [emitted]  [big]  vendor
   app.js  470 kB       1  [emitted]  [big]  app

That's 470 KB, because it contains the entire moment source in the bundle and even worse, another bundle that also uses moment, contains the entire source as well. The vendor isn't supposed to use it here, but think of another bundle that would need to use it. When you change something in the app, the user would have to download the entire 470 KB again.

With the CommonsChunkPlugin:

   app.js  504 bytes       0  [emitted]         app
vendor.js     473 kB       1  [emitted]  [big]  vendor

Now the app went down to 504 bytes. And when you change the app, the user will only have to download this small bundle (assuming vendor.js is already cached). This also means any additional bundle that uses moment would also refer to vendor.js instead of including the source in the bundle.

The size of vendor.js slightly increased, because webpack needs some extra code to handle the imports from another bundle. This also requires the vendor.js to be loaded before the app.js.

I left out the hashes in the filenames for brevity, but they would be required for cache busting. For more information see Caching.

like image 117
Michael Jungo Avatar answered Oct 09 '22 18:10

Michael Jungo


I see the problem, and it's not related with code splitting. There are different ways to achieve this, depending on your specific case:

Require vendor.js in index.js:

vendor.js

export default moment from 'moment';

index.js

var moment = require('vendor.js');
console.log(moment());

Using import loaders:

The imports loader allows you to use modules that depend on specific global variables. This is useful for third-party modules that rely on global variables like $ or this being the window object. The imports loader can add the necessary require('whatever') calls, so those modules work with webpack.

require("imports-loader?$=moment,angular!./index.js");

Via plugins:

You can expose momentJS to the windows object via plugins, so you can access from index.js as window.moment.

new webpack.ProvidePlugin({
    "window.moment": "moment"
}),
like image 20
Tom Sarduy Avatar answered Oct 09 '22 17:10

Tom Sarduy