The React family bucket was used in writing the Tencent intern Mini project these days, and of course Webpack was introduced as a packaging tool. However, during the development process, there were hundreds of modules in react and other third-party wheels such as React-Router, Material-UI, Superagent and Eventproxy. The packaging speed of Webpack was very slow. This is a very bad experience for development and very inefficient.

Problem analysis

Let’s take a look at the packaging speed of Webpack (loader using JSX and Babel) without any optimization at all. Here is our test file:

//test.js
var react = require('react');
var ReactAddonsCssTransitionGroup = require('react-addons-css-transition-group');
var reactDOM = require('react-dom');
var reactRouter = require('react-router');
var superagent = require("superagent");
var eventproxy = require("eventproxy");
Copy the code

In the case of my 2015 RMBP13, I5 processor and full SSD, the performance is as follows:

Yes, you read that right, those third party wheels add up to 668 modules, which take more than 20 seconds to pack.

What does that mean? Every time you make a change to your business code, gulp or Webpack detects it and repackages it, and you have to wait a good 20 seconds to see the result of your change.

But the only thing that needs to be repackaged is your business code. These third-party libraries don’t need to be repackaged at all, and their presence only slows down packaging performance. So we’re going to have to find some way to optimize the process.

Configuration externals

Webpack can configure externals to point a dependent library to a global variable, so that the library is no longer packaged, such as for a file like this:

import React from 'react';
console.log(React);
Copy the code

If you configure externals in webpack.config.js:

Module.exports = {externals: {'react': 'window.react'} // Ignore...... for other configurations };Copy the code

React: Don’t pack the react module, just point it at window.react. But don’t forget to load the react.min.js variable to include the react variable globally.

React doesn’t have to be packaged so it’s fast and small:




Defects in configuring externals

React-addons-css-transition-group {react} {react-addons-css-transition-group} {react-addons-css-transition-group}

import React from 'react';
import ReactAddonsCssTransitionGroup from 'react-addons-css-transition-group';
console.log(React);
Copy the code

Yeah, you read it right, and I didn’t screenshot it wrong, but after adding a very, very small animation library, performance exploded again. From the number of modules, Webpack must have repackaged react.

Let’s take a look at why a very, very small animation library caused Webpack to foolishly repackage React. Find the react-addons-CSS-transition-group module and see what it says:

/ / the react - addons - CSS - the transition - group module / / entry document index. Js module. Exports = the require (' react/lib/ReactCSSTransitionGroup ');Copy the code

The react module is a single line of code that points to a submodule under react.

/ / / / the react module react/lib/ReactCSSTransitionGroup js var react = the require ('. / react '); var ReactTransitionGroup = require('./ReactTransitionGroup'); var ReactCSSTransitionGroupChild = require('./ReactCSSTransitionGroupChild'); / /... Residual code ignoredCopy the code

This submodule in turn relies on the react library entry, which is the main drag on Webpack.

In summary, the question arises as follows:

  1. Webpack finds that we rely on react-Addons-CSS-transition-group;
  2. The react-addons-CSs-transition-group depends on the reactTransitionGroup.js file in the React module, so Webpack packages the reacttransitionGroup.js file.
  3. Reacttransitiongroup.js relies on the entire react import file react.js. Although externals is set, Webpack does not know that this import file is equivalent to the React module itself. So our lovely and dedicated Webpack repackaged the whole react.

At this point you may be wondering why you can’t set this animation library to externals as well, so you don’t have to pack it.

The problem is that the animation library does not provide production files, or the library does not provide react-addons-CSS-transition-group.min.js at all.

This problem is not limited to react addons-CSS-transition-group. Most existing react libraries have complex dependencies.

Primary solution

So the solution to this problem is to manually package these modules and then set externals so that Webpack doesn’t pack them anymore.

We need a lib-bundle.js file like this:

window.__LIB["react"] = require("react"); window.__LIB["react-addons-css-transition-group"] = require("react-addons-css-transition-group"); / /... Other dependency packagesCopy the code

Here we have registered some third party libraries under window.__lib, which can be used as the underlying base libraries without repeated packaging.

Then perform

webpack lib-bundle.js lib.js
Copy the code

Get the lib.js package. Then go set our externals:

var webpack = require('webpack'); module.exports = { externals: { 'react': 'window.__LIB["react"]', 'react-addons-css-transition-group': 'window. __LIB [" react - the addons - CSS - the transition - group "]', / / ignore other library} / / other configuration... };Copy the code

Because of externals, Webpack will avoid these libraries with too many modules and complex dependencies, and will direct the entry of these third-party modules to the pre-packaged lib.js entry window.__lib, thus packaging only our business code.

The ultimate solution

The method we mentioned above is essentially the idea of a “dynamically linked library” (DLL), which is a common idea under Windows systems. A DLL package is a pure dependency library that does not run on its own and is used to reference your app or business code.

The same Webpack has recently added this functionality: webpack.dllPlugin. To use this feature, you need to break the packaging process into two steps:

To package the DDL package, first configure a ddl.config.js that looks like this:

const webpack = require('webpack'); Const vendors = ['react', 'react-dom', 'react-router', //... other libraries]; module.exports = { output: { path: 'build', filename: '[name].js', library: '[name]', }, entry: { "lib": vendors, }, plugins: [ new webpack.DllPlugin({ path: 'manifest.json', name: '[name]', context: __dirname, }), ], };Copy the code

Among the options for webpack.dllPlugin:

  • Path is the output path of the manifest.json file, which is used for subsequent business code packaging;
  • Name is the name of the object exposed by the DLL, which must be consistent with output.library.
  • Context is the context in which the package path will be resolved, as is the case with the webpack.config.js configuration that follows.

When you run Webpack, you will output two files: a packaged lib.js file and a manifest.json file that looks something like this:

{ "name": "vendor_ac51ba426d4f259b8b18", "content": { "./node_modules/react/react.js": 1, "./node_modules/react/lib/React.js": 2, "./node_modules/react/node_modules/object-assign/index.js": 3, "./node_modules/react/lib/ReactChildren.js": 4, "./node_modules/react/lib/PooledClass.js": 5, "./node_modules/react/lib/reactProdInvariant.js": 6, // ............ }}Copy the code

Now we can happily package the business code by writing the package configuration file webpack.config.js:

const webpack = require('webpack');
module.exports = {
    output: {
        path: 'build',
        filename: '[name].js',
    },
    entry: {
        app: './src/index.js',
    },
    plugins: [
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./manifest.json'),
        }),
    ],
};
Copy the code

Webpack. DllReferencePlugin options:

  • Context needs to be the same as before. This is used to direct Webpack to match the library path in manifest.json;
  • Manifest is used to import the manifest.json file that was just output.

The DllPlugin essentially does the same thing as we do manually separating these third-party libraries, but for applications with a large number of packages, automation significantly accelerates productivity.