简单聊聊 SWC
随着在编译中越来越卷,SWC 由于速度极快也出现在大众的视野中,本文会简单介绍一下 SWC。前文中有对 Babel 做相关介绍,大家可以一起阅读。
SWC 是什么?
SWC 出现的很大部分原因其实是替换掉工程中的 Babel,因此它的功能和 Babel 差不多。
最大的区别可能就是:SWC is father than Babel
🤔️ 其实 SWC 快的原因是由于底层语言 Rust 带来的。
SWC 怎么用?
和 babel 一样,提供了命令行工具包 @swc/cli 和核心包 @swc/core
pnpm swc ./src/code.js // 将文件编译输出到控制台
一样的在 @swc/core 包中也导出相关的 Node API 供大家使用。
const swc = require('@swc/core');
const path = require('path');
const fs = require('fs');
swc
.transformFile(path.join(__dirname, './code.js'), {
sourceMaps: true,
isModule: false,
jsc: {
parser: {
syntax: 'ecmascript',
},
transform: {},
},
env: {
targets: {
ie: '8',
},
},
})
.then((output) => {
fs.writeFileSync(path.join(__dirname, './targetCode.js'), output.code, {
encoding: 'utf-8',
});
});
通过swc.parser也能够拿到 AST 相关数据
关于配置
const arr = [1, 2, 3, 4];
const arr1 = [...arr];
arr.includes(1);
const p = new Promise((resolve, reject) => {
resolve('FBB');
});
class Person {
constructor() {}
say(word) {
console.log(':::', word);
}
}
target
当我们在配置中写入对应的browserslist,swc 能够将对应的语法转换到浏览器支持的语法
https://github.com/swc-project/swc/issues/104
{
"jsc": {
"parser": {
"syntax": "ecmascript"
},
},
"env": {
"targets": {
"ie": "8"
},
}
}
将会被编译为如下代码:
function _array_like_to_array(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _array_without_holes(arr) {
if (Array.isArray(arr)) return _array_like_to_array(arr);
}
function _class_call_check(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _create_class(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _iterable_to_array(iter) {
if (
(typeof Symbol !== 'undefined' && iter[Symbol.iterator] != null) ||
iter['@@iterator'] != null
)
return Array.from(iter);
}
function _non_iterable_spread() {
throw new TypeError(
'Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _to_consumable_array(arr) {
return (
_array_without_holes(arr) ||
_iterable_to_array(arr) ||
_unsupported_iterable_to_array(arr) ||
_non_iterable_spread()
);
}
function _unsupported_iterable_to_array(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _array_like_to_array(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(n);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _array_like_to_array(o, minLen);
}
var arr = [1, 2, 3, 4];
var arr1 = _to_consumable_array(arr);
arr.includes(1);
var p = new Promise(function (resolve, reject) {
resolve('FBB');
});
var Person = /*#__PURE__*/ (function () {
'use strict';
function Person() {
_class_call_check(this, Person);
}
_create_class(Person, [
{
key: 'say',
value: function say(word) {
console.log(':::', word);
},
},
]);
return Person;
})();
mode
和 babel 的 useBuiltIns 配置一样,swc/mode
https://github.com/swc-project/swc/issues/397
{
"jsc": {
"parser": {
"syntax": "ecmascript"
}
},
"env": {
"targets": {
"ie": "8"
},
"mode": "usage",
"coreJs": "3.22"
}
}
将会被编译为如下代码:
function _array_like_to_array(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _array_without_holes(arr) {
if (Array.isArray(arr)) return _array_like_to_array(arr);
}
function _class_call_check(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _create_class(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _iterable_to_array(iter) {
if (
(typeof Symbol !== 'undefined' && iter[Symbol.iterator] != null) ||
iter['@@iterator'] != null
)
return Array.from(iter);
}
function _non_iterable_spread() {
throw new TypeError(
'Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _to_consumable_array(arr) {
return (
_array_without_holes(arr) ||
_iterable_to_array(arr) ||
_unsupported_iterable_to_array(arr) ||
_non_iterable_spread()
);
}
function _unsupported_iterable_to_array(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _array_like_to_array(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(n);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _array_like_to_array(o, minLen);
}
import 'core-js/modules/es.array.includes.js';
import 'core-js/modules/es.string.includes.js';
import 'core-js/modules/es.promise.js';
import 'core-js/modules/es.object.to-string.js';
var arr = [1, 2, 3, 4];
var arr1 = _to_consumable_array(arr);
arr.includes(1);
var p = new Promise(function (resolve, reject) {
resolve('FBB');
});
var Person = /*#__PURE__*/ (function () {
'use strict';
function Person() {
_class_call_check(this, Person);
}
_create_class(Person, [
{
key: 'say',
value: function say(word) {
console.log(':::', word);
},
},
]);
return Person;
})();
helper function
就如同我们在 babel 上说的一样,每一个都会引入这么多的辅助函数,明显不是我们所期望的,swc 也通过配置解决了这个问题
{
"jsc": {
"parser": {
"syntax": "ecmascript"
},
"externalHelpers": true
},
"env": {
"targets": {
"ie": "8"
},
"mode": "usage",
"coreJs": "3.22"
}
}
将会被编译为如下代码:
import { _ as _class_call_check } from '@swc/helpers/_/_class_call_check';
import { _ as _create_class } from '@swc/helpers/_/_create_class';
import { _ as _to_consumable_array } from '@swc/helpers/_/_to_consumable_array';
import 'core-js/modules/es.array.includes.js';
import 'core-js/modules/es.string.includes.js';
import 'core-js/modules/es.promise.js';
import 'core-js/modules/es.object.to-string.js';
var arr = [1, 2, 3, 4];
var arr1 = _to_consumable_array(arr);
arr.includes(1);
var p = new Promise(function (resolve, reject) {
resolve('FBB');
});
var Person = /*#__PURE__*/ (function () {
'use strict';
function Person() {
_class_call_check(this, Person);
}
_create_class(Person, [
{
key: 'say',
value: function say(word) {
console.log(':::', word);
},
},
]);
return Person;
})();
在 swc 中,将所有的辅助函数放在了@swc/helpers中,也不需要像 babel 那样去引入额外的 plugin 来解决这个问题。
关于 Plugin
在 SWC 中,也可以编写 JS plugin,但是提供的相关 API 是比较局限的。
plugin-utils/src/visitor.ts at master · swc-project/plugin-utils
编写 SWC 的 JS plugin 时,需要继承 Visitor,然后通过访问对应的 AST 节点,对对应的 AST 节点进行处理的到我们想要的节点内容,返回回来
const Visitor = require('@swc/core/Visitor').default;
class ConsoleStripper extends Visitor {
visitCallExpression(expression) {
if (expression.callee.type !== 'MemberExpression') {
return expression;
}
if (
expression.callee.object.type === 'Identifier' &&
expression.callee.object.value === 'console'
) {
if (expression.callee.property.type === 'Identifier') {
expression.arguments = [...expression.arguments];
expression.arguments.unshift({
spread: null,
expression: {
type: 'StringLiteral',
span: expression.span,
value: `filename: ${expression.span.start} and ${expression.span.end}`,
raw: `filename: ${expression.span.start} and ${expression.span.end}`,
},
});
return {
...expression,
};
}
}
return expression;
}
}
当我们想要使用这个插件时,需要在 option 中声明该 plugin
swc
.transformFile(path.join(__dirname, './code.js'), {
plugin: (m) => new ConsoleStripper().visitProgram(m),
})
.then((output) => {
fs.writeFileSync(path.join(__dirname, './targetCode.js'), output.code, {
encoding: 'utf-8',
});
});
formatjs/packages/swc-plugin/tests/utils.ts at main · formatjs/formatjs
SWC 编译
例如上述我们调用 transformFile 时,其源码如下
transformFile(path, options) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
//....
const { plugin } = options, newOptions = __rest(options, ["plugin"]);
//....
if (plugin) {
const m = yield this.parseFile(path, (_c = options === null || options === void 0 ? void 0 : options.jsc) === null || _c === void 0 ? void 0 : _c.parser);
return this.transform(plugin(m), newOptions);
}
return bindings.transformFile(path, false, toBuffer(newOptions));
});
}
在无 plugin 的情况下,调用的是bindings.transformFile,有 plugin 的情况下调用的是this.transform,在this.transform中也会调用bindings.xxx方法
那 bindings 是啥呢?
const bindings = (() => {
let binding;
try {
binding = !!bindingsOverride
? require((0, path_1.resolve)(bindingsOverride))
: require('./binding');
// If native binding loaded successfully, it should return proper target triple constant.
const triple = binding.getTargetTriple();
assert.ok(triple, 'Failed to read target triple from native binary.');
return binding;
} catch (_) {
// postinstall supposed to install `@swc/wasm` already
fallbackBindings = require('@swc/wasm');
} finally {
return binding;
}
})();
binding 文件中是对 platform 和 arch 的判断,然后返回不同的 nativeBinding,最后我们使用的相关 Node API 就是从 nativeBinding 中获取到的


大体的流程图如下:

总结
整体来说,SWC 和 Babel 是大同小异的,但是 SWC 在速度上完胜 Babel,一方面是 Rust 语言和 JS 的差距,另一方面是 SWC 相关的 API 均已经编译成为了二进制文件。