Christoph Nakazawa

Fastest Frontend Tooling in 2022

I tried tons of frontend tools this year in my pursuit to optimize my Developer Experience. I published an incredibly fast minimal template with sensible defaults which you can use to quickly spin up new projects: cpojer/vite-ts-react-tailwind-template.

This is not a does-it-all starter kit. The template comes with the essential tools for frontend development with minimal sensible defaults to make it easy to use, quick to get started, and adaptable to any frameworks on top. It is tuned for performance, not just in terms of actual speed, but also to maximize the time you stay focused while writing code to increase how quickly you can ship.

“Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.”

Technologies

Vite

Vite is the best and fastest dev server I’ve used. I was skeptical at first, but now I don’t enjoy working on any front-end project that isn’t using Vite. With esbuild under the hood, it builds whole projects faster than it takes others to hot reload a single change. While other bundlers can be made fast, Vite does it all out of the box with almost no configuration required.

If you haven’t used it, try it on a small project and don’t look back.

Tailwind

Tailwind is another project I was initially skeptical about. After a year of using it, Tailwind’s tradeoffs make sense to me. The way it abstracts styling into a system of CSS variables instead of direct CSS property assignments is genius. I recommend using it if you want sensible defaults for styling, smart composition for styles, and consistency across your project. The way it handles dark-mode styles is nice, too.

Alternatives considered:

  • Emotion: Use emotion if the above criteria don’t apply to you, like if you have a well-defined design system at your company, or you have highly specific styling needs.
  • Many other CSS-in-JS Libraries. Emotion is currently my favorite one.

pnpm

pnpm is the JavaScript package manager that won my heart. It’s a joy to use for basically anything related to package management or monorepos, it is fast, and offers escape hatches when you are running into problems with third-party packages. However, while I updated many projects to use pnpm and recommend it to everyone, this template is not opinionated about the package manager you are using.

Note:

I’ve found that for long-running projects it’s ideal to update your dependencies often. Depending on how many automated tests there are and at which stage of development a project is in, I might even update dependencies daily. This allows me to isolate issues in third-party updates to a smaller set of changes and ensures I never fall too far behind to make updates a chore.

ESLint & Prettier

ESLint and Prettier are widely adopted and you are likely using them already. They both serve overlapping concerns around code style, and on the surface, it seems like a good idea to run Prettier within ESLint. However, upon profiling why ESLint was slow on a bunch of projects, I noticed prettier consistently taking about 50% of the total ESLint runtime. If you are formatting your documents on save, there is no point in running Prettier within ESLint at the same time.

For CI or local test runs, I now run prettier --cache --check . and eslint --cache . as separate commands in parallel. Both projects now support a --cache flag to reduce the amount of work they do during local development. You should use these flags!

Despite the existence of Prettier, arguments about code style such as how to sort ES module imports still exist. Manually sorting ES modules wastes time, and usually leads to losing context when you are writing code and then have to navigate to the top of a file to modify your import statements. I love using the @trivago/prettier-plugin-sort-imports plugin which automatically sorts new imports, and works perfectly together with TypeScript’s auto-import feature. Similarly, prettier-plugin-tailwindcss automatically sorts Tailwind classes in your code.

Finally, my default ESLint config and Prettier config are designed for consistency. I am not opinionated about which specific order some things like ES module imports are, but I do care about having a consistent order within a single project.

Note:

I’m incredibly excited about the potential of Rome tools. Rome’s formatter and linter are close to production ready, and hopefully when I refresh this post next year, I’ll be able to drop a bunch of paragraphs and replace them with just rome.

npm-run-all

I like running all tests and checks while developing locally. npm-run-all1 parallelizes your scripts and fails instantly if one check fails, making sure it doesn’t slow you down.

json
"scripts": {
"build": "vite build",
"dev": "vite dev",
"format": "prettier --write .",
"lint:format": "prettier --cache --check .",
"lint": "eslint --cache .",
"test": "npm-run-all --parallel tsc:check lint lint:format",
"tsc:check": "tsc"
}

ECMAScript Modules in Node.js

ES Modules in Node.js (ESM) are great in isolation, but the ecosystem integration and legacy third-party packages are making ESM hard to use. I spent way too much time trying to figure out how to use ESM with Node without breaking everything with cryptic error messages. Like seriously, I attempted it at least five times unsuccessfully and ran into trade-offs I wasn’t willing to make. Here are my requirements:

  • Use native ESM.
  • Fast JS compilation.
  • Immediately restart scripts when files change.

I have since figured it out. Here is how to do it:

  • Run pnpm add -D ts-node @swc/core

  • Add "type": "module" to your package.json.

  • In your tsconfig.json, add:

    json
    "ts-node": {
    "transpileOnly": true,
    "transpiler": "ts-node/transpilers/swc",
    "files": true,
    "compilerOptions": {
    "module": "esnext",
    "isolatedModules": false
    }
    }
  • Create a script.ts file, run chmod x script.ts and execute it via ./script.ts:

    ts
    #!/usr/bin/env node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm
    console.log('Your code goes here.');
  • As of Node.js v18.11.0, node supports a --watch command that you can use to instantly restart your scripts when a file changes:

    ts
    #!/usr/bin/env NODE_ENV=development node --watch --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm
    console.log('This processes instantly restarts when a file changes.');

It’s a bit unfortunate that we have to use swc for scripts when we are already using esbuild for the frontend. However, any esbuild related solution was much slower in my testing.

Alternatives considered:

  • Before switching to the above I used to use ts-node-dev but it does not support ESM or swc properly.
  • I spent a few hours attempting to use vite-node but it doesn’t gracefully handle HTTP server restarts when doing hot module reloading and it crashes the process when it encounters a syntax error. I built a solution for the former issue but the latter is a dealbreaker because it requires manually restarting the process which is too disruptive.
  • Direct use of esbuild or swc to bundle the script/app before running it. This solution took much longer to restart on file changes.
  • I tried tsx multiple times. It always adds a 10x performance overhead for API requests in development and I ran out of time figuring out why.

TypeScript

TypeScript (and to a lesser extent React) are unwritten standards at this point. TypeScript especially offers an incredible productivity boost to any project. I assume all new frontend projects use TypeScript unless there is a really strong reason not to, and I’m just including this paragraph for completeness and to show my appreciation that’s been ongoing since January 11th 2013. Thanks TypeScript team ❤️

Screenshot of a GitHub commit introducing TypeScript to a codebase in 2013

VS Code Extensions

Here are four extensions that keep me in the flow state for longer, especially All Autocomplete and Error Lens:

bash
code --install-extension bradlc.vscode-tailwindcss
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension usernamehw.errorlens

If you want more VS Code protips, I wrote about my favorite VS Code extensions just recently.


Frontend development has never been more fun. I still get excited whenever someone creates a game-changing tool. Many of the above tools have impressed me with how fast and delightful they are. I dare you to show me even faster and better tools 😜

Footnotes

  1. Despite the name it works works with all JavaScript package managers

Tweet about this article, or share it with your friends. Thank you for reading, and have a great day!

Ready for Another Post?

Subscribe for More