blog icon indicating copy to clipboard operation
blog copied to clipboard

2021-10-14 凯多 巨石应用解决方案 nopack

Open xiaoxiaojx opened this issue 2 years ago • 11 comments

    1. 背景
    1. vite 与 snowpack
    1. nopack
      • 3.1. 原理图
      • 3.2. 时序图
      • 3.3. Q: 如何做到项目 0 改动接入 ?
      • 3.4. Q: nopack 这个名字是啥意思 ?
      • 3.5. Q: 如何彻底消除 cjs 这些语法了 ?
      • 3.6. Q: nopack 不是坚决不打包吗 ?
      • 3.7. Q: 这样看来比 vite 更快 ?
      • 3.8. Q: 最终接入的效果如何 ?
      • 3.9. Q: esm 开发的劣势?
    1. swc 与 esbuild
    • 4.1. swc
    • 4.2. esbuild
    • 4.3. 测试数据
    1. 最后的话

image

1. 背景

最近一段时间陆续有同学吐槽,现有的开发环境打包 📦 太慢了,原话如下

  • 同学 a: xxx 项目冷启动,刚刚计时是 3 分 45 秒 左右,有空看看是否有优化空间哈。 视频是 xxx 项目 f-xxx-6196 分支(yarn dev 回车后开始计时)
  • 同学 b: 本地运行太慢了,想砍人
  • 同学 c: 我们项目启动很慢,咋整
  • 同学 d: xxx 项目编译时间太长了,改个东西容易电脑卡,不排除我的电脑问题,体验很差

也确实,3 分 45 秒 🐢🐢🐢 的等待时间谁又受得了 😣! 那么我们现在的脚手架的问题出在哪里 🤔?

现有脚手架是基于 webpack 打包,对于 babelTypeScript 等编译做了缓存优化,甚至 hard-source 这样的持久缓存。但是由于业务需求的快速迭代,一切换分支导致大量 node_modules 依赖变化使得大部分缓存都未能命中 ❌

所以当现有的优化手段都命中 ✅ 的时候,时间才能勉强减少到 40 秒 🐢 左右。

image

webpack5 也尝试去解决慢的这个问题,比如新增了比较重量级的持久缓存 cache.type: filesystem 功能以及仍在实验中的懒编译 experiments.lazyCompilation 功能,不过从 esbuild 官方给大家的数据来看,webpack5 却是最慢的 😢!

关于 webpack 的 experiments.lazyCompilation 功能, 在内网的同学可以继续阅读 2019-09-10 凯多 动态路由插件 分享, 其实两年前我已经实现了该功能来解决巨石应用的打包慢的问题,只是最后的提升很有限。

这可能是 webpack5 被黑得最惨的一次... 因为第一次打包有一部分时间是在生成与写入缓存,怎么不拿第二次缓存生效后的时间来遛一遛比一比了?在这种情况下,我们升级 webpack5 可能还是解决不了痛点,频繁的分支切换又会回到解放前 !

当项目逐渐膨胀时,似乎是已经到了 webpack 的瓶颈,近期特别火的 ViteSnowpack 或许才是真正的解决方案 🤔?

2. vite 与 snowpack

image 最初我想到的是接入 Vite 或者 Snowpack 解决现有的问题,关于提到的这些工具的原理,有不少文章都讲得很好了,这里就不做过多的介绍。

Snowpack 刚出来不久就认真充满好奇的读了它的代码,发现里面有不少 hack 的实现,比如 define 是通过字符串的替换去实现(这明显就会误伤无辜),css 文件中的 import 也没处理,最近作者说会交给社区去维护,自己有些力不从心了。

Real-world testing is super important. I'm sure that sounds cliche,but its true. We had a few starter projects that we could test Snowpack against,but they were all small and simple. This created a huge experience gap between our internal projects and our actual users.

文章出自 6 More Things I Learned Building Snowpack to 20,000 Stars (Part 2),而我们就是实际的业务,近 100 个大型项目,包含各类系统与 h5,几乎能踩完所有的坑。

To be honest,I'm not sure where Snowpack goes from here. I burnt out on it at the end of last year,and haven't found the energy to return. Usage and downloads began to trend down and the community has gotten quieter.

At the same time,Vite (that Snowpack alternative that now powers SvelteKit) is taking off. To their credit,they do a lot of things really well. The good news is that two tools are very similar and easy to switch out. Even Astro is experimenting with moving from Snowpack to Vite in a future release.

虽如此 Snowpack 依然是个可敬的先行者 ❤️

所以综合体验下来还是 Vite 值得信赖,如果此时是新项目毫无疑问我推荐大家使用 Vite,但是我们这是老项目...

何为老项目? 就是前阵子升级了一个 ts-loader 的大版本,有俩项目测试环境白屏了 😨。在试图接入 Vite 时遇见了各种奇怪的编译错误 ❌ 以及未能正确找到目标 js 与 scss 文件路径 ❌ 等问题。

到这里我认为 Vite 是没有任何问题的,有些稀奇古怪,百花齐放的问题的代码就得你自己去改,当然实际继续挣扎下去可能会发现更多的奇葩问题,其解决沟通成本和时间成本是无法估计的。

3. nopack

image

最终我决定自己开发,并且业务项目可以随意切换新的 ES Module 开发模式与现有的 webpack 开发模式。

nopack 将以全局安装的形式存在,即对现有项目 0 侵入,对线上环境 0 危险0 修改 即可接入。

名称 nopack Vite
定位 让存量 webpack 强绑定的项目开发体验由 🐢 变为 ⚡️ 功能全面,下一代前端构建工具 ⚡️
开发环境 ✅ ES Module + esbuild 编译 ✅ ES Module + esbuild 编译
生产环境 ❌ 不支持(⚠️ 生产环境无需变化) ✅ rollup 打包(💡 类似于 webpack 的生产打包)

3.1. 原理图

image

3.2. 时序图

image

3.3. Q: 如何做到项目 0 改动接入 ?

  • Vite 的大部分零件是偏向于 Rollup 体系。要想无缝兼容现在项目中配置的 _moduleAliases 与 sass 文件中各种 import 路径寻找,于是这部分我采用的 webpack 的零件,主要是 enhanced-resolvesass-loader 的处理逻辑。
  • 对于不规范的 npm 包, nopack 会对其进行硬编码矫正。主要考虑的问题是升级该包到最新版本可能会带来较多的 BREAK-CHANGE, 甚至最新版本可能都没修复, 业务回归测试任务较重可能就会放弃接入 nopack
  • 通过 importmapexternals 等技术方案来兼容使用了内部微前端架构、qiankun 微前端架构、Module Federation 架构的项目
  • 如果存在必需的 babel 插件, nopack 会简单的实现该插件同样的功能

3.4. Q: nopack 这个名字是啥意思 ?

  • 只想做一个纯净的文件转译服务(理念类似于 esm.sh),竭尽全力的不打包 ❌📦 。想法还是天真了,因为有太多 CommonJs 的 npm 包, 而 CommonJs to ES Module 又是一个不成立的事情,require 是运行时的,import 是静态的,可能你想到了 import() 函数不也是运行时的吗 ? 不过 import() 返回的却是 Promise ...

  • 只想做转译的想法行不通 ❌ image

3.5. Q: 如何彻底消除 CommonJs 这些语法了 ?

  • 那就只有预构建打包 📦 了,合成一个文件,require 的代码直接塞在对应的位置了,哪还会有 require 这些东西了

3.6. Q: nopack 不是坚决不打包吗 ?

  • 为了这些不规范的 npm 包,只能忍气吞声了,起初 nopack 与 Vite 这里有些区别,nopack 会试图判断这个 npm 包是不是 ES Module 的包,比如 package.json 中有 module 或者 browser 字段,或者 import 的路径包含 es,esm 目录的文件如 packageA/es/a.js 这种或许也是 ES Module 就不进行预构建了。这样下来比如 a 项目 🔍 扫描到了 336 个 npm 包,最后只会对 199 个包进行预构建 📦 。

3.7. Q: 这样看来比 vite 更快 ?

  • 快是快了,但是具有迷惑性行为的包也不少,比如某个包也声明了 module 字段,但我其实是 CommonJs 的代码。更有甚者一个包中大部分文件是 ES Module 的,有 1 - 2 文件个是 CommonJs 的 🤯! 没办法,你不是 nopack 吗,这些包我忍你了,我 hardcode 到一个数组中把你们加入黑名单。

3.8. Q: 最终接入的效果如何 ?

  • 号称 3 分 45 秒 的项目通过 esbuild 预构建大概只花 8s 左右,然后页面刷新时会对 src 下的 ts 与 jsx 通过 esbuid 进行转译,大概 4s 的时间,所以最终开始预构建到页面完全展示出来只花了 12s,esbuild 远比我想象中的更快 ⚡️。

3.9. Q: ES Module 开发的劣势?

  1. 刷新页面的等待时间会慢一点

    • ES Module 模式强调即时转译,浏览器运行阶段代码从入口文件开始运行的时候,会不断有 import 语法发起新的文件请求,对于 jsx 与 ts 文件的请求还需要通过 esbuild 进行转译,对于 scss 请求需要 dart-sass 转译,直到所有的代码运行完成。
    • webpack 模式强调打包,打包阶段会把入口文件开始依赖分析,最后打包出一个 main.js 交给浏览器运行,所以浏览器运行阶段就会快很多。
  2. 对性能有一定的要求

    • 在其中一个项目接入时,使用 windows 开发的同学说快是快了,但是页面刷新的那一刻有点卡 🤔,排查时发现这个项目 network 中发了 3000 多个 js 的请求,Chrome 把 CPU 拉满了。所以 nopack 不得不又退步 😢,只能把 node_modules 的包进行全量的预构建来达到合并更多的文件,一下请求数降低到了 700,Chrome 即使拉满了 CPU 也是短暂就下来了。所以最终 nopack 又进行了妥协,采用了全量预构建的方式。事实证明文件请求减少页面刷新时白屏时间减少了,预构建也只增加了 2s 左右的时间,开发体验会更好一点。
  3. esbuild 不会对 ts 类型进行检查

4. swc 与 esbuild

image 选择编译器的时候,调研了 swc 与 esbuild

4.1. swc

Next.js 已经实验性使用 swc

image

4.2. esbuild

Vite 与 Snowpack 都是使用的 esbuild。

目前仅发现范型函数赋值给变量编译会有问题。

image

4.3. 测试数据

下面是 webpack 模式下测试 esbuild 与 swc 相关的测试数据

  • 项目: xxx
  • 版本: 91a69d4e
  • 编译速度: 首次无缓存状态下的构建时间

image

  • 编译兼容:

    • swc: 编译 componets/*.js 多个文件存在问题
    • esbuild: js 模式下不支持编译装饰器,故疑似存在装饰器的文件换成 ts 模式进行编译
  • 小结:

    • esbuild 与 swc 速度相差无几,但是 esbuild 对老项目的编译兼容性高于 swc。

5. 最后的话

站在巨人的肩膀总是能看得更远,首先感谢 Vite 与 Snowpack 从 CommonJs 到 ES Module 过渡时期提供的各种解决方案,最后感谢背后的 esbuild 与 swc 提供的强大心脏 ⚡️

image

谢谢阅读 ~

xiaoxiaojx avatar Oct 29 '21 16:10 xiaoxiaojx

谢谢分享思路

watsonhaw5566 avatar Dec 13 '21 02:12 watsonhaw5566

谢谢分享思路

冲 ~

xiaoxiaojx avatar Dec 13 '21 17:12 xiaoxiaojx

请问 nopack 有仓库可以看吗

sanyuan0704 avatar Dec 25 '21 01:12 sanyuan0704

请问 nopack 有仓库可以看吗

暂时没有哈,公司没有开源的文化,参考学习的话 vite 就特别优秀了。我们做的更多的是无痛兼容老项目 ~

xiaoxiaojx avatar Dec 26 '21 03:12 xiaoxiaojx

我看为了兼容老项目,重新写了一个类似 vite 的构建工具是吗吗?

---原始邮件--- 发件人: @.> 发送时间: 2021年12月26日(周日) 中午11:36 收件人: @.>; 抄送: @.@.>; 主题: Re: [xiaoxiaojx/blog] 2021-10-14 凯多 巨石应用解决方案 nopack (Issue #20)

请问 nopack 有仓库可以看吗

暂时没有哈,公司没有开源的文化,参考学习的话 vite 就特别优秀了。我们做的更多的是无痛兼容老项目 ~

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

sanyuan0704 avatar Dec 26 '21 03:12 sanyuan0704

是的,和 vite 干的是同一件事情。就是用的一些零件不一样, vite 是 rollup 相关的零件, nopack 是 webpack 相关的零件

xiaoxiaojx avatar Dec 26 '21 03:12 xiaoxiaojx

感谢分享

hunter2009 avatar Aug 12 '22 08:08 hunter2009

感谢分享思路

tc93025 avatar Nov 24 '22 07:11 tc93025

image

我们组里边的这个项目体量是22年从webpack迁移到 vite 后,基于vite做了个类似vue-cli的脚手架,无缝迁移适配公司所有的项目。首屏的性能大概是30s(还是得看本机的性能,因为预构建的esbuild)。后来经排查导致慢的有几种原因: 一、装饰器(esbuild支持ts装饰器,不支持js装饰器),然后使用babel在vite transform时做了层转义,但是在vite插件加上babel后相当于对项目全部的js、ts转译,然后又做了个匹配规则的处理,只对指定的目录、文件进行转译,首屏大概到了15s

二、less语法,现在比价普遍的css预编译器,但是发现在vite运行时转译less语法比转译sfc的速度还要慢,然后又决定贴近原生,使用css next 替换了less😂,然后又给首屏提升了速度。

三、前端模块还不够 "微",既然all in esm,那就一干到底,后来因为公司的项目运行模式,把所有项目都拆成了业务模块,统一入口导出业务组件,然后就是提供个运行平台,根据配置可以从业务模块入口拿页面组件组成路由运行,也是基于的importmap { imports:{ "business-A","://localhost:8001/index.js", "business-B","://localhost:8001/index.js" } } 相当于所有的业务模块都是按需的,然后业务模块也比较细,可变的不可变的,业务模块并行构建也就10s,共平台相当于就是个主应用,代码量更少,构建完3s。业务模块服务再配合后端的微服务一并部署,相当于前端整个构建也就10s😂(可能这才是真正的微前端吧)

最后,楼主可以加个V吗,交流交流

yzydeveloper avatar Mar 07 '23 04:03 yzydeveloper

@yzydeveloper 一: 可以用 esbuild 的 tsx loader 去编译带有装饰器的 js 文件,速度也很快,这个也是作者目前推荐的,详细见: esbuild/issues/104 image 二: less 我倒没接触过,我这边用的是 dart-sass,速度还不错 三: 微前端模式我这边也有,相当于容器从测试环境拉取,然后容器去加载对应的子应用。通常这种微前端模式容器与子应用共享了很多基础模块,所以子应用构建的包少了很多,速度也比较可观

我的微信是 xjx784487301 可以多交流学习一下

xiaoxiaojx avatar Mar 07 '23 05:03 xiaoxiaojx

先赞后看👍🏻

mshmyw avatar Apr 09 '24 14:04 mshmyw