如何将 Webpack 4 SplitChunksPlugin 与 HtmlWebpackPlugin 一起用于多页应用程序?

Posted

技术标签:

【中文标题】如何将 Webpack 4 SplitChunksPlugin 与 HtmlWebpackPlugin 一起用于多页应用程序?【英文标题】:How to use Webpack 4 SplitChunksPlugin with HtmlWebpackPlugin for Multiple Page Application? 【发布时间】:2018-11-21 00:43:54 【问题描述】:

我正在尝试利用 SplitChunksPlugin 为 MPA 中的每个页面/模板生成单独的包。当我使用 htmlWebpackPlugin 时,我会为每个页面获取一个 html 文件,其中包含指向正确包的脚本标记。这太棒了!但是,我遇到的问题是我的供应商文件。我希望单独的 html 文件仅指向他们需要的供应商捆绑包。当 SplitChunksPlugin 创建多个供应商捆绑包时,我无法让每个单独的 html 文件指向正确的供应商捆绑包。生成的捆绑包是:

home.bundle.js
product.bundle.js
cart.bundle.js
vendors~cart~home~product.bundle.js
vendors~cart~product.bundle.js

所以基本上 home 模板应该引用 home.bundle.js、vendors~cart~home~product.bundle.js,而不是第二个 vendor bundle。只有购物车和产品模板应该引用这两个供应商捆绑包。我正在使用 HtmlWebpackPlugin 的 chunks 选项,但除非我像这样明确引用它的名称,否则无法让它提取正确的供应商包:

chunks: ['vendors~cart~home~product.bundle','home']

但这有点违背了动态呈现脚本标签的目的。我尝试创建一个供应商入口点,但这将我所有的供应商聚集在一起。 我缺少一些简单的配置吗?

我的 webpack.config.js:

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Visualizer = require('webpack-visualizer-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = 
    mode: 'development',
    devtool: 'cheap-module-eval-source-map',
    entry: 
        home: './src/js/page-types/home.js',
        product: './src/js/page-types/product.js',
        cart: './src/js/page-types/cart.js'
    ,
    output: 
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist/js')
    ,
    optimization: 
        splitChunks: 
            chunks: 'all'
        
    ,
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new Visualizer(),
        new HtmlWebpackPlugin(
            filename: 'home.html',
            chunks: ['vendors','home']
        ),
        new HtmlWebpackPlugin(
            filename: 'product.html',
            chunks: ['vendors','product']
        ),
        new HtmlWebpackPlugin(
            filename: 'cart.html',
            chunks: ['vendors~cart~product','cart']
        ),
    ], ...

我的 js 模块:

/* home.js */
    import jQuery from 'jquery';
    import 'bootstrap';

cart 和 product 也引用了 react 库:

/* cart.js */
    import jQuery from 'jquery';
    import 'bootstrap';
    import React from 'react';
    import ReactDOM from 'react-dom';

 

/* product.js */
    import jQuery from 'jquery';
    import 'bootstrap';
    import React from 'react';
    import ReactDOM from 'react-dom';

示例html输出home.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
  <script type="text/javascript" src="home.bundle.js"></script></body>
</html>

【问题讨论】:

不是每个块都会有 react、jquery 和 bootstrap 吗?理想情况下,我们希望它们位于单独的供应商文件中,这样它就不会被复制到每个块中? @anvarik 非常好的问题。我知道我最初有同样的问题,因为我们不希望浏览器不得不一遍又一遍地重复下载库。这将与我们试图实现的性能背道而驰。幸运的是,webpack 已经通过 SplitChunksPlugin 自动处理了这个问题。它会智能地将您的所有供应商拆分为单独的捆绑包,并根据您导入它们的方式将一些供应商组合成一个捆绑包。您可以在此处查看相关文档:webpack.js.org/guides/code-splitting/#splitchunksplugin 【参考方案1】:

一种选择是手动创建您的供应商块,然后将页面所需的任何块包含在HtmlWebpackPluginchunks 选项中。

webpack.config.js:

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Visualizer = require('webpack-visualizer-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = 
    mode: 'development',
    devtool: 'cheap-module-eval-source-map',
    entry: 
        home: './src/js/page-types/home.js',
        product: './src/js/page-types/product.js',
        cart: './src/js/page-types/cart.js'
    ,
    output: 
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist/js')
    ,
    optimization: 
        splitChunks: 
            cacheGroups: 
                'vendor-bootstrap': 
                    name: 'vendor-bootstrap',
                    test: /[\\/]node_modules[\\/](jquery|bootstrap)[\\/]/,
                    chunks: 'initial',
                    priority: 2
                ,
                'vendor-react': 
                    name: 'vendor-react',
                    test: /[\\/]node_modules[\\/]react.*?[\\/]/,
                    chunks: 'initial',
                    priority: 2
                ,
                'vendor-all': 
                    name: 'vendor-all',
                    test: /[\\/]node_modules[\\/]/,
                    chunks: 'initial',
                    priority: 1
                ,
            
        
    ,
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new Visualizer(),
        new HtmlWebpackPlugin(
            filename: 'home.html',
            chunks: ['vendor-bootstrap', 'vendor-all', 'home']
        ),
        new HtmlWebpackPlugin(
            filename: 'product.html',
            chunks: ['vendor-bootstrap', 'vendor-react', 'vendor-all', 'product']
        ),
        new HtmlWebpackPlugin(
            filename: 'cart.html',
            chunks: ['vendor-bootstrap', 'vendor-react', 'vendor-all', 'cart']
        ),
    ], ...

vendor-all 块用于捕获未包含在其他块中的任何其他供应商库。

【讨论】:

很好的尝试。但我永远不会为我的项目推荐这种方法。维护这些块和模块依赖会很烦人。 从长远来看这很难维护,html-webpack-plugin 的 4.x 版本足够智能,无需进一步配置即可在模板中包含正确的块集,这是我想要的推荐使用。 @dennisbot 是的,试过了,像魅力一样工作。【参考方案2】:

使用 html-webpack-plugin 的第 4 版(现在处于测试阶段),并且只在 chunks 选项中包含入口块。

npm i -D html-webpack-plugin@next

module.exports = 
    new HtmlWebpackPlugin(
        filename: 'home.html',
        chunks: ['home']
    ),
    new HtmlWebpackPlugin(
        filename: 'product.html',
        chunks: ['product']
    ),
    new HtmlWebpackPlugin(
        filename: 'cart.html',
        chunks: ['cart']
    ),
;

这将自动包含相关的块。

【讨论】:

我实际上在 HtmlWebpackPlugin 版本 3 中扩展了一个方法来解决这个问题,但我不喜欢我的解决方案。对于另一个项目,我使用了这个解决方案。 @nilptr 这与新版本 4 测试版完美配合。谢谢! @Dave 对于那些宁愿仍然使用 HtmlWebpackPlugin 3 的好奇的少数人,您介意给出您的解决方案的概要作为答案吗?我认为管理层不会太喜欢我使用测试版软件。

以上是关于如何将 Webpack 4 SplitChunksPlugin 与 HtmlWebpackPlugin 一起用于多页应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

错误:webpack.optimize.CommonsChunkPlugin 已被移除,请改用 config.optimization.splitChunks

前端资讯Webpack 4 将移除 CommonsChunkPlugin

Webpack项目优化之CDN加速Gzip压缩和SplitChunks拆分

webpack 4 - 用于多个条目的拆分块插件

Webpack splitChunks 插件 - 为啥设置块的优先级使其初始化异步?

webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks ins