Webpack4学习笔记(二)——代码分割(单入口) - 简书

这是一个关于Webpack4的文章系列,其中包含以下文章:

本系列文章源代码:

git clone https://github.com/davenkin/webpack-learning.git
    • *

上一篇文章中,我们讲到了Webpack4的基本使用,本文将讲到单入口项目的代码分割。

欢迎访问本文github源代码)。

Webpack会在以下三种情况下对代码进行分割:

  • 入口文件,每个入口文件将有单独的一次代码分割;
  • 使用SplitChunksPlugin插件;
  • 异步加载,比如使用import()

代码分割(Code Split)主要出于两种原因:一是减少代码重复,比如在多页面的应用中,如果多个页面都同时引用了某些module的情况;二是支持缓存,比如第三方库通常是不会怎么变的,将他们单独抽离出来有利于浏览器缓存。

具体来讲,在做分割时,我们通常会针对以下情况进行处理:

  • 为第三方依赖库(vendor)单独打包
  • 为webpack自己的runtime代码(manifest)单独打包
  • 为公共业务代码单独打包
  • 为异步加载的module单独打包

Webpack4默认分割配置

Webpack4放弃了CommonsChunkPlugin,转而使用SplitChunksPlugin,并通过内置的optimization配置段进行配置,production模式下默认配置请参考该插件的官方文档,这里列出重要的几项:

  • 默认只对两种情况进行分割:一是异步加载的module,二是被其他chunk引用次数大于等于2的module
  • 默认生产chunk最小为30k
  • 默认有两个cacheGroup,一个为vendors用于处理第三方依赖库;一个是default(处理当module被引用等于或超过2次时情况)

由于一个module有可能同属于多个cacheGroup,因此可以通过设置某个cacheGroup的优先级(priority)来解决,priority值越大,表示优先级越高,也即会优先其作用。Webpack的两个默认cacheGroup的优先级都被设置成了负数,而我们自定义的cacheGroup的默认priority为0,因此可以初步保证自定义的cacheGroup总会优先于默认的起作用。

Webpack默认配置下的代码分割

为了模拟真实项目,创建项目结构如下:

  • index.js依赖于A-module,axios库以及mathjs库,并异步依赖于lodash库(async-lodash)和underscore库(async-underscore)
  • index.js异步依赖于B-module(async-b)和C-module(async-c)
  • B-module和C-module都依赖于D-module
  • C-module还依赖于E-module

在默认配置下,运行cnpm run build,得到输出如下:

distribution/
├── async-b.a6a6717b3a9e94564a8f.js
├── async-c.cad9d85f1f375c3b56d7.js
├── bundle-analyzer-report.html
├── index.html
├── main.fa1c3667353182bc989d.js
├── vendors~async-lodash.d98165c58c58f225c6fd.js
└── vendors~async-underscore.44ac919350197fdd72c7.js

可以看出,B-module和C-module由于是index.js异步引入的,因此分别为其创建了一个输出文件(async-b.a6a6717b3a9e94564a8f.jsasync-c.cad9d85f1f375c3b56d7.js);对于异步引入的lodash和underscore而言,由于来自于第三方库,webpack对第三方库有默认的配置(配置有名为vendors的cacheGroup),因此webpakc会为每个第三方库单独默认生成对应的异步加载文件(vendors~async-lodash.d98165c58c58f225c6fd.jsvendors~async-underscore.44ac919350197fdd72c7.js)。main.fa1c3667353182bc989d.js中包含了所有非异步加载的模块,包含了第三方库axios和MathJS,以及我们自己的A-module。生成的bundler报告(bundle-analyzer-report.html)如下:

bundle分析报告

定制化代码分割

以上在默认配置下生成的bundle有以下缺点:

  1. webpack本身的runtime代码没有分离出来
  2. main.fa1c3667353182bc989d.js既包含了第三方库,有包含了我们自己的代码
  3. B-module和C-module同时依赖了D-module,但是D-module在B-module和C-module中重复存在
  4. webpack默认通过数字编号给module起名,如果module发生增减,会导致包含第三方库在内的bundle都会发生改变

对于第1点,可以通过webpack自带的runtimeChunk配置解决:

...
optimization: {
        runtimeChunk: {
            "name": "manifest"
        },
...

此时,webpack会为runtime代码单独生成名为manifest-*.js的文件。

对于第2点,我们需要将所有的第三方库抽离出来。前文讲到,webpack默认情况下只会对异步加载的第三方库进行分割,此时我们需要修改一下配置:

...
splitChunks: {
            chunks:'all'
}
...

将chunks值改为all(先前的默认值为async),表示对所有的第三方库进行代码分割(包括async和initial)。此时生产的代码中多了一个vendors~main.*.js文件。

对于第3点,需要新加一个cacheGroup,该cacheGroup对于被引用
此时大于等于2的module进行分割:

...
cacheGroups: {
                common: {
                    minChunks: 2,
                    name:'commons',
                    chunks: 'async',
                    priority: 10,
                    reuseExistingChunk: true,
                    enforce: true
                }
            }

...

在以上配置中,minChunks:2表示被引用次数大于等于2的module符合该cacheGroup的条件;name:'commons'表示将所有符合条件的module都放到同一个名为commons的文件中,如果不配置此项,webapck会默认根据module被引用情况生成多个bundle文件,webpack官网其实并不建议设置name属性,原文如下:

When assigning equal names to different split chunks, all vendor modules are placed into a single shared chunk, though it's not recommend since it can result in more code downloaded.

chunks: 'async',表示通过异步加载的module符合条件,这里笔者有些不解:B-module和C-module虽然本身是通过async引入的,但是他们对D-module的引用是通过initial的方式引入的,因此按理应该设置成chunks: 'initial‘才对,然而事实上这里必须设置成async,D-module才会独立出来。

priority: 10,表示优先处理,因为webpack默认的两个cacheGroup的优先级为负数。

此时,运行cnpm start build的输出结果中多了一个commons.*.js文件。

对于第4点,加入HashedModuleIdsPlugin插件即可:

...
const webpack = require('webpack');
...
   new webpack.HashedModuleIdsPlugin()
...

综合以上,对于单入口项目来说,建议配置如下:

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

function resolve(dir) {
    return path.join(__dirname, '..', dir)
}


module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'distribution')
    },
    //use inline-source-map for development:
    devtool: 'inline-source-map',

    //use source-map for production:
    // devtool: 'source-map',
    devServer: {
        contentBase: './distribution'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [resolve('src')],
                exclude: [resolve('node_modules')]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['distribution']),
        new BundleAnalyzerPlugin({
            openAnalyzer: false,
            analyzerMode: 'static',
            reportFilename: 'bundle-analyzer-report.html'
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new webpack.HashedModuleIdsPlugin()

    ],
    optimization: {
        runtimeChunk: {
            "name": "manifest"
        },
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                common: {
                    minChunks: 2,
                    name: 'commons',
                    chunks: 'async',
                    priority: 10,
                    reuseExistingChunk: true,
                    enforce: true
                }
            }
        }
    }

}
;

运行cnpm run build输出如下:

distribution/
├── async-b.e367719620c9ca8ce61f.js
├── async-c.ed6ccbf81ee739e1680d.js
├── bundle-analyzer-report.html
├── commons.69d2ac742d5253325cb1.js
├── index.html
├── main.d144c2daa62efff6dcc9.js
├── manifest.20a9103ae59dd6003f45.js
├── vendors~async-lodash.d810246d214be5df29ec.js
├── vendors~async-underscore.7ca24e0a09ca4a78e339.js
└── vendors~main.5016e1c2cb426d8b4eef.js

生产的bundle报告(bundle-analyzer-report.html)如下:

bundle报告

下一篇文章中,我们将讲到多入口项目的代码分割。


Original url: Access
Created at: 2019-05-05 16:58:23
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论