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:
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:
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:
There are cases where the usage of a transpiler might not be necessary:
// 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!");
// 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 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 easierBut 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 } } }
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.
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:
So if you are using Babel and thinking about transitioning to gain faster build time, make sure to:
Having said that, the idea behind SWC sounds promising and we will all keep an eye on its progress.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ 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>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]