blog icon indicating copy to clipboard operation
blog copied to clipboard

webpack 长缓存相关阅读笔记

Open AnnVoV opened this issue 7 years ago • 0 comments

这是一篇阅读笔记,重点推荐阅读下面3篇文章: 1.Predictable long term caching with Webpack https://medium.com/webpack/predictable-long-term-caching-with-webpack-d3eee1d3fa31 2.手摸手合理地带你使用webpack4 上 https://segmentfault.com/a/1190000015919863 3.手摸手带你合理使用webpack4 下 https://segmentfault.com/a/1190000015919928

我的总结:

  • 首先要了解webpack 打包的基本原理,知道webpack里面使用自增的id 来标记模块, 可以回顾下之前的文章写一个mini webpack
  • 模块的增加或者删除会导致id的变化,所以引发了就算添加了[chunkhash]也未达到预期缓存的问题
  • webpack4 有了nameModulesPlugin 和 hashedModuleIdsPlugin,为什么会有两个,前者与后者的区别是什么呢?
  • 维护模块列表的映射配置是放置在了哪里的,我们是不是要抽取出来?

参考资料: 1.Predictable long term caching with Webpack (重点参考) https://medium.com/webpack/predictable-long-term-caching-with-webpack-d3eee1d3fa31 2.希望做浏览器长缓存?关于Webpack生成的Hash,你应该知道这些 https://segmentfault.com/a/1190000011980729 3.手摸手合理地带你使用webpack4 上 https://segmentfault.com/a/1190000015919863 4.手摸手带你合理使用webpack4 下 (重点参考) https://segmentfault.com/a/1190000015919928 5.webpack4 之SplitChunksPlugin https://juejin.im/post/5af15e895188256715479a9a 6.webpack4 持久缓存小结 https://juejin.im/post/5a705f6cf265da3e36418dc5 7.http1 与 http2 的差别?以及http2的优势 https://medium.com/@factoryhr/http-2-the-difference-between-http-1-1-benefits-and-how-to-use-it-38094fa0e95b 8.如何理解http2 的多路复用 https://segmentfault.com/q/1010000005167289 9.HTTP/2.0 相比1.0有哪些重大改进? https://www.zhihu.com/question/34074946

下面列举一些文中提到的一些问题:

在配置的路由模块里,我们新加一个异步模块,模块的chunkhash都发生了变化

因为新加入了一个模块,导致我们的模块的id顺序已经发生了变化了,所以导致我们打包出来的文件内容都发生了改变,所以我们需要使用另一个插件去替代id来命名的方式

解决方案: 1.在我们的路由里面,我们需要命名chunk 异步组件参考文档: https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

用这种注释的方式,我们最终得到的组件是以group-foo命名的,并且这3个异步组件打包到了一起

2.我们要开启namedChunks namedModules 让模块依赖不是用id去命名,这样就不会有添加一个组件,其他组件就收到影响的问题了 webpack4 默认在生产环境下开启了namedModules 和 namedChunks,webpack3 我们使用的是 webpack.NamedModulesPlugin 来解决这个问题的

IMAGE

当添加了externals 时,0号文件顺序变了,导致vendor又变了

查看文章: https://medium.com/webpack/predictable-long-term-caching-with-webpack-d3eee1d3fa31

webpack4 下面几个配置的区别

  • RuntimeChunk(manifest)
  • Module vs Chunk
  • HashedModuleIdsPlugin
  • NamedModulesPlugin

RuntimeChunks

将模块的映射列表从app.js 中抽取出来了,只要设置

{
  runtimeChuk: true
}

其实我们发现打包生成的 runtime.js非常的小,gzip 之后一般只有几 kb,但这个文件又经常会改变,我们每次都需要重新请求它,它的 http 耗时远大于它的执行时间了,所以建议不要将它单独拆包,而是将它内联到我们的 index.html 之中(index.html 本来每次打包都会变)。

这里我选用了 script-ext-html-webpack-plugin,主要是因为它还支持preload和 prefetch,正好需要就不想再多引用一个插件了,你完全可以使用 inline-manifest-webpack-plugin或者 assets-webpack-plugin等来实现相同的效果。

HashedModuleIdsPlugin

这个之前也提到了,我们的模块的id 是自增的, 这就导致了,添加一个模块或者删除一个模块,都会影响当前的id, 这就导致了引入一个文件就会使原先的缓存失效了 image

webpack4里有一个HashedModuleIdsPlugin 这个和 NamedModulesPlugin的区别 NamedModulesPlugin 直接使用了模块的路径作为原先的模块id, 而使用HashedModuleIdsPlugin 会使用hash过的路径作为模块id image

NamedModulesPlugin 和 HashedModuleIdsPlugin 原理是相同的,将文件路径作为 id,只不过没有把路径 hash 而已,适用于开发环境方便调试。不建议在生产环境配置,因为这样不仅会增加文件的大小(路径一般偶读比较长),更重要的是为暴露你的文件路径。

同理,我们在固定moduleId的时候,同时也要固定chunkId 因为我们增加chunk 或者减少chunk的时候,也会导致顺序乱掉

webpack4 内置的打包策略

它内置的代码分割策略是:

  • 新chunk 是否被共享或者来自node_modules模块
  • 新chunk 体积在压缩前是否> 30kb
  • 按需加载chunk的并发数量<=5
  • 页面初始加载并发数量<=3

所以有一些小的组件,即使被多个页面进行了引用,但它如果本身比较小(比如只有5k),还是会被分别打包到各自使用的组件中。 【理由】5k的代码在gzip压缩之后,可能只有1.5k左右,你为了这1.5k却要花费额外的http请求,是不值得的

但有些场景下这些规则可能就显得不怎么合理了。比如我有一个管理后台,它大部分的页面都是表单和 Table,我使用了一个第三方 table 组件,几乎后台每个页面都需要它,但它的体积也就 15kb,不具备单独拆包的标准,它就这样被打包到每个页面的 bundle 中了,这就很浪费资源了。这种情况下建议把大部分页面能共用的组件单独抽出来,合并成一个component-vendor.js的包(后面会介绍)。

优化分包策略

  • 基础类库 chunk-libs (eg. vue + vue-router + vuex + axios)

  • UI组件库 (单独抽出来的原因是它比较大,且升级概率高)

  • 自定义组件/函数 提取出app.js 和chunk-commons.js

    • 必要组件 路由表,侧边栏,导航栏,全局state 这些在入口文件中依赖的内容 打到app.js里
    • 非必要组件 指被大部分页面使用,但是在入口文件entry中未被引入的模块,比如管理后台中我们封装的chinese-text, select 或者tableList等组件,他们默认都会被分别打到每一个懒加载页面的chunk中,会造成不少的浪费
  • 低频组件 只会在一些特定的业务场景下使用,比如富文本编辑器,js-xlsx 一般这些库的大小也都>30kb 所以webpack默认会把他们独立打包成一个Bundle

  • 业务代码 这个就是由我们的路由,入口文件配置决定的

配置参考:

  // ...
  optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          // 类似原先的CommonChunksPlugin
          libs: {
              name: 'chunk-libs',
              test: /[\\/]node_modules[\\/]/,
              priority: 10, // 注意这里的权重
              chunks: 'initial', // ?这个配置不了解 有 all async initial
          },
          elementUI: {
              name: 'chunk-elementUI',
              priority: 20, // 权重要大于上面的10
              test: /[\\/]node_modules[\\/]element-ui[\\/]/,
          },
          commons: {
              name: 'chunk-commons',
              test: resolve("src/components"), // 可自定义拓展你的规则
              priority: 5,
              reuseExistingChunk: true
          }   
        }
    }
  }

博弈

优化是一个博弈的过程,是让首次加载快一点还是更多的利用cache。拆包的时候不要过分的追求颗粒化,如果什么都单独打成一个bundle,会导致请求过多受到阻塞。但是如果是在http2的情况下,那我们的打包策略又可以有新的改变了

AnnVoV avatar Nov 23 '18 09:11 AnnVoV