因为...
大家可能在日常工作中遇到过, 项目的构建时间随着项目体积和复杂度逐渐递增, 有的时候本地编辑一个项目要等上个大几分钟(此处@Webpack)
这个是ESBuild官网对于其打包10份three.js的速度对比
那么ESBuild & SWC是真的有这么快? 还是开发者的自说自话? 我们通过实验来检验一下, 先看ESBuild
# 编译
> build-esb
> esbuild ./src/app.jsx --bundle --outfile=out_esb.js --minify
# 构建产物的大小和构建时间
out_esb.js 27.4kb
⚡ Done in 13ms
# 运行产物
node out_esb.js
<h1 data-reactroot="">Hello, world!</h1>
# 编译
> build-wp
> webpack --mode=production
# 构建产物
asset out_webpack.js 25.9 KiB [compared for emit] [minimized] (name: main) 1 related asset
modules by path ./node_modules/react/ 8.5 KiB
./node_modules/react/index.js 189 bytes [built] [code generated]
./node_modules/react/cjs/react.production.min.js 8.32 KiB [built] [code generated]
modules by path ./node_modules/react-dom/ 28.2 KiB
./node_modules/react-dom/server.browser.js 227 bytes [built] [code generated]
./node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 28 KiB [built] [code generated]
./src/app.jsx 254 bytes [built] [code generated]
./node_modules/object-assign/index.js 2.17 KiB [built] [code generated]
# 构建时间
webpack 5.72.0 compiled successfully in 1680 ms
npm run build-wp 2.79s user 0.61s system 84% cpu 4.033 total
# 运行
node out_webpack.js
<h1 data-reactroot="">Hello, world!</h1>
让我们先写一段非常简单的代码
import * as React from 'react'
import * as ReactServer from 'react-dom/server'
const Greet = () => <h1>Hello, world!</h1>
console.log(ReactServer.renderToString(<Greet />))
然后我们来通过Webpack & ESBuild构建它
再来看看swc的编译效率
// 一些变量声明
const PI = 3.1415;
let x = 1;
// spread
let [foo, [[bar], baz]] = [1, [[2], 3]];
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
// arrow function
var sum = (num1, num2) => { return num1 + num2; }
// set
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
// class
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
yarn compile-babel
yarn run v1.16.0
warning package.json: No license field
$ babel src/es6.js -o es6_babel.js
✨ Done in 2.38s.
yarn compile-swc
yarn run v1.16.0
warning package.json: No license field
$ swc src/es6.js -o es6_swc.js
Successfully compiled 1 file with swc.
✨ Done in 0.63s.
// es6_babel
"use strict";
function _classCallCheck(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 _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
var PI = 3.1415;
var x = 1;
var foo = 1,
bar = 2,
baz = 3;
var node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
var loc = node.loc,
start = node.loc.start,
line = node.loc.start.line;
var sum = function sum(num1, num2) {
return num1 + num2;
};
var s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(function (x) {
return s.add(x);
});
var Point = /*#__PURE__*/function () {
function Point(x, y) {
_classCallCheck(this, Point);
this.x = x;
this.y = y;
}
_createClass(Point, [{
key: "toString",
value: function toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}]);
return Point;
}();
// es6 swc
function _classCallCheck(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 _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var PI = 3.1415;
var x = 1;
var foo = 1, bar = 2, baz = 3;
var node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
var loc = node.loc, start = node.loc.start, _loc = node.loc, line = _loc.start.line;
var sum = function(num1, num2) {
return num1 + num2;
};
var s = new Set();
[
2,
3,
5,
4,
5,
2,
2
].forEach(function(x1) {
return s.add(x1);
});
var Point = /*#__PURE__*/ function() {
"use strict";
function Point(x2, y) {
_classCallCheck(this, Point);
this.x = x2;
this.y = y;
}
_createClass(Point, [
{
key: "toString",
value: function toString() {
return "(" + this.x + ", " + this.y + ")";
}
}
]);
return Point;
}();
//# sourceMappingURL=es6_swc.js.map
从上面的数据可以看出
从上面的截图中选择几个我们日常接触最频繁的前端工程化工具:
Task Runner 任务运行器: 开发者设置脚本让构建工具完成开发、构建、部署中的一系列任务, 大家日常常用的是npm/yarn的脚本功能; 在更早一些时候, 比较流行Gulp/Grunt这样的工具
Package Manager 包管理器: 这个大家都不会陌生, npm/Yarn/pnmp帮开发者下载并管理好依赖, 对于现在的前端开发来说必不可少.
Compiler/Transpiler 编译器: 在市场上很多浏览器还只支持ES5语法的时候, Babel这样的Comipler在前端开发中必不可少; 如果你是用TypeScript的话, 也需要通过tsc或者ts-loader进行编译.
Bundler 打包工具: 从开发者设置的入口出发, 分析模块依赖, 加载并将各类资源最终打包成1个或多个文件的工具.
ESBuild 中的一切代码从零实现: 通过自行实现所有逻辑来避免第三方库带来的性能问题, 统一的数据结构可以减少数据转换开销, 并且可以根据需要改变架构, 当然最大的缺点就是工作量倍增.
ESBuild在API层面上非常简洁, 主要的API只有两个: Transform和Build, 这两个API可以通过CLI, JavaScript, Go的方式调用
# 用CLI方式调用, 将ts代码转化为js代码
echo 'let x: number = 1' | esbuild --loader=ts => let x = 1;
// 用JS模式调用build方法
require('esbuild').buildSync({
entryPoints: ['in.js'],
bundle: true,
outfile: 'out.js',
})
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.js': 'jsx' },
outfile: 'out.js',
})
// 来自于官网的插件示范
let envPlugin = {
name: 'env',
setup(build) {
// Intercept import paths called "env" so esbuild doesn't attempt
// to map them to a file system location. Tag them with the "env-ns"
// namespace to reserve them for this plugin.
build.onResolve({ filter: /^env$/ }, args => ({
path: args.path,
namespace: 'env-ns',
}))
// Load paths tagged with the "env-ns" namespace and behave as if
// they point to a JSON file containing the environment variables.
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
contents: JSON.stringify(process.env),
loader: 'json',
}))
},
}
// 使用插件
require('esbuild').build({
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
plugins: [envPlugin],
}).catch(() => process.exit(1))
在其他工具中使用ESBuild
const { ESBuildMinifyPlugin } = require('esbuild-loader')
module.exports = {
rules: [
{
test: /.js$/,
// 使用esbuild作为js/ts/jsx/tsx loader
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2015'
}
},
],
// 或者使用esbuild-loader作为JS压缩工具
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
target: 'es2015'
})
]
}
}
注意点
Comilation
可以使用swc命令行工具(swc/cli)配合配置文件[6]对文件进行编译
# Transpile one file and emit to stdout
npx swc ./file.js
# Transpile one file and emit to `output.js`
npx swc ./file.js -o output.js
# Transpile and write to /output dir
npx swc ./my-dir -d output
swc的核心部分swc/core主要有三种API
swc也推出了swc/wasm模块, 可以让用户在浏览器环境使用wasm进行代码转换
如果你想在Webpack体系下使用swc(替代babel), 也可以使用swc-loader
Bundle
ESBuild: https://esbuild.github.io/
[2]SWC: https://swc.rs/
[3]FAQ: https://esbuild.github.io/faq/
[4]博客: https://swc.rs/blog/perf-swc-vs-babel
[5]esbuild-loader: https://github.com/privatenumber/esbuild-loader
[6]配置文件: https://swc.rs/docs/configuration/swcrc
[7]spack.config.js: https://swc.rs/docs/configuration/bundling
以上便是本次分享的全部内容,希望对你有所帮助^_^
喜欢的话别忘了 分享、点赞、收藏 三连哦~。
欢迎关注公众号 ELab团队 收获大厂一手好文章~
我们来自字节跳动,是旗下大力教育前端部门,负责字节跳动教育全线产品前端开发工作。
我们围绕产品品质提升、开发效率、创意与前沿技术等方向沉淀与传播专业知识及案例,为业界贡献经验价值。包括但不限于性能监控、组件库、多端技术、Serverless、可视化搭建、音视频、人工智能、产品设计与营销等内容。
欢迎感兴趣的同学在评论区或使用内推码内推到作者部门拍砖哦 🤪
字节跳动校/社招投递链接: https://job.toutiao.com/s/FFsm2o3
内推码:3RTMNZM