Webpack系列-提取公共资源

4,161 阅读4分钟

前言

其他公共资源主要有两个用户:
一、项目是多页应用,有多个入口,多页面之间有共同使用的模块、代码等。这时可以把多个页面的公共部分代码提取出来。
二、我们可以把整个C端项目提取出公共代码,这样用户在打开一个页面的时候,顺便加载缓存了公共文件,后续再打开其他页面的时候,可以直接命中公共文件的缓存,减少资源的下载。

如何提取公共代码

有两种方法都可以达到提取公共代码的目的,一是使用 SplitChunksPlugin 插件,另一种是使用 DllPlugin、DllReferencePlugin两个插件的组合完成。

在webpack3及之前我们使用 CommonsChunkPlugin 来抽取公共资源,webpack4后删除了 CommonsChunkPlugin,改用 SplitChunksPlugin。

SplitChunksPlugin

若不加以配置,webpack4 会自动使用 splitChunks 对资源块进行拆分,默认拆分规则如下:

  • 块被多处引用或者来自 node_modules 目录
  • 块大小大于 30kb(压缩前)
  • 按需加载的块并行请求数不能大于 5 个
  • 初始化加载的块并行请求数不能大于 3 个

也就是说 splitChunks 只会拆分被多次引用的块,或者该块来自于 node_modules 目录;并且该块在压缩前的大小应大于 30kb,否则不拆离。同时,最后两个条件对并行请求数做出了限制,这就意味着为了满足后两个条件,可能会产生体积较大的块。

自定义配置

splitChunksPlugin 插件提供了配置参数,使得开发者能够对包进行自定义配置和拆分块。

module.exports = {
    //...
    optimization: {
        splitChunks: {
            chunks: 'async',  // all async initial     选择对哪些块进行优化
            minSize: 30000,  // 被拆分的最小大小(压缩前)
            minChunks: 1,  // 被共享的最小次数
            maxAsyncRequests: 5,  // 最大按需求并行请次数
            maxInitialRequests: 3,  // 最大初始化并行请求数
            automaticNameDelimiter: '~',  // 自动命名分隔符
            name: true, // 自动为块命名
            cacheGroups: {
                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(查看源码没有发现默认值)
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    }
}

DllPlugin

相对于 splitChunksPlugin,DllPlugin 的配置要相对复杂些。
DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

DllPlugin 需要设置打包的配置文件,并先于项目打包将第三方组件打包;这个插件会创建一个只有dll的bundle,并且生成manifest.json的文件,这样来让 DllReferencePlugin 映射到相关的依赖上去。

DllReferencePlugin 是在 webpack 主配置文件中设置的, 这个插件把只有 dll 的 bundle(们)(dll-only-bundle(s)) 引用到需要的预编译的依赖。

dll应该只在生成环境使用,否则调试会有些问题。

配置webpack.dll.config.js

先配置webpack.dll.config.js 来打包生成公用JS

const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
    mode: 'production',
    devtool: false,
    entry: {
        react: [
            'react',
            'react-dom',
            'react-router-dom',
            'prop-types',
            'react-fastclick',
            'classnames',
        ],
        common: [
            'axios',
        ]
    },
    output: {
        path: path.join(__dirname, '../dist'),
        filename: 'lib/[name]_[hash:4].dll.js',
        library: '[name]_[hash:4]'
    },
    performance: {
        hints: false,
        maxAssetSize: 300000, //单文件超过300k,命令行告警
        maxEntrypointSize: 300000, //首次加载文件总和超过300k,命令行告警
    },
    optimization: {
        minimizer: [
            new UglifyJsPlugin({ 
                parallel: true  // 开启多线程并行
            })
        ]
    },
    plugins: [
        new webpack.DllPlugin({
            context: __dirname,
            path: path.join(__dirname, '../dist/lib', '[name]-manifest.json'),
            name: '[name]_[hash:4]'
        })
    ]
}

在package.json 中增加命令

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

当我们运行npm run dll命令后,就会把配置的公共代码或第三方包,先打包出来,并生成manifest.json文件。

运行npm run dll 会生成如下代码

dist
├── lib
│   ├── common-manifest.json
│   ├── common_cf4c.dll.js
│   ├── react-manifest.json
│   └── react_cf4c.dll.js

配置 webpack.pro.config.js

dll 文件生成完后,就可以配置webpack.pro.config.js文件, 除了需要使用 DllReferencePlugin 插件来映射dll库外,我们还需要用到 html-webpack-include-assets-plugin 插件把公共JS库插入到HTML中。

webpack.pro.config.js的配置:

const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin')
// ...
plugins: [
    new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require('../dist/lib/react-manifest.json')
    }),
    new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require('../dist/lib/common-manifest.json')
    }),
    new HtmlWebpackIncludeAssetsPlugin({
        assets: [{ path: 'lib', glob: '*.dll.js', globPath: 'dist/lib/' }],
        append: false
    })
]

package.json的配置

"build": "cross-env NODE_ENV=production node ./build/build.js",

在运行 npm run build 就可以打包线上包,因为提前把dll库打包好,后续再打包线上包的时候就节省了很多时间。