lmk123 / blog

个人技术博客,博文写在 Issues 里。

Home Page:https://github.com/lmk123/blog/issues

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

在 Webpack 中分离 vendor 与 app

lmk123 opened this issue · comments

当我们开发一个单页应用时,常见的优化做法是生成两个文件:

  • vendor.js:包含一些第三方依赖,例如 jQueryVue
  • app.js:包含项目代码

然后给这两个文件根据文件内容计算出一个 hash 加在文件名中,并配置一个长达一年的 Cache Control,这样能大大加快应用的访问速度。

因为 vendor.js 的内容基本上很少更新,所以下一次我们更改了 app.js 的内容时,vendor.js 仍然在浏览器的缓存中,那么用户就只需要重新下载 app.js 了。

但为了在 Webpack 中做到这一点,真是费了不少功夫。

为了将项目中的第三方依赖代码抽离出来,官方文档上推荐使用 CommonsChunkPlugin

当我们在项目里实际使用之后,发现一旦更改了 app.js 内的代码,vendor.js 的 hash 也会改变,那么下次上线时,用户仍然需要重新下载 vendor.js 与 app.js——这样就失去了缓存的意义了。

在网上搜索一番后,发现这是 Webpack 的一个 "Bug": webpack/webpack#1315

webpack 内部使用数字作为模块的 id,一旦 app.js 的内容发生改变,就会引起 vendor.js 内模块 id 的变化,所以导致了这个 bug 发生。

在刚才那个 issue 里,有人推荐使用 webpack-md5-hash,所以我也试了试,发现在更改了 app.js 的内容后,vendor.js 的 hash 真的没有改变。

看了下源码,原来 webpack-md5-hash 是根据 webpack 编译前的文件内容生成的 hash,所以才能做到 vendor.js 的 hash 不变。本来以为满心欢喜的找到了解决方案,但是在项目上线后却发现整个网页一片空白,并且控制台里报了一个错误。

检查之后才知道,虽然 vendor.js 的 hash 没变,但是 app.js 里的模块 id 仍然变了。举例来说,app.js 里使用的 id 为 40 的模块,在 vendor.js 里的模块 id 为 41,这样就导致 app.js 里使用了错误的模块,于是就报了错。

归根到底,这些问题都是由于模块 id 为数字导致的,所以后来我又试了试 NamedModulesPlugin。顾名思义,它能将模块 id 由数字的形式改为字符串(文件的相对路径)的形式。

虽然这个插件能解决我们的问题,但 app.js 的大小相较于以前大了几乎 60%。虽然 gzip 之后只比以前大了 15%,但我仍然觉得这有点不值得。

踩过这么多次坑之后,我发现根本原因在于 vendor.js 与 app.js 紧耦合了,它们之间的模块 id 会相互影响,所以,如果我们单独打包 vendor.js 和 app.js,问题就解决了。一开始我自己写了一个 CombinePlugin 用来单独打包 vendor.js,然后通过全局变量的方式抛给 app.js 使用,后来翻文档时,才发现官方已经提供了这么一个插件:DLLPlugin

可后来我又发现,虽然 DLLPlugin 解决了修改 app.js 时 vendor.js hash 会变的问题,但在 app.js 里异步加载的文件(使用 Webpack 的 code split 功能)的 hash 会因为 app.js 里新增/删除模块而改变。

最后,我使用了 Webpack 作者写给 webpack 2.0 的 HashedModuleIdsPlugin,发现它居然完整的解决了上面遇到的所有问题:

修改 app.js 或者在 app.js 里添加新的模块时,vendor.js 的 hash 不会变,在 app.js 里异步加载的 chunk 的 hash 也不会变。

我立刻将它应用在了我自用的 Webpack 模版中:lmk123/webpack-boilerplate

至此,心中的一块大石头总算落地了。

所以说 HashedModuleIdsPlugin 这个插件只适用于 webpack2.0 而不适用 1.0 的意思吗?

@liyanlong HashedModuleIdsPlugin 这个插件也适用于 1.0

@lmk123 好的 :)

commented

HashedModuleIdsPlugin 这个插件看起来会把 id 变为非数字呢?
webpack 1.x 真的可用的话 md5 内容当 id 不更好吗?