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

本文是对《设计一个无懈可击的浏览器缓存》文章的延申,其中应该有以两个系列的文章:

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

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

  • 知道那种文件分割file-spliting策略最优于你的项目
  • 如何进行文件分割

根据Webpack术语表中可知,文件分割有两种不同的类型,两个虽然听起来差不多,但确是两种十分不一样的技术:

  • Bundle Splitting -- 为SPA生成多个独立的包,以便于浏览器更好地缓存。
  • Code Splitting -- 在Vue和React中一般用为路由分割,把代码分成多个小块,动态加载当前页面需要使用的内容。

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

Let's code.

Bundle Splitting

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

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

( 在很多前端优化文章中经常会提要压缩资源,合并请求,这里的观点其实跟旧的优化方案有点相违背的,但是从HTTP1.1中已经有了HTTP管线化,又或者HTTP2中的多路复用,能够在一次连接中发送多个请求,加上现代浏览器提供的Preload\Prefetch等技术,多个HTTP的请求的性能损耗在缓存中提供的性能提升应该是不值一提的 )

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包

如果使用 Vue-cli ,创建的项目,构建出来一般都有分为主入口问价,外加一个vendor.js的文件。

在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 Guide中的缓存有很好地解释为什么要使用上方配置,除此之外还有下面一些常规模块需要注意一下的:

  • Webpack很多配置都与缓存相悖,像每个入口只能分割出3个文件,最小分割文件大小限制为30k(小文件都会打包在一起)。上方配置重新配置了这两部分内容。
  • cacheGroups配置项能告诉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,全部样式都按组件粒度提取出来了,这里建议只提取公共和第三方的样式,具体可以参考mini-css-extract-plugin的配置

最后我们总结一下要点:

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

另外,多看文档 🌚

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容