新一代的編譯工具 SWC

Shenfq發表於2022-01-14

最近前端圈掀起了一陣 rust 風,凡是能用 rust 重寫的前端工具就用 rust 重寫,今天介紹的工具就是通過 rust 實現的 bable:swc,一個將 ES6 轉化為 ES5 的工具。

而且在 swc 的官網,很直白說自己和 babel 對標,swcbabel 命令可以相互替換,並且大部分的 babel 外掛也已經實現。

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

上手

swc 與 babel 一樣,將命令列工具、編譯核心模組分化為兩個包。

  • @swc/cli 類似於 @babel/cli;
  • @swc/core 類似於 @babel/core;
npm i -D @swc/cli @swc/core

通過如下命令,可以將一個 ES6 的 JS 檔案轉化為 ES5。

npx swc source.js -o dist.js

下面是 source.js 的程式碼:

const start = () => {
  console.log('app started')
}

程式碼中囊括了 ES6 的兩個特性,const 宣告箭頭函式。經過 swc 轉化後,這兩個特性分別被轉化成了 var 宣告function 匿名函式

配置檔案

swc 與 babel 一樣,支援類似於 .babelrc 的配置檔案:.swcrc,配置的格式為 JSON。

{
  "jsc": { // 編譯規則
    "target": "es5", // 輸出js的規範
    "parser": {
      // 除了 ecmascript,還支援 typescript
      "syntax": "ecmascript",
      // 是否解析jsx,對應外掛 @babel/plugin-transform-react-jsx
      "jsx": false,
      // 是否支援裝飾器,對應外掛 @babel/plugin-syntax-decorators
      "decorators": false,
      // 是否支援動態匯入,對應外掛 @babel/plugin-syntax-dynamic-import
      "dynamicImport": false,
      // ……
      // babel 的大部分外掛都能在這裡找到對應配置
    },
    "minify": {}, // 壓縮相關配置,需要先開啟壓縮
  },
  "env": { // 編譯結果相關配置
    "targets": { // 編譯結果需要適配的瀏覽器
      "ie": "11" // 只相容到 ie 11
    },
    "corejs": "3" // corejs 的版本
  },
  "minify": true // 是否開啟壓縮
}

babel 的外掛系統被 swc 整合成了 jsc.parser 內的配置,基本上大部分外掛都能照顧到。而且,swc 還繼承了壓縮的能力,通過 minify 屬性開啟,jsc.minify 用於配置壓縮相關的規則,更詳細的配置可檢視文件

Node APIs

通過在 node.js 程式碼中,匯入 @swc/core 模組,可以在 node.js 中呼叫 api 直接進行程式碼的編譯,這對 CLI 工具的開發來說是常規操作。

// 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()

打包程式碼

除了將程式碼轉義,swc 還提供了一個簡易的打包能力。我們新建一個 src 資料夾,在裡面新建兩個檔案:index.jsutils.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 匯入了 utils.js 中的一個方法,然後我們新建一個 spack.config.js 檔案,該檔案是 swc 打包的配置檔案。

// spack.config.js
module.exports = {
  entry: {
    // 打包的入口
    web: __dirname + "/src/index.js",
  },
  output: {
    // 打包後輸出的資料夾
    path: __dirname + "/dist",
  },
};

然後在命令列執行:

$ npx spack

打包成功後,會在 dist 目錄輸出一個 web.js 檔案。

可以看到,不僅將 index.jsutils.js 打包成了一個檔案,還進行了 tree shaking,將 utils.js 中沒有使用的 errorLog 方法刪掉了。

能不能用?

babel 畢竟經過了這麼多年的發展,不管是 bug 輸了,還是社群活躍度都遠遠優於 swc。所以,如果是小產品試水還是可以試一下 swc 的,舊專案如果已經使用了 babel 還是不建議進行遷移。

在使用的過程,還是發現了一些小問題。比如,如果我使用了 async function,swc 會自動匯入 regenerator-runtime 模組。

// 編譯前,有個 async 方法
const start = async () => {
  console.log('app started')
}

呼叫 swc 編譯後,程式碼如下:

這個結果看起來是沒問題的,但是 swc 與 babel 類似,也有 helpers(@swc/helpers),同時提供了 externalHelpers 開關, 如果把 externalHelpers 設定為 true,swc 會將一些工具類,通過模組的形式匯入。

// .swcrc
{
  "jsc": {
    "externalHelpers": true
  }
}

externalHelpers 的預設值是 false,那這個時候,regenerator-runtime ,到底是通過模組的形式匯入,還是把整個程式碼寫入到檔案?

swc 正好有個 [issue [https://github.com/swc-projec...]](https://github.com/swc-projec...) 在討論這個問題。

除了上面說的這個問題,其實還有一點,就是作者覺得之前的架構有問題,正在加緊重寫 2.0 版本,感覺可以期待一下,另外提一句,swc 的作者是一個 97 年的韓國小哥,目前大學都還沒畢業,最後我也只能說一句:牛逼!

相關文章