热门搜索 :
考研考公
您的当前位置:首页正文

使用Webpack设计一个所有项目适用的分包配置

来源:东饰资讯网
  1. Webpack生成能够持久缓存的分包配置(即此文)
  2. 使用Service Worker缓存资源支持离线访问

现在大部分现代的前端工程里应该都会使用Webpack去构建项目。虽然Webpack十分强大,但也十分复杂,在不同场景,不同技术里配置都不一样,而且里面还包含这太多专业术语。所以在此文里,希望能帮助你:

  • 知道那种文件分割file-spliting策略最优于你的项目
  • 如何进行文件分割
  • Bundle Splitting -- 为SPA生成多个独立的包,以便于浏览器更好地缓存。
  • Code Splitting -- 在Vue和React中一般用为路由分割,把代码分成多个小块,动态加载当前页面需要使用的内容。

在大型应用中,静态资源持久缓存带来的效果提升会十分明显,想象你有一个2M的应用,分割成10个200k的包,每次更新内容只是其中一个包,用户只需要请求200k的数据即可,而不用每次更新都请求2M的数据。对流量的节省提升也是巨大的。

Let's code.

Bundle Splitting

⚠️ 在此文中Bundle Splitting都简称为包分割。

包分割的目的其实很简单,假如你用Vue-cli生成项目,那么构建出来的代码会有一个巨大的vendor包,假如用户每次访问都需要请求这个更新包,可想而知,每次都需要长时间的等待,和耗费巨大的流量。如果把这个包分成两个,用户每次访问只需下载更新的包,另一个则从浏览器缓存中获取。

Let's talk with data. 下面我们会使用表格去对比优化前后的不同及优化后的收益,所以我们需要锁定在一个固定的场景中,以便测试和分析缓存的收益

  • John在8周里每周都访问我们的网站
  • 我们每周都需要发版更新网站
  • 我们有一个任务列表页面需要每周迭代更新
  • 在第四周我们添加了一个npm package
  • 在第七周我们更新了所有npm package

基本配置

我们的项目是一个400KB左右的SPA,有一个main.js的入口文件,我们的Webpack配置看起来应该像以下这样的(下面的配置只显示主要配置)

const path = require('path')

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resovle(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  }
}

构建出来的文件名应该和index.mx4fd8c53.js差不多,那串看不懂的东西就是上方ouput里面的[contenthash],就是根据文件内容生产的哈希值,也就以为着每次更新内容,哈希值就会更新,浏览器就要重新下载这个 400KB 的文件。

那么每周的访问情况应该和下表一样

main.png

分割第三方vendor包

在Webpack 4分包配置做了很多简化,通过一些简单的配置项就可做包分割,而不用每次写一大堆function和正则去匹配包,pretty good👏🏻

回到主题,在webpack配置中加上optimization.splitChunks.chunks = 'all'就可以将所有node_module分割成vendor.js

有了这个vendor.js包,我们的John同学每次访问时候就变成了下载两个200kb的包,但是每周更新的时候只需下载200k内容即可。

vendor.png

只有2.24M,节省了23%的流量,只需几行配置,这个数值还会随着时间增加而不断增加,我想这个数值对于各位看官已经有点吸引了是吧,毕竟更少的请求流量也代表着更快的访问速度。

我们还能进一步提升这个数值。

Splitting out each package

上方的vendor.js其实是一个split all in one的状态,所以它也会遇到刚开始的问题,只要更新某个模块,就要全量更新。知道了问题,我们可以做的更好的,不是吗。

在这时,相信很多看官都能想到,把所有第三方依赖都分割开单独缓存. Right, 那么我们将把vue, vue-router, moment等分割开来:

const path = require('path')
const webpack = require('webpack')

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resovle(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  },
  plugins: [ new webpack.HashedModuleIdsPlugin() ],
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequest: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
            return `pkg.${packageName.replace('@', '')}`
          }
        }
      }
    }
  }
}
  • Webpack很多配置都与缓存相悖,像每个入口只能分割出3个文件,最小分割文件大小限制为30k(小文件都会打包在一起)。上方配置重新配置了这两部分内容。
  • 配置项能告诉webpack怎么做包分割,基本配置就是抽出node_modules中所有第三方库,打包成vendor.js。一般使用该配置项时候key包名字,在这里我们使用了一个函数,匹配到node_modules包就返回对应包名字,例如pkg.vue.m87df6g2.js
  • 这样做还有一个好处就是,每次修改依赖包不需要手动去维护配置。

John依然要每周下载一个200kb的主包,还有在首次加载时候加载200kb的第三方依赖,但是后面就不需要重复下载这些依赖了。

pkg.png

对比3.3M的配置,这里足足减少了45%请求流量,that’s pretty cool.

我想我们还能把这个数值提高到50%以上🤔

继续分割我们主应用的代码

我们的main.js主包还是要每周下载的,从上方还提及到我们有一个任务列表页面需要每周更新,那么我们应该怎么把这个页面单独分割出来呢。

配置entry配置

添加TaskList入口配置,我们以上方的配置文件为例子,添加一个TaskList的配置:

/** some code */
module.exports = {
  entry: {
    main: path.resolve(__dirname, 'src/index.js'),
    TaskList: path.resolve(__dirname, 'src/pages/TaskList.js'),
    TaskDetail: path.resolve(__dirname, 'src/pages/TaskDetail.js')
  }
}

使用code splitting

SPA中我们一般使我们的路由动态加载,简称路由分割,以vue-router为例,我们的路由配置应该如下:

export default [
  {
    path: 'tasklist',
    name: 'TaskList',
    component: () => import('@/pages/TaskList')
  },
  {
    path: 'taskdetail',
    name: 'TaskDetail',
    component: () => import('@/pages/TaskDetail')
  }
]

Good, 现在webpack分离了ProductList.jsProductDetail两个文件,我们的John同学又能少下载50kb的文件了。

Look like this.

module.png

现在只有1.44M了!

我们减少了John57%的下载文件大小,随着访问时间的增长这个值也会越来越大。

为什么代码分割这么重要,除了能单独缓存和减少文件请求大小外,更小的包也以为着更快的脚本解析时间更快的首屏渲染时间

Summary

关于文件数量这里还要再插播一下,如果旧项目使用此配置时候,应该会生成很多零碎的文件,主要原因可能有以下几方面:

  1. 项目积累太多无用依赖没有及时清理
  2. css全部extract,全部样式都按组件粒度提取出来了,这里建议只提取公共和第三方的样式,具体可以参考

最后我们总结一下要点:

  • 将文件分割成多个更小的文件
  • SPA中,减少入口文件第三方插件的数量,分散到各个模块中加载,这样能加快应用启动速度,减少首屏所需资源的数量。
  • 使用contentHash避免每次构建生成新的文件id,便于浏览器缓存

另外,多看文档 🌚

Top