近年来,单页应用(SPA)已经成为前端开发的主流,而 Webpack 作为一个强大的模块打包工具,也在这个领域大放异彩。在使用 Webpack4 构建 SPA 应用时,很多开发者会遇到一个棘手的问题——chunkHash 值不变,导致浏览器缓存失效,影响应用性能。本文将分享如何解决这个问题,以及一些相关的技术细节。
chunkHash 是什么?
在 Webpack 的构建中,chunkHash 是一个重要的概念。它是根据 chunk 的内容计算出来的哈希值,用于区分不同的 chunk。举个例子,我们可以通过以下配置让 Webpack 生成带有 chunkHash 的文件名:
output: { filename: '[name].[chunkHash].js' }
这样,每次构建时,Webpack 会自动根据文件内容生成唯一的 chunkHash 值,并将其作为文件名的一部分。
然而,在构建 SPA 应用时,我们往往会发现一个棘手的问题——chunkHash 值不变。这意味着,即使我们修改了代码,重新构建应用,但由于某些原因(后文会详细介绍),新生成的 chunk 文件名仍然和旧的一样。这就导致了浏览器缓存的失效,每次访问页面都需要重新请求这些 chunk 文件,大大降低了应用性能。
那么,如何解决这个问题呢?
解决方案
在绝大多数情况下,chunkHash 值不变的原因是因为 Webpack 认为 chunk 内容没有发生变化,因此不重新生成新的 chunk 文件。这往往是出于效率考虑,但也可能会导致我们上述的问题。因此,我们需要让 Webpack 强制重新生成新的 chunk 文件,即使它们的内容没有变化。这可以通过以下方式实现:
方案一:使用 NamedChunksPlugin 插件
我们可以通过在 Webpack 配置中使用 NamedChunksPlugin 插件,来给每个 chunk 设置一个唯一的名称,这样就能确保每次构建时都会生成新的 chunk 文件。具体配置如下:
-- -------------------- ---- ------- ----- - ----------------- - - ------------------- -------------- - - ------- - --------- ----------------------- -- -------- - --- ----------------------- -- - -- ------------ - ------ ----------- - ----- ---- - -------------------- ----- ---------- - -------------------------------------- - -- ----------------- ------ ---------------------- -- - --
这里的代码主要作用是生成一个唯一的 chunk 名称,如果原本的 chunk 有 name 属性,则直接使用它。否则,自动生成一个唯一的名称,以保证每次构建时都能生成新的文件。
方案二:使用 HashedModuleIdsPlugin 插件
除了 NamedChunksPlugin 插件,我们还可以使用另一个插件——HashedModuleIdsPlugin 来实现相同的效果。它的原理是给每个模块生成一个 id,然后根据这个 id 计算出一个唯一的 hash 值来替代 chunkHash。
-- -------------------- ---- ------- ----- - --------------------- - - ------------------- -------------- - - ------- - --------- ----------------------- -- -------- - --- ----------------------- - --
这个插件会替代 chunkHash,所以我们需要使用 [name].[chunkHash].js
的文件名格式来确保文件名的唯一性。
注意事项
虽然上述两种方案可以解决 chunkHash 值不变的问题,但我们还需要注意以下几点细节:
使用 MiniCssExtractPlugin 时的注意事项
当我们使用 MiniCssExtractPlugin 插件提取 CSS 时,Webpack4 会默认将 CSS 中的文件名替换为 chunkHash 值,以实现浏览器缓存。但这会导致与上述方案冲突,因为每个 CSS 文件的 chunkHash 值都是相同的,因此即使我们重新构建了应用,CSS 文件也不会更新。
为了解决这个问题,我们可以通过在 MiniCssExtractPlugin 的 options 中配置 ignoreOrder: true 来禁用默认的 chunkHash 值,然后加上我们自己的 hash 值,在 CSS 文件中手动替换掉文件名和 url 中的 hash。具体方法可以参考如下代码:
-- -------------------- ---- ------- ----- - --------------------- - - ------------------- ----- -------------------- - ----------------------------------- -------------- - - ------- - --------- ----------------------- -- ------- - ------ - - ----- --------- ---- - ---------------------------- ------------ - - - -- -------- - --- ------------------------ --- ---------------------- --------- --------------------------- ------------ ---- -- - --
上面的代码中,我们通过将 MiniCssExtractPlugin 的 filename 设置为 [name].[contentHash].css
,使用 contentHash 来代替 chunkHash,这样就与我们之前的方案不会冲突。然后,在 CSS 文件中手动替换掉文件名和 url 中的 hash,代码示例如下:
/* index.css */ body { background: url(../images/bg.jpg?hash=[contentHash]); }
使用动态 import 时的注意事项
如果我们使用了 Webpack4 的动态 import 功能来异步加载模块,那么也需要注意一个细节。由于动态 import 是异步加载的,它可能会在其他模块已经加载完毕的情况下才被加载。因此,在动态 import 中使用 chunkHash 时要格外小心,因为它可能并不是最新的。
为了解决这个问题,我们可以使用 ImportedOnDemandPlugin 插件来实现。这个插件会在动态 import 导入的模块中注入一个占位符,等到这个模块被加载时,再动态地替换占位符为正确的 chunkHash 值。使用方法如下:
首先安装这个插件:
npm i -D webpack-imported-on-demand
然后在 Webpack 配置中使用:
-- -------------------- ---- ------- ----- ---------------------- - ---------------------------------------------- -------------- - - ------- - --------- ----------------------- -- -------- - --- ------------------------ --------- ---------- -- - ------ ----------------------------------------- - -- - --
这里的代码中,我们使用了一个 generate 回调函数,它会在插件注入占位符时被调用。这里我们将使用动态 import 导入的模块的 chunkHash 作为占位符的初始值,然后在真正加载这个模块时,通过回调函数计算出正确的 chunkHash 值,并再次替换占位符。
总结
本文介绍了 Webpack4 中如何解决 chunkHash 值不变的问题,并详细介绍了两种常用的解决方案。同时,还针对使用 MiniCssExtractPlugin 和动态 import 时的注意事项进行了说明。希望这篇文章能够对大家在构建 SPA 应用时有所帮助。
示例代码
下面是完整的 Webpack 配置示例代码,包括上述方案和注意事项的实现:
-- -------------------- ---- ------- ----- - ----------------- - - ------------------- ----- - --------------------- - - ------------------- ----- -------------------- - ----------------------------------- ----- ---------------------- - ---------------------------------------------- -------------- - - ------- - --------- ----------------------- -- ------- - ------ - - ----- --------- ---- - ---------------------------- ------------ - - - -- -------- - --- ----------------------- -- - -- ------------ - ------ ----------- - ----- ---- - -------------------- ----- ---------- - -------------------------------------- - -- ----------------- ------ ---------------------- --- --- ------------------------ --- ---------------------- --------- --------------------------- ------------ ---- --- --- ------------------------ --------- ---------- -- - ------ ----------------------------------------- - -- - --
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/65316b777d4982a6eb31c18e