代码分割学习总结

代码分割原理和配置

代码分割是利用现代前端打包构建工具的能力,将单个构建产物文件主动拆分为多个文件,从而提高缓存命中率、改善用户体验的优化。

以 Webpack@5 为例,代码分割的配置项主要有:

1.chunks
   值类型: String | function (chunks) => string
   功能:指定哪些类型的区块可以被纳入分割出的新区块。

   chunks 有4种值:
   'async' :分割出的新区块只允许包含动态加载的区块。
   'initial' :分割出的新区块只允许包含非动态加载的区块。
   'all' :分割出的新区块可以包含动态加载和非动态加载的区块。
   函数 :配置一个函数,接收目标区块的数据作为参数,返回布尔 值,表示目标区块能否被纳入分割出的新区块,例如

  1. minSize
    值类型: Number
    功能:指定分割所产生的新区块的最小体积,单位为字节(byte),小于 minSize 字节的新区块,将不会被创建,也就不会产生对应的打包产物文件。
    minSize 和 maxSize 衡量的都是组成区块的未压缩前的源码体积,不一定等于构建产物文件的体积。
  2. maxSize
    值类型: Number
    功能:指定新区块的最大体积,单位为字节(byte),大于 maxSize 字节的新区块,将被拆分为多个更小的新区块。
  3. minChunks
    值类型: Number
    功能:指定模块最少被多少个区块共同引用,才能被纳入分割出的新区块。
  4. maxInitialRequests
    值类型: Number
    功能:指定最多可以拆分为多少个同步加载的新区块,常用于和 maxAsyncRequests 配合,控制代码分割产生的最大文件数量
  5. maxAsyncRequests
    值类型: Number
    功能:指定最多可以分割出多少个异步加载(即动态加载 import() )的新区块,常用于和maxInitialRequests 配合控制代码分割产生的最大文件数量。
  6. name
    值类型: String | Boolean | function (module, chunks, cacheGroupKey) => string
    功能:指定分割出的区块名,区块名是Webpack运行时内部用来区分不同区块的id。
    区块名不一定等于打包产物的文件名,当没有指定 cacheGroup.filename 时,区块名才会被用作产物文件名。
    另外,对多个新区块或多个 cacheGroup[i] 配置相同的 name ,会使这些区块被合并,最终会被打包进同一产物文件中。
    相反地,对多个区块配置不同的 name ,会使这些区块各自独立,最终多个独立的产物文件。
  7. cacheGroups
    值类型: Object
    功能:指定有独立配置的区块,既可以继承上述 splitChunks 的配置,也可以指定专属当前区块的独立配置。
    可以理解为继承自 splitChunks 的子类,一方面继承了父类 splitChunks 分割区块的能力和配置属性,另一方面也有自己的私有属性。
  8. test
    值类型: Regex | String | function (module, { chunkGraph,moduleGraph }) => boolean
    指定当前缓存组 cacheGroup 区块包含模块的匹配规则。
    其值有3种类型:
    (1)正则表达式:用于对模块文件的绝对路径,调用 regExp.test(modulePath) 方法,判断当前缓存组区块是否包含目标模块文件。
    (2)函数:接收 (module, { chunkGraph, moduleGraph }) 作为参数,返回布尔值表示当前缓存组区块是否包含目标模块文件。
    (3)字符串:用于对模块文件的绝对路径,调用 modulePath.startsWith(str) 方法,判断当前缓存组区块是否包含目标模块文件。
  9. priority
    值类型: Number
    指定当前缓存组区块的优先级,当一个模块文件满足多个缓存组区块的匹配规则( .test 属性)时,最终会将模块文件分割进 priority 值更大的那个缓存组区块。
  10. filename
    值类型: String | Boolean | function (pathData, assetInfo) => string
    指定区块对应打包产物文件的文件名,支持:(1)使用 [contenthash] 等文件名替换符。(2)指定文件类型,即文件的后缀名。
  11. enforce
    值类型: Boolean
    指定是否忽略 maxSize, minSize, maxAsyncRequests, maxInitialRequests 等配置项的限制,强制生成当前缓存组对应的区块。

传统代码分割的痛点

1.配置复杂,开发体验不佳:各类繁杂的配置项令开发者困惑,难以确定拆分目标模块。
2.配置方案健壮性不强,可维护性不好:拆分配置方案无法适应项目的快速迭代变化,需要经常调整;
3.用户体验不好:拆分效果不好,拆分出的模块每次打包上线都会变化,不便于配合增量构建进行缓存,没有实现最优缓存效果,甚至使用户体验恶化;

如何解决这些痛点?代码分割的最佳实践是什么?

细粒度代码分割(Granular Code Split) 是近年来发明的代码分割通用解决方案,其经过Next.js, Gastby 等前端SSR框架多年的实践验证,能有效解决上述痛点,显著改善开发体验和用户体验。
其核心思路是通过拆分出更多的区块、更多产物文件,让每个产物文件拥有自己的哈希版本号文件名,对产物文件的缓存有效性做细粒度的控制,让前端项目在多次打包上线后,仍然能复用之前的产物文件,不必重新下载静态资源。

  1. 核心配置
    细粒度代码分割的核心Webpack配置如下:
// webpack.production.config.js
const crypto = require('crypto');
const MAX_REQUEST_NUM = 20;
// 指定一个 module 可以被拆分为独立 区块(chunk) 的最小源码体积(单位:byte)
const MIN_LIB_CHUNK_SIZE = 10 * 1000;
const isModuleCSS = (module) => {
 return (/*...*/)
};
module.exports = {
 mode: 'production',
 optimization: {
   splitChunks: {
     maxInitialRequests: MAX_REQUEST_NUM,
     maxAsyncRequests: MAX_REQUEST_NUM,
     minSize: MIN_LIB_CHUNK_SIZE,
     cacheGroups: {
       defaultVendors: false,
       default: false,
       lib: {
          chunks: 'all',
          test(module){
            return(
              module.size()>MIN_LIB_CHUNK_SIZE&&
              /node_modules[/\\]/.test(module.identifier())
                  );
                },
          name(module){
             return'lib_'+ hash.digest('hex').substring(0,8)
                },
          priority:3,
          minChunks:1,
          reuseExistingChunk:true,
             },
        shared:{
          chunks:'all',
          name(module,chunks){
            return`shared.${crypto.createHash('sha1').update(
               chunks.reduce((acc,chunk)=>{
                  return acc+chunk.name;
                   },''),
                )
              .digest('hex')
              .substring(0,8)}${isModuleCSS(module)?'.CSS':''}`;
               },
          priority:1,
          minChunks:2,
          reuseExistingChunk:true,
            },
          },
      },
    },
};

这份代码分割配置分割出了2类区块:
1.lib :主要匹配规则为 test(module) ,指定 lib 缓存组包含来自 node_modules 目录,源码体积大于 MIN_LIB_CHUNK_SIZE 的模块。
lib 缓存组用于把体积较大的NPM包模块,拆分为独立区块,产生独立产物文件,从而在多次打包发版、更新哈希版本号文件名的同时,避免让用户再次下载这些大体积模块,提高缓存命中率,减少资源下载体积,改善用户体验。
2.shared : 主要匹配规则为 minChunks: 2 ,指定 shared 缓存组包含被2个及以上区块共用的模块代码。
2.优点
1.开发体验好:配置统一通用,自动选择拆分目标模块,不必人工判断哪些模块需要拆分,降低了代码分割的使用门槛;
2.健壮性强:以不变应万变,用这套不变的代码分割配置可以应对不断更新迭代的各类型前端项目,不必经常更新配置,便于维护;
3.用户体验好:分割颗粒度较细,产物文件稳定,多次构建部署后,仍有较多文件名称内容不变,缓存命中率高,缓存效果好,有利于改善用户体验。

为前端项目增加细粒度代码分割

1.确认优化前状态
优化开始前,我们先通过 webpack-bundle-analyzer 插件,确认前端工程项目当前的编译打包产物的状况。

  1. 增加细粒度代码分割缓存组
    具体做法是,在Webpack配置中增加 lib , shared 2个缓存组( cacheGroup),将 vendors 这样体积较大的模块进一步合理、精细地分割。
    具体的改动主要有3步:
    (1) 删除原有的 vendors 缓存组:释放其包含的模块。
    (2) 禁用默认缓存组:避免干扰细粒度代码分割。
    (3) 新增 lib , shared 2个缓存组配置
  2. 确认分割效果
    完成上述配置后,即可再次编译打包,通过 bundle-analyzer-plugin 确认分割后的模块。
  3. 获取全部产物清单
    细粒度代码分割后的产物数量较多,如果需要完整清单,可以使用 webpack-manifest-plugin 随编译打包生成一份JSON格式的产物清单文件,用于服务端渲染时加载、生产环境部署。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容