编译 JavaScript,SWC 和 Babel 哪个更出色
在 JavaScript 开发领域,编译工具对于项目的效率和兼容性起着至关重要的作用。Babel 和 SWC 作为两款热门的 JavaScript 编译工具,各自有着独特的优势和特点。本文将深入探讨它们的功能、性能表现,并通过实际案例对比,帮助开发者更好地选择适合自己项目的工具。
一、Babel:JavaScript 编译器的中流砥柱
(一)核心功能剖析
- 语法转换:Babel 的核心能力之一是将新版本的 JavaScript 语法转换成旧版本的语法,以确保代码在不同环境中都能正常运行。例如,箭头函数
const a = (params = 2) => 1 + params;会被转换为普通函数:
var a = function () {
var params =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
return 1 + params;
};
- Polyfill:通过引入额外代码,Babel 能让新的 JavaScript 功能在旧浏览器中使用。比如
Object.assign、Array.prototype.find等新特性,配合core - js库,根据配置的浏览器兼容和代码中使用的 API 进行按需加载或全部引入。在项目中安装core-js后,Babel 转换代码时会根据配置引入相应的core - js模块:
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.symbol.to-primitive.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.date.to-primitive.js");
require("core-js/modules/es.number.constructor.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
function _typeof(o) {
"@babel/helpers - typeof";
return (
(_typeof =
"function" == typeof Symbol && "symbol" == typeof Symbol.iterator
? function (o) {
return typeof o;
}
: function (o) {
return o &&
"function" == typeof Symbol &&
o.constructor === Symbol &&
o !== Symbol.prototype
? "symbol"
: typeof o;
}),
_typeof(o)
);
}
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.filter.js");
require("core-js/modules/es.object.assign.js");
require("core-js/modules/es.object.to-string.js");
- JSX 处理:Babel 可以将 JSX 语法转换成普通的 JavaScript 语法。在 React 项目中,
return <div>我是谁</div>会被转换为:
return /*#__PURE__*/ React.createElement("div", null, "\u6211\u662F\u8C01");
- 插件系统:插件为 Babel 提供了强大的自定义功能。开发者可以编写插件来实现特定的代码转换需求,如将箭头函数转换为普通函数的插件示例:
import Babel from "@babel/core";
import fs from "node:fs";
const transformFunction = ({ types: t }) => {
return {
name: "transformFunction",
visitor: {
ArrowFunctionExpression(path) {
const node = path.node;
const arrowFunction = t.functionExpression(
null,
node.params,
t.blockStatement([t.returnStatement(node.body)]),
node.async
);
path.replaceWith(arrowFunction);
},
},
};
};
const file = fs.readFileSync("./test.js", "utf8");
const result = Babel.transform(file, {
presets: [],
plugins: [transformFunction],
});
console.log(result.code);
(二)实际案例展示
- 语法转换案例:首先使用
npm init -y初始化项目,然后安装@babel/core、@babel/cli和@babel/preset - env依赖:
npm init -y
npm install --save-dev @babel/core @babel/cli @babel/preset-env
编写测试代码后,通过核心转换代码读取文件内容并转换,最终输出转换后的代码:
import Babel from "@babel/core";
import presetEnv from "@babel/preset-env";
import fs from "node:fs";
const file = fs.readFileSync("./test.js", "utf8");
const result = Babel.transform(file, {
presets: [presetEnv],
});
console.log(result.code);
- JSX 转换案例:在 React 项目中,安装
react、react - dom和@babel/preset - react后:
npm i react react-dom
npm install @babel/preset-react -D
编写 JSX 代码,Babel 会按照 React.createElement 的规则将其转换。例如,以下 JSX 代码:
import react from "react";
import { createRoot } from "react-dom/client";
const App = () => {
return <div>我是谁</div>;
};
createRoot(document.getElementById("root")).render(<App />);
转换后的代码为:
"use strict";
var _react = _interopRequireDefault(require("react"));
var _client = require("react-dom/client");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
var App = function App() {
return /*#__PURE__*/ React.createElement("div", null, "\u6211\u662F\u8C01");
};
(0, _client.createRoot)(document.getElementById("root")).render(
/*#__PURE__*/ React.createElement(App, null)
);
二、SWC:后起之秀的崛起
(一)功能亮点解读
- JS/TS 转换:SWC 能够将现代 JS/TS 代码转换为旧版本 JavaScript 环境的代码,涵盖语法转换和 polyfill 处理,与 Babel 类似,但在性能上更胜一筹。
- 模块打包:提供基础的打包功能,可以将多个模块打包成一个单独的文件。通过创建
spack.config.js配置文件,指定入口和出口路径,执行npx spack即可打包。配置文件示例如下:
const { config } = require("@swc/core/spack");
const path = require("path");
module.exports = config({
entry: {
web: path.join(__dirname, "./test.js"),
},
output: {
path: path.join(__dirname, "./dist"),
name: "test.js",
},
});
- 代码压缩优化:类似 Terser,SWC 支持代码压缩,去除空白和注释,减小文件体积。
- 原生支持 TS 和 React:可以直接将 TS 转换为 JS,并且对 React 和 JSX 语法有良好的支持,能将 JSX 转换为标准的 JS 代码,还支持 React 的新特性。
(二)实际应用示例
- JS 语法转换:使用
@swc/core库,配置jsc.target为es5,指定parser.syntax为ecmascript,即可将新版本 JS 代码转换为 ES5 代码。转换后的代码包含一些辅助函数,用于处理数组扩展、类检查等操作:
import swc from "@swc/core";
const result = swc.transformFileSync("./test.js", {
jsc: {
target: "es5",
parser: {
syntax: "ecmascript",
},
},
});
console.log(result.code);
转换后的代码片段:
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 _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 a = function () {
var params =
arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 2;
return 1 + params;
};
var b = [1, 2, 3];
var c = _to_consumable_array(b).concat([4, 5]);
var Babel = function Babel() {
"use strict";
_class_call_check(this, Babel);
};
new Babel();
//API
var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(function (x) {
return x % 2 === 0;
});
var y = Object.assign(
{},
{
name: 1,
}
);
- React JSX 转换:在处理 JSX 文件时,除了基本的配置外,启用
jsx选项,并设置transform.react.runtime为automatic,SWC 会按照新的 JSX 转换方式进行转换,引入react/jsx - runtime中的jsx函数:
import swc from "@swc/core";
const result = swc.transformFileSync("./test.jsx", {
jsc: {
target: "es5",
parser: {
syntax: "ecmascript",
jsx: true,
},
transform: {
react: {
runtime: "automatic",
},
},
},
});
console.log(result.code);
转换后的代码:
import { jsx as _jsx } from "react/jsx-runtime";
import react from "react";
import { createRoot } from "react-dom/client";
var App = function () {
return /*#__PURE__*/ _jsx("div", {
children: "我是谁",
});
};
createRoot(document.getElementById("root")).render(/*#__PURE__*/ _jsx(App, {}));
三、性能大对决:SWC vs Babel
SWC 在性能方面表现卓越,单线程上比 Babel 快 20 倍,四核上快 70 倍。这主要得益于其采用编译型的 Rust 语言开发,在编译时将代码转化为机器码,执行效率高,几乎无额外开销。而 JavaScript 是解释型语言,尽管现代引擎使用 JIT 技术提升性能,但本质上运行时开销更大。在实际转换代码的测试中,SWC 转换用时明显短于 Babel。例如,转换相同的 JS 代码,SWC 用时default: 8.088ms,Babel 则需要default: 417.59ms;转换 React JSX 代码时,SWC 用时default: 4.088ms,Babel 用时default: 80.59ms。
四、如何抉择:根据项目需求做选择
如果项目对兼容性要求极高,需要支持老旧浏览器,并且对自定义功能有较多需求,Babel 丰富的插件生态和成熟的语法转换能力是更好的选择。例如在一些大型企业级项目中,需要兼容多种不同版本的浏览器,Babel 的 polyfill 和插件可以满足复杂的需求。而如果项目追求极致的性能,尤其是在处理大量代码转换或打包任务时,SWC 无疑是最佳选择。在新兴的前端项目中,使用 TypeScript 和 React 的场景下,SWC 对 TS 和 React 的原生支持以及高效的转换速度,能够大大提升开发效率。对于一些简单的打包需求,SWC 的打包功能也可以作为一个轻量级的解决方案。
Babel 和 SWC 都为 JavaScript 开发提供了强大的编译能力。开发者应根据项目的具体需求,综合考虑功能特性和性能表现,选择最适合的工具,以提升项目的开发效率和质量。希望通过本文的介绍,能帮助大家在开发过程中做出更明智的决策。