webpack or esbuild: why not both?

[translated from John Reilly on LogRocket website webpack or esbuild: Why not both?]
Use image esbuild Tools can be built faster. However, if you are investing in webpack But if you still want to take advantage of faster builds, there is a way.

In this tutorial, we will show you how to combine esbuild with webpack esbuild-loader Use together.

The world of Web development is developing

Apologizing to those who suffer from JavaScript fatigue, the world of Web development has developed again. For a long time, it has been common to run JavaScript and TypeScript through some Node.js based build tool (such as webpack or roll. JS). These tools are written in the same language they are compiled into - that is, JavaScript or TypeScript.

The new member on the blog is esbuild,Vite and swc The significant difference between them and their predecessors is that the new tools are written in languages such as Go and Rust. Go and Rust The performance of is much better than JavaScript. This translates into Significantly faster build.

These new tools are transformative and may represent the future of Web building tools Vite Tools such as and friends are likely to replace the current standard build tools - webbacks, rollups, etc.

However, this is long-term. Many projects have invested a lot of money in their current build tools - mainly webpack. Migrating to a new build tool is not easy. New projects may start with Vite, but existing projects are unlikely to be transplanted. Webpack is so popular for a reason; it can do a lot of things well. It goes through large projects The purpose of practical testing is very mature and can handle a wide range of use cases.

So if your team wants to build faster but doesn't have time for large-scale migration, is there anything you can do? Yes, there is a middle ground to explore.

There is a relatively new project called esbuild-loader By esbuild loader hiroki osame Development is a webpack loader based on esbuild, which allows users to exchange TS loader or Babel loader through it, which greatly improves the construction speed.

To declare an interest here for full disclosure, I am ts-loader As the main maintainer of, this is a popular TypeScript loader, which is usually used with webpack. However, I strongly believe that what is important here is the productivity of developers. As a Node.js based project, TS loader and Babel loader will never compete with esbuild loader in the same way. As a language, Go is really, er, feasible!

Although esbuild may not be suitable for all use cases, it is suitable for most tasks. Therefore, esbuild loader represents a middle ground -- an early way to achieve the higher build speed provided by esbuild without saying goodbye to webpack. This walkthrough will explore the use of esbuild loader in your webpack settings.

Migrate existing projects to esbuild

Either Babel loader or TS loader can be used to directly migrate a project to esbuild loader. First, install dependencies:

npm i -D esbuild-loader

If you are using Babel loader, modify your webpack.config.js as follows:

module.exports = {
    module: {
    rules: [
-       {
-         test: /\.js$/,
-         use: 'babel-loader',
-       },
+       {
+         test: /\.js$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'jsx',  // Remove this if you're not using JSX
+           target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         }
+       },

Create baseline application

Let's see how esbuild loader works in the exercise Create React App Create a new React application:

npx create-react-app my-app --template typescript

This will build a new React application using TypeScript in the my app directory. It is worth mentioning that Create React App uses Babel loader behind the scenes.

CRA also uses Fork TS Checker Webpack plug-in To provide TypeScript type checking. This is very useful because esbuild Just translate, not design to provide type checking support So luckily we still have that plug-in. Otherwise, we will lose type checking.

Now that you understand the advantages of migrating esbuild, we need a baseline to understand how Babel loader will perform. We run time npm run build to build a simple app.

Our complete build, TypeScript type checking, translation, reduction, etc. all took 22.08 seconds. The question now is, what would happen if we put esbuild into a combination?

Introduce esbuild loader

One way to customize the build of Create React App is to run npm run eject and then customize the code output by CRA. This is good, but it means that you can't keep up with the development of CRA. Another way is to use such as Create React App Configuration Override (CRACO) CRACO describes itself as "an easy to understand configuration layer for create react app."

Let's add esbuild loader and CRACO dependencies

npm install @craco/craco esbuild-loader --save-dev

Then we will exchange our various scripts in our package.json to use CRACO

"start": "craco start",
"build": "craco build",
"test": "craco test",

Our application uses CRACO, but we haven't configured it yet. So we are adding a file of craco.config.js to the root directory of our project. This is Babel loader for esbuild loader:

`const { addAfterLoader, removeLoaders, loaderByName, getLoaders, throwUnexpectedConfigError } = require('@craco/craco');
const { ESBuildMinifyPlugin } = require('esbuild-loader');

const throwError = (message) =>
        packageName: 'craco',
        githubRepo: 'gsoft-inc/craco',
        githubIssueQuery: 'webpack',

module.exports = {
    webpack: {
        configure: (webpackConfig, { paths }) => {
            const { hasFoundAny, matches } = getLoaders(webpackConfig, loaderByName('babel-loader'));
            if (!hasFoundAny) throwError('failed to find babel-loader');

            console.log('removing babel-loader');
            const { hasRemovedAny, removedCount } = removeLoaders(webpackConfig, loaderByName('babel-loader'));
            if (!hasRemovedAny) throwError('no babel-loader to remove');
            if (removedCount !== 2) throwError('had expected to remove 2 babel loader instances');

            console.log('adding esbuild-loader');

            const tsLoader = {
                test: /\.(js|mjs|jsx|ts|tsx)$/,
                include: paths.appSrc,
                loader: require.resolve('esbuild-loader'),
                options: { 
                loader: 'tsx',
                target: 'es2015'

            const { isAdded: tsLoaderIsAdded } = addAfterLoader(webpackConfig, loaderByName('url-loader'), tsLoader);
            if (!tsLoaderIsAdded) throwError('failed to add esbuild-loader');
            console.log('added esbuild-loader');

            console.log('adding non-application JS babel-loader back');
            const { isAdded: babelLoaderIsAdded } = addAfterLoader(
                matches[1].loader // babel-loader
            if (!babelLoaderIsAdded) throwError('failed to add back babel-loader for non-application JS');
            console.log('added non-application JS babel-loader back');

            console.log('replacing TerserPlugin with ESBuildMinifyPlugin');
            webpackConfig.optimization.minimizer = [
                new ESBuildMinifyPlugin({
                    target: 'es2015' 

            return webpackConfig;

So what happens here? The script looks for the usage of Babel loader in the default Create React App configuration. There will be two: one for TypeScript/JavaScript application code (we want to replace it) , a kind of JavaScript code for non application. It is not clear what non application JavaScript code exists or may exist, so we keep it; this may be important. The code we really care about is application code.

You cannot delete a single loader using CRACO, so we will delete both and re add non application JavaScript Babel loader. We will also add esbuild loader set with the {loader: 'tsx', target: 'es2015'} option to ensure that we can handle JSX / TSX.

Finally, we will also use Terser Replace with JavaScript for esbuild.

Huge performance improvements

Our migration is complete. The next time we build, we will run Create React App with esbuild loader without pop-up. Again, we will run time npm run build to perform the build of our simple application and determine how long it will take:

Our complete build, TypeScript type checking, translation, reduction, etc. all took 13.85 seconds. By migrating to esbuild loader, we reduced the overall compilation time by about one third. This is a great progress!

With the expansion of the code base and the growth of the application, the compilation time may surge. With esbuild loader, you should get continuous benefits from the construction time.

Tags: Javascript Front-end Webpack

Posted on Tue, 26 Oct 2021 00:18:31 -0400 by Shiki