抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
1866 words
9 minutes
webpack2 Guide (Part 1)
2017-01-17

Definitions#

Bundle Chunk

Installation#

npm install webpack —save-dev

Code Splitting#

Code splitting is one of webpack’s most compelling features. It allows you to split your code into various bundles that can be loaded on demand, such as when a user navigates to a specific route or triggers an event. This enables smaller modules, allows you to control resource loading priority, and, if used correctly, can significantly impact (reduce) your application’s load time.

Resource Splitting for Caching and Parallel Loading#

Third-Party Code Splitting#

A typical application relies on many third-party frameworks and libraries. Unlike your application code, these third-party dependencies don’t change very often. If we keep these libraries in their own bundles, separate from the application code itself, then we can leverage browser caching strategies to cache these files on the end-user’s machine for an extended period.

To achieve this, the hash portion of the vendor bundle must remain consistent, regardless of changes to the application code. Learn how to split vendor/library code using CommonsChunkPlugin.

CSS Splitting#

You might also want to split your stylesheet files into a separate bundle, independent of your application logic. This enhances the cacheability of your stylesheets and allows browsers to load them in parallel with your application code, thus avoiding FOUC (Flash Of Unstyled Content). Learn how to split CSS using ExtractTextWebpackPlugin.

On-Demand Code Splitting#

While the previous resource splitting methods require users to pre-define split points in the configuration file, it’s also possible to create dynamic split points within the application code itself.

This feature is particularly useful when dealing with many fine-grained code blocks, for example, for each application route or based on anticipated user behavior. This allows users to load resources only when they are needed.

Code Splitting with require.ensure()#

require.ensure is a CommonJS-style way to asynchronously load resources. By adding require.ensure([<fileurl>]), we can define a split point in our code. Webpack can then create a bundle containing all the code within this split point. Learn how to split code using require.ensure().

TODO System.import()

Code Splitting - CSS#

In webpack, when you use css-loader and import CSS files within your JavaScript, the CSS files are bundled directly into your JavaScript files. A drawback of this approach is that you cannot leverage the browser’s ability to asynchronously load CSS in parallel. Instead, your page will wait for the entire JavaScript file to load before the stylesheets are applied. Webpack can address this issue by separating stylesheet files using extract-text-webpack-plugin and css-loader.

Using css-loader#

To import CSS into your JavaScript, you need to configure css-loader in your webpack configuration file.

//webpack.config.js

modules.exports = function(env){
    entry: '..',
    ...
    module: {
        rules: [{
            test: /\.css$/,
            exclude: /node_modules/,
            loader: 'css-loader'
        }]
    }
    ...
}

Using extract-text-webpack-plugin - ExtractTextPlugin#

Installation:

npm I --save-dev extract-text-webpack-plugin

To use ExtractTextPlugin, you need to configure it in two steps within webpack.config.js.

In the Loader#

Adapting from the previous css-loader configuration, we should add ExtractTextPlugin as follows.

loader: ExtractTextPlugin.extract('css-loader?sourceMap') //Can be used without sourcemaps too.

In the Plugin#

new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })

With these two steps, a new bundle containing all CSS modules will be generated and then added to the head of index.html. You can learn more about its API via ExtractTextPlugin.

The complete configuration file is as follows:

var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = function () {
  return {
    entry: "./main.js",
    output: {
      path: "./dist",
      filename: "bundle.js",
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          exclude: /node_modules/,
          loader: ExtractTextPlugin.extract({
            loader: "css-loader?sourceMap",
          }),
        },
      ],
    },
    devtool: "source-map",
    plugins: [
      new ExtractTextPlugin({
        filename: "bundle.css",
        disable: false,
        allChunks: true,
      }),
    ],
  };
};

Code Splitting - Libraries#

A typical application relies on many third-party libraries for framework or feature support. The code for fixed-version libraries/frameworks used in a project generally does not change, whereas the application’s own business logic code often changes frequently.

Bundling application code and library code together is highly inefficient. This is because browsers can cache these resource files locally based on cache headers, avoiding repeated requests to the server or CDN if the file content hasn’t changed. To leverage this benefit, we need to keep the hash of third-party files unchanged, regardless of how the application’s own code changes.

We can only achieve this by separating application code from third-party code.

Let’s consider a simple application that uses moment.js, a common library for date and time formatting.

Install moment:

npm install --save moment

The index file will import moment as a dependency and print the current time:

Index.js

var moment = require("moment");
console.log(moment().format());

We can bundle this application using the following configuration file:

Webpack.config.js

module.exports = function (env) {
  return {
    entry: "./index.js",
    output: {
      filename: "[chunkhash].[name].js",
      path: "./dist",
    },
  };
};

When running the webpack command, if you inspect the bundled file, you’ll find that both moment and index.js are bundled together in bundle.js.

This is not an ideal solution. If index.js is modified, the bundle will be rebuilt, and the browser will need to reload this file, even if the moment.js file itself hasn’t changed.

Multiple Entry Points#

To mitigate this issue, let’s add a new entry point for moment, named vendors.

Webpack.config.js

module.exports = function (env) {
  return {
    entry: {
      main: "./index.js",
      vendor: "moment",
    },
    output: {
      filename: "[chunkhash].[name].js",
      path: "./dist",
    },
  };
};

Now, when you execute the webpack command, you’ll see two bundled files. If you inspect the code, you’ll notice that moment’s code appears in both bundles.

To resolve this, we need to use CommonsChunkPlugin.

CommonsChunkPlugin#

This is a rather complex plugin. Essentially, it allows you to extract all common modules from different bundles and merge them into a shared bundle. If this shared bundle doesn’t exist, a new one is created.

We can modify the webpack configuration file to use CommonsChunkPlugin.

Webpack.config.js

var webpack = require("webpack");
module.exports = function (env) {
  return {
    entry: {
      main: "./index.js",
      vendor: "moment",
    },
    output: {
      filename: "[chunkhash].[name].js",
      path: "./dist",
    },
    plugins: [
      new webpack.optimize.CommonsChunkPlugin({
        name: "vendor", // Specify the common bundle's name.
      }),
    ],
  };
};

With this, moment’s code will now only appear in the vendor bundle.

Manifest File#

However, if we modify the application code and run the webpack command again, we’ll notice that the vendor file’s hash still changes. Even though we’ve separated the vendor and main bundles, the vendor bundle still changes when the application code is modified. This means we still cannot fully benefit from browser caching, as each recompilation modifies the vendor’s hash value.

This issue arises because with each compilation, webpack generates some webpack runtime code to help it do its job. When there’s a single bundle, the runtime resides within it. However, when multiple bundles are generated, the runtime code is extracted into a common module, which is the vendor file in this case.

To prevent this, we need to extract the runtime into a separate Manifest File. While this creates yet another bundle, its overhead is offset by the long-term caching benefits gained for the vendor file.

Webpack.config.js

var webpack = require("webpack");
module.exports = function (env) {
  return {
    entry: {
      main: "./index.js",
      vendor: "moment",
    },
    output: {
      filename: "[chunkhash].[name].js",
      path: "./dist",
    },
    plugins: [
      new webpack.optimize.CommonsChunkPlugin({
        names: ["vendor", "manifest"], // Specify the common bundle's name.
      }),
    ],
  };
};

With the above configuration file, we will see three bundles generated: vendor, main, and manifest. This way, when the application code is modified and re-bundled, only main and manifest will change. The manifest is modified because it contains references to the generated file hash values.

Code Splitting - Using RequireJS#

In this section, we discuss how webpack splits code using require.ensure().

require.ensure()#

Webpack statically analyzes require.ensure() calls in the code during compilation and adds modules to separate chunks. These new chunks are then loaded by webpack via JSONP when needed.

Its syntax is as follows:

require.ensure(dependencies: String[], callback: function(require), chunkName: String)

Dependencies#

This is an array of strings used to declare all modules that need to be pre-loaded and available before the callback function is executed.

Callback#

A callback function is executed by webpack once all dependencies have been loaded. The require object implementation is passed as an argument to this callback function. This allows us to further require needed dependencies and other modules to be executed.

Chunk Name#

The chunk name is used to name the chunk created by require.ensure(). By giving chunks created from different require.ensure() split points the same name, we can ensure that all dependencies are bundled into the same chunk.

Let’s look at a project with the following structure:

\\ file structure
    |
    js --|
    |    |-- entry.js
    |    |-- a.js
    |    |-- b.js
    webpack.config.js
    |
    dist
// entry.js

require("a");
require.ensure([], function (require) {
  require("b");
});

// a.js
console.log("***** I AM a *****");

// b.js
console.log("***** I AM b *****");
// webpack.config.js

module.exports = function (env) {
  return {
    entry: "./js/entry.js",
    output: {
      filename: "bundle.js",
      path: "./dist",
    },
  };
};

When running the webpack command, we’ll find that webpack creates two new bundles: bundle.js and 0.bundle.js.

entry.js and a.js are bundled into bundle.js.

b.js is bundled into 0.bundle.js.

Pitfalls of require.ensure()#

Empty Array as an Argument#

require.ensure([], function (require) {
  require("./a.js");
});

The above code ensures that a split point is created, and a.js will be bundled by webpack into a separate file.

Dependencies as Arguments#

require.ensure(["./a.js"], function (require) {
  require("./b.js");
});

In the above code, a.js and b.js will be bundled together and separated from the main bundle. However, only the content of b.js is executed. The content of a.js is available but not executed. To execute a.js, we need to require it synchronously, for example, require('./a.js), so that JavaScript can execute it.

Dependency Management#

Ø ES6 Modules
Ø CommonJS
Ø AMD

Expression Dependencies (require with expression)#

When you import a module using an expression, a context is created, and thus we don’t know the exact module at compile time.

Example:

require("./template/" + name + ".ejs");

Webpack parses the require() call and extracts some information:

Directory:./template
Regularexpression:/^.*\.ejs$/

Context Module#

A context module is generated. It contains references to all modules in that folder that can be matched by the above regular expression. The context module contains a map that resolves requests to module IDs. Example:

{
  "./table.ejs": 42,
  "./table-row.ejs": 43,
  "./directory/folder.ejs": 44
}

The context module also contains some runtime code to access this map.

This means dynamic imports are supported, but it results in all potentially used modules being bundled into the final package.

require.context#

You can create your own context using the require.context() method. It allows you to pass in a directory to search, a flag to determine whether to recursively look for subdirectories, and a regular expression to match files.

Webpack parses require.context() during code bundling.

Its syntax is as follows:

require.context(directory, (useSubdirectories = false), (regExp = /^\.\//));

Example:

require.context("./test", false, /\.test\.js$/);
// a context with files from the test directory that can be required with a request endings with `.test.js`.

```

```javascript
require.context("../", true, /\.stories\.js$/);
// a context with all files in the parent folder and descending folders ending with `.stories.js`.

```

## Context Module API
A context module exposes a method that accepts one argument: the request content.
The exposed function has three properties: `resolve`, `key`, `id`
	• `resolve` is a function that, when executed, returns the resolved module ID of the request content.
	• `keys` is a function that, when executed, returns an array containing the IDs of all modules that could be requested by the context module.
	This is very useful when you want to import all modules in a folder via a regular expression:

```javascript
function importAll (r) {
  r.keys().forEach(r);
}
importAll(require.context('../components/', true, /\.js$/))

```

````javascript
var cache = {};
function importAll (r) {
  r.keys().forEach(key => cache[key] = r(key));
}
importAll(require.context('../components/', true, /\.js$/));
// At build-time cache will be polulated with all required modules.

```

`id` is the module ID generated by the context module. This is very useful when using `module.hot.accept`.

This article was published on January 17, 2017 and last updated on January 17, 2017, 3183 days ago. The content may be outdated.

webpack2 Guide (Part 1)
https://blog.kisnows.com/en-US/2017/01/17/webpack2-guide-1/
Author
Kisnows
Published at
2017-01-17
License
CC BY-NC-ND 4.0