Webpack4学习笔记(三)——代码分割(多入口)

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

本系列文章源代码:

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

上一篇中,我们讲到了单入口文件的代码分割配置,本文将对多入口的项目进行代码分割。

欢迎访问本文github源代码

Webpack4默认配置

创建项目结构:

  • 一共3个入口文件index1.html,index2.html和index3.html
  • 每个入口文件对应一个js入口文件,分别为index1.js,index2.js和index3.js
  • index1.js依赖于A-module,F-module,G-module,axios,lodash
  • index2.js依赖于A-module,G-module,H-module,axios,jquery
  • index3.js依赖于A-module,F-module,H-module,lodash,jquery
  • index1.js异步依赖于B-module和C-module
  • index2.js异步依赖于B-module和lodash
  • index3.js异步依赖于C-module
  • B-module和C-module同时依赖于D-module
  • C-module依赖于E-module

配置多入口以及对应的HtmlWebpackPlugin如下:

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: {
        'index1': './src/index1.js',
        'index2': './src/index2.js',
        'index3': './src/index3.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/index1.html',
            filename: 'index1.html'
        }),
        new HtmlWebpackPlugin({
            template: './src/index2.html',
            filename: 'index2.html'
        }),
        new HtmlWebpackPlugin({
            template: './src/index3.html',
            filename: 'index3.html'
        }),
        new webpack.HashedModuleIdsPlugin()

    ]

}
;

此时运行cnpm run build,输出如下:

distribution/
├── async-b.3e6a567c8ad867591384.js
├── async-c.faa08dc1c722e8898326.js
├── bundle-analyzer-report.html
├── index1.c1bbbb105e63289639ac.js
├── index1.html
├── index2.08a076e70009b6c186ba.js
├── index2.html
├── index3.d500c907afa610e6d5f8.js
├── index3.html
└── vendors~async-lodash.d810246d214be5df29ec.js

回顾一下webpack的默认配置:

  • 只对async的module进行分割处理
  • 生成的分割文件要大于30k
  • 对第三方库(node_modules目录下的库)进行处理
  • 对引用大于等于2的module进行分割

可以看出,在默认情况下,webpack只对异步加载的库做了分割处理(生成了async-b.3e6a567c8ad867591384.jsasync-c.faa08dc1c722e8898326.jsvendors~async-lodash.d810246d214be5df29ec.js),而其他所有的module都打包到了对应的index.*.js文件中。

改进默认配置

以上默认配置当然不是我们想要的,此时我们可以对配置稍作修改,即将默认的两个cacheGroup的chunks改为all,表示同时对静态加载(initial)和动态加载(async)起作用,以及设置生产的chunk不受最大文件大小限制。

...
    optimization: {
        runtimeChunk: {
            "name": "manifest"
        },
        splitChunks: {
            chunks: 'all',
            minSize: 0
        }
    }
...

此时输出:

distribution/
├── async-b.fea9cc8ccb4c8c61f9c6.js
├── async-b~async-c.3290278114f34ed3a1cd.js
├── async-c.dab143479fff8a09776f.js
├── bundle-analyzer-report.html
├── index1.ebb1247940df7f5b54b7.js
├── index1.html
├── index2.d75b21ef39cced9d7c7a.js
├── index2.html
├── index3.823261ed02e3ede4c764.js
├── index3.html
├── manifest.0c4e4338d067666d041d.js
├── vendors~async-lodash~index1~index3.8cf11deabd773d32d46c.js
├── vendors~index1~index2.3bcd0a827baa477b6164.js
└── vendors~index2~index3.3c1c550be5409b787677.js

生成的bundle报告如下:

 

默认配置bundle报告

此时的解释如下:

  • async-b~async-c.3290278114f34ed3a1cd.js文件是新生产的,是由于B-module和C-module同时依赖于D-module产生的;
  • vendors~async-lodash~index1~index3.8cf11deabd773d32d46c.js包含了lodash库,是由于index1.js、index3.js静态依赖了lodash,同时index2.js中对lodash有异步依赖;
  • vendors~index1~index2.3bcd0a827baa477b6164.js包含了axios库,是由于index1和index2同时引用了axios;
  • vendors~index2~index3.3c1c550be5409b787677.js包含了jquery库,是由于index2和index3同时引用了jquery。

此时有以下问题:

  • 通过HtmlWebpackPlugin生成的三个index.html文件引用了所有的静态依赖,即index1引用了index2和index3的依赖,反之亦然,这当然没有达到分离页面的目的;
  • 三个index.js文件共享或者部分共享了A-module、F-module、G-module和H-module,但是共享模块还是重复出现在了生成的index.js文件中。

总结起来,我们希望:

  1. 提取webpack的runtime代码到单独的文件
  2. 所有静态依赖第三方库被分割到同一个文件中
  3. 所有动态依赖的第三方库分别分割到单独的文件,这样才能享受异步加载的好处,即只加载所需要的
  4. 所有动态依赖的自研发模块分别分割到单独的文件
  5. 被多次引用的自研发模块统一放到一个文件中,便于多个入口共享
  6. 配置缓存

定制化配置

为了全新配置webpack,我们先去除掉默认的2个cacheGroup:

...
optimization: {
        splitChunks: {
            cacheGroups: {
                default: false,
                vendors: false
            }
        }
    }
...

此时输出:

distribution/
├── async-b.307ba84dd97982af85a7.js
├── async-c.772e9f7104fef1be4aa1.js
├── async-lodash.2a8965ea7a405e680873.js
├── bundle-analyzer-report.html
├── index1.293bba8e0b6d369ab674.js
├── index1.html
├── index2.3b788dea9a926b1f8c76.js
├── index2.html
├── index3.182fc2fbba6ad900fe00.js
├── index3.html
└── manifest.a4f48ab9287b147792e1.js

可以自行分析以上输出结果的原因。

对于第1点,webpack自带支持:

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

对于2点——将所有静态的第三方依赖放到同一个文件中,加入新的cacheGroup:

...
vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    chunks: 'initial',
                    enforce: true,
                    priority: 10,
                    name:'vendor'
                },
...

此时输出结果为:

distribution/
├── async-b.746904f01aa1ee1a3ebf.js
├── async-c.ae11130d2a156dd0cd38.js
├── async-lodash.94774aad37c3e3b3ffd5.js
├── bundle-analyzer-report.html
├── index1.1a681451c20c863521af.js
├── index1.html
├── index2.75fd2ae406bc3e9c80e8.js
├── index2.html
├── index3.2346ee3070558cfd4514.js
├── index3.html
├── manifest.01548d0ad9752b82550b.js
└── vendor.1cebf8197aed820a040a.js

可以看到多处了一个vendor.1cebf8197aed820a040a.js,其中包含了所有共享的第三方依赖(包含jqueyr,lodash和axios):

自定义vendor时生成的bundle文件

 

这里需要注意的是,如果我们将name:'vendor'注释掉,得到输出为:

distribution/
├── async-b.e5df1370e07de32529d8.js
├── async-c.f895770163eede087e4b.js
├── async-lodash.bd05aecc5150c500c4ca.js
├── bundle-analyzer-report.html
├── index1.c414820b7d2a7e2c7ee1.js
├── index1.html
├── index2.6df28b7e4f8781eb350e.js
├── index2.html
├── index3.b9c0043a4618a996a90d.js
├── index3.html
├── manifest.8afba4dce338a6b3895e.js
├── vendor~index1~index2.3bcd0a827baa477b6164.js
├── vendor~index1~index3.46207ef9f2a7f266d467.js
└── vendor~index2~index3.7f319d43972c29e2233f.js

此时webpack会根据实际模块的共享关系,分开生成精确的chunk文件,比如如果A和B同时依赖于C,那么webpack会专门为A和B生成对应的chunk文件。

相比之下,当配置了name:'vendor'时,所有的依赖都在一个池子里,好处是指定了名字在稍后配置HtmlWebpackPlugin时好操作一下,缺点是如果一个chunk只依赖于池子中的某一小部分,那么也需要引用整个池子。webpack官网推荐不要配置name:'vendor',但是在多页面的场景下,笔者发现配置了name:'vendor'会更方便一下,下文在配置HtmlWebpackPlugin会讲到。

对于第3点——将所有动态依赖的第三方库放到各自单独的文件中,这一点是webpack的默认行为,因此我们不用做什么操作。

对于第4点——所有动态依赖的自研发模块分别分割到单独的文件,情况跟第3点一样,不用做操作。

对于第5点——将所有依赖的自研发模块放到同一个文件中,这里我们配置只有被依赖2次或者以上的模块才被放到common中,并且优先级低于vendor,配置如下:

...
common: {
                    chunks: "all",
                    minChunks: 2,
                    name:'common',
                    enforce: true,
                    priority: 5
                },
...

此时的输出中会多一个common.d5aa5a7133b80ff5743b.js文件,其中包含了A-module,F-module,G-moduel,H-module和D-module;而对于E-module,由于只被C-module依赖了一次,因此直接消化在了C-module对应的输出文件中。

请注意,这里的chunks的值应该设置成all,是因为B-module和C-module是动态加载的,而他们虽然静态引用了D-module,但是要使D-module出现在common中,只能设置chunks为all。

对于第6点,配置HashedModuleIdsPlugin

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

此时,输出的3个index.html文件为:
index1.html:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Webpack4代码分隔(多入口)-index1</title>
</head>
<body>
<script type="text/javascript" src="manifest.89d7160aa8609b204e02.js"></script>
<script type="text/javascript" src="vendor.d67e22b3a803d25120b1.js"></script>
<script type="text/javascript" src="common.692ea5e573281bbd10f0.js"></script>
<script type="text/javascript" src="index1.eb968553899c0c501f02.js"></script>
<script type="text/javascript" src="index2.f55a8bf4583a3fbd72dd.js"></script>
<script type="text/javascript" src="index3.7d3b7f38bf1680eb7d87.js"></script>
</body>
</html>

index2.html:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Webpack4代码分隔(多入口)-index2</title>
</head>
<body>
<script type="text/javascript" src="manifest.89d7160aa8609b204e02.js"></script>
<script type="text/javascript" src="vendor.d67e22b3a803d25120b1.js"></script>
<script type="text/javascript" src="common.692ea5e573281bbd10f0.js"></script>
<script type="text/javascript" src="index1.eb968553899c0c501f02.js"></script>
<script type="text/javascript" src="index2.f55a8bf4583a3fbd72dd.js"></script>
<script type="text/javascript" src="index3.7d3b7f38bf1680eb7d87.js"></script>
</body>
</html>

index3.html:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Webpack4代码分隔(多入口)-index3</title>
</head>
<body>
<script type="text/javascript" src="manifest.89d7160aa8609b204e02.js"></script>
<script type="text/javascript" src="vendor.d67e22b3a803d25120b1.js"></script>
<script type="text/javascript" src="common.692ea5e573281bbd10f0.js"></script>
<script type="text/javascript" src="index1.eb968553899c0c501f02.js"></script>
<script type="text/javascript" src="index2.f55a8bf4583a3fbd72dd.js"></script>
<script type="text/javascript" src="index3.7d3b7f38bf1680eb7d87.js"></script>
</body>
</html>

可以看到(正如前文所说),所有index.html文件中都包含了所有的依赖文件,这当然不是我们想要的,因此修改HtmlWebpackPlugin插件:

        new HtmlWebpackPlugin({
            template: './src/index1.html',
            filename: 'index1.html',
            chunks:['index1','manifest','vendor','common']
        }),
        new HtmlWebpackPlugin({
            template: './src/index2.html',
            filename: 'index2.html',
            chunks:['index2','manifest','vendor','common']

        }),
        new HtmlWebpackPlugin({
            template: './src/index3.html',
            filename: 'index3.html',
            chunks:['index3','manifest','vendor','common']

        }),

可以看到,HtmlWebpackPlugin依赖于chunk的名字,这也是为什么前文提到需要为cacheGroup设置name字段的原因,不然webpack会自动为我们生成很多动态的名字,这样无法配置HtmlWebpackPlugin。

最后,总结一下,对于多入口的项目来说,配置如下:

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: {
        'index1': './src/index1.js',
        'index2': './src/index2.js',
        'index3': './src/index3.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/index1.html',
            filename: 'index1.html',
            chunks: ['index1', 'manifest', 'vendor', 'common']
        }),
        new HtmlWebpackPlugin({
            template: './src/index2.html',
            filename: 'index2.html',
            chunks: ['index2', 'manifest', 'vendor', 'common']

        }),
        new HtmlWebpackPlugin({
            template: './src/index3.html',
            filename: 'index3.html',
            chunks: ['index3', 'manifest', 'vendor', 'common']

        }),
        new webpack.HashedModuleIdsPlugin()

    ],
    optimization: {
        runtimeChunk: {
            "name": "manifest"
        },
        splitChunks: {
            cacheGroups: {
                default: false,
                vendors: false,
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    chunks: 'initial',
                    enforce: true,
                    priority: 10,
                    name: 'vendor'
                },
                common: {
                    chunks: "all",
                    minChunks: 2,
                    name: 'common',
                    enforce: true,
                    priority: 5
                }
            }
        }
    }

}
;

下一篇文章中,我们将讲到CSS的处理。



作者:无知者云
链接:https://www.jianshu.com/p/741d9c98c395
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值