February 2022 seems like a good moment to discuss about the recent trends happening in the web frontend tooling field. Indeed, we are seeing a large part of the frontend community dropping some major tools that were both important and dominant in the past 5 years, such as Babel and Webpack, in favor of substitute newcomers. So what changed ? Let’s dig in.
The hunt for performance
This problem has been solved by moving this stack of tools on the framework side. Create-react-app, Angular CLI and Vue CLI all came in with preconfigured Webpack and plugins for local dev and production build. All these tools were configured with consistent and conventional defaults adopted over the years by the JS community. Then, the frameworks scaffolding tools no longer exposed the configuration and proposed optional overloading instead, drastically reducing the amount of configuration found in web projects root folder.
So what else could be done to further improve the DX ? Turns out the last remaining problem is the performance. Although this was not a critical issue and the JS developer tooling was already much faster than other tech stacks (cough Java cough), there was room for improvement. On large, complex projects, the hot reload times could take as long as 30 seconds. Unbearable when the initial reload times took less than a second at the beginning of the project ! Thus began the hunt for performance.
JS tooling is no longer written in JS
To be fair, this choice made sense in many ways. It’s easier to work on code transform and code analysis tools when the language you are working on is the same that the language you are working with. It enables to reuse the existing NPM ecosystem for both application and development dependencies (leading to
devDependencies entry in your
package.json). And it keeps the door open for the whole JS community (most popular language in the world, shall we remind), so that they can invest their time and creativity into contributing on open source projects that they actually use on a daily basis.
So 2020’s will mark the rise of compile-to-native languages for front-end tooling, with languages like
Dart. The entry barrier for JS developers to contribute on their tooling stack will be much higher, if not completely out of reach. But in return, they will have extremely performant and normalized tools. Time will tell if this was a good trade. In the mean time, expect the front-end tooling to look more and more like a all-in-one black box designed by leading experts. The closest thing we currently have for this black box project is the Rome tools, a set of tools written in Rust including a linter, a compiler, a bundler and more. They aim to replace Babel, ESLint, webpack, Prettier, Jest, and others, just that !
ES Modules to the rescue
ES Modules have quickly gained popularity in the JS community which struggled with CJS, AMD and UMD frankeinstein module patterns for years. However, it took at least 5 years to be broadly adopted by Node.js and inside web browsers, due to the complexity of the transition while preserving backwards compatibility.
But here we are, with ESM support on all the platforms, starting to unplug our bundlers for local development since they are natively supported in both our web browser and our test suite in Node.js. Browsers do a great job at resolving the module tree in very little time. Unbundled code is certainly larger, but this is not a concern when loaded from the local disk of the developer machine. And the development servers quickly evolve to take advantage of ESM, instead on relying on automatic chunking features like we used to have with Webpack.
Vite is a good example of such an ESM-based dev server. It is built on the idea that the way we split our application code in modules would also be the perfect split points for hot reloading. This makes sense when considering that all modern JS frameworks follow the component approach, with our webpages being built with small bricks, each associated to a file. Change the file, reload the brick, without losing context. Tracking the dependencies for hot reloading has never been so easy, and so fast, with the static imports of ES Modules. This led to blazing fast startup and reloading times for modern dev servers, which sounds appropriate when you decide to call your project “Vite”.
This does not mean that we are getting rid of the bundlers completely. Loading a chain of ten nested module dependencies in your browser is still much slower than loading a single bundle. And Webpack has improved its chunking strategy for more than 10 years now. UX is still much more valuable than DX and we surely tolerate a few additional minutes of build time if it saves a few seconds of loading time for each of our lovely users. So bundlers are still there ! They will just move to your build process and CI logs alongside test suites, code quality checks and audit tools which occasionnally send you a few warnings in the face.
That means the toolchain for local dev vs bundling for prod are now very different, with dedicated tools and different objectives. Dev tools aim for super-fast startups and reloads so that you can focus on code, like a rally co-driver. Build tools aim for super-optimized bundling, chunking and minifying rocket science, with a hint of code transforms required to make it work on the worst browsers your users will dare to show up with.
Code quality tooling get out of the build chain
Bundling, chunking, minifying, what are we missing ? Our good old linter friend of course ! How are we going to live without our daily dose of Unnecessary trailing comma warnings ? Yet, on modern front-end tool stacks, these pleasant reminders have disappeared from our build logs. Where did they go ?
Linters in the editor
A quick answer comes with the editors, which integrates much better with the popular linters like ESLint or Prettier. In early 2010’s, JS code analysis was almost exclusively done by dedicated tools built into paid JS editors, behind close doors. But developers were frustrated by the price barrier and the fact that these tools were not open source. That’s why they accepted to take the linter out of their editor and use external JS validators and syntax checkers.
These tools have matured with time and reduced to a few leading projects like ESLint for linting and Prettier for code formatting. We reached the point where it makes sense for editors to bring built-in support for these tools. Visual Studio Code, to name one, became the most popular free code editor in the front-end community and has excellent support for ESLint and Prettier thanks to its plugin system. The formatters are applied automatically at file save or at every character input. The linters and syntax checkers are run in the background and warning and error messages are displayed directly over your code, in pretty convenient tooltips.
Considering that most developers now have this base level of linting/formatting built in their editor, it no longer makes sense for frameworks to keep them in the local development build step. The idea is to not have to wait for Control+S and server reload to notice the errors, if these errors can be displayed directly in the IDE. It also reduces compile & reload time for local dev experience, helping a lot for the performance hunt we mentionned before.
Another interesting evolution of editors is the built-in support for TypeScript and static type-checking in the background. This led to very opinionated choices on dev tooling, such as esbuild that compiles TypeScript code into JS without even checking the types ! Just doing the bare minimum work of removing the type annotations. Why should they bother to run a type-check step if it is already done sooner and better by the editor ?
So the new trend is to remove code quality tools from development dependencies, and rely on editor support instead. Linting and type-checking are still done in CI for production bundles, to prevent any prankster dev working with vim or notepad to push a mistake on production.
Quality Scoring on CI and Userland
Recently, we also saw lot of progress in the field of automatic project scanning and quality scoring. Security checks, performance tests, mobile responsiveness, accessibility audits, many aspects of the quality of a web project can now be deeply analyzed by automatic scanning tools. One of these tools in particular, Lighthouse, gained a lot of popularity since it gathers all kind of audits in a single and efficient tool. Moreover, this tool is built into the Chrome devtools, which come with Webkit and so are also included within Edge, Opera, Brave and all the other Webkit-based browsers. Lighthouse can also run as a standalone program on all platforms, or directly in your CI with Lighthouse CI.
Not only these audit tools are very useful, but they can no longer be ignored. Since every user on the Internet can run these audits on your website with a few clicks, using websites like web.dev/measure, the major downsides of your applications are now publicly exposed, and can give you a bad press in the tech community.
On another spectrum, systemic package analysis is now the norm. You get a list of vulnerabilities found in your dependencies at every
npm install, and a dependabot opens hundreds of pull requests on your Github projects to fix security issues by upgrading a dependency version. Security analysis is everywhere and no longer an opt-in, nice-to-have feature in your dev toolkit. Again, these audits cannot be ignored because they are publicly exposed for every public package. Have a look at snyk.io or npms.io to see the score of your favorite NPM package.
JS dev community also came together in a common effort to reduce the amount of dependencies in common tools and libraries on NPM. Mostly because they can no longer bear with the node_modules is a blackhole meme, but also to reduce the risks and burden associated with too many dependencies. Therefore, you can expect the
node_modules folder for a fresh new web framework project to be much smaller than it used to be in 2015. This is, without a doubt, very good news.
In short, all the quality analysis tools have taken off and are no longer part of the build chain of your web project. Now, they are part of the platforms, the browsers and the code editors.
The new status quo of “modern JS”
TypeScript has won, in an interesting way
But TypeScript also won in a curious, interesting way. TypeScript has a feature called definition files, which are
tsc is surprisingly rarely used. We can conclude that TypeScript shines more as a language than as a toolkit.
Transpilers are less crucial
We are living the dream in an evergreen browser world. With IE dead for good in 2022, we live in an era where browsers are automatically updated and, in general, very good at CSS/JS support. For both mobile & desktop.
Safari is the new lowest target in terms of browser support, but Apple receives a lot of pressure recently from web dev community to try to change that.
This general broad support for a modern, post-ES6 JS reduce the importance of the role of transpilers like Babel (historically named 6to5). The new additions to JS after ES2018 have also been mainly ignored by developers, not because they are bad, but because they are much less significant than what ES2015 brought to the table. That led to a decline in popularity for Babel, especially for local dev usage. We still expect transpilers to be part of the production build toolchain, in a targeted compilation setup with Babel-preset-env. Same for CSS transpiling wiht PostCSS-preset-env. Developers are also less likely to use proposal, pre-stage 4 language features, except for niche use cases.
Smart compilers and framework magic
Since frameworks now embed the build tooling and their preconfiguration as we explained in the introduction, it is not a surprise that they started to implement specific code transforms in their compilers for the specific needs of the framework. Angular has its own compiler
We also need to mention the latest outsider in the JS framework landscape, Svelte by Rich Harris. Svelte is a new generation of framework based exclusively around its compiler: its promise is to have a zero runtime footprint after build. The compiler sets up everything needed for your component architecture to work properly, leaving no trace of the framework after build (although the bundled code is recognizable and the savings in size are less and less significant as the application grows). Svelte also uses some strange syntax to add its reactivity, by reappropiating an old feature of the language: JS labels
In conclusion, modern JS no longer shines as a language, but as an universal compilation target. Most of the interest of the web dev community is now targeted at proprietary languages or compiler hints. It does not mean that JS is no longer popular or attractive, on the contrary it’s more popular than ever. But it looks like there is less room for progress on the language side, and the new interesting stuff is built on top of it.
This sums up the shift in front-end tooling we are witnessing these years. From the user’s point of view, the maturity of the tooling is supposed to induce better and faster websites, with less bugs and state-of-the-art features. But from the developer’s point of view, his working tools are now very complex, coded in a language he does not master, and integrated into a stack to which he has less and less access. We are a long way from the days when anyone could create their site by opening a text file and scribbling a bit of HTML.