Front-end Build Tools in 2023

The Standard

When we say a front-end build tool, we mean:

  • It is able to build production assets.
  • It has a dev server with HMR.
  • It supports common technology like Sass, LESS, CSS modules, code splitting.
  • It supports plugins.
  • It does NOT restrict UI framework to use or bundle unnecessary application runtime, like Redux.

Ready for production

When we say a build tool is ready for production, we mean:

  • It is popular. (>100k weekly downloads from npm)
  • It is actively maintained. (at least 1 release in last 6 month)
  • It has no critical performance or compatibility issues.

Vite 4

Vite 4 is based on esbuild (written in Go) and Rollup. It adopts un-bundled dev server for faster HMR and bundled dependencies for faster cold booting.

Vite 4 has a new swc based React plugin @vitejs/plugin-react-swc to replace the old babel based plugin. This makes React Fast Refresh 20x faster than before. It is still one of the fastest solutions.

Vite is easy to use. You can start without any configuration. When need customization, the user document is detailed and helpful. Writing plugins, or even create your own build tools based on Vite, is relatively easy. (Thanks to Vite's JS API design)

Vite has a large community and eco-system, making it easier to find plugins for your needs and solutions for your questions.

Vite 4 isn't perfect:

  • In my own tests, Vite 4's pre-bundling is much slower than Vite 2, and not comparable to esbuild. See #8850. Luckily, this only affect cold start of dev server, and Vite team is working on a fix.

Parcel 2

Parcel 2 is based on a collection of efficient tools written in Rust, like swc, Parcel CSS, to achieve better performance than Webpack 5. Parcel 2, which default configuration, has overall performance in the middle of Vite 4 and Webpack 5. But you can increase performance by enable some experimental features, like swc minification.

If HMR and build performance is your main concern (code base with millions of lines of source code), Parcel 2 is the best choice for you.

Parcel 2 isn't perfect:

  • For React (still CJS only in 2023!) projects, you need to polyfill node built-in modules, like process. In theory, parcel will install these polyfill packages automatically when needed. However, it may not work when you use pnpm rather than npm.
  • Plugin eco-system is still small.
  • JS API is not as complete as Vite. When you create your own build tools, Vite is probably a better choice than Parcel as a basement.

Webpack 5

Webpack is still the most popular choice, with the most complete features and largest eco-system.

Webpack is slow, but you can make it faster by using plugins and loaders:

Observation list

Some exciting & promising projects in early stage.


A webpack alternative written in Rust. Created by ByteDance.

Not Included


High performance bundler for Next.js, support SSR. Unqualified because it only works in Next.js framework (for now).


Build tool from Ant Group. Unqualified because it is heavily customized, only supports React, and bundled too many runtime, like Redux.


Build tool from Alibaba. Unqualified because it is heavily customized, only supports React, and bundled too many runtime, like Redux.

Create React App

React's official build tool. Unqualified because it only supports React and doesn't support configuration and plugins.

Performance Benchmark

dev server cold startdev server warm startproduction build
parcel 2.8.313.0s1.3s15.7s

More details, see

Technology Trends

Technology that can potentially make build tools better and faster in future.

Node.js Module Format: Native vs WASM vs JS

Here are more and more Node.js modules written in native language, like Rust and Go. However, written in native languages doesn't mean native performance. It depends on the type of output modules: native, WASM or JS. Without threading, performance from highest to lowest is native > WASM > JS. JS and WASM are single threaded by default, unless you use cluster mode (I haven't see successful story in front-end builders). Native modules, like esbuild, can make full use of multi-threading to achieve even better performance.

However, native modules must be compiled individually for each platform, and it doesn't run in browsers. On the contrary, JS and WASM are "compile once, run everywhere", which is much easier for distribution.

In practice, native modules are mainly used in core components with heavy loads, like rspack, swc, esbuild. Plugins etc. are still written in JS. WASM is not as popular as the other two. One use case is swc plugin development.

Faster Sass compiler

Sass' old node-sass compiler was deprecated. The new official Dart Sass compiler is confusing:

  • The native build has no way to be integrated into existing Node.js tool chains, unless you use Node.js' exec function (not recommended in production usage).
  • The JavaScript build is 4x to 30x slower than node-sass (depend on project size). When your build tool use Sass in a wrong way, the speed can be worse. See issue #1557 and #1534

This is a deal breaker for production projects that heavily use Sass. Many projects choose to stick with deprecated node-sass compiler.

Here are new Sass compilers written in Rust:

  • grass. It only released as WASM, not native module, which doesn't have much advantage over Dart Sass (JS).
  • rsass. Unfortunately, it doesn't support Node.js API, which makes it difficult to integrate with existing front-end eco-system.

Faster TypeScript checker

TypeScript is slow cause it is written entirely in JS. Luckily, we usually don't need to check types when building a front-end project. Babel, esbuild or swc will simply ignore all types. However, if your project has strict QA and you have to run tsc, your CI workflow will be slow down even if your project is small (10k lines) or medium size (100k lines).

Re-implementation of TypeScript has become a popular idea. We have seen some attempts:

position: fix not working? Check transform

In CSS, many position/layout properties are context-aware. For example, position: absolute; only works if the desired parent has position: related/absolute/fixed;. However, position: fixed; is very simple. It is always related to window, barely affected by parent container. But, I stuck in this small issue:

position:fixed; right:10px; bottom:10px;

This is strange, I have position: fixed; but why it doesn't work?

So I checked its parent elements one by one and found the modal library left a transform: scale(1);. It doesn't make any visual difference and seems a side effect of animation. In browsers' implementation, even if scale(1) and translateX(0) don't make any sense, the transform property will recreate a coordinate system. As result, position:fixed of inner elements will not be related to window, but the transform element.

The solution is simple, change transform: scale(1) to transform: unset. In practice, avoid using transform in large containers, like a sidebar or modal. If you need it, make sure here is no position: fixed inside, such as some popups.

Integrate Crowdin To RetroArch

RetroArch project uses *.h files to store translation strings. Here aren't any tools to make the translation process easier. When source strings changed, you have to manually review the changes, locate and update translation strings. It is a hard work. As a big fan of RetroArch, I was thinking if it can be improved with modern i18n platforms.

Continue reading →

HTML vs. JavaScript Input Validation

If you want to validate a form input and so it only accepts integers, here is an easy way in HTML5:

<input type="number" pattern="[0-9]*" />

However, users can still type something invalid:

  1. In Firefox, you can basically type anything, like "fsielfs".
  2. In Chrome, you can type numbers with dots, like "1..2.2.2".
Continue reading →

Create React WebExtension for Firefox and Chrome

We will create a React app and turn it into a Firefox/Chrome extension.

Continue reading →

Code Journey #11

Highlights for the last month: HiDPI bug fixes and emulator packaging.


  • Kompare HiDPI [patch]
  • Filelight HiDPI [patch]
  • KSysGuard HiDPI, except the sensor graphy [patch]
  • Font manager HiDPI [patch]
  • enablefont and disablefont icon for font manager [patch]
  • KWallet HiDPI [patch]
  • KWin HiDPI [patch]
  • Krita splash screen HiDPI [patch]
  • Kate/KonsolePart dual screen rendering issue [bug] [patch]
  • Spectale Dual Screen issue [bug] [patch]


  • Update python-PyMuPDF package and fix linking issue
  • Update arcanist package and submit to Factory [request]
  • Submit PCSX2 package to Factory [request]
  • Update retroarch package to work out-of-box [request 1] [request 2]
  • Create retroarch-assets package [request]
  • Create retroarch-joypad-autoconfig package [request]
  • Create libretro-core-info package [request]
  • Create libretro-database package [request]
  • Create libretro-mame2000 core package [request]
  • Create libretro-genesis-plus-gx core package [request]
  • Create libretro-flycast core package [request]
  • Create libretro-yabause core package [request]

Code Journey #10


  • Fixed JuK folder dialog always open on start
  • Translate tech base
  • POTD doesn't change daily
  • Bluetooth headset not detected when auto connect. Identify it as Linux kernel bug. Report to upstream


  • Update Wiki FAQ page, still in progress
  • Chameleon theme: new navbar and footer design
  • Chameleon theme: dark mode
  • Apply theme to
  • Update wiki theme

Code Journey #9

Code Journey #8



  • Update FAQ pages

Plasma theme

  • Update openSUSE color scheme
  • Improve panel transparency


  • Fix system version number space issue in Leap 15.1

Geeko Store

  • OBS/PMBS search API integration
  • RPM package listing


  • Update php-composer
  • Create npm2rpm
  • Packaging npmjs-gulp-cli
  • Packaging npmjs-create-react-app
  • Packaging npmjs-webpack-cli
  • Update patterns-base, remove dejavu-fonts recommendation,to improve emoji support



  • KDE Connect

Code Journey #7



  • Fixed hard-coded credential issue. Use a proxy server to send API requests
  • Support Packman package search
  • Resolved dependency not found issue


  • Added theme document web page
  • Support hot reload during development
  • Added dark mode buttons and form controls
  • Improve theme style


  • Update python-PyMuPDF


Rabbit Lyrics

  • Support multiple lyrics blocks