深究 webpack 代码分离优化(一)

出发版本 webpack 4.26.1

阅读之前

需要你事先了解一点关于 webpack 的基本配置方法和理论知识。

准备知识

  • 根据 webpack 官网介绍,第四版 webpack 已经删除掉了原 webpack.optimize.CommonsChunkPlugin,需要使用在 config 里面配置的 optimization 属性下的属性 splitChunks 替换之。所以如果你是用的是 webpack 第三版或更老的版本,请略过本文,直接搜索CommonsChunkPlugin。
  • 使用 html-webpack-plugin 辅助生成 html 模板。
    你可以选择参考我的文章

第一节、代码分离

我们先写打包前的源码,并梳理逻辑关系。
我们的文件分布如下

+ dist
+ node_modules
- src
   app1.js
   app2.js
   app3.js
   app4.js
   index.js
   index2.js
   index3.js
index.html
index2.html
index3.html
package.json
webpack.config.js

src 文件夹中 index.js 用于配合 index.html 入口; index2.js 用于配合 index2.html 入口; index3.js 用于配合 index3.html 入口。
逻辑关系如下:

//index.js
import './app1';
import './app2';
//index2.js
import './app1';
import './app2';
import './app3';
//index3.js
import './app3';
import './app4';
//app1.js
console.log("app1");
...    //八千行
console.log("app1");
alert("app1");
//app2.js
console.log("app2");
...    //八千行
console.log("app2");
alert("app2");
//app3.js
console.log("app3");
...    //八千行
console.log("app3");
alert("app3");
//app4.js
console.log("app4");
...    //八千行
console.log("app4");
alert("app4");

【解释】我们用简单的 console.log()来表示动作发生,以数量堆积来模拟一个计算量大的包,并最终用 alert 来标记执行结束。

1.默认效果

我们先不使用代码分离,设置3个入口文件并设置打包查看效果

配置 webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    entry: {
        index: "./src/index.js",
        index2: "./src/index2.js",
        index3: "./src/index3.js"
    },
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, "dist")
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: "index.html",
            template: "./index.html",
            chunks: ["index"]
        }),
        new HtmlWebpackPlugin({
            filename: "index2.html",
            template: "./index2.html",
            chunks: ["index2"]
        }),
        new HtmlWebpackPlugin({
            filename: "index3.html",
            template: "./index3.html",
            chunks: ["index3"]
        })
    ]
};

可以清楚滴看到,三个入口命名为 index/index2/index3 ,这将指示 webpack 至少要打包出3个文件来。并且下面的每一个 HtmlWebpackPlugin 模板都配置了相应的 chunks。 这表示的是将来生成的html文件将会依赖谁,如果不注明的话,这三个html模板都将会引入全部入口文件。
此时运行

 npm run build

生成文件如下(dist文件夹内部)

index.bundle.js          //296k
index.html               //1k
index2.bundle.js         //446k
index2.html              //1k
index3.bundle.js         //292k
index3.html              //1k

观察其打包结果: webpack 解析了每一个入口 js 文件的依赖关系并进行了按需分配,最终给每一个入口文件打包成一个文件(当中包含所有依赖)。对于小型项目这样已经可以了,但是如果是比较庞大的项目就会有进一步的优化空间。index 和 index2 都依赖了 app1 和 app2;index2 又有自己独需的 app3,假如一个请求同时要求 index 和 index2,那么毫无疑问,传输的两个 bundle 文件就会产生重复代码。这个影响是大型项目中明显的性能问题。所有已有必要进行代码分离。

2.使用 splitChunks 去重

使用此组件无需再安装,已经被放置在 webpack 当中(是 cli 里面还是 webpack 里面,这个我也不知道 -_-!)。所以直接配置就好。

配置 webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

//代码分离
module.exports = {
    entry: {
        index: "./src/index.js",
        index2: "./src/index2.js",
        index3: "./src/index3.js"
    },
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, "dist")
    },
    optimization: {
        splitChunks: {
            chunks: "async",
            // minSize: 30000,      //指定当文件包超过多大时应该被分离出来
            minSize: 1,             //这里我们期望的实验效果是只要是可以被分离出来的复用代码,都给我出来
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: "~",
            name: true,
            cacheGroups: {
                commons: {
                    name: "commons",
                    chunks: "initial",
                    minChunks: 2
                },
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    },

    plugins: [
        new HtmlWebpackPlugin({
            filename: "index.html",
            template: "./index.html",
            chunks: ["index", "commons"]
        }),
        new HtmlWebpackPlugin({
            filename: "index2.html",
            template: "./index2.html",
            chunks: ["index2", "commons"]
        }),
        new HtmlWebpackPlugin({
            filename: "index3.html",
            template: "./index3.html",
            chunks: ["index3", "commons"]
        })
    ]
};

这里我们将3个 html 模板的依赖都加了一个 “commons”。
执行

npm run build

生成文件如下(dist文件夹内部)

commons.bundle.js          //446k
index.bundle.js          //2k
index.html               //1k
index2.bundle.js         //2k
index2.html              //1k
index3.bundle.js         //143k
index3.html              //1k

现在你可以看到,凡是可以被抽离出来的复用代码都被打包进了commons.bundle.js ,形成了一个“库”,可供我们调取其中的方法。index3里面有着他自己独需的 app4,app4的内容就被封至index3 的内部了,所以比其他 index 文件大。这样我们的代码在理论上达到了最小化的“瘦身”效果,总体上来看,代码总量是最小的了,已经删去了所有不必要的重复。

但是做到这样还是不行,因为你会发现每当你想访问 index3 时,他要加载自己的 index3.html 和 index3.bundle.js 这不必解释,但是他为了要表达 app3 的动作就还要加载 commons.bundle.js这个库。但是加载这个库就同时加载了不必要的 app1 和 app2 动作,不使用的巨量代码带来的网速迟缓也是一个大型项目中的巨大问题。如何才能只加载当前入口需要的文件呢?我认为应该进一步分割代码。
下一节我将探索利用缓存以及 manifest 来提升应用性能?


作者知识水平有限,如有错误,敬请交流指正。
-------------结束线----------------

你可能感兴趣的:(深究 webpack 代码分离优化(一))