Web bundler esbuild and its usage — how to bundle and minify JavaScript and CSS in Deno

Esbuild is a web bundler tool. According to the official site, it is much faster than other current build tools like webpack, rollup and Parcel. Here you will see how to use esbuild to bundle, minify JavaScript, CSS in Deno. The esbuild usage in Node is almost the same.

What is a bundler?

A bundler is a tool that bundles multiple scripts together into one script that is production-ready in the browser. It often performs transformations on the scripts and is able to bundle other static assets like images, CSS, ect.

The benefits of using a web bundler

  • Help developers to manage dependencies.
  • Minify file which saves bandwidth.
  • Transform code to make your product available on more enrionments.
  • Optimize the code to be more efficient.
  • Help to optimize assets loading.

Features of esbuild

Main features:

  • Extrem speed without needing a cache.
    Esbuild is written in Go by which esbuild is fast.
  • JavaScript, CSS, TypeScript, and JSX built-in.
    The content type can be:

    • JavaScript, TypeScript — Boundle, minify, transform (TypeScript to JavaScript, to Node or browser platform).
    • JSX — Parse JSX to normal JavaScript.
    • JSON — Parse JSON to JavaScript object.
    • CSS — Boundle and minify a CSS file without importing it from JavaScript.
    • Other type: text, binary, data URL, external file…
  • Plugins are supported satisfy more needs.
    Some pliugins like:

Unsupported feature:

  • ES5 is not supported
    ES5 is not supported well: Transforming ES6+ syntax to ES5 is not supported yet (2023.07).
    However, if you’re using esbuild to transform ES5 code, you should still set the target to es5 to prevent introducing ES6 syntax.
  • TypeScript type checking

Esbuild API

Esbuild provides two commonly-used API:

  • build
    Takes one or more files and various options and writes the result back to the file system.
  • transform
    A limited special-case of build that transforms string of code in an isolated environment (like it works in the browser) and returns the result as string.

Esbuild provides differenct interface like CLI, JavaScript API and Go API/CLI. Here we check how to use esbuild API in JavaScript and focus on the build API for the tranform is similar.

Asynchonous and synchronous

You can use esbuild API in asynchonous and synchronous:
– Aync API
– The main advantage is you can use plugins with the asynchonous build API (not with the transform API).
– The main disadvantage is it does not work in situations that must be synchronous like require.extensions.
– Sync API
– You can’t use plugins with the synchronous API since plugins are asynchronous.

Esbuild API — build

Minify a JavaScript file

An example to minify js-example.js in Deno (A replacement for Node.js).

The js-example.js:

const DEBUG = false;

function one() {
    DEBUG && console.log('one');
}

function two() {
    console.log('two');
}

one();

Our code:

import * as esbuild from 'https://deno.land/x/esbuild@v0.17.18/mod.js';

// Use build() method
async function minifyJs() {
    await esbuild.build({
        entryPoints: ['js-example.js'],
        minify: true,
        outfile: 'js-example-min.js',
    });

    console.log(Deno.readTextFileSync('js-example-min.js'));
    // Output:
    // const DEBUG=!1;function one(){}function two(){console.log("two")}

    esbuild.stop(); // It is necessary in Deno environment.
}

minifyJs();

You may notice that the one(); is removed for the call actually do nothing. If we add bundle option, the unused function function two() {...} will be removed either. See the example in the next section.

Be aware to call esbuild.stop() in Deno. According to Esbuild: Deno instead of node:

You need to call stop() when you’re done because unlike node, Deno doesn’t provide the necessary APIs to allow Deno to exit while esbuild’s internal child process is still running.

Minify a JavaScript with a more bundle option

import * as esbuild from 'https://deno.land/x/esbuild@v0.17.18/mod.js';

// Use build() method
async function minifyJs() {
    await esbuild.build({
        entryPoints: ['js-example.js'],
        bundle: true,
        minify: true,
        outfile: 'js-example-bundle-min.js',
    });

    console.log(Deno.readTextFileSync('js-example-bundle-min.js'));
    // Output:
    // (()=>{})();

    esbuild.stop(); // It is necessary in Deno environment.
}

minifyJs();

More simplified, isn’t it?

Note

Esbuild does not boundle the multiple files occured in the entry points, it will create multiple separate files. To bundle multiple files, import them into one file use that file as entry point.

Bundle multiple JavaScript files into one

The main.js:

import A from a.js;
import B from b.js;

...

The code:

import * as esbuild from 'https://deno.land/x/esbuild@v0.17.18/mod.js';

async function bundleJs() {
    await esbuild.build({
        entryPoints: ['main.js'],
        bundle: true,
        outfile: 'main.bundle.js',
    });

    esbuild.stop(); // It is necessary in Deno environment.
}

bundleJs();

Minify a CSS file

The main.css:

:root {
  --background-main: #333; /* Use a dark mode */
  --text-main: #ccc;
}
body {
  background: var(--background-main);
  color: var(--text-main);
  font-family: "Helvetica Neue", Helvetica, Arial, "MS Pゴシック";
}
.warning{
  color: rgba(255, 0, 0, 0.5);
}

The code:

import * as esbuild from 'https://deno.land/x/esbuild@v0.17.18/mod.js';

function bundleAndMinifyCss() {
    esbuild.buildSync({
        entryPoints: ['main.css'],
        bundle: true,
        minify: true,
        outfile: 'main.min.css',
    });

    console.log(Deno.readTextFileSync('main.min.css'));
    // Output:
    // :root{--background-main: #333;--text-main: #ccc}body{background:var(--background-main);color:var(--text-main);font-family:Helvetica Neue,Helvetica,Arial,ff2dff33  ff3030b430b730c330af}.warning{color:#ff000080}


    esbuild.stop(); // It is necessary in Deno environment.
}

bundleCss();

You will see below things are done:

  • The uncessary white spaces and ; are removed;
  • The comments in CSS are removed;
  • The non-ASCII characters are escaped in form of xxxx.
  • The color rgba(255, 0, 0, 0.5) is changed to #ff000080.

Note

If you choose to use synchronous API esbuildSync() in Deno, you will see below error:

Error: The "buildSync" API does not work in Deno

Bundle a CSS file

Esbuild can bundle a CSS file which contains @import other other CSS files and reference image and font files with url() and esbuild will bundle everything together.

For image and font files, you need to configure a loader which is usually either the data URL loader or the external file loader.

Esbuild API — transform

Minify a JavaScript file

import * as esbuild from 'https://deno.land/x/esbuild@v0.17.18/mod.js';

async function minifyJs2() {
    const src = Deno.readTextFileSync('js-example.js');
    const { code } = await esbuild.transform(src, {
        // bundle: true, // error: Invalid option in transform() call: "bundle"
        minify: true,
    });

    console.log(code);
    // Output:
    // const DEBUG=!1;function one(){}function two(){console.log("two")}

    esbuild.stop(); // It is necessary in Deno environment.
}

minifyJs2();

Note transform() does not support bundle option.

Transform TypeScript to JavaScript

mport * as esbuild from 'https://deno.land/x/esbuild@v0.17.18/mod.js';

async function tranformTsToJs() {
    const ts = 'let x: number = 1';
    let result = await esbuild.transform(ts, {
        loader: 'ts',
    });

    console.log(result);
    // Output:
    // {
    //     warnings: [],
    //     code: "let x = 1;n",
    //     map: "",
    //     mangleCache: undefined,
    //     legalComments: undefined
    // }

    esbuild.stop();
}

tranformTsToJs();

Resource

  • esbuild
  • Modern Web Bundlers in 2023
    • Vite — Dev server, build command to bundle code with Rollup.
    • Webpack — Bundle JavaScript and static assets like images, CSS, fonts.
    • Parcel (known for zero configuration) — Automatically detect and boundle all assests and dependencies without configuration.
    • SWC — Work for JavaScript/TypeScript, compile, bundle, minify, transform and more. Used by Next.js, Parcel, Deno.
    • Rollup — Bundle JavaScript code.