How to lazy load with esbuild
Tags: esbuild, javascript
Published: 2021-07-21 06:47:35

In this article, I will show you how to lazy load with esbuild. It's achieved by using a work-in-progress flag `--splitting`, so you may want to check out the [documentation](https://esbuild.github.io/api/#splitting) before you will start building something very complex with it.

# Lazy loading
Is a pattern of delaying the download of a resource until it's needed. A common approach in web applications is to split critical & non-critical code into different files. In this way, non-critical code can be lazy-loaded in the background, while the user has already access to most of the features of the app.

# The example
Similar to what I used in the [webpack example](https://how-to.dev/how-to-lazy-load-library-in-webpack-5), here we will have a simple js application, that happens to depend on a big, 3rd party library. The library I use, [PDF-LIB](https://pdf-lib.js.org/) was already discussed in an [earlier post](https://how-to.dev/how-to-create-pdf-in-browser-js). PDF creation is a complex task, which requires a lot of code. Let's imagine an invoice application - one that allows for creating invoices & generating PDFs. it's an important feature of an application, but only called from some route & even there not needed immediately.

# The code
For the example application, I have few files. `index.html`:
```html
<!-- index.html -->
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Lazy load in esbuild</title>
    <link rel="shortcut icon" href="#" />

    <div id="view"></div>

    <script type="module" src="./dist/index.js"></script>
  </head>
  <body></body>
</html>
```

Simple loading the builts JS from the `dist` folder.

`src/index.js`:
```js
const view = document.getElementById("view");

view.innerHTML = `<button id="pdf-button">Generate PDF</button>
<br>
<iframe id="pdf" style="width: 350px; height: 600px"></iframe>`;

import("pdf-lib").then(({ PDFDocument }) => {
  const pdfButton = document.getElementById("pdf-button");

  pdfButton.addEventListener("click", async () => {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage([350, 400]);
    page.moveTo(110, 200);
    page.drawText("Hello World!");
    const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
    document.getElementById("pdf").src = pdfDataUri;
  });
});
```

In this one file, we have 2 sections that will be executed in a different moments. The first 2 lines are run immediately after loading the js. They have our critical path - they set up the view for the user to interact with, while we load the rest of JS. The other is the callback for the dynamic import of `pdf-lib`. You can read more about dynamic imports on [mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports), but in short, they are a part of the es-module specification. In short - it's loading another file during the runtime, and resolving a promise when it's available.

For the best user experience, you could set the *Generate PDF* button inactive here, and turn it active after PDF-LIB is available. For the sake of simplicity of the example code, I left the button unresponsive while the library loads.

# Dependencies

After initializing your package with:
```sh
$ npm init -y
```

you can install all dependencies with:
```sh
$ npm install --save esbuild pdf-lib
```

# Build code

You can add the build CLI command as an npm script to `package.json`:
```json
{
  ...
  "scripts": {
    ...
    "build": "esbuild src/index.js --bundle --outdir=dist --splitting --format=esm"
  }
...
```

The values we have here:
* `src/index.js` - the entry point of the application
* `--bundle` - we tell the esbuild to bundle the whole application
* `--outdir=dist` - because of using splitting, just specifying the output file with `--outfile` is not enough - esbuild needs directory to put all chunks it creates there
* `--splitting` - we turn on the experimental splitting behavior
* `--format=esm` - another requirement of splitting to work - as of now, it's only working with es-modules output

# Links
* [my video course about esbuild](https://bit.ly/esbuild-course)

# Summary
After all this, our application will lazy load the big 3rd party dependency:

![esbuild-lazy-load](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dd2bsvf5mi3ljlt3sqwe.png)

If you want to see it in action yourself, the application is available [here](https://marcin-wosinek.github.io/esbuild-lazy-load/), and the source code:

{% github https://github.com/marcin-wosinek/esbuild-lazy-load no-readme %}