Webpack 代码分离

代码分离

代码分离是 webpack 中最引人注目的特性之一。

代码分离能把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影提高项目的性能。

代码分离的三种方法:

  • 多个入口:配置多个入口(entry),手动分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重、提取公共模块。
  • 动态导入:通过模块的内联函数调用来分离代码。

多个入口

配置多个入口是最简单、最直观的分离代码的方式。不过,这种方式手动配置较多,并有一些陷阱,我们将会解决这些问题。

src/index.js 文件,内容如下:

import _ from 'lodash';

function component() {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    return element;
}

document.body.appendChild(component());

新建 src/another-module.js 文件,内容如下:

import _ from 'lodash';

console.log(
  _.join(['Another', 'module', 'loaded!'], ' ')
);

确保 package.json 文件中,存在下面的 npm script 内容:

"dev": "webpack --mode development  --config webpack.config.js"

修改 webpack.config.js 文件,以便配置多个入口(entry):

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/index.js',
        vendor: './src/another-module.js'
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'Code Splitting--代码分离'
        }),
        new CleanWebpackPlugin(['dist'])
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

打开终端,切换到项目目录,运行命令:

npm run dev

这将生成如下构建结果:

Hash: 7ce7198831b386f081df
Version: webpack 4.0.0
Time: 606ms
Built at: 2018-3-3 14:43:02
           Asset       Size  Chunks                    Chunk Names
 index.bundle.js    550 KiB   index  [emitted]  [big]  index
vendor.bundle.js    550 KiB  vendor  [emitted]  [big]  vendor
      index.html  268 bytes          [emitted]
Entrypoint index [big] = index.bundle.js
Entrypoint vendor [big] = vendor.bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {vendor} {index} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {vendor} {index} [built]
[./src/another-module.js] 94 bytes {vendor} [built]
[./src/index.js] 226 bytes {index} [built]

虽然,现在确实生成了两个bundle(index.bundle.js 和 vendor.bundle.js),但却存在两个问题:

  • 如果多个入口中包含重复的模块,重复的模块就会存在各个 bundle 中。
  • 这种方法不够灵活,并且不能将核心的逻辑进行动态拆分代码。

第一点对我们的示例来说是个大问题,因为之前我们在 src/index.js 中也引入过 lodash,这样生成的两个 bundle 中就会有重复。

下面,通过使用 CommonsChunkPlugin 来移除重复的模块。

防止重复(prevent duplication)

CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的 chunk 中,或者提取到一个新生成的 chunk。

让我们使用这个插件,将上面示例中重复的 lodash 模块去除。

修改 webpack.config.js 配置文件:

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/index.js',
        vendor: './src/another-module.js'
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'Code Splitting--代码分离'
        }),
        new CleanWebpackPlugin(['dist']),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'common' // 指定公共 bundle 的名称。
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

运行命令:

npm run dev

这时,就报错了。报错信息为:

Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.

这是因为,在 webpack 4.0.0 版本,CommonsChunkPlugin 被移除了,改用 config.optimization.splitChunks 替换。

那么,重新修改 webpack.config.js 文件,内容如下:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/index.js',
        vendor: './src/another-module.js'
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'Code Splitting--代码分离'
        }),
        new CleanWebpackPlugin(['dist'])
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    optimization: {
        splitChunks: {
            // chunks: "initial", //  "initial" | "all"(默认就是all) | "async"
            // minSize: 0, // 最小尺寸,默认0
            // minChunks: 1, // 最小 chunk ,默认1
            // maxAsyncRequests: 1, // 最大异步请求数, 默认1
            // maxInitialRequests : 1, // 最大初始化请求数,默认1
            cacheGroups:{ // 这里开始设置缓存的 chunks
                // priority: 0, // 缓存组优先级
                vendor: { // key 为entry中定义的 入口名称
                    chunks: "initial", //  "initial" | "all" | "async"(默认就是异步)
                    test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk
                    name: "vendor", // 要缓存的 分隔出来的 chunk 名称
                    // minSize: 0,
                    // minChunks: 1,
                    enforce: true,
                    // maxAsyncRequests: 1, // 最大异步请求数, 默认1
                    // maxInitialRequests : 1, // 最大初始化请求数,默认1
                    // reuseExistingChunk: true // 可设置是否重用该chunk(查看源码没有发现默认值)
                }
            }
        }
    },
};

再来运行命令:

npm run dev

结果如下:

Hash: 8bb93388ae63800ea88c
Version: webpack 4.0.0
Time: 590ms
Built at: 2018-3-3 14:59:15
           Asset       Size  Chunks                    Chunk Names
 index.bundle.js   7.67 KiB   index  [emitted]         index
vendor.bundle.js    547 KiB  vendor  [emitted]  [big]  vendor
      index.html  268 bytes          [emitted]
Entrypoint index [big] = vendor.bundle.js index.bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {vendor} {index} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {vendor} {index} [built]
[./src/another-module.js] 94 bytes {vendor} [built]
[./src/index.js] 226 bytes {index} [built]

可以发现,index.bundle.js 文件的大小明显比之前小了许多。

动态导入

当涉及到动态代码的拆分时,可以使用 import() 语法来实现。

先修改 webpack.config.js 配置文件:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/index.js'
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'Code Splitting--代码分离'
        }),
        new CleanWebpackPlugin(['dist'])
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        chunkFilename: '[name].bundle.js'    // chunkFilename 指定的是非入口 chunk 的名称
    }
};

注:这里使用了 chunkFilename,它决定非入口 chunk 的名称。

现在,我们不再使用静态导入 lodash,而是通过使用动态导入来分离一个 chunk。

修改 src/index.js 文件:

function getComponent() {
    return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
        var element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    return element;
}).catch(error => 'An error occurred while loading the component');
}
getComponent().then(component => {
    document.body.appendChild(component);
})

注: 在注释中使用了 webpackChunkName。可以使非入口的 bundle 被命名为 lodash.bundle.js ,而不是 [id].bundle.js 。

执行命令:

npm run dev

结果如下:

Hash: 07e5a1638ac146171b8f
Version: webpack 4.0.0
Time: 555ms
Built at: 2018-3-3 20:10:20
                   Asset       Size          Chunks                    Chunk Names
         index.bundle.js   7.57 KiB           index  [emitted]         index
vendors~lodash.bundle.js    547 KiB  vendors~lodash  [emitted]  [big]  vendors~lodash
              index.html  205 bytes                  [emitted]
Entrypoint index = index.bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {vendors~lodash} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {vendors~lodash} [built]
[./src/index.js] 391 bytes {index} [built]

可以看出,lodash 被拆分到了一个单独的 bundle 。

由于 import() 会返回一个 promise,因此它可以和 async 函数一起使用。但是,需要使用像 Babel 这样的预处理器和 Syntax Dynamic Import Babel Plugin。

下面通过 async 函数简化代码,修改 src/index.js 文件:

async function getComponent() {
    var element = document.createElement('div');
    const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    return element;
}

getComponent().then(component => {
    document.body.appendChild(component);
})

你可能感兴趣的:(Webpack)