swc初体验

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

swc 是什么

在开始说swc之前,我们先来了解一下Rust这门编程语言。

Rust是一种快速、可靠、内存高效的编程语言。它是 C++C 等语言的现代替代品。 Rust与其他语言最大的不同就是在内存管理方面建立了一种新的概念叫做所有权。

可能这样说比较抽象,下面有一段代码可以感受一下所有权。

fn main() {
    let arr = vec![1, 2, 3];
    let new_arr = arr;

    // 无奖竞猜,打印啥
    println!("{:?}", arr);
}
复制代码

点击查看结果

其他的关于Rust的一些特性和细节就不过多介绍了,我们依旧把重心放到swc上。

对Rust感兴趣的小伙伴可以看一下我的(Rust学习笔记)

目前Rust被应用在前端工具链方面,如压缩(Terser)、编译(Babel)、格式化(Prettier)、打包(webpack)等场景中。而今天我们要讲的swc做的就是做的这样一件事。

我们来看一下swc的官方介绍:

SWC (stands for Speedy Web Compiler) is a super-fast TypeScript / JavaScript compiler written in Rust.

swc的出现其实很大一部分原因是要替换掉工程中的babel,所以babel有的功能他几乎都有。

与babel最大的区别可能就是: swc is super-fast

swc官网中还有这样一句话,体现了他的速度:

除了swc的官方宣传外,Next.js基于swc实现了一个Rust编译器,用来解析编译、打包代码。下面是Next.js结合swc之后给出的一个数据:

所以从上面这些数据也可以简单看出来swc的优势:可以提高开发效率,提升开发体验

这也是目前很多工程选择接入它的原因。

swc 怎么用

基本使用

  1. 安装依赖:npm i -D @swc/cli @swc/core
  2. 运行命令:npx swc ./index.js -o output.js(编译单个文件)

执行过命令之后会把结果打印在标准输出里,并没有生成文件之类的。

如果想要输出的文件中需要携带参数来完成-o ouput.js或者-d dist编译到dist目录下

Bundling

  1. 根目录下需要有一个配置文件spack.config.js
const { config } = require('@swc/core/spack');

module.exports = config({
  entry: {
    web: './src/index.js',
    // 可以配置多入口
  },
  output: {
    path: './bundle/',
  },
  module: {},
  options:{},
});
复制代码
  1. 运行命令npx spack进行打包

打包支持tree-shakingCommonjs模块、提取公共模块等。

详细配置项:swc.rs/docs/config…

Plugin

swcplugin其实就是将核心包中的一些API暴露出来,给开发者做一些自定义的操作。

看个例子,这个例子的作用就是将代码中的console.log()过滤掉,用void 0代替

用过Bable的同学看起来可能会简单一些。

const Visitor = require('@swc/core/Visitor').default;
const { transformSync } = require('@swc/core');

module.exports = class ConsoleStripper extends Visitor {
  visitCallExpression(expression) {
    if (expression.callee.type !== 'MemberExpression') {
      return expression;
    }
    // 判断代码类型以及对应的value是否为console
    if (
      expression.callee.object.type === 'Identifier' &&
      expression.callee.object.value === 'console'
    ) {
      // 如果是就替换为`void 0`
      if (expression.callee.property.type === 'Identifier') {
        return {
          type: 'UnaryExpression',
          span: expression.span,
          operator: 'void',
          argument: {
            type: 'NumericLiteral',
            span: expression.span,
            value: 0,
          },
        };
      }
    }

    return expression;
  }
};

const out = transformSync(
  `
if (foo) {
    console.log("Foo")
} else {
    console.log("Bar")
}`,
  {
    plugin: (m) => new ConsoleStripper().visitProgram(m),
  }
);
复制代码

值得一提的是,目前swc的插件系统是有性能问题存在的。该性能问题主要聚焦在两个方面

  1. AST通过Rust传递给JS时会产生通讯上的损耗
  2. 在JS中通过JSON.parese()去转换代码时所产生的损耗。

目前swc也正在着手解决这个问题。点击吃瓜

上面提到的是我认为可能比较常用的一些东西,当然除此之外swc还提供了像Jest、和wasm等工具,而且swc还提供了一个loader供开发者在webpack中使用。

swc 为什么快

因为JavaScript本身就有点慢。

我们先来看一下js的执行流程:

这其中转换为AST以及编译成字节码应该是最耗费性能的。

swc是直接将代码根据不同平台来编译成对应的二进制文件,省略了前面最耗时的步骤。

接下来我们拿上面那个Plugin来看一下swc在代码转换过程中大概是怎么执行的

接下来的整个过程就是其实就是证实swc在编译代码的时候是直接由二进制文件中的代码来执行的。

我们将断点打在transformSync处,看一下此处的执行:

来看一下比较重要的一部分代码:

transformSync(src, options) {
  
        // ...
  
        const { plugin } = options, newOptions = __rest(options, ["plugin"]);
  	// 是否有plugin
        if (plugin) {
            const m = typeof src === "string" ? this.parseSync(src, (_c = options === null || options === void 0 ? void 0 : options.jsc) === null || _c === void 0 ? void 0 : _c.parser, options.filename) : src;
            return this.transformSync(plugin(m), newOptions);
        }
  	// 最终的输出都是bindings.transformSync
        return bindings.transformSync(isModule ? JSON.stringify(src) : src, isModule, toBuffer(newOptions));
    }
复制代码

大致执行流程:

从上面的执行图中可以看出来,我们获取到的结果,最终都是由bindings.transformSync解析完成,然后输出的结果。

从源码中可以找到bindings的入口,我们在这里打个断点,从这里看一下bindings的执行流程

function loadBinding(dirname, filename = 'index', packageName) {
   
    // 获取系统信息
    const triples = triples_1.platformArchTriples[PlatformName][ArchName];
    
    // 遍历系统信息
    for (const triple of triples) {
        if (packageName) {
            try {
                // 获取到需要加载的二进制文件路径
                // /Users/xx/swc-demo/node_modules/@swc/core-darwin-x64/swc.darwin-x64.node
                return require(
                  require.resolve(
                    `${packageName}-${triple.platformArchABI}`,
                    { paths: [dirname] }
                  ));
            }
            catch (e) {
               // ...
            }
        }
       // ...
}
复制代码

流程图:

我们最后得到的结果就是require进来一个二进制文件

这个文件的路径大概是这样的:/Users/xx/swc-demo/node_modules/@swc/core-darwin-x64/swc.darwin-x64.node,当然这会根据不同电脑生成不同的路径~

来看一下,swc的包里这个文件:

为了证实它是一个二进制文件,我们可以点开看一下(这里是用了vscode的插件,才可以看到)

打开之后是这样的,这里我们就不做过多解读了,有兴趣的可以逐行翻译一下

总结

其实从上面看来swc的执行流程并不算很复杂,当然我们看到的是编译过的代码,大家也可以尝试去看一下swcrust源码。看完之后应该会有一个感受:

JS是世界上最美丽的语言。

ok,本篇文章就是浅出的了解一下swc是什么,有兴趣的同学可以自己下来玩一下,下面我把我自己体验时候的代码仓库放到下面,有一些debug的配置已经配好了,大家可以尝试玩一下。

Guess you like

Origin juejin.im/post/7034316603890237477