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.
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.
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-all
parallelizes your scripts and fails instantly if one check fails, making sure it doesn't slow you down. Despite the name it works works with all JavaScript package managers
"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 with 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 nodemon
- Add
"type": "module"
to yourpackage.json
. - In your
tsconfig.json
, add:
"ts-node": {
"transpileOnly": true,
"transpiler": "ts-node/transpilers/swc",
"files": true,
"compilerOptions": {
"module": "esnext",
"isolatedModules": false
}
}
- Create a
script.ts
file, runchmod x script.ts
and execute it via./script.ts
:
#!/usr/bin/env node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm
console.log('Your code goes here.');
- To instantly restart your scripts when a file changes:
#!/usr/bin/env NODE_ENV=development node_modules/.bin/nodemon -q -I --exec node --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 orswc
properly. - Direct use of
esbuild
orswc
to bundle the script/app before running it. This solution took much longer to restart on file changes. - Given my use of
vite
, I would like to usetsx
in the future but after multiple attempts, it always added 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 ❤️
VS Code Extensions
Here are four extensions that keep me in the flow state for longer, especially All Autocomplete and Error Lens:
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 😜
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.