新一代的編譯工具 SWC
最近前端圈掀起了一陣 rust 風(fēng),凡是能用 rust 重寫的前端工具就用 rust 重寫,今天介紹的工具就是通過 rust 實(shí)現(xiàn)的 bable:swc,一個將 ES6 轉(zhuǎn)化為 ES5 的工具。
而且在 swc 的官網(wǎng),很直白說自己和 babel 對標(biāo),swc 和 babel 命令可以相互替換,并且大部分的 babel 插件也已經(jīng)實(shí)現(xiàn)。

使用 rust 的一個優(yōu)勢就是快,比如我們之前的一個項目,將 babel 替換成 swc 后,編譯速度從原來的 7 秒提升到了 1 秒,效率直接爆炸。

上手
swc 與 babel 一樣,將命令行工具、編譯核心模塊分化為兩個包。
@swc/cli類似于@babel/cli;@swc/core類似于@babel/core;
npm?i?-D?@swc/cli?@swc/core
通過如下命令,可以將一個 ES6 的 JS 文件轉(zhuǎn)化為 ES5。
npx?swc?source.js?-o?dist.js
下面是 source.js 的代碼:
const?start?=?()?=>?{
??console.log('app?started')
}
代碼中囊括了 ES6 的兩個特性,const 聲明 和 箭頭函數(shù)。經(jīng)過 swc 轉(zhuǎn)化后,這兩個特性分別被轉(zhuǎn)化成了 var 聲明 和 function 匿名函數(shù)。

配置文件
swc 與 babel 一樣,支持類似于 .babelrc 的配置文件:.swcrc,配置的格式為 JSON。
{
??"jsc":?{?//?編譯規(guī)則
????"target":?"es5",?//?輸出js的規(guī)范
????"parser":?{
??????//?除了?ecmascript,還支持?typescript
??????"syntax":?"ecmascript",
??????//?是否解析jsx,對應(yīng)插件?@babel/plugin-transform-react-jsx
??????"jsx":?false,
??????//?是否支持裝飾器,對應(yīng)插件?@babel/plugin-syntax-decorators
??????"decorators":?false,
??????//?是否支持動態(tài)導(dǎo)入,對應(yīng)插件?@babel/plugin-syntax-dynamic-import
??????"dynamicImport":?false,
??????//?……
??????//?babel?的大部分插件都能在這里找到對應(yīng)配置
????},
????"minify":?{},?//?壓縮相關(guān)配置,需要先開啟壓縮
??},
??"env":?{?//?編譯結(jié)果相關(guān)配置
????"targets":?{?//?編譯結(jié)果需要適配的瀏覽器
??????"ie":?"11"?//?只兼容到?ie?11
????},
????"corejs":?"3"?//?corejs?的版本
??},
??"minify":?true?//?是否開啟壓縮
}
babel 的插件系統(tǒng)被 swc 整合成了 jsc.parser 內(nèi)的配置,基本上大部分插件都能照顧到。而且,swc 還繼承了壓縮的能力,通過 minify 屬性開啟,jsc.minify 用于配置壓縮相關(guān)的規(guī)則,更詳細(xì)的配置可查看文檔。
Node APIs
通過在 node.js 代碼中,導(dǎo)入 @swc/core 模塊,可以在 node.js 中調(diào)用 api 直接進(jìn)行代碼的編譯,這對 CLI 工具的開發(fā)來說是常規(guī)操作。
//?swc.mjs
import?{?readFileSync?}?from?'fs'
import?{?transform?}?from?'@swc/core'
const?run?=?async?()?=>?{
??const?code?=?readFileSync('./source.js',?'utf-8')
?const?result?=?await?transform(code,?{
????filename:?"source.js",
??})
??//?輸出編譯后代碼
??console.log(result.code)
}
run()

打包代碼
除了將代碼轉(zhuǎn)義,swc 還提供了一個簡易的打包能力。我們新建一個 src 文件夾,在里面新建兩個文件:index.js、utils.js。
//?src/index.js
import?{?log?}?from?'./utils.js'
const?start?=?()?=>?log('app?started')
start()
//?src/utils.js
export?const?log?=?function?()?{
??console.log(...arguments)
}
export?const?errorLog?=?function?()?{
??console.error(...arguments)
}
可以看到 index.js 導(dǎo)入了 utils.js 中的一個方法,然后我們新建一個 spack.config.js 文件,該文件是 swc 打包的配置文件。
//?spack.config.js
module.exports?=?{
??entry:?{
????//?打包的入口
????web:?__dirname?+?"/src/index.js",
??},
??output:?{
????//?打包后輸出的文件夾
????path:?__dirname?+?"/dist",
??},
};
然后在命令行運(yùn)行:
$?npx?spack

打包成功后,會在 dist 目錄輸出一個 web.js 文件。

可以看到,不僅將 index.js、utils.js 打包成了一個文件,還進(jìn)行了 tree shaking,將 utils.js 中沒有使用的 errorLog 方法刪掉了。
能不能用?
babel 畢竟經(jīng)過了這么多年的發(fā)展,不管是 bug 輸了,還是社區(qū)活躍度都遠(yuǎn)遠(yuǎn)優(yōu)于 swc。所以,如果是小產(chǎn)品試水還是可以試一下 swc 的,舊項目如果已經(jīng)使用了 babel 還是不建議進(jìn)行遷移。
在使用的過程,還是發(fā)現(xiàn)了一些小問題。比如,如果我使用了 async function,swc 會自動導(dǎo)入 regenerator-runtime 模塊。
//?編譯前,有個?async?方法
const?start?=?async?()?=>?{
??console.log('app?started')
}
調(diào)用 swc 編譯后,代碼如下:

這個結(jié)果看起來是沒問題的,但是 swc 與 babel 類似,也有 helpers(@swc/helpers),同時提供了 externalHelpers 開關(guān), 如果把 externalHelpers 設(shè)置為 true,swc 會將一些工具類,通過模塊的形式導(dǎo)入。
//?.swcrc
{
??"jsc":?{
????"externalHelpers":?true
??}
}

而 externalHelpers 的默認(rèn)值是 false,那這個時候,regenerator-runtime ,到底是通過模塊的形式導(dǎo)入,還是把整個代碼寫入到文件?
swc 正好有個 issue [https://github.com/swc-project/swc/issues/1461] 在討論這個問題。
除了上面說的這個問題,其實(shí)還有一點(diǎn),就是作者覺得之前的架構(gòu)有問題,正在加緊重寫 2.0 版本,感覺可以期待一下,另外提一句,swc 的作者是一個 97 年的韓國小哥,目前大學(xué)都還沒畢業(yè),最后我也只能說一句:牛逼!
- END -