Extract duplicate javascript code using WebPack CommonsChunkPlugin
Asked Answered
G

2

10

I'm using WebPack CommonsChunkPlugin to extract duplicate code and reduce JavaScript code size. I have two html pages and two entries for them. Also i've added ReactJs vendor entry. So far, in webpack.config.js we have:

var path = require("path");
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    context: __dirname,
    entry: {
        react: ["react", "react-dom"],
        home: './assets/js/home.jsx',
        about: './assets/js/about.jsx',
    },

    output: {
        path: path.resolve('./assets/bundles/'),
        filename: "[name].js",
    },

    plugins: [
        new BundleTracker({filename: './webpack-stats.json'}),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'react',
            minChunks: Infinity
        }),

        new BundleAnalyzerPlugin(),
    ],

    module: {
        rules: [
            {
              test: /\.jsx?$/, 
              exclude: /node_modules/,
              loader: 'babel-loader',
              options: { 
                  plugins: [["lodash", { "id": ["semantic-ui-react"] }]],
                  presets: ["es2015", "react"]
              }  
            },
        ],
    },

    resolve: {
        modules: ['node_modules', 'bower_components'],
        extensions: ['*', '.js', '.jsx']
    },
};

This configuration result with webpack-bundle-analyzer:

webpack bundle analyzer output

As you can see, there are some duplicate code, some in red area and some other in green area. I want to extract this js codes from home and about bundles into a separate bundle. To extract red area code, namely lodash library, i added these lines to webpack config:

new webpack.optimize.CommonsChunkPlugin({
    name: 'lodash',
    minChunks: function(module, count) {
        return module.context.indexOf('node_modules/lodash') >= 0;
    }
}), 

But it's not working as expected and lodash library code is still in both home and about bundles, also webpack creates a bundle named lodash that is almost empty and contains no js library.

Any idea on how to fix it ? How about extracting green are codes?

Guan answered 1/8, 2017 at 21:20 Comment(1)
Link: Split “vendor” chunk using webpack 2Tortricid
G
1

I managed to solve the problem by adding a common chunk to plugins. So final webpack config is :

var path = require("path");
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');

module.exports = {

    context: __dirname,

    entry: {
        react: ["react", "react-dom"],
        home: './assets/js/home.jsx',
        about: './assets/js/about.jsx',
    },

    output: {
        path: path.resolve('./assets/bundles/'),
        filename: "[name].js",
    },

    plugins: [

        new BundleTracker({filename: './webpack-stats.json'}),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'react',
            filename: '[name].js',
            minChunks: Infinity,
        }),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            chunks: ['home', 'about'],
            filename: '[name].js',
        }),
    ],

    module: {
        rules: [
            {
                test: /\.jsx?$/, 
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: { 
                    plugins: [
                      ["lodash", { "id": ["semantic-ui-react"] }]
                    ],
                    presets: ["es2015", "react"]
                }
            },
        ],
    },

    resolve: {
        modules: ['node_modules', 'bower_components'],
        extensions: ['*', '.js', '.jsx']
    },
};

And now bundle analyzer output is like this:

analyzer output

As it's shown in the picture, common semantic-ui-react and lodash libraries are now just in common bundle and not duplicated anymore.

Guan answered 2/8, 2017 at 21:23 Comment(0)
N
3

Your problem is that your are importing third party libs in each .js/.jsx file without importing it previously in a common file (normally called vendor.js).
If you have this file that import all you dependencies and you include it as entry and to CommonsChunkPlugin, webpack won't include again your libs in your final bundles (home.js and about.js). The technique is called code splitting in webpack docs.

vendor.js (or a name that fit for your case)

import 'react';
import 'react-dom';
import 'lodash';
import 'semantic-ui-react';
//... all your npm packages

webpack.config.js

var webpack = require('webpack');
var path = require('path');

module.exports = {
    context: __dirname,
    entry: {
        vendor: './assets/js/vendor.js,
        home: './assets/js/home.jsx',
        about: './assets/js/about.jsx',
    },
    output: {
        path: path.resolve('./assets/bundles/'),
        filename: '[name].js',
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: Infinity
        }),
    ],

    //Rest of Your config ...
};

index.html

<body>
    <!-- AFTER YOUR HTML CODE -->

    <script type="text/javascript" src="/assets/bundles/vendor.js"></script>
    <script type="text/javascript" src="/assets/bundles/home.js"></script>
    <script type="text/javascript" src="/assets/bundles/about.js"></script>
</body>


Check webpack code splitting docs:

Nynorsk answered 2/8, 2017 at 2:40 Comment(6)
I did whatever you said and ran webpack again. But the result is the same as before, lodash library is still in both files, just react and react-dom are bundled into vendor.js file. Here is bundle-analyzer output: linkGuan
Does it make a difference to use require('lodash') instead of import 'lodash' ? when using import 'lodash', there is no lodash code in vendor bundle but when i use require('lodash') instead, there is node_modules/lodash/lodash.js in vendor bundle. Do you know the reason?Guan
I've note that the only difference between my webpack.config.js and yours is the plugins: [["lodash", { "id": ["semantic-ui-react"] }]], try to remove that from the babel-loader options. I think that could be that. If not, is weird.Nynorsk
require('lodash') and import 'lodash' has the same effect. import is ES6 syntax and try to use it if you can. I think if you have different results you could have some bad config in your babel. But use require if works for you.Nynorsk
You are right, the problem is babel plugin option. I removed it and used your config and it worked but removing plugin option creates a bigger problem. When babel lodash plugin is removed, unused imports are bundled into vendor. For example, all semantic-ui-react components are imported but most of them are unused. Before this question, i had the problem of bundling unused imports and asked it here. Solution is to use babel-lodash-plugin.Guan
After all, i managed to solve the problem without removing babel-lodash plugin. I will post the config. Thanks for all your efforts :)Guan
G
1

I managed to solve the problem by adding a common chunk to plugins. So final webpack config is :

var path = require("path");
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');

module.exports = {

    context: __dirname,

    entry: {
        react: ["react", "react-dom"],
        home: './assets/js/home.jsx',
        about: './assets/js/about.jsx',
    },

    output: {
        path: path.resolve('./assets/bundles/'),
        filename: "[name].js",
    },

    plugins: [

        new BundleTracker({filename: './webpack-stats.json'}),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'react',
            filename: '[name].js',
            minChunks: Infinity,
        }),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            chunks: ['home', 'about'],
            filename: '[name].js',
        }),
    ],

    module: {
        rules: [
            {
                test: /\.jsx?$/, 
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: { 
                    plugins: [
                      ["lodash", { "id": ["semantic-ui-react"] }]
                    ],
                    presets: ["es2015", "react"]
                }
            },
        ],
    },

    resolve: {
        modules: ['node_modules', 'bower_components'],
        extensions: ['*', '.js', '.jsx']
    },
};

And now bundle analyzer output is like this:

analyzer output

As it's shown in the picture, common semantic-ui-react and lodash libraries are now just in common bundle and not duplicated anymore.

Guan answered 2/8, 2017 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.