Vite 4.3 正式发布,极致的性能优化!

大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

前言

大家好,我是考拉🐨

Vite 4.3 正式发布!

彼时彼刻,Vite 以其极致的项目启动速度和 HMR 速度,在前端工具链领域一骑绝尘无人能敌,后来,挑战者出现了,它就是由 webpack 创始人联合开发的 Turbopack,基于 Rust,号称比 Vite 快 10X!

此时此刻,Vite 要拿回属于自己的荣耀,用手中的 JavaScript 和架构上的设计优化,对抗气势汹汹的 Rust!

和其他工具的对比(项目启动):025704e76960038dd9a3c3fbf33231f9.jpeg

和其他工具的对比(HMR):218a0e4507fb7aeb189381cd3493883a.jpeg

和之前版本的对比(项目启动):dca925f1d0170d91769345074bc3c96b.jpeg

和之前版本的对比(HMR):a826b53ba36c2c924401b6791c27cf8b.jpeg

Vite 架构设计方面的优化这里不做介绍,本文只会从 JavaScript 语言层面,介绍 Vite 4.3 利用语言特性做了哪些优化,帮助大家更好的掌握 JavaScript 💪

介绍每个优化点时,会简单介绍如何优化的、相关的 commit、优化前和优化后的(伪)代码、测试代码并给出结果。

并行 await

相关 commit:

  • perf: parallelize await exportsData from depsInfo[1]

  • perf: parallelize imports processing in import analysis plugin[2]

  • perf(moduleGraph): resolve dep urls in parallel[3]

主要变动:

ts复制代码// 🙅 beforefor (let promise of promiseList) {    await promise}// 😍 afterawait Promise.all(promiseList)

测试代码:

ts复制代码const COUNT = 1_000_000function makePromiseList() {  const promiseList: Promise<any>[] = []  for (let i = 0; i < COUNT; i++) {    const promise = new Promise((resolve) => {      setTimeout(() => resolve(0), 200)    })    promiseList.push(promise)  }  return promiseList}const promiseList = makePromiseList()async function before() {  console.time('before')  for (let promise of promiseList) {    await promise  }  console.timeEnd('before')}async function after() {  console.time('after')  await Promise.all(promiseList)  console.timeEnd('after')}setTimeout(() => {  before()  after()}, 1000)

测试结果:

makefile复制代码before: 185msafter: 131ms

避免使用 new URL()

相关 commit:

  • perf: avoid new URL() in hot path[4]

主要变动:

ts复制代码// 🙅 beforenew URL(url)// 😍 after(通过操作字符串获得新的 url)

测试代码:

ts复制代码const COUNT = 1_000_000function before() {  console.time('before')  for (let i = 0; i < COUNT; i++) {    const url = new URL('http://lccl.cc')    url.protocol = 'https://'    const newUrl = url.origin  }  console.timeEnd('before')}function after() {  console.time('after')  for (let i = 0; i < COUNT; i++) {    const newUrl = 'http://lccl.cc'.replace('http://', 'https://')  }  console.timeEnd('after')}before()after()

测试结果:

makefile复制代码before: 1033msafter: 68ms

提取正则

相关 commit:

  • perf: extract regex and use Map in data-uri plugin[5]

  • perf: more regex improvements[6]

  • perf: reuse regex in plugins[7]

主要变动:

ts复制代码// 🙅 before/\d/i.test('')// 😍 afterconst reg = /\d/ireg.test('')

测试代码:

ts复制代码const COUNT = 10_000_000function before() {  console.time('before')  for (let i = 0; i < COUNT; i++) {    /base64/i.test('')  }  console.timeEnd('before')}function after() {  console.time('after')  const reg = /base64/i  for (let i = 0; i < COUNT; i++) {    reg.test('')  }  console.timeEnd('after')}before()after()

测试结果:

makefile复制代码before: 111msafter: 95ms

使用 startsWith/slice 代替正则替换

相关 commit:

  • perf: regex to startsWith/slice in utils[8]

主要变动:

ts复制代码// 🙅 beforestr.replace(/^node:/, '')// 😍 afterconst prefix = 'node:'str.startsWith(prefix) ? str.slice(prefix.length) : str

测试代码:

ts复制代码const COUNT = 10_000_000const module = 'node:http'function before() {  console.time('before')  const reg = /^node:/  for (let i = 0; i < COUNT; i++) {    module.replace(reg, '')  }  console.timeEnd('before')}function after() {  console.time('after')  const prefix = 'node:'  for (let i = 0; i < COUNT; i++) {    module.startsWith(prefix) ? module.slice(prefix.length) : module  }  console.timeEnd('after')}before()after()

输出:

makefile复制代码before: 298msafter: 112ms

使用 includes 代替正则匹配

相关 commit:

  • perf: remove regex in ImportMetaURL plugins[9]

主要变动:

ts复制代码// 🙅 before/生命/.test(str)// 😍 afterstr.includes('生命')

测试代码:

ts复制代码const COUNT = 10_000_000const str = '于 你的生命之中'function before() {  console.time('before')  for (let i = 0; i < COUNT; i++) {    /生命/.test(str)  }  console.timeEnd('before')}function after() {  console.time('after')  for (let i = 0; i < COUNT; i++) {    str.includes('生命')  }  console.timeEnd('after')}before()after()

输出:

makefile复制代码before: 173msafter: 141ms

这个示例中, before() 效率低的原因有两个:

  1. 构建正则表达式比构建字符串更耗时

  2. RegExp.prototype.test() 比 String.prototype.includes() 更耗时

使用 === 代替 endsWith

相关 commit:

  • perf: replace endsWith with ===[10]

主要变动:

ts复制代码// 🙅 beforestr.endsWith('/')// 😍 afterstr[str.length - 1] === '/'

测试代码:

ts复制代码const COUNT = 10_000_000const str = '你陪我步入蝉夏,越过城市喧嚣'const tail = str[str.length - 1]function before() {  console.time('before')  for (let i = 0; i < COUNT; i++) {    str.endsWith(tail)  }  console.timeEnd('before')}function after() {  console.time('after')  for (let i = 0; i < COUNT; i++) {    str[str.length - 1] === tail  }  console.timeEnd('after')}before()after()

输出:

makefile复制代码before: 85msafter: 20ms

String.prototype.startsWith() 也是同样的道理。

其他

还有一个 commit[11],使用 Map<string, string> 代替了 { [key: string]: string },也就是使用 map 存储键值对都是字符串的数据结构,理论上来说效率会比 object 高,但是实际测试发现并没有,有知道为什么的小伙伴欢迎留言 👏

🆕 4-24 更新: 感谢评论区 @markthree 提供的文章资料,Map 为删除键值对做了特别的性能优化,但是如果只涉及添加、获取键值对的操作,Map 和 Object 相比性能是不占优势的。

测试代码:

ts复制代码const COUNT = 10_000_000function before() {  console.time('before')  const map: Record<number, number> = {}  for (let i = 0; i < COUNT; i++) {    map[i] = i    map[i]  }  console.timeEnd('before')}function after() {  console.time('after')  const map: Map<number, number> = new Map()  for (let i = 0; i < COUNT; i++) {    map.set(i, i)    map.get(i)  }  console.timeEnd('after')}before()after()

测试结果:

makefile复制代码before: 184msafter: 1627ms

接下来我们把读取键值对的操作改为删除键值对。

测试代码:

ts复制代码function before() {  ...  for () {    map[i] = i    delete map[i]  }}function after() {  ...  for () {    map.set(i, i)    map.delete(i)  }}

测试结果:

makefile复制代码before: 1321msafter: 410ms

总结

  • 对于 Promise 实例列表,尽可能的使用 Promise.all() 并发执行

  • new URL() 是很耗时的,如果可以,请通过操作字符串得到新的 url

  • 如果一个正则会被多次使用,最好提取出来成为一个常量,因为这样只会构建一次

  • 正则表达式这把瑞士军刀,很强大、很方便,但大部分情况下,性能不如 String.prototype 上的 API 性能好

  • 如果涉及到大量的删除键值对的操作,Map 对象的性能更优一些,如果只是添加、查找键值对, Object 对象性能更优

最后,从测试代码中可以看到, COUNT 设为上百万、上千万的时候,最终执行的结果才会有几十毫秒、几百毫秒的差距。在日常开发中,除非数据量巨大、对性能有要求的场景(如虚拟列表、基础库)可以考虑这种极致的性能压榨写法,否则,建议还是从可读性、可维护性、易用性方面去写代码。

作者:人间观察员

链接:https://juejin.cn/post/7224310314807345209

来源:稀土掘金

结语

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

2f13e1f6ed36e9a92f6e84c91dc911d1.png

“分享、点赞、在看” 支持一下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值