ESBuild & SWC浅谈: 新一代构建工具

786次阅读  |  发布于2年以前

首先, ESBuild & swc是什么?

为什么要关注这两个工具?

 # 编译
> 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 />))
// 一些变量声明
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

ESBuild/swc在前端生态中的定位

ESBuild/SWC为何这么快?

ESBuild的实现(参考ESBuild FAQ[3])

swc的实现

一点总结

如何用ESBuild/swc提效?

使用ESBuild

# 用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))
 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'
        })
      ]
    }
}

使用Vite

使用swc

  # 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

一点点总结和思考

全文总结

延伸思考

❤️感谢收看❤️

参考资料

参考资料

[1] 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

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8