webpack4之DllPlugin实践分析

4,106 阅读5分钟

本文基于 webpack4

DLLPluginDLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

可以阅读 DllPlugin 获取更多相关内容。

与 splitChunks 的区别

说到拆分,大家会不会想到 splitChunks ?

splitChunks 作用是将第三方的组件拆分出来,打包成一个或几个包,用于长期缓存。这个行为可以在webpack中设置并自动完成。

DllPlugin 也能将第三方组件拆分出来,打包成一个或几个包,用于长期缓存且能加速打包过程。

那么它们的差异在于:

  1. DllPlugin 需要设置打包的配置文件,并先于项目打包将第三方组件打包;
  2. DllPlugin 需要手动插入到对应的页面(可以使用 add-asset-html-webpack-plugin 在打包项目的时候自动插入 );
  3. Dllplugin 内含有的组件在 webpack 打包项目的时候,不经过打包过程。所以能加快打包速度。(这个其实可以使用 webpack 中的 external 来排除打包某些组件,然后通过链接将对应的组件链入页面,达到相同效果);
  4. 如果库可以按需加载,Dllplugin 将不能按需加载,它方式是全量的引入的,而 splitChunks 可以按需加载地打包。

其实看起来,splitChunks 就是 DllPlugin 的自动版本。

DllPlugin的使用

// webpack.dll.config.js
const webpack = require('webpack'),
      path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const resolvePath = path.join(__dirname, "../lib");

module.exports = {
    mode: "production",
    resolve: {
        extensions: [".js", ".jsx"]
    },
    entry: {
        react: ['react', 'react-dom', 'react-router-dom', 'redux', 'react-redux'],
    },
    output: {
        path: resolvePath,
        filename: "[name].[chunkhash:8].js",
        library: "lib_[name]"
    },
    plugins: [
        new CleanWebpackPlugin(),
        new webpack.DllPlugin({
            context: process.cwd(),
            path: path.resolve(resolvePath, "[name]-manifest.json"),
            name: 'lib_[name]'
        })
    ]
};
// webpack.config.js
module.export = {
  ...
  plugins: [
    ...
    new webpack.DllReferencePlugin({
        context: process.cwd(),
        manifest: require(path.join(__dirname, '../lib/react-manifest.json'))
      }),

      new AddAssetHtmlPlugin({
        filePath: path.resolve(__dirname, '../lib/react.edij4567.js')
      }),
    ...
  ]
  ...
}

先运行 webpack.dll.config.js (在 package.json 中设置命令 )

// package.json
...
"scripts": {
  ...
  "build:dll": "webpack --config config/webpack.dll.config.js",
}

将对应的插件打包,然后再运行项目打包命令,将对应的dll打包进去。其中,应该自动检测 lib 文件夹中的文件,在 webpack.config.js 插入对应的 webpack.DllReferencePluginnew AddAssetHtmlPlugin

效果对比

建议配合 webpack-bundle-analyzer 食用,更添美味!

由于打包机器不一样,结果可能会有所差异,而且webpack会利用缓存来加速打包,所以可以看到,第一次打包在没有缓存的情况下,时间会比较长。利用多次打包的表现来观察其中差异,应该能得到一个相对准确的结论。

只对react全家桶(react, react-dom,react-router-dom,redux,reacr-redux)进行操作,dll包大小为56.28KB。

此为交叉运行结果(秒):

dll: 25.85 10.43 11.02 10.53 11.48 10.38 12.44 11.86

正常打包:25.86 11.64 11.97 11.01 10.72 10.61 12.66 12.32

看的出来,优化的确是有效果,但是效果不明显。但有可能是因为dll的包小,导致效果不明显,加入antd 组件,再测试。

dll包(react, react-dom,react-router-dom,redux,reacr-redux,antd)大小为2.54MB。

结果(秒):

dll: 8.52 5.97 6.02 5.96 6.52 6.07 6.24 6.07

由此可以得出个结论:

dll包的大小,或者是dll包里的库文件需要webpack处理的越多越大,dll方案的性能提升效果越是明显

以上说的dll包并不指一个包,根据包拆分策略,一般限制244KB内,那么以上的2.54MB的包可能会拆分为好几个。所以上面说的dll包指的是抽离出来的所有库的总包。

但由于实际开发中,一般不会全量引入 antd 等较大的库,一般采取的方案是按需加载,这样引入的包变小,需要webpack 处理的文件也少了。

上文中说到的 与 splitChunks 的区别 的第三点,使用 externals 和引入对应组件的方案,观察一下效果:

先只排除react全家桶(react, react-dom,react-router-dom,redux,reacr-redux)。

结果(秒):

externals: 9.36 8.56 8.80 8.86 9.29 8.96 8.86 8.84

排除(react, react-dom,react-router-dom,redux,reacr-redux,antd)。

结果(秒):

externals: 4.78 4.81 4.71 4.57 4.59 4.74 4.63 4.74

这里解释(猜测)一下,为何第一次打包没有以上打包那样长耗时,因为 externals 会完全排除相关依赖,即不处理(相当于SVIP直接走了贵宾通道);而 DllPlugin 需要进行处理(即需要经历webpack的处理流程,只是不去解析对应依赖,但是遇到依赖会去dll文件那里引入)(相当于VIP不需要额外处理只需要跟着走,但是跟非VIP的一样还是走完了整个过程,非VIP则需要进行全套处理)。

总结一下

  1. DllPlugin 进行项目打包(只打包一次项目代码然后上线的,而不是开发时更改文件的打包构建时的情况)还是有效果的,但是微乎其微;
  2. DllPlugin 包含原webpack需要处理的文件越多越大时,性能效率效果越明显;
  3. externals 效果更佳
  4. Dllpluginexternals均是全量引入,由于不经过webpack,按需加载的福利自然也无法享受到
  5. 此处说一个使用 DllPlugin 有可能会出现的问题,当抽离相关的组件到dll文件后,假如某个组件是依赖于dll内的某个组件下的某库,又可能将会将dll的那个组件重新引入打包到对应位置。即某个组件 import abc from 'xxx/abc' 那么xxx 组件将再次打包(xxx 已打包在dll文件内)到项目里。

BTW,vue-cli (RFC: beta.10, Upgrading to webpack 4 + vue-loader 15 #1205) 和 create-react-app 都选择了不使用 dll 了。从上面的打包结果,你也可以知道原因:dll 能提升的效果太小了,或者说,webpack4 太牛了,打包性能大大超越以前。

时间