sts 的js代码不变色_webpack优化之玩转代码分割和公共代码提取

4eaf19405cf0919c9c8397993bcf5d75.png

前言

开发多页应用的时候,如果不对webpack打包进行优化,当某个模块被多个入口模块引用时,它就会被打包多次(在最终打包出来的某几个文件里,它们都会有一份相同的代码)。当项目业务越来越复杂,打包出来的代码会非常冗余,文件体积会非常庞大。大体积文件会增加编译时间,影响开发效率;如果直接上线,还会拉长请求和加载时长,影响网站体验。作为一个追求极致体验的攻城狮,是不能忍的。所以在多页应用中优化打包尤为必要。那么如何优化webpack打包呢?

一、概念

在一切开始前,有必要先理清一下这三个概念:

  • module: 模块,在webpack眼里,任何可以被导入导出的文件都是一个模块。
  • chunk: chunk是webpack拆分出来的:
    • 每个入口文件都是一个chunk
    • 通过 import、require 引入的代码也是
    • 通过 splitChunks 拆分出来的代码也是
  • bundle: webpack打包出来的文件,也可以理解为就是对chunk编译压缩打包等处理后的产出。

二、问题分析

首先,简单分析下,我们刚才提到的打包问题:

  • 核心问题就是:多页应用打包后代码冗余,文件体积大。
  • 究其原因就是:相同模块在不同入口之间没有得到复用,bundle之间比较独立。

弄明白了问题的原因,那么大致的解决思路也就出来了:

  1. 我们在打包的时候,应该把不同入口之间,共同引用的模块,抽离出来,放到一个公共模块中。这样不管这个模块被多少个入口引用,都只会在最终打包结果中出现一次。——解决代码冗余。
  2. 另外,当我们把这些共同引用的模块都堆在一个模块中,这个文件可能异常巨大,也是不利于网络请求和页面加载的。所以我们需要把这个公共模块再按照一定规则进一步拆分成几个模块文件。——减小文件体积。
  3. 至于如何拆分,方式因人而异,因项目而异。我个人的拆分原则是:
  • 业务代码和第三方库分离打包,实现代码分割;
  • 业务代码中的公共业务模块提取打包到一个模块;
  • 第三方库最好也不要全部打包到一个文件中,因为第三方库加起来通常会很大,我会把一些特别大的库分别独立打包,剩下的加起来如果还很大,就把它按照一定大小切割成若干模块。

optimization.splitChunks

webpack提供了一个非常好的内置插件帮我们实现这一需求:`CommonsChunkPlugin`。不过在 webpack4 中`CommonsChunkPlugin`被删除,取而代之的是`optimization.splitChunks`, 所幸的是`optimization.splitChunks`更强大!

三、 实现

通过一个多页应用的小demo,我们一步一步来实现上述思路的配置。

demo目录结构:

|--public/
|   |--a.html
|   |--index.html
|--src/
|   |--a.js
|   |--b.js
|   |--c.js
|   |--index.js
|--package.json
|--webpack.config.js

代码逻辑很简单,`index`模块中引用了 `a` 和 `b` 2个模块,`a` 模块中引用了 `c` 模块和 `jquery`库,`b` 模块中也引用了 `c` 模块和 `jquery` 库,`c` 是一个独立的模块没有其他依赖。

index.js代码如下:

//index.js
import a from './a.js';
import b from './b.js';
function fn() {
    console.log('index-------');
}
fn();

a.js代码如下:

//a.js
require('./c.js');
const $ = require('jquery')
function fn() {
    console.log('a-------');
}
module.exports = fn();

b.js代码如下:

//b.js
require('./c.js');
const $ = require('jquery')
function fn() {
    console.log('b-------');
}
module.exports = fn();

c.js代码如下:

//c.js
function fn() {
    console.log('c-------');
}
module.exports = fn();

1. 基本配置

webpack先不做优化,只做基本配置看看效果。项目配置了2个入口,搭配`html-webpack-plugin`实现多页打包:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/index.js',
        a: './src/a.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html'
        }),
        new HtmlWebpackPlugin({
            template: './public/a.html',
            filename: 'a.html'
        })
    ]
}

在开发模式下运行webpack:

92a2fbc6257a944c2a273c4aebc2b847.png

可以看到,打包出两个html和两个体积很大的(300多K)的文件`a.js`,`index.js`。

进入dist目录检查js文件:

  • `a.js`里包含`c`模块代码和`jquery`代码
  • `index.js`里包含`a`模块、`b`模块、`c`模块和`jquery`代码

看,同样的代码`c`和`jquery`被打包了2遍。

2. 初步添加splitChunks优化配置

首先解决相同代码打包2次的问题,我们需要让webpack把`c`和`jquery`提取出来打包为公共模块。

在webpack配置文件添加splitChunks:

//webpack.config.js

optimization: {
    splitChunks: {
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial'
            }
        }
    }
}

- cacheGroups

  • `cacheGroups`是`splitChunks`配置的核心,对代码的拆分规则全在`cacheGroups`缓存组里配置。
  • 缓存组的每一个属性都是一个配置规则,我这里给他的`default`属性进行了配置,属性名可以不叫default可以自己定。
  • 属性的值是一个对象,里面放的我们对一个代码拆分规则的描述。

- name

提取出来的公共模块将会以这个来命名,可以不配置,如果不配置,就会生成默认的文件名,大致格式是`index~a.js`这样的。

- chunks

指定哪些类型的chunk参与拆分,值可以是string可以是函数。如果是string,可以是这个三个值之一:`all`,`async`,`initial`,`all`代表所有模块,`async`代表只管异步加载的, `initial`代表初始化时就能获取的模块。如果是函数,则可以根据chunk参数的name等属性进行更细致的筛选。

再次打包:

9b197a1b9c0ae84d04c6a18279ff7658.png

可以看到`a.js`,`index.js`从300多K减少到6点几K。同时增加了一个`common.js`文件,并且两个打包入口都自动添加了`common.js`这个公共模块:

4d52acdf02b633f0dea238a4ebdbbbe7.png

进入dist目录,依次查看这3个js文件:

  • `a.js`里不包含任何模块的代码了,只有webpack生成的默认代码。
  • `index.js`里同样不包含任何模块的代码了,只有webpack生成的默认代码。
  • `common.js`里有`a`,`b`,`c`,`index`,`jquery`代码。

发现,提是提取了,但是似乎跟我们预料的不太一样,所有的模块都跑到`common.js`里去了。

这是因为我们没有告诉webpack(`splitChunks`)什么样的代码为公共代码,`splitChunks`默认任何模块都会被提取。

- minChunks

`splitChunks`是自带默认配置的,而缓存组默认会继承这些配置,其中有个`minChunks`属性:

  • 它控制的是每个模块什么时候被抽离出去:当模块被不同entry引用的次数大于等于这个配置值时,才会被抽离出去。
  • 它的默认值是1。也就是任何模块都会被抽离出去(入口模块其实也会被webpack引入一次)。

我们上面没有配置`minChunks`,只配置了`name`和`chunk`两个属性,所以`minChunks`的默认值 `1` 生效。也难怪所有的模块都被抽离到`common.js`中了。

优化一下,在缓存组里配置`minChunks`覆盖默认值:

//webpack.config.js

optimization: {
    splitChunks: {
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2  //模块被引用2次以上的才抽离
            }
        }
    }
}

然后运行webpack

d32796904bdda2e7ee98a231fe5fb62b.png

可以看到有2个文件的大小发生了变化:`common.js`由314K减小到311K,`index.js`由6.22K增大到7.56K。

进入dist目录查看:

  • `a.js`里依然不包含任何模块的代码(正常,因为`a`作为模块被`index`引入了一次,又作为入口被webpack引入了一次,所以`a`是有2次引用的)。
  • `index.js`里出现了`b`和`index`模块的代码了。
  • `common.js`里只剩`a`,`c`,和`jquery`模块的代码。

现在我们把共同引用的模块`a`, `c`, `jquery`,从`a`和`index`这两个入口模块里抽取到`common.js`里了。有点符合我们的预期了。

3. 配置多个拆分规则

3.1 实现代码分离,拆分第三方库

接下来,我希望公共模块`common.js`中,业务代码和第三方模块jquery能够剥离开来。

我们需要再添加一个拆分规则。

//webpack.config.js

optimization: {
    splitChunks: {
    	minSize: 30,  //提取出的chunk的最小大小
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2,  //模块被引用2次以上的才抽离
                priority: -20
            },
            vendors: {  //拆分第三方库(通过npm|yarn安装的库)
            	test: /[/]node_modules[/]/,
                name: 'vendor',
                chunks: 'initial',
                priority: -10
            }
        }
    }
}

我给cacheGroups添加了一个vendors属性(属性名可以自己取,只要不跟缓存组下其他定义过的属性同名就行,否则后面的拆分规则会把前面的配置覆盖掉)。

- minSize

minSize设置的是生成文件的最小大小,单位是字节。如果一个模块符合之前所说的拆分规则,但是如果提取出来最后生成文件大小比minSize要小,那它仍然不会被提取出来。这个属性可以在每个缓存组属性中设置,也可以在splitChunks属性中设置,这样在每个缓存组都会继承这个配置。这里由于我的demo中文件非常小,为了演示效果,我把minSize设置为30字节,好让公共模块可以被提取出来,正常项目中不用设这么小。

- priority

priority属性的值为数字,可以为负数。作用是当缓存组中设置有多个拆分规则,而某个模块同时符合好几个规则的时候,则需要通过优先级属性priority来决定使用哪个拆分规则。优先级高者执行。我这里给业务代码组设置的优先级为-20,给第三方库组设置的优先级为-10,这样当一个第三方库被引用超过2次的时候,就不会打包到业务模块里了。

- test

test属性用于进一步控制缓存组选择的模块,与chunks属性的作用有一点像,但是维度不一样。test的值可以是一个正则表达式,也可以是一个函数。它可以匹配模块的绝对资源路径或chunk名称,匹配chunk名称时,将选择chunk中的所有模块。我这里用了一个正则`/[/]node_modules[/]/`来匹配第三方模块的绝对路径,因为通过npm或者yarn安装的模块,都会存放在node_modules目录下。

运行一下webpack:

f7d9e65718bb5f7af4316d2a85d3ff15.png

可以看到新产生了一个叫`vendor.js`的文件(name属性的值),同时`common.js`文件体积由原来的311k减少到了861bytes!

进入dist目录,检查js文件:

  • `a.js`里不包含任何模块代码。
  • `common.js`只包含`a`和`c`模块的代码。
  • `index.js`只包含`b`和`index`模块的代码。
  • `vendor.js`只包含`jquery`模块的代码。

现在,我们在上一步的基础上,成功从`common.js`里把第三方库`jquery`抽离出来放到了`vendor.js`里。

3.2 拆分指定文件

如果我们还想把项目中的某一些文件单独拎出来打包(比如工程本地开发的组件库),可以继续添加拆分规则。比如我的src下有个`locallib.js`文件要单独打包,假设`a.js`中引入了它。

//a.js
require('./c.js');
require('./locallib.js');  //引入自己本地的库
const $ = require('jquery')
function fn() {
    console.log('a-------');
}
module.exports = fn();

可以这么配置:

//webpack.config.js

optimization: {
    splitChunks: {
        minSize: 30,  //提取出的chunk的最小大小
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2,  //模块被引用2次以上的才抽离
                priority: -20
            },
            vendors: {  //拆分第三方库(通过npm|yarn安装的库)
            	test: /[/]node_modules[/]/,
                name: 'vendor',
                chunks: 'initial',
                priority: -10
            },
            locallib: {  //拆分指定文件
            	test: /(src/locallib.js)$/,
                name: 'locallib',
                chunks: 'initial',
                priority: -9
            }
        }
    }
}

我在缓存组下又新增了一个拆分规则,通过test正则指定我就要单独打包`src/locallib.js`文件,并且把优先级设置为-9,这样当它被多次引用时,不会进入其他拆分规则组,因为另外两个规则的优先级都比它要低。

运行webpack打包后:

f72f38e661ec41e551e76b72f496d03a.png

可以看到新产生了一个`locallib.js`文件。进入dist目录查看:

  • `a.js`里不包含任何模块代码。
  • `common.js`只包含`a`和`c`模块的代码。
  • `index.js`只包含`b`和`index`模块的代码。
  • `vendor.js`只包含`jquery`模块的代码。
  • `locallib.js`里只包含`locallib`模块的代码。

现在我们又在上一步的基础上独立打包了一个指定的模块`locallib.js`。

至此,我们就成功实现了抽离公共模块、业务代码和第三方代码剥离、独立打包指定模块。

对比一下,优化前,打包出来js一共有633KB:

c868883dc6190139c6ef74b2dce45987.png

优化后,打包出来js一共不到330KB:

4ba18be8488cbe1ff8ade97375354ae8.png

优化打包后的文件分类清晰,体积比优化前缩小了几乎50%,有点小完美是不是!击掌!这还只是我举的一个简单例子,在实际多页应用中,优化力度说不定还不止这么多。

小结

webpack很强大,以上只是冰山一角,但是只要掌握了上述`optimization.splitChunks`的核心配置,我们就可以几乎随心所欲地按照自己的想法来拆分优化代码控制打包文件了,是不是很酷?玩转代码拆分,你也可以!

如果觉得这些依然不能满足你的需求,还想更精(bian)细(tai)地定制打包规则,可以到SplitChunksPlugin | webpack查看更多配置。

欢迎交流~

本文的完整webpack配置和demo源码可以在这里获取:yc111/webpack-optimize-demo

--

欢迎转载,转载请注明出处:

webpack优化之玩转代码分割和公共代码提取​champyin.com
a1bff00b111c8f0a2e38cb2f7135fe3e.png

本文同步发表于:

webpack优化之玩转代码分割和公共代码提取 | 掘金

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值