cheungseol / cheungseol.github.io

2 stars 0 forks source link

Webpack in Action (1) DLL #9

Open cheungseol opened 7 years ago

cheungseol commented 7 years ago

webpack 提供了一些 Plugins 来提升打包时的性能。

CommonsChunkPlugin

当 webpack 配置了多个入口文件时,如果这些文件都 require 了相同的模块,webpack 会为每个入口文件引入一份相同的模块,显然这样做,会使得相同模块变化时,所有引入的 entry 都需要一次 rebuild,造成了性能的浪费。

CommonsChunkPlugin 把相同的模块提取出来单独打包,从而减小 rebuild 时的性能消耗。

DLLPlugin 和 DllReferencePlugin

项目中常常会引入许多第三方 npm 包,尽管我们不会修改这些包,但是webpack 仍会要在每次 build 的过程中重复打包,消耗构建性能。

DLLPlugin 通过前置这些依赖包的构建,来提高真正的 build 和 rebuild 的构建效率。

例子:

比如我们的项目中使用了 react ,我们把 react 和 react-dom 打包成为 dll bundle。

DLLPlugin 需要一个单独的 webpack 配置文件:

webpack.dll.config.js

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

module.exports = {
  entry: {
    vendor: ['react', 'react-dom']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    /**
     * output.library
     * 将会定义为 window.${output.library}
     * 在这次的例子中,将会定义为`window.vendor_library`
     */
    library: '[name]_library'
  },
  plugins: [
    new webpack.DllPlugin({
      /**
       * path
       * 定义 manifest 文件生成的位置
       * [name]的部分由entry的名字替换
       */
      path: path.join(__dirname, 'dist', '[name]-manifest.json'),
      /**
       * name
       * dll bundle 输出到那个全局变量上
       * 和 output.library 一样即可。 
       */
      name: '[name]_library'
    })
  ]
};

用 webpack 运行 webpack.dll.config.js 文件后,就会在 dist 目录下生成 dll bundle 和对应的 manifest 文件


$ ./node_modules/.bin/webpack --config webpack.dll.config.js
Hash: 36187493b1d9a06b228d
Version: webpack 1.13.1
Time: 860ms
        Asset    Size  Chunks             Chunk Names
vendor.dll.js  699 kB       0  [emitted]  vendor
   [0] dll vendor 12 bytes {0} [built]
    + 167 hidden modules

$ ls dist
./                    vendor-manifest.json
../                   vendor.dll.js

manifest 文件的格式大致如下,由包含的 module 和对应的 id 的键值对构成:

{
  "name": "vendor_library",
  "content": {
    "./node_modules/react/react.js": 1,
    "./node_modules/react/lib/React.js": 2,
    "./node_modules/process/browser.js": 3,
    "./node_modules/object-assign/index.js": 4,
    "./node_modules/react/lib/ReactChildren.js": 5,
    "./node_modules/react/lib/PooledClass.js": 6,
    "./node_modules/fbjs/lib/invariant.js": 7,
...

在项目的 webpack 配置文件(不是上面的 webpack.dll.config.js )中通过 DLLReferencePlugin 来使用刚才生成的 DLL Bundle。

webpack.conf.js

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

module.exports = {
  entry: {
    'dll-user': ['./index.js']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].bundle.js'
  },
  // ----在这里追加----
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      /**
       * 在这里引入 manifest 文件
       */
      manifest: require('./dist/vendor-manifest.json')
    })
  ]
  // ----在这里追加----
};

执行 webpack,会在 dist 下生成 dll-user.bundle.js,约 700K,耗时 801ms:

./node_modules/.bin/webpack
Hash: 3bc7bf760779b4ca8523
Version: webpack 1.13.1
Time: 70ms
             Asset     Size  Chunks             Chunk Names
dll-user.bundle.js  2.01 kB       0  [emitted]  dll-user
   [0] multi dll-user 28 bytes {0} [built]
   [1] ./index.js 145 bytes {0} [built]
    + 3 hidden modules

打包出来的文件大小是 2.01K,耗时 70 ms,大大提高了 build 和 rebuild 的效率。

dll 和 external 的比较

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

module.exports = {
  entry: {
    'ex': ['./index.js']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].bundle.js'
  },
  externals: {
    // require('react')
    'react': 'React',
    // require('react-dom')
    'react-dom': 'ReactDOM'
  }
};
  1. 像是 react 这种已经打好了生产包的使用 externals 很方便,但是也有很多 npm 包是没有提供的,这种情况下 DLLBundle 仍可以使用。

  2. 如果只是引入 npm 包一部分的功能,比如 require('react/lib/React') 或者 require('lodash/fp/extend') ,这种情况下 DLLBundle 仍可以使用。

  3. 如果只是引用了 react 这类的话,externals 因为配置简单所以也推荐使用。

dll 和 entry vendor 的比较

vendor配置本质目的是为了跨页面加载库不必重复。 webpack的资源入口通常是以entry为单元进行编译提取,那么当多entry共存的时候,CommonsChunkPlugin的作用就会发挥出来,对所有依赖的chunk进行公共部分的提取,但是在这里可能很多人会误认为抽取公共部分指的是能抽取某个代码片段,其实并非如此,它是以module为单位进行提取。

参考

Optimizing Webpack build times and improving caching with DLL bundles

https://juejin.im/entry/5769f8dc128fe10057d2f4ae

Webpack的dll功能

彻底解决Webpack打包性能问题

http://dev.dafan.info/detail/329716?p=34-14

用 webpack 实现持久化缓存

cheungseol commented 6 years ago

在html 模板中自动引入打包文件

通常使用 webpack 打包的项目,会使用 HtmlWebpackPlugin 在模板中自动插入 script 标签,引入打包文件(script 脚本引入的路径是 output 中的 publicPath)。

new HtmlWebpackPlugin({
            title: 'test',
            template: '!!raw-loader!' + '/template.html',  // 模板文件路径
            filename: './server/views/index.ejs',           // 生成的文件路径,供 node serve 
            inject: 'body', 
            hash: false,
            minify: {
                removeComments: false,
                collapseWhitespace: false,
            },
}),
cheungseol commented 6 years ago

在html 模板中自动引入 dll 打包文件

有时候,会将 dll 文件放在 cdn 上,并给 dll 包名以hash值区分。为了在 html 模板文件中自动引入 dll 包文件,需要 AddAssetHtmlPlugin。

webpack.config.js

        new AddAssetHtmlPlugin({
            includeSourcemap: false, 
            filepath: path.resolve(__dirname, 'server/static/js/lib.*.js'),   // * 符号匹配 dll 哈希名称
        }),

webpack 编译时提示 ”...dll.js.map not found“, 需要配置插件选项:includeSourcemap: false,

dll.config.js

module.exports = {
    entry: {
        lib: ['react', 'react-dom', 'react-router-dom', 'react-router-redux', 'react-router-config', 'react-redux'],
    },
    output: {
        path: path.resolve(__dirname, 'server/public/js'),
        filename: '[name].[hash:8].js',   // 生成的 dll 文件以hash命名
        library: '[name]',
    },
    plugins: [
        new webpack.DllPlugin({
            path: './manifest.json',
            name: '[name]',
            context: path.resolve(__dirname, 'client/'),
        }),
    ],
    cache: true,
};

参考

add-asset-html-webpack-plugin

教你如何玩转webpack.DllPlugin

devtool: 'eval' causes vendor.dll.js.map error #13