This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021

What are these SWC

Before we start talking about SWC, let’s take a look at the programming language Rust.

Rust is a fast, reliable, memory efficient programming language. It is a modern alternative to languages like C++ or C. The biggest difference between Rust and other languages is that it establishes a new concept of memory management called ownership.

At the risk of sounding abstract, here’s a piece of code to get a sense of ownership.

fn main() {
    let arr = vec![1.2.3];
    let new_arr = arr;

    // No prizes for guessing, print what
    println!("{:? }", arr);
}
Copy the code

Click to see the results

Other features and details of Rust will not be covered, but we will continue to focus on SWC.

For those of you interested in Rust, take a look at my (Rust Learning Notes)

Rust is currently used in front-end toolchains such as compression (Terser), compilation (Babel), formatting (Prettier), packaging (webpack), and so on. And the SWC that we’re going to talk about today does just that.

Let’s take a look at the official introduction of SWC:

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

A big part of the reason for SWC was to replace Babel in the project, so it had almost everything Babel had.

Perhaps the biggest difference from Babel is that SWC is super-fast

The SWC website also has this quote to show his speed:

In addition to SWC’s official hype, Next. Js implements a Rust compiler based on SWC that parses compiled and packaged code. Here is the data from Next. Js combined with SWC:

Therefore, the advantages of SWC can be easily seen from the above data: it can improve development efficiency and improve the development experience.

This is why many projects now choose to access it.

How to use these SWC

The basic use

  1. Install dependencies:npm i -D @swc/cli @swc/core
  2. Run the command:npx swc ./index.js -o output.js(Compiling a single file)

After the command is executed, the result is printed in standard output. There is no generated file or anything like that.

If you want to output the file needs to carry the parameter to complete the -o ouput.js or -d dist compilation to the dist directory

Bundling

  1. A configuration file is required in the root directoryspack.config.js
const { config } = require('@swc/core/spack');

module.exports = config({
  entry: {
    web: './src/index.js'.// Multiple entries can be configured
  },
  output: {
    path: './bundle/',},module: {},
  options:{},
});
Copy the code
  1. Run the commandnpx spackpackage

Packaging supports tree-shaking, Commonjs modules, extraction of public modules, etc.

Detailed configuration items: swc.rs/docs/config…

Plugin

Plugins in SWC simply expose apis in the core package and allow developers to customize their operations.

Take a look at an example that filters console.log() out of your code and replaces it with void 0

Those of you who have used Bable might look a little easier.

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;
    }
    // Determine the code type and whether the corresponding value is console
    if (
      expression.callee.object.type === 'Identifier' &&
      expression.callee.object.value === 'console'
    ) {
      // If yes, replace with 'void 0'
      if (expression.callee.property.type === 'Identifier') {
        return {
          type: 'UnaryExpression'.span: expression.span,
          operator: 'void'.argument: {
            type: 'NumericLiteral'.span: expression.span,
            value: 0,}}; }}returnexpression; }};const out = transformSync(
  ` if (foo) { console.log("Foo") } else { console.log("Bar") }`,
  {
    plugin: (m) = > new ConsoleStripper().visitProgram(m),
  }
);
Copy the code

It is worth mentioning that the current SWC plug-in system has some performance issues. This performance problem focuses on two areas

  1. willASTthroughRustCommunication loss occurs when passed to JS
  2. Pass in JSJSON.parese()The wear and tear that occurs when code is converted.

SWC is also working to address this issue. Click to eat the melon

These are some of the things THAT I think might be more common, but of course SWC also provides tools like Jest, wASM, and a loader for developers to use in WebPack.

Why is SWC fast

Because JavaScript is inherently slow.

Let’s take a look at the js execution flow:

Of these, converting to AST and compiling to bytecode should be the most performance costly.

SWC, on the other hand, compiles the code directly into binaries for different platforms, bypassing the most time-consuming step.

Now let’s take the Plugin above to see how SWC performs in the code conversion process

The whole process is to verify that SWC is executed directly by code in the binary when it compiles the code.

We put the breakpoint in transformSync and look at the execution here:

Take a look at some of the more important code:

transformSync(src, options) {
  
        // ...
  
        const { plugin } = options, newOptions = __rest(options, ["plugin"]);
  	// Whether there is a 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);
        }
  	// The final output is binding.transformsync
        return bindings.transformSync(isModule ? JSON.stringify(src) : src, isModule, toBuffer(newOptions));
    }
Copy the code

General execution process:

As can be seen from the above execution diagram, the binding. transformSync parsing is the final output of the results we get.

Bindings entry can be found in the source code. We can make a breakpoint here to look at the bindings execution process

function loadBinding(dirname, filename = 'index', packageName) {
   
    // Obtain system information
    const triples = triples_1.platformArchTriples[PlatformName][ArchName];
    
    // Traverses the system information
    for (const triple of triples) {
        if (packageName) {
            try {
                // Get the binary file path to load
                // /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) {
               // ...}}// ...
}
Copy the code

Flow chart:

What we end up with is requiring in a binary file.

/Users/xx/swc-demo/node_modules/@swc/ core-Darwin-x64 /swc.darwin-x64

Take a look at this file in the SWC package:

To verify that it is a binary file, we can click on it (here is a plug-in using vscode to see it).

When I open it up, it looks something like this, so I’m not going to read too much here, but if you’re interested, you can translate it line by line.

conclusion

In fact, the SWC implementation process is not very complicated, of course we see the compiled code, you can also try to see the SWC rust source code. After watching it, you should have a feeling:

JS is the most beautiful language in the world.

Ok, this article is just a brief understanding of what SWC is. If you are interested, you can play it by yourself. I put the code warehouse when I experience it below, and some debug configurations have been prepared, so you can have a try.

  • Code: github.com/rust-toys/s…
  • SWC: SWC. Rs /