之前项目中引入了一个插件,把webpack的chunk id使用md5代替,使得其无序化,开发环境没有问题,但是在配合CDN使用的时候,却发现每次发布新版本之后,页面直接报错打不开,提示加载某某chunk的时候无法找到,清除缓存之后问题解决,然而把id无序化插件去掉之后就没问题了,看来问题和缓存和chunk id有关。

起因

webpack chunk

先说说webpack的chunk是怎么一回事,如下图:

有四个项目文件,分别为:file1.js, file2.js, file3.js, file4.js.

1
2
3
4
5
file1.js
file2.js

file3.js
file4.js

其中,file1依赖了file2file3依赖了file4,在webpack里面,通常情况下一个文件就是一个module,有时候也有一个文件多个module的情况,不过不多见,webpack会给每一个module分配一个id,这个id是自增的:

1
2
3
4
5
file1.js -> module1, id: 1
file2.js -> module2, id: 2

file3.js -> module3, id: 3
file4.js -> module4, id: 4

因为file1file2对于file3file4都没有什么依赖关系,所以webpack把这四个文件分成了两个chunk,同时分别给了一个id,也是自增的:

1
2
3
4
5
6
7
chunkA: ChunkId1
-> module1
-> module2

chunkB: ChunkId2
-> module3
-> module4

一个chunk就对应于webpack打包之后的文件,于是,打包之后我们现在有两个文件:

1
2
3
4
5
6
7
8
9
10

file1.js
filea.hash.js
file2.js

----------------------------------

file3.js
fileb.hash.js
file4.js

两个chunk所对应的打包后的文件名为: ${fileName}.${md5}.js.

CDN缓存

项目中使用了覆盖式的CDN缓存方式,也就是不同发布之间没有版本概念,而是直接覆盖原来的文件,因为文件名中有MD5值,所以只要文件内容改变,那么文件名也就改变。

好处是如果新版本没有变更的文件,可以让浏览器复用之前的缓存,达到最小更新目的。

问题出现

原本这一切都运行得不错,直到后来在项目中加入了一个webpack插件,用来做chunk id的无序化,至于加入这个插件的原因,是因为webpack自带的chunk id的生成规则:

webpack chunk id生成方式

webpack的chunk id是从1开始自增的,具体有自己的规则,比如根据引用顺序来给chunk id给定一个id,文件内容的改变并不会影响id,只要引用顺序不变,那么即使文件内容如何改变,都不会影响chunk id,多次打包之后的值都是一样的。

chunk id无序化

上面说chunk id从1开始自增,也就是不同项目之间,chunk id可能都是一样的,这就导致跨项目间调用chunk变得困难了,为了解决这个问题,就引入了一个插件,可以让chunk id无序化,实质上就是用MD5值来代替了chunk id。

为何在CDN场景下发布新版本会导致找不到chunk呢?

把chunk id用MD5值代替,看起来确实没有问题,在版本化的CDN场景下使用或者说在本地开发环境中使用确实是没有问题的,问题出在和覆盖式的CDN同时使用时候的问题。

问题根源

首先我们看chunka和chunkb打包之后的代码:

chunka:

1
2
3
...
__webpack__require.e( '1' ).then( '....' );
...

chunkb:

1
2
3
webpackJsonp([1],{
...
});

chunka中代码,打包前的代码为:

1
import( './file3' );

chunka中需要引用id为1的chunk,然后chunkb的代码中,首先用webpackJsonp方法定义了自己的chunk id为:1,这样,整个代码就联系起来了,也就能正常运行了。

MD5替代chunk id

如果使用MD5来替换原先的chunk id呢?

chunka:

1
2
3
...
__webpack__require.e( 'md51' ).then( '....' );
...

chunkb:

1
2
3
webpackJsonp(['md51'],{
...
});

看起来也没什么问题,反正只要引用的chunk id和声明的chunk id对应上就可以了。尝试修改了一些file3的内容,也就是chunkb的内容有了变更之后:

chunka:

1
2
3
...
__webpack__require.e( 'md52' ).then( '....' );
...

chunkb:

1
2
3
webpackJsonp(['md52'],{
...
});

chunkb的chunk id变成了: md52,同时引用和声明的chunk id都能对应上,运行没问题,那么问题出在哪里呢?我们看看webpack生成文件的文件名:

1
${fileName}.${md5}.js

因为文件名中有MD5值,所以当修改了chunkb之后,那么chunkb对应的文件名肯定就变更了,这个没问题,而chunka其实内容并没有变更,所以chunka的md5没有变化,也就是说chunka的文件名不变。

然而在引入chunk id无序化插件之前,chunkb的内容变化了,chunk id是不会变的,所以chunk b变化之后,打包后的chunka的文件名和内容都是没有变化的,使用浏览器缓存也不会有任何问题。

缓存带来的问题

chunk id变成MD5之后,chunkb的改变,会导致chunka的文件名没有变,然而chuna的代码其实是变化了的,因为引用chunkb的时候是要填写chunkb的chunk id的,因为chunk b的内容改变,其chunk id也跟着改变,浏览器根据文件名缓存,因为文件名没有变化,所以一直读取的都是以前的文件,也就导致了找不到某某chunk的根源,以前因为内容变化了,chunk id是不会变化的,所以使用并没有问题。

webpack MD5生成

也许你会说chunka的内容其实是变化了,为啥md5没有改变?其实一开始我也是这么想的,后来看过webpack代码之后就知道原因了:

上面是生成hash的事件,而替换代码则远远在这之后,其实之前也猜到个大概,从业务上讲其实chunka的内容并没有任何变更,hash也是不应该改变的。