因为打包太慢,我没吃上午饭

职场   2024-07-13 14:38   浙江  



事情的起因是这样的:


鄙人呢,在公司负责一个小小的后台管理系统。


这天中午临近饭点的时候,测试小哥坐我工位旁边现场监督我改一个Bug。


Bug本身它倒是不复杂,甚至时隔几天之后我已不记清具体内容是什么。当时只见我是三下C两下V、提交、合并测试分支、登录Jekins点击deploy,一顿操作如行云流水一般丝滑。



说时迟,那时快。公司给极品廉价劳动力们我们安排的午饭送到了,一人一份。但你也知道,这两年经济下行,无人幸免;有的人食不果腹,有的人衣不蔽体,保不齐谁饿的紧,拿了两份饭,那可就意味着有一个人要饿肚子,我可万万不希望那个倒霉蛋是我。


看着Jekins的deploy进度条,我对测试小哥说:


“你先回去,等会发完了你再看一下,应该没有问题,我先去干饭了”


说罢,我转头看向测试小哥:他面无表情盯着屏幕,似乎是默许了我的提议。于是我便准备起身——


只见他头也不回一手把我按住,缓缓吐出四个字:


“看完再吃”

...

...

...


大约半个小时后,KFC。


我:“我都告诉你了,不会有问题,先干饭,你非不听”


测试小哥:“......”


我:“这下好了吧,上个月的工资还没发,现在又来付费上班”


测试小哥:“我就问你,星期四的这个辣翅,它香不香”


我:“香”




罪魁祸首


所以,项目到底deploy了多久?


Jekins的记录中可以看到,编译打包环节耗时基本在五六分钟,最近几次成功构建的整体耗时平均在5分50秒。


这个项目本身呢,说大不大,说小也不算小。是个普通CRUD页面居多的管理后台,没有太多其他乱七八糟的东西。如果以页面、组件数量的维度来看:


使用资源管理器在项目的/src目录下通配*.vue可以看到有561个文件




说实话,这样的体量打包5-6分钟,属实有点过分。


我又找来公司的一个巨石应用来对比:由于巨石应用历史比较悠久,横跨了多个技术栈(HandleBars模板引擎、使用jQuery的原生HTML、Vue2),不能只看SFC的数量,所以这次就来比较一下,用来存放页面文件的文件夹的体积


我的项目



巨石应用

先不算其他的资源,单就页面文件体积已经接近5倍,如果其他东西都算上,打包时间就算没有5倍,一两倍总是要有的吧?

结果呢,时间甚至更短


好好好,有活干了。为了避免下次面试官问我对webpack做过什么优化时复述那些网上千篇一律的答案,现在就来实际操作一下。


本文真实记录了一次对项目构建耗时、产物体积优化的过程。没有对知识点系统的梳理,主要突出的是思路:面对问题时的解决思路。



日志分析


曾经有位技术能力超强的架构师说过:遇到问题不要慌,先看日志。


既然这么慢的构建过程是发生在Jekins上,那就先来捋一捋Jekins的log,有没有什么值得注意的地方。


这个项目的构建脚本中,抛开(Jekins)工具的准备、从git上拉取代码以及最后的部署这些动作,只看跟前端的打包有关的部分,命令很简单,只有四行:

rm -rf node_modulesrm package-lock.jsonnpm inpm run build

在日志中体现如下图:



开局就是一记暴击!

11:22:08 + npm i11:25:59 + added 1863 packages from 1199 contributors balabala.........11:25:59 + npm run build......


合着这五六分钟的打包时间,安装依赖就占了一大半,阿西巴!

在继续往下进行之前,请允许我先介绍些项目的其他背景:


deploy脚本拉取代码这一步简单来说就是:cd进项目目录 -> git pull -> 切换至要构建的分支

项目的开发人员较少,算我在内三个人

项目的依赖变动频率十分低,以月或数月为单位


背景铺垫完了,开始研究npm i为什么这么耗时,相关的命令有三句:

rm -rf node_modulesrm package-lock.jsonnpm i


其中npm i这句是必须的,没什么好说的;rm -rf node_modulesrm package-lock.json这两句是变量,挨个做耗时的对比测试。


首先,我在本地使用跟Jekins上相同的node版本(14.16.1),使用相同的npm源(官方源),新建一个目录clone项目代码:


1.完整执行三行命令,耗时与Jekins上相差无几

2.(此时已经有了package-lock.json文件)执行rm -rf node_modules + npm i,耗时极短

3.(此时已经有了node_modules目录)执行rm package-lock.json + npm i,耗时也极短


第三步其实没什么意义,npm文档中有提到,这种情况就相当于梳理了node_modules的结构并生成了package-lock.json,并没有安装任何东西。



而步骤一和步骤二之间为何耗时相差比较大,可以参考这篇我觉得npm install流程写的比较好的文章:简单来说就是花费了大量时间去远端获取包信息


结合项目背景一,我们的package-lock.json会提交到仓库,每次肯定都是最新的,所以Jekins deploy脚本中rm package-lock.json这一步属实是没有必要,本地计算完了到Jekins上又来一遍,纯纯的浪费时间。


联系运维哥把测试环境的deploy脚本修改一下,去掉了rm package-lock.json这一句,测试下耗时:



如图中下边两次构建,【编译打包】环节时间都去到了2分30秒左右,直接缩短了一半多。部署成功后,在测试环境的页面咔咔一顿点,似乎也没有什么依赖包引起的报错。


效果是不是很显著,你以为这就完了?不不不,图中那条49秒的构建我可不是大意截进去的,伪装成不经意的失误,就是为了丝滑的承上启下



既然这个项目的开发人员很少,而且依赖的添加/更新频率又极低,也就意味着每次npm i所安装的东西,基本都是一样的,既然都一样,为什么我还要rm -rf node_modules再安装?


想象一下你日常本地开发时,如果某次需求要用到一个新的npm包,你一定是先npm install xxx,除非碰到了依赖冲突,否则不会清除node_moduels重新安装。


明明npm提供了梳理依赖树、只做局部更新的逻辑,我们却偏偏每次清除node_modulesinstall,这种行为吧,我感觉就像明明是个Vue项目,却在里边到处使用Document API


哎嘿,我就不用你的响应式,就是玩~



冒着被打的风险又私聊了运维哥,把rm -rf node_modules去掉,再发布了一次看看效果



优秀!打包时间从5分多直接干到了50秒,优化率80%+!


本文结束!


在正式结束前,觉得还是有必要补充两点

  1. 各位读者在做打包优化时,部署脚本是否清除package-lock.jsonnode_modules还是主要取决于项目实际情况和团队协作模式,不能因为一味的追求构建速度而导致频繁的构建失败/安装依赖失败[滑稽]

  2. 如果您经过深思熟虑后觉得还是有必要清除package-lock.jsonnode_modules,山人还有一计可供大王优化构建速度:打包时离不开babel,但babel又是个老大难,好在它能缓存转换结果。一般情况下缓存会放在项目目录下的/node_modules/.cache/,那我们把删除node_modules的命令稍微改那么亿点:

    find node_modules/ -mindepth 1 -maxdepth 1 ! -name '.cache' -exec rm -rf {} +

    删除node_modules里面除了/.cache目录以外的其他内容,这样在构建过程中babel还是能使用到之前的缓存。那速度,体验过的都说好!(看babel-loader的缓存文件有多大)



全面升级


如果是本着以后不影响吃午饭的目的,那现在确实可以结束了。但我自幼便深受中国四大名句之一来都来了的兄弟句式——干都干了的文化熏陶:既然已经开始了,那就干脆给项目做个全套大保健!



不过此时我和在座的各位都一样,对打包优化这块着实没什么经验,可以说是毫无头绪。


浅浅百度了下webpack打包优化,有两个工具基本每篇文章都有提到:打包耗时分析speed-measure-webpack-plugin、打包体积分析webpack-bundle-analyzer(vue-cli内置)


目前的痛点是,那就先来个耗时分析试试水。


使用方法还是老样子,自己去查,别人都写的我就不再重复写了


效果如图:




此时因为babeleslint还没有缓存,耗时多是意料之中的;其他的loader或多或少的三两组合,展示了一个module计数和耗时小计,我从中并没有办法获得什么有用的信息;并且多次构建对比发现loader组合的规律和耗时的排名也无迹可寻。在我的认知里:所有被命中的文件会按照loader配置的顺序依次处理,所以面对这样的结果我实在是无计可施。(有会看的朋友可以补充一下)


翻看speed-measure-webpack-plugin的文档,发现有可以打印耗时top N文件的配置项,但开启后再次构建得到的这些文件,同样令我摸不着头脑:一个寥寥数十行的SFC小组件,css-loader耗时竟然能用四到五秒!要知道里边只有一条scoped的样式规则。


无奈只好放弃,看了下项目用的是vue-cli@4.x创建的,对应的webpack@4.x,那就去webpack的文档里逛逛碰碰运气吧!


可惜,福无双至祸不单行。文档里翻了半天,耳熟能详、配置简单的路子,例如babel-loader、eslint-loader的编译缓存多线程打包chunk分割代码混淆压缩tree shaking这些,要么是之前已经被配置过了,要么是webpack内置了。而复杂、高级一些的优化方式,我的项目又用不到...


直到我看到了这里:



升级webpack简单(呸),npm upgrade webpack嘛,先来搞这个~~


回到项目的package.json里,咦,好奇怪,没有webpack,也没有vue-cli


vue-cli是装在全局的,而webpack是作为依赖的依赖安装的,没有体现在package.json中,所以直接npm upgrade webpack应该是不行的。vue-cli的文档提供了一个升级的命令:vue upgrade


既然要升级,干脆全上新的!Node也给他干到20!(我也不知道我当时为什么要这么做,但这为后来的事情埋下了伏笔。。。)


vue-cli升级完,扫了一遍webpack升级指南,发现我项目里的配置文件也没什么好改的,Nice!


本地浅浅的run了一下server、run了一下build,发现也都OK!那就提交上去在Jekins上试试Node V14o不ok

emmmm...

报错倒是没报错,只是...



本地build的时候没注意,Jekins上跑才发现,怎么慢了这么多!说好的更新到最新版本均有助于提高性能呢?

再看看这构建物的体积

Hà的我赶紧又本地build了一次,还真让我发现了些东西:似乎build了两次



按理来说应该只有下边这个print,那上边的legacy bundle又是什么东西?百度上随便那么一搜,应该是不少人都被这么坑过,很容易搜到:这是一种兼容性的构建产物,主要是为了兼容一些很古老版本的浏览器/客户端。想控制也很容易,改package.json里的browserslist字段即可。


这就好办了,这项目是我们公司的内部项目,考虑兼容性?不存在的。

{    "browserslist": [        "> 1%",        "last 2 versions",        "not dead",        "not ie 11"    ]}


配置了之后又试了下,基本恢复到了升级webpack之前的水平,但还是慢一点点..


构建速度的优化这块,实在是没头绪了,明明升级了webpack版本,构建速度却变慢了。


不过刚才提到的两个工具,构建耗时分析的用过了,还有个vue-cli内置的构建体积分析工具没体验;如果需要打包的东西变少了,那构建速度应该也能快一点吧!(吧?)


塑形瘦身


在正式瘦身前,有一个小插曲:

不知道在座的各位,项目里有没有这样的东西

console.log(123123)// orconsole.log('asdfasdf')

我是一个崇尚极简的人,我能接受的底线也就是

console.log('list data: ', data);


仅此而已


你要打印接口返回数据,Network里能看


你要打印函数中某个变量的值,可以打断点


我实在是想不出什么必须console.log的场景


如果你说为了方便线上调试


我能接受的最多也就是按规范打印有意义的log


更别提项目首屏就要翻好几页的无意义log,要知道,大量的console.log也是会影响首屏加载性能


在之前,我通过husky + lint-staged进行过限制,但还是有人以我这个有用这之前不是我写的等等诸多借口绕过了eslint检测,提交了无意义的log。所以这次我最终还是决定,你不仁就休怪我不义,TerserPlugin drop_console走起,本地开发你随便log,只要发到线上我就删掉。

{    plugins: [        new TerserPlugin({        terserOptions: {          compress: {            drop_console: true,            pure_funcs: ["console.log", "console.info"],          },        },      })    ]}

毕竟删掉几句console.log,也算瘦身

________________________________________________


接着就webpack-bundle-analyzer走起,vue-cli内置的使用方式是

vue-cli-service build --report

打包后会在你输出的目录里边生成一个report.html,当时的截图找不到了,用语言描述一下就是:从node_moduels里打进去的依赖包,面积直接占了整个屏幕的大概三分之一。那个图网上很容易找到,内容就是打包产物按照体积和来源绘制成一个个的矩形在页面里。


这其实是好事,打进去的依赖包多,我们的可操作空间就大,先拿Vue开个刀。

// vue.config.jsmodule.exports = {    // ...    configureWebpack: {        externals: {          vue: "Vue",        },    },    // ...}

也不要忘了把package.json里的vue依赖删除掉、在/public的模版HTML中,通过<script>引入CDN文件。

再打个包看看效果:


可以看到vue确实咩有了

但在调试的过程中,发现第三方CDN不稳定,时而获取超时



为了保险起见,只得把CDN文件copy到本地/public里来(我司没有自己的CDN或者依赖私仓,正在筹备中)

暂时没什么问题了,下一个就是我们的UI组件库ant-design-vue@1.7.8,按照相同的方式配置一下,不过这次运行后有报错了:


可以看到报错是和moment有关系,在antd的文档也找到了原因:如果使用已经构建好的文件,要自行引入moment


为什么antdv不做按需引入?原因有二:

  1. 项目的入口main.js中全量导入了antdv进行注册,页面中直接使用。如果要改成按需引入,要么每个页面里新增按需引入的语句,要么统计使用了哪些组件在main.js里改为按需引入(似乎有plugin解决这个问题,记不太清了)

  2. 按需的这个需,基本等于全量了。。。粗略的扫了一下文档,除了像CommentMentions这种带有互动性质的组件,其他的基本都用上了,所以改按需好像意义也不大


moment的时候国际化有一个小问题,CDN网站一般会提供以下几种文件:


  1. 无国际化的moment主体文件

  2. 带全部语言包的moment主体文件

  3. 单个语言包文件(无功能)


如果没有国际化的需求,那是万万没有必要引入全部语言包的moment。但moment默认是英语,至少需要引入一个中文语言包。碰巧antdv也需要做国际化处理,是相同的问题。


momentantdv的国际化方式很相似:

<a-config-provider :locale="antdLocale" />
moment.locale(momentLocale);
data() {return { antdLocale, momentLocale }}


我们只需要知道这个locale运行时的值,把它提取出来就行了。打印后发现其实就是个很简单的key-value对象(不是JSON),在node_modules中的源码里找到 它们复制出来在/public下新建zhCN文件:

window.momentLocale = xxx /* 复制出来的对象 */window.antdLocale = xxx /* 复制出来的对象 */



使用时:

<a-config-provider :locale="antdLocale" />

moment.locale(momentLocale);

data() {return {antdLocale: window.antdLocale,momentLocale: window.momentLocale }}

以后如果有别的依赖也有类似的国际化需求,继续向zhCN.js里添加就行。只需要新增一http请求,就解决了所有依赖的国际化问题。

剔除了antdvmoment之后的report.html

惊喜的发现,antdvicons也被一起干掉了。

少了这么几个大家伙,此时必须要Jekins上build一波看看效果!

还记得之前把Node给升到20了吗

于是就...报错了...Node版本太低...

本地切回NodeV14发现连server也起不来了。。


摸黑前行


预警:这将是一段枯燥且艰难的黑暗时光

搞过的都知道,处理Node版本兼容问题时,如果是需要升级还好;如果是要降级,Node内置的各种包会出现稀奇古怪的报错,而且这些报错还难以trace...


由于这趴的问题实在过于稀奇,甚至在google上都搜不到有用的信息,所以基本都没有截图,但我会尽可能的描述出我对问题的看法。看文字也许你觉得云淡风轻解决起来很轻松,但实际上花费了我接近一整天的时间以及一撮撮掉落的头发...

  1.  npm run server出现大量的.vue单文件报错

    具体的报错信息记不太清,但报错顺序与路由表注册的顺序相符(和动态路由懒加载是两码事,路由懒加载是在运行时访问到页面才会加载对应的chunk,但编译打包时,只要是代码中webpacktrace到的文件,都会被处理)。目测是所有的.vue都有报错,那问题就应该不是出在代码上,而是整体配置上。


    翻看vue-loader文档时看到了这个



升级vue-cli时确实也升级了vue-loader,按照指引配置了下,resolve

2. jsx语法报错

这个问题就有点奇怪了,在升级前,是没有给webpack做过什么支持jsx语法的配置的。升级后,却都报错了。


翻阅了一些资料和支持jsx的解决方案,大部分都是说把SFC<script>加上lang="jsx",里边的内容全部当作jsx解析。这种方式对eslintbabel的配置改动比较大,曾数次尝试无法成功,最后都把所有配置还原重新开始。


后来灵光一闪,不如直接用刚更新的vue-cli创建一个新项目,看是否支持,如果可行的话,直接把各个配置文件照搬即可




结果还真可以。babel.config.jsvue.config.js以及package.jsoneslintConfig字段,先全部按照官方脚手架的配置改掉,成功启动之后再挨个把我们自定义的配置添加回去。这过程当中没有出现什么问题,且按下不表,resolve

3. 启动之后,页面白屏报错(0 , vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent) is not a function

其中resolveComponent也有可能是其他一些Vue3暴露出来的Api,通过打断点观察,推测是Vue内部在初始化的时候出了问题。


不确定是哪里出了问题,但在把之前删除的Vue依赖安装回来(只是开发环境会用到,打包不会打进去)以及把添加的VueLoaderPlugin去掉以后,resolve


迎接黎明


以上这些问题解决以后,已经可以正常启动、打包项目了。但刚才的bundle analyzer进行到一半还没结束,图中的第三方依赖库应该还有一些可以剔除掉的,比如隐藏在一个业务代码chunk里的echarts



检索了代码后,发现有按需引入的:

import {xxx, xxx} from "echarts"

也有全量引入的:

import * as echarts from 'echarts';

在分析代码后整理了所使用到的echarts Api和组件,把全量引入改为按需引入,重新打包后发现包体积没有变。我好奇难道echarts只要有一个地方使用了按需引入,其他地方也能自动分析把全量引入改为按需引入?遂把按需引入的也反向改为全量引入重新打包,结果:



第二次的改动体积变化了,那就只能说明....


问了写那段代码的同事,果然,在需求迭代的过程当中技术方案变更了,所以那个文件废弃掉没有用了,改了个寂寞...


此时还剩下jquerylodash计划剔除掉,其他的依赖包有一部分已经是比较规范的按需引入,剔除掉改为cdn引入带来的收益不大,当然主要的原因还是因为


jquery:这个npm包有点意思



打进来的是非压缩版本,因为package.json中设置的main就确实是这个,但dist包中明明提供了压缩后的版本。两个版本的体积差距在三倍多,不知包作者的意图是什么



但最后还是把jquery这个依赖彻底放弃了:整个项目中只有一个远古时期添加的图片预览组件依赖了它,而我们现在开发了样式、功能更为强大的新组件,所以把所有使用到这个组件的地方都改为使用新组件,然后顺带把jquery uninstall了。


lodash:官网本身提供了可按需引入的版本lodash-es,但项目中太多地方都是全量引入的方式在使用

import * as _ from "lodash"

暂且先改成CDN的方式全量引入

至此,bundle analyzer的分析图变成了这样:


三方依赖的chunk已经比包含了echarts的那个业务代码chunk体积还要小。瘦身瘦到这里感觉差不多了,那些更小的依赖包本身体积不大,换成一个http请求也未必是一件划算的事。


然后就还是回到webpack的配置上来,前边一直在琢磨怎么添加配置去做优化,但vue-cli本身已经封装了一套久经考验的配置,不如从这个配置着手,看能否针对我们项目的实际情况做一些修改


获取配置命令(融合了自定义的配置)

vue inspect --mode=production > file-name.js

mode不传的话默认是development。下载下来打开,1400多行猛的一看似乎有点唬人,但实际上有1000行左右都是对样式文件的loader配置。



粗略的看下vue-cli@5.0.8中有哪些值得注意的配置

  • 解析文件的优先级

// 导入模块时如果不提供文件后缀,同名文件 后缀名的优先级extensions: [".mjs", ".js", ".jsx", ".vue", ".json", ".wasm"]
  • Hash

optimization: {realContentHash: false, // 使用非严格的hash计算,减少耗时}
  • 代码压缩:css使用的是CssMinimizerPluginjs使用的是TerserPlugin

minimizer: [// 已经内置了js压缩工具tersernew TerserPlugin({        terserOptions: {          compress: {            arrows: false,            collapse_vars: false,            comparisons: false,            computed_props: false,            hoist_funs: false,            hoist_props: false,            hoist_vars: false,inline: false,            loops: false,            negate_iife: false,            properties: false,            reduce_funcs: false,            reduce_vars: false,            switches: false,            toplevel: false,            typeofs: false,            booleans: true,            if_return: true,            sequences: true,            unused: true,            conditionals: true,            dead_code: true,            evaluate: true,          },          mangle: {            safari10: true, // 代码混淆时兼容使用`let`关键字声明的循环迭代器变量可能会出现无法重复声明let变量的错误。          },        },        parallel: true, // 多进程打包        extractComments: false, // 不将注释单独提取到一个文件中      }),new CssMinimizerPlugin({        parallel: true,        minimizerOptions: {          preset: ["default",            {              mergeLonghand: false,              cssDeclarationSorter: false,            },          ],        },      }),]

Loader

  • 大量的篇幅编排不同样式文件相关的Loader,分别有csspostcssscsssasslessstylus,按照css moduels in SFC -> SFC style -> normal css modules -> normal css的顺序依次处理。

  • 对于脚本文件,已经开启了多线程转译以及babel缓存功能

{test: /\.m?jsx?$/,exclude: [function () {/* omitted long function */      },    ],use: [      {loader:"path-to-your-project/node_modules/thread-loader/dist/cjs.js",      },      {loader:"path-to-your-project/node_modules/babel-loader/lib/index.js",options: {cacheCompression: false,cacheDirectory:"path-to-your-project/node_modules/.cache/babel-loader",cacheIdentifier: "1d489a9c",        },      },    ],  }
  • Plugin


  • VueLoaderPlugin:已经内置了

  • DefinePlugin:注入编译时的全局配置

  • CaseSensitivePathsPlugin:路径的大小写严格匹配

  • FriendlyErrorsWebpackPlugin:优化报错信息

  • MiniCssExtractPlugin

  • HtmlWebpackPlugin

  • CopyPlugin:配置了info.minimized = true,copy的同时也会压缩

  • ESLintWebpackPlugin:同样开启了缓存


得,不仅没找到有啥可优化的地方,甚至还污染了人自带的配置:


已经内置了TerserPlugin,前边为了打包时去除consoleplugin里边又配置了一次,通过speed-measure-webpack-plugin分析时发现似乎是走了两遍TerserPlugin


只好通过webpack-chain去注入一下,顺便把项目中其他修改webpack配置的地方也改为注入的形式。(使用ConfigureWebpack去改,无法改到已有的TerserPlugin配置):

chainWebpack: (config) => {    config.when(process.env.NODE_ENV === "production", (config) => {      config.devtool(false);      config.optimization.minimizer("terser").tap((args) => {const compress = args[0].terserOptions.compress;        args[0].terserOptions.compress = {          ...compress,          drop_console: true,          pure_funcs: ["console.log", "console.info"],        };return args;      });    });    config      .externals({        vue: "Vue",        moment: "moment","moment/locale/zh-cn": "moment.local","ant-design-vue": "antd",        lodash: "_",      })      .resolve.alias      .set("@", path.join(__dirname, "src"))      .set("@worker", path.resolve(__dirname, "public/worker.js"))      .end();    config.plugin("speed-measure").use(SpeedMeasurePlugin);  }  },


如果使用ConfigureWebpack

configureWebpack: {minimizer: [      new TerserPlugin({        terserOptions: {          compress: {            drop_console: true,            pure_funcs: ["console.log", "console.info"],          },        },      }),    ],


集成的配置最下方会出现一个新的minimizer数组,不是我们想要的效果



截止到目前,构建速度变成这样(果然还是没有变更快)



项目剔出去的第三方依赖,体积是这么多


不过虽然没打进chunk里去,但还是作为静态依赖在构建产物中,下一步就是搭一个公司内部的简易缓存服务器(有关缓存的内容可以看我上一篇文章)。届时这部分体积才算真正从项目里移除了,不过此时我们还是可以把它视作优化的成果,由于没有对别的类型的资源做什么优化处理,也压根就没什么别的资源,所以只看打包后的js体积的话:


优化前




优化后


数据也基本对的上,所以综合来看:


  • 平均构建速度(average full time):从5分50秒减少到54秒,优化率84.48%1 - 54秒 / 3分50秒

  • 脚本构建体积(script size):从5.5M减少到3.6M,优化率35.55%1 - 3.6M / 5.5M



先这样吧,至少下次被问到webpack,多少有点自己的东西能讲


欢迎真诚交流,但如果你来抬杠?阿,对对对~ 你说的都对~


l来源:https://juejin.cn/post/7389044903940603945

Java面试宝典
深耕IT,8年开发老铁帮你少走弯路,分享我的工作与学习经验,技术涉及一线互联网公司Java架构、Java面试题、大数据、人工智能、Python、全栈开发等各个热门领域,是你IT之路的良师益友。
 最新文章