海阔天空的云

我们在自己的世界里独自狂欢

0%

前端项目使用swc踩坑

图片

从rust说起

大概是一个多月以前,我刷推特的时候,偶然说到一条推文,大概内容是讲rust语言在前端开发中的应用的。

Rust语言经过多年的不懈努力终于找到自己最擅长的领域:重写命令行小工具。
之后再接再厉,进一步找到自己最擅长并可以实现降维打击的细分领域:重写前端开发命令行小工具。(象牙山刘能)

后来就简单了解了一下,rust,发现确实有点东西。

比如这个:

依托于WebAssembly,Rust可以运行在浏览器上,在某些场景(如视频直播或需要大量运算)具有卓越的性能,例如我们经常用的figma就有使用到WebAssembly

当然也有另外一个用法,也就是使用rust语言去写前端命令行工具,也就是这个推文里所提到的,后来就进一步探索,发现了另外一个工具swc

swc

经过了解,发现swc也有点东西,借助AI问答了一下。

对比一下babel和swc?

Babel是一个JavaScript编译器,可以将ES6,ES7和JSX等新特性转换为浏览器可以理解的JavaScript代码。它还可以将ES2015模块转换为其他模块系统,如CommonJS或AMD。

Swc是一个快速的JavaScript编译器,它使用Rust语言开发,旨在提供最佳的性能。它可以将ES6,ES7和JSX代码转换为浏览器可以理解的JavaScript代码。它还可以将ES2015模块转换为CommonJS或UMD格式。此外,Swc还具有静态分析功能,可用于找出错误并生成错误信息。

总之,Babel是一个功能强大的JavaScript编译器,而Swc则是一个快速而高效的JavaScript编译器。

可以看到swc最显著的特殊就是快速

这是一个韩国小哥开发的开源程序,使用rust语言编写。虽然没有中文文档,但是英文文档写得还是比较简洁清晰的,于是试着在公司项目中使用了一下,结果发现效果确实不错,什么效果?当时是速度,构建速度。

这里就还要说几句题外话了。公司的项目结构比较复杂,说简单点是用微前端的结构去处理页面。微前端当然意味着每个小组有自己相对独立的一块工作,一些项目,可以不用去影响其他小组的工作,然而代价就是很多东西是我自己很难去处理的,这里就包括了项目构建这一块,目前的通用方案仍然是使用webpack去做项目构建,并且我们还在使用webpack 的module federation去进行微前端项目之间的通信,不能去动webpack这个大的框架,所以我当时去测试,就只能小改,也就是使用swc-loader去替换babel-loader,在webpack中loader是很重要的一个概念,每一种loader都能够去处理一些文件类型的资源,之前我们使用babel-loader去处理js,ts资源,但是现在在我的测试中,我用了swc-loader去替换掉他,经过测试,整个完整的webpack项目的启动时间缩小了一半!从我的电脑来看,大概是从60s,缩短到了30s,如果考虑到整个webpack构建的时间,并不光光只是处理js,ts文件,那么这个速度相对于之前使用babel,优势就更大了。

是否有意义替换babel

我做完测试之后,紧接着遇到一个问题,是否有意义替换babel。首先在我看来,babel有点太复杂了。我之前写过一篇博文,讲了很多babel相关的概念(FYI 2)。这种复杂度也体现在我们的项目中安装有很多babel的插件,babel的config文件也比较复杂,package.json里面很多babel相关的包,印象中之前做过两次升级的工作,babel及其插件挨个地升级也是实在很烦人(FYI3)。当然你也可以说,你直接进一步封装一下不就好了,当然也可以,但目前我们没有这么做。

我的另外一个问题是,时间已经来到了2023年,到底还有没有必要使用一个最初目的是为浏览器上能够跑ES2015写的代码而准备的库?我想是没有太大目的的,当然其实babel还有别的目的,比如说可以转化一些还处于stage阶段的语法规范,这种情况,如果用的不多,甚至不需要,完全可以忽略掉。当然,我也相信随着swc生态的完善,swc也会支持更多插件,实现类似的目的。(事实是,swc的开源项目里,也的确已经有了几个官方的插件)

总结而言我对babel有几点不满

  • 2023年了,babel存在意义不大
  • babel配置复杂

swc 替换踩坑

使用swc乍一看还很完美,我搜了下公司代码库,发现已经有其他组正在用了。然而再仔细推敲一下,发现还有些不完美。当时发现两点问题。

一个问题比较好解决,当时我在测试的时候,很快就发现,我们的某些页面打不开,结果debug之后发现是因为有些变量未声明就先使用了,这个之前使用babel并不会报错,因为我们在babel配置中将target定位到了es5,es5不关心这些,但是使用swc的时候,就有问题了,因为使用swc我们将target定位到了es6,问题就出现了。我的解决办法,就是使用eslint去做检查,把出错的语法一条条修改。

一个问题是我们使用了一个比较小众的babel插件,也就是babel-plugin-styled-components-css-namespace ,这个插件是我最早引入到项目当中的,目的是解决styled components写的css与其他css同时作用于一个元素时,有些样式标识不符合预期的问题(不符合预期的原因时,其他css的权重更高,造成styled 没表现)。如果去掉babel的话,就意味着这样一个插件不能用了,这些权重的设置也无法表现,会有很多潜在的bug出现。针对这个问题,当时我的应对办法是写一个swc插件,通过看swc官方文档,我了解到swc也是支持写插件的,通过写插件,也能实现和babel类似的效果,一来我对rust很感兴趣,二来也想使用类chatgpt bot去看看写代码的表现,于是花了一些时间写了个插件,还真实现了类似的效果,但是我自己仍然对这个插件不是非常满意。原因也有一些:

首先,这个rust语言的插件,会让其他同事看起来很痛苦,我在学习rust的过程中,也发现这个门槛比较高。另外,我也卡在了npm包的发布上,rust写的npm包与我们项目中之前用的npm包有很大不同,于是由此另外引入一套npm包打包发布流程,这是我不是很满意的。(参见swc官方插件库)

正当我犹豫不决之时,又迎来了一些转机,我们项目由于要支持阿拉伯语,也就需要应对rtl布局,之前这一块一直做的不太好,需要手动去写一些变量替换,比如padding-left要写成$pos('padding-left') ,否则原来的代码就会在rtl布局下有问题,因为这个pos方法就是为了应对rtl去做变量转换的。每次都要手写变量实在太麻烦,刚好那些天不是很忙,我就自告奋勇接过了这个活,结果新的路径就打开了。

长话短说,不卖关子了。在styled component v5引入了插件的概念,他甚至在官方文档里推荐了stylis-plugin-rtl这个插件,去实现rtl的效果。通过阅读文档,当时就有一种柳暗花明的感觉,既然可以使用插件来实现rtl,当然也可以使用插件来实现权重提升,况且这个插件还都是使用js编写,前面说的两个问题就都不存在了。

后来的事情,就是在项目中引入了styled components 的插件,同时在我们的npm monorepo里写了一个styled components 插件来实现权重提升,去替换掉babel的对应插件。

解决完以上的两个问题之后,本以为就没有什么能够阻止我们使用swc了。然而,还是有些意外。

替换成swc 插曲

本来以为一切顺利的,结果发现还是有些问题,主要问题出在依赖上。前面已经提到的,我司使用微前端架构,我们平时开发会服务两个网站,这两个网站都有一个base的项目,一些常用的依赖,比如react,react-dom,styled-components是这些base项目直接以umd的方式暴露到window对象上的,结果就是我最早替换的A网站没有问题,但是后来准备替换的B网站,base项目中的styled-components版本还是v4版本,根本不能使用v5的plugin新特性。我当然也是可以改这个base项目的依赖,给它升级,但是由于发布时间的关系,如果我在我的项目中,贸然使用v5的新特性,就有可能有问题。

最终,由于要处理依赖关系,我最终没有在B项目中使用swc。当然,一旦B网站新版本上线之后,我就可以放开去替换了,这也是后话了。

回顾

回顾整个过程,其实还蛮好玩的,整个过程历时一个多月,从一开始以为这件事很简单,到后来发现有坑,再后来寻求不同的解决方案,最后还是将方案落地,施行,期间也在思考怎样将影响降到最小,怎样对这样的替换过程更有自信。当然,有自信也有一些原因,有一部分原因来自于项目当中的E2E,由于有了E2E,的确发现了一些问题,我写的styled-components插件是有问题的,通过E2E发现了问题,后来又修复了问题,整个过程也再次让我意识到前端工程化的好处,工程化,当然要有前端测试喽。

FYI: