What is Babel?
Babel is a tool to help us transpile newer versions of JavaScript code such as ES6 into older versions of JavaScript — it even helps you transpile TypeScript.
Babel reads the source code based on the configs you define for it and compiles newer JavaScript features such as arrow functions or optional chaining. This happens with Babel’s three major tools:
- First, Babel’s parser takes the JavaScript code and converts it to an Abstract Syntax Tree (AST) which is the structure of the source code understandable by the computer
- Next, Babel’s traverser takes the AST, explores it and modify it to the intended code we defined in our Babel configs
- Lastly, Babel’s generator will translate the modified AST back to the regular code

Babel’s alternative (written in Rust)
SWC is also a transpiler for JavaScript, it is written in Rust and is much faster than Babel. Rust is known for its performance and reliability and has encouraged many businesses to rewrite partial or full parts of their code with it. For example:
- Firefox has decided to rewrite its CSS renderer called Quantum CSS and gained substantial performance improvement
- Tilde rewrote certain parts of their Java HTTP endpoints in Rust and gained great performance increase by reducing memory usage from 5GB to 50MB
One of the reasons Rust is so performant is its way of handling garbage collection which is a memory management approach to free up memory resources by data objects that no longer have use for them. Since Rust decides which memory resources are no longer necessary at compile time and does not have to run continuously, processing time decreases while performance gains increase.
As we all know, code transpiling is an expensive process and that’s why having a transpiler written in Rust can be much more performant. We are gonna explore this further, but first, we should determine if we need a transpiler:
Why do we even need a transpiler?
There are cases where the usage of a transpiler might not be necessary:
- If you are building a simple project that mostly relies on a well-supported version of JavaScript like ES3. For example, running this code will work on almost all of the browsers, so if your usage of JavaScript is mostly like this, you should be fine without a transpiler:
// we print it, but we don't agree with it function saySomething (something) { console.log(`${something}. But don't tell anyone.`); } saySomething("I don't like Javascript!");
- If you are building a simple project that relies on newer versions of JavaScript like arrow functions, but the browsers you need to support also do support those new features. For example, running the below code in a newer version of Chrome (45+) should be fine:
// we print it, but we don't agree with it const saySomething = something => { console.log(`${something}. But don't tell anyone.`); }; saySomething("I don't like Javascript!");
Other than these cases, the need for a transpiler in an application is necessary. Browsers use different types of JavaScript engines like V8 (Chrome), SpiderMonkey (Firefox), and Chakra (IE). This means that even with a standard JavaScript specification, the timing of getting standards in different browsers and the level of their support is varied extensively.
That is why we need a consistent handling of our JavaScript code across many different browsers, without the worry of breaking something or losing the chance of using new features.
Our reliance on transpilers is not only limited to converting ES6 or TypeScript to ES5; transpilers bring the future of JavaScript to us today and let us handle many cases of JavaScript conversion like ES2019. This is a very powerful tool for today’s JavaScript developers.
So we have established why we need transpilers. Now it is time to test SWC usage with a simple setup and later compare its relative performance and speed to Babel.
SWC usage
SWC can be installed as a package from NPM package manager.
First, start running this command in the root of your directory:
// use `cd` to go to the right directory and then run mkdir swc_project // initialize a package.json npm init // install swc core as well as its cli tool npm install --save-dev @swc/core @swc/cli
By running this, we now have both the SWC core as well as the CLI. The core package will help us in our build setup, while the CLI package can be run with a command in the terminal.
As a first step, we focus on the CLI tool to transpile our JavaScript files. Imagining we have the below JavaScript file which exists in the root of directory:
// async.js const fetch = require("node-fetch"); async function getData() { let res = await fetch("https://jsonplaceholder.typicode.com/todos/1"); let json = await res.json(); console.log('data', json); } getData(); // result: // ▶Object {userId: 1, id: 1, title: "delectus aut autem", completed: false}
You can run the below commands for transpilation against this file like this:
// running this command will emit the transpiled data to stdout // and it will be printed out in the terminal npx swc async.js // running this will create a new file called `output.js` // with the transpiled data npx swc async.js -o output.js // running this will create a new directory called `transpiledDir` // and will transpile all th files in the original dir npx swc src -d transpiledDir
Note — to see what the transpiled file looks like, you can use this SWC playground.
Now as a second step, we want to include SWC as a tool in our build system. For this one, we want to use Webpack as a more advanced and configurable builder.
For starters, let’s see how our package.json
would look for a setup of Webpack and SWC. With this setup, we can run npm run-script build
to let webpack build our package; additionally, we can run npm run-script start
to let Webpack serve our application:
{ "name": "swc-project", "version": "1.0.0", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "rm -rf ./dist && webpack", "start": "webpack-dev-server" }, "license": "MIT", "devDependencies": { "@swc/core": "^1.1.39", "css-loader": "^3.4.0", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "sass-loader": "^8.0.0", "style-loader": "^1.1.1", "swc-loader": "^0.1.9", "webpack": "^4.41.4", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.1" }, "dependencies": { "node-fetch": "2.6.0", "react": "^16.12.0", "react-dom": "^16.12.0", "regenerator-runtime": "^0.13.5" } }
Our above configuration for building and starting an application is stored in a webpack.config.js
file, which will automatically be picked up by Webpack. There are a few things going on in this file:
output
: We are setting the name and location for Webpack to output your bundles, assets, and files including all the transpiled filesdevServer
: We are serving our Webpack app through this config by telling Webpack where to serve the content from as well as defining a port to listen for requests onHTMLWebpackPlugin
: We are defining this plugin to make the process of serving our HTML file with Webpack bundles included easier
But the most important part of this config is the swc-loader
, which allows us to transpile JavaScript files with .js
or .jsx
file extensions:
// global dependencies const path = require('path'); const HTMLWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "development", output: { path: path.resolve(__dirname, './dist'), filename: 'index_bundle.js' }, devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000 }, module: { rules: [ { test: /\.jsx?$/ , exclude: /(node_modules|bower_components)/, use: { // `.swcrc` in the root can be used to configure swc loader: "swc-loader" } }, { test: /\.html$/, use: [ { loader: "html-loader", options: { minimize: true } } ] }, { test: /\.scss/i, use: ["style-loader", "css-loader", "sass-loader"] } ] }, plugins: [ new HTMLWebpackPlugin({ filename: "./index.html", template: path.join(__dirname, 'public/index.html') }) ] };
With swc-loader
set up in our Webpack config, we have come halfway for transpiling our JavaScript files. However, we still need to instruct SWC on how to transpile our files. It turns out, SWC has a similar approach to Babel by defining a config file in the root directory called .swcrc . Let’s see what this config looks like for a project that wants to transpile TypeScript.
In this config, we are using the test
config as a Regex to only match with files that have a .ts
file extension. Additionally, with the jsx.parser
config, we are instructing SWC which parser to use for the transpilation (could be typescript / ecmascript
).
However, we still have more control over the syntax parsing by defining which options of the transpilation are intended for our use case. For example, in this example, we are interested in transpiling Typescript decorators and dynamic imports, but ignore transpiling files with .tsx
file extension:
// .swcrc { "test": ".*.ts$", "jsc": { "parser": { "syntax": "typescript", "tsx": false, "decorators": true, "dynamicImport": true } } }
Now, let’s imagine that we want to use React
in our webpack SWC
example above. As we know, in React we can use a certain file extension called .jsx
for writing React components:
// App.jsx // global dependencies import React from 'react'; import ReactDOM from 'react-dom'; const App = () => { return <h1>My SWC App</h1>; }; ReactDOM.render(<App />, document.querySelector("#root"));
Serving this file through Webpack needs the correct webpack loader
which we already have and defined above. It also requires the right transpilation settings in .swcrc
file. Now with this approach, we are using the latest features of modern JavaScript (ES2019) as well as supporting .jsx
file when transpiling. Additionally, if we need extra transpilation settings for our React project, we have plenty of settings at hand:
// .swcrc { "jsc": { "parser": { "syntax": "ecmascript", "jsx": true } } }
Speed comparison between Babel and SWC
As we discussed before, the speed of a transpiler is critical since it is baked into the build process, and, for many developers, any time that can be saved in this area is precious. Let’s see how these two tools compare in terms of speed.
First, we compare them in an artificial way and that is running code transformation for Babel and SWC in a synchronous manner. As we know, JavaScript is single-threaded and it would be impossible to run heavy computations in an asynchronous way in a real-life application. But this would still give us an indicator of the speed comparison. Let’s see these benchmark comparisons run on a single core CPU (tests performed by the maintainer of SWC project):
Transform | Speed (operation/second) | Sample Runs |
---|---|---|
SWC (ES3) | 616 ops/sec | 88 |
Babel (ES5) | 34.05 ops/sec | 58 |
This indicates that even though with a more expensive process of ES3 transformation for SWC, the speed of SWC transpilation is evident compared to Babel.
Now, if we want to benchmark a more realistic scenario, we can run samples against await Promise.all()
, which is a more expensive and real scenario for handling the operations in JavaScript. With this benchmark, the number of CPU cores and parallel computations come into play. In another benchmark that was run, two experiments were done. Both used a computer with 8 CPU cores with a parallelism of 4.
The first experiment ran with 4 promises:
Transform | Speed (operation/second) | Sample runs |
---|---|---|
SWC (ES3) | 1704 ops/sec | 73 |
Babel (ES5) | 27.28 ops/sec | 40 |
The second experiment ran with 100 promises:
Transform | Speed (operation/second) | Sample runs |
---|---|---|
SWC (ES3) | 2199 ops/sec | 54 |
Babel (ES5) | 32 ops/sec | 6 |
Note — if you are interested in running the tests yourself and compare these benchmarks, you can clone this repository and then run the following commands in terminal:
// clone and cd into the cloned repository cd node-swc // Node.js benchmark runner, modelled after Mocha and bencha, based on Benchmark.js. npm i benchr -g // run the multicore.js or any other benchmarks under /benches directory benchr ./benches/multicore.js
The main thing we can spot in these numbers is that the performance of Babel is dropping in async operations as Babel works on an event loop. This is in contrast to SWC which runs on a worker thread and can nicely scale up with the number of CPU cores.
In general, we see a clear speed gap between the two tools, as SWC tends to be around 20 times faster than Babel on a single thread and CPU core basis while being around 60 times faster in a multi-core async operation process.
Conclusion
We have covered the basics of transpilers in this article together and compared two of the JavaScript transpilers based on setup, execution, and speed perspective. By doing this, we have learned that:
- Their setup for the build workflow are similar
- However, SWC has a significant speed advantage compared to Babel
So if you are using Babel and thinking about transitioning to gain faster build time, make sure to:
- Check all the features that are already fully, partially or not supported by SWC
- Check that your build system supports it (even though Webpack does, other tools like Parcel might not)
Having said that, the idea behind SWC sounds promising and we will all keep an eye on its progress.
Resources
- https://www.reddit.com/r/programming/comments/a8i4ar/swc_superfast_alternative_for_babel/
- https://stackoverflow.blog/2020/01/20/what-is-rust-and-why-is-it-so-popular/
- https://medium.com/@rajithaeye/what-is-babel-8dcfdf84ea3e
- https://scotch.io/tutorials/javascript-transpilers-what-they-are-why-we-need-them
- https://github.com/swc-project/swchttps://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/
- https://medium.com/@boolean/swc-super-fast-javascript-transpiler-written-in-rust-f8f436195cb8
- https://medium.com/@pramonowang/transpilers-do-you-really-need-it-e9c63686e5fe
- https://news.ycombinator.com/item?id=17175186
- http://nicholasjohnson.com/blog/what-is-babel/https://www.sitepoint.com/understanding-asts-building-babel-plugin/
Get setup with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>