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
- Install dependencies:
npm i -D @swc/cli @swc/core
- 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
- A configuration file is required in the root directory
spack.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
- Run the command
npx spack
package
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
- will
AST
throughRust
Communication loss occurs when passed to JS- Pass in JS
JSON.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 /