持久缓存
使用 webpack 构建工程的时候, 我们往常会把功能不同的代码打包到不同的包里 (如 lib,vendor, 业务代码). 而持久缓存的目的就是每一次更新线上代码的时候, 尽可能使内容未做更改的模块的名字和之前保持一致.
使用 webpack 实现持久缓存主要需要解决:
1. webpack runtime 代码分离
2. 稳定 moduleID
3. 稳定 chunkID
基本配置
webpack 有提供 2 种 hash 命名的方式 ,
1. [hash]: 整次 build 生成一个唯一的 hash 值, 赋给所有生成的文件.
2. [chunkhash]: 每个文件会根据其内容生成不同的 hash 值.
显然, 为了将文件名和内容相关联, 应该使用 chunkhash.
webpack 配置
业务代码
build 结果
这样配置虽然可以实现持久缓存, 但是把所有的代码都打到了一起 (vendor, 业务代码等).
Webpack-Runtime
使用 CommonsChunkplugin 将 vendor 单独打包:
vendor 打包配置
build 结果
现在如果我对业务代码进行更改:
更新后的业务代码
按道理 vendor 已经单独打包, 改变业务代码并不应该改变 vendor 的 hash 值, 然而
新生成的 vendor 名变了
原因是 CommonsChunkplugin 把 vendor 单独打出来的时候, 还会将 webpack 自己生成的一部分 runtime 代码一起打进 vendor. 如下图, runtime 里牵扯到 chunkid 等容易频繁变更的元素, 所以当业务代码发生变化的时候, runtime 代码也会变.
这个问题也容易解决, 再写一层 CommonsChunkplugin 把 runtime 代码单独打出来 (CommonsChunkplugin 会把 runtime 的代码打到配置指定的最后一个 chunk 里):
单独打包 runtime 代码
vendor, 业务代码, runtime 代码都单独打包
现在再更改业务代码, 业务代码和 manifest 的内容会变, vendor 文件的内容不会受影响.
ModuleID
然而还没完, 当我们在业务代码里增加一个 entry,vendor 的 hash 值又发生了变化.
增加一个 entry
增加 entry 后 build 结果
造成这个问题的原因是当我们加入一个新 entry 的时候, 会在业务代码里新增一个 module, 而 webpack 默认会依次用整数给这些 module 命名. 比如说当只有一个 entry 的时候, 业务代码里定义了 module: 0,1,2,3.vendor 里定义了 module 4,5. 增加一个 entry 后: 业务代码里会定义: 0,1,2,3,4. vendor 里定义 module 5,6.
一个 entry 时的 vendor 代码头部
二个 entry 时的 vendor 代码头部
可见, 要生成稳定的 chunkhash 值, 首先必须解决 moduleID 的问题.
NamedModulesPlugin & HashedModuleIdsPlugin
NamedModulesPlugin : 使用文件的相对路径作为 moduleID
相对路径代替整数
不过也带来 2 个问题:
1. 用相对路径代替数字, 文件变大了
2. 相对路径暴露了
HashedModuleIdsPlugin : 主要就是为了解决以上 2 个问题, 它对相对路径进行一个 md5 的摘要, 不仅避免文件过大, 也隐藏了路径.
ChunkID
有时候一些模块可能在页面初始化的时候并用不到, 可能会在之后的过程中 (比如用户点击事件) 才会用到, 这一类模块可以通过动态 import() 来引入.
动态引入 foo
动态引入后的 build 结果
可以看到, 动态引入的代码会被单独打包到 chunks / 里的文件里, 而且 vendor 的值再次发生了改变. 造成这个的原因是和 moduleID 类似, webpack 默认使用整数作为 chunkID, 并且异步加载的 chunk 会先被赋值. 也就是说在没有动态引入之前, vendor 的 chunkID 是 0, 动态引入之后, vendor 文件的 chunkID 变为了 1, 所以造成了内容变化.
NamedChunksPlugin: 这个插件会用 chunk 的字符串 name 代替整数作为 chunkID. 不过要注意的是, 动态生成的 chunk 并没有名字, 所以需要手动给取个名字.
配置
来源: http://www.jianshu.com/p/d214138899c9