Deluxe Blog Tips About Projects

My first experience with esbuild

I’ve been a Webpack user for a long time. I built Meta Box Builder and Slim SEO Schema with React using Webpack and some of the WordPress’s npm packages. Webpack is powerful and does everything I need. However, I feel it’s hard to configure, especially setting up it the first time, and generates a big output bundle file.

In the last couple of days, I had a chance to try esbuild and was amazed by its simplicity and result. I know esbuild for a long time but didn’t try it until recently. Everything with Webpack just works, so why change? It’s because in a recent project with a friend, he told me to use Vite, which uses esbuild internally to bundle dependencies. So, I learned about it and applied it to Slim SEO Schema. The result was fantastic!

Esbuild is easy to configure

The first thing about esbuild is its simplicity. I remember the first day I configured Webpack and Babel to work with React in WordPress, it was a nightmare. I read many tutorials I found on the Internet just to learn how to set it up correctly.

However, with esbuild, everything is straightforward. It has only 2 steps:

Installing esbuild

npm install --save-dev esbuild esbuild-plugin-external-global

I use esbuild-plugin-external-global to make esbuild recognize the external global libraries in WordPress, like React, react-dom or @wordpress/components.

Note that I don’t have to install Babel and its presets/plugins to transform JSX to JS. This is everything I need to install.

Configuring esbuild

To use JS in WordPress, we have to bundle it into one file to enqueue in a WordPress page. Here is a minified version of my Webpack config file:

const path = require( 'path' );
const webpack = require( 'webpack' );
const TerserPlugin = require( 'terser-webpack-plugin' );

const commonModules = {
    rules: [
        {
            test: /\.js/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader',
                options: {
                    plugins: [ '@babel/plugin-transform-react-jsx' ],
                },
            },
        },
    ],
};

const externals = {
    'react': 'React',
    'react-dom': 'ReactDOM',
    '@wordpress/i18n': 'wp.i18n',
    '@wordpress/element': 'wp.element',
    '@wordpress/components': 'wp.components',
    '@wordpress/hooks': 'wp.hooks',
    'jquery': 'jQuery',
};

const plugins = [
    new webpack.optimize.LimitChunkCountPlugin( {
        maxChunks: 1,
    } ),
];

const optimization = {
    minimize: true,
    minimizer: [
        new TerserPlugin( {
            extractComments: false,
            terserOptions: {
                format: {
                    comments: false,
                },
            },
        } ),
    ],
};

const schemas = {
    entry: './js/import-export/App.js',
    output: {
        filename: 'import-export.js',
        path: path.resolve( __dirname, './js/' ),
    },
    externals,
    plugins,
    optimization,
    module: commonModules,
};

module.exports = [ schemas ];

What it does are:

  • Tells Babel to transform JSX to JS
  • Sets up external global libraries
  • Tells Webpack to bundle into one file
  • Minifies code with Terser

With esbuild, everything is much simpler. Here is the configuration file for esbuild that does the same things:

const esbuild = require( "esbuild" );
const { externalGlobalPlugin } = require( "esbuild-plugin-external-global" );

const config = {
    bundle: true,
    minify: true,
    loader: {
        '.js': 'jsx',
    },
    plugins: [
        externalGlobalPlugin( {
            'react': 'React',
            'react-dom': 'ReactDOM',
            '@wordpress/i18n': 'wp.i18n',
            '@wordpress/element': 'wp.element',
            '@wordpress/components': 'wp.components',
            '@wordpress/hooks': 'wp.hooks',
            'jquery': 'jQuery',
        } ),
    ],
};

esbuild.build( {
    ...config,
    entryPoints: [ 'js/import-export/App.js' ],
    outfile: 'js/import-export.js',
} );

The configuration is shorter and easier to understand! esbuild can read our minds!

Esbuild generates very small output files

The second thing I love about esbuild is the output files are very small! Unlike Webpack, esbuild use ES Modules, which now are supported by most browsers. So most of the modern syntax remains while building. No extra code is added to convert the syntax to ES5, thus the output file is small.

Here is the file size generated by Webpack, which is 143k:

$ ll js
Permissions Size User   Date Modified Git Name
drwxr-xr-x     - rilwis 14 Oct 16:53   -- import-export
.rw-r--r--  143k rilwis 15 Oct 07:29   -I import-export.js

And here is the file size generated by esbuild, which is only 3k!

$ ll js        
Permissions Size User   Date Modified Git Name
drwxr-xr-x     - rilwis 14 Oct 16:53   -- import-export
.rw-r--r--  3.0k rilwis 15 Oct 07:31   -I import-export.js

So basically esbuild generates files at almost 50x smaller than Webpack!

Disadvantages of esbuild

However, esbuild has some issues that stop me from using it in every case. It’s still new and hasn’t reached the 1.0.0 version yet! There are 2 features that I miss from Webpack:

Configuring path alias

In Webpack, I can configure the root path of the project like this:

resolve: {
    roots: [ path.resolve( 'app' ) ]
}

And in JS files, I can simply import other files with:

import Input from '/components/Input';

I found something similar in esbuild but it requires jsconfig.js or tsconfig.js file and I don’t know how to set up it right 🙁

No support for dynamic imports

In Meta Box Builder and Slim SEO Schema, I use a lot of dynamic imports to lazy load components, like this:

let typeCache = {};
export const getFieldType = name => lazy( () => {
    if ( !typeCache[ name ] ) {
        typeCache[ name ] = import( `./components/Fields/${ name }` );
    }
    return typeCache[ name ];
} );

This feature is not available in esbuild yet and is still in discussion. I found a plugin for esbuild to do that but the docs are vague and I don’t understand how to set it up 🙁

Because of these reasons, I can only use esbuild for small parts of the projects where I don’t need path alias (a workaround is to use relative path only) or dynamic imports (no workarounds). Anyway, its benefit is huge and it’s still being developed. I hope in the future esbuild will add more features and at that time, I believe it’s the best build tool for JavaScript.