DEV Community

Cover image for Bundling Figma Plugin With Esbuild
David Dal Busco
David Dal Busco

Posted on • Updated on • Originally published at daviddalbusco.Medium

Bundling Figma Plugin With Esbuild

I recently published a new open source plugin to export Figma frames to DeckDeckGo slides.

As I like to benefit from my experiences to learn and try new concept, instead of using a bundler as described in the Figma documentation, I decided to give a try to esbuild.

The least I can say, I loved it ❤️.


Following solution is the one I set up for my plugin. It does work like a charm but, notably because it was the first time I used esbuild, it might need some improvements. If you notice improvements or issues, let me know, I would like to hear from you!

Contributions to my plugin and PR are also welcomed 😉.


In a Figma plugin, install both esbuild and rimraf .

npm i esbuild rimraf --save-dev
Enter fullscreen mode Exit fullscreen mode

rimraf might not be needed, if you only build your project in a CI, nevertheless, for a local build, I think it is safer to delete the output directory before any new build.

In package.json add, or modify, the build script.

"scripts": {
  "build": "rimraf dist && node ./esbuild.js"
Enter fullscreen mode Exit fullscreen mode

You might notice that the last command target a script called esbuild.js. This file will contain our bundling steps, therefore create such a new file at the root of your project.

touch esbuild.js
Enter fullscreen mode Exit fullscreen mode

Finally, in this newly created file, import esbuild .

const esbuild = require('esbuild');
Enter fullscreen mode Exit fullscreen mode


A Figma plugin run (see documentation) in a combination of a sandbox, to access to the Figma nodes, and an iFrame, for the presentation layer. We set up firstly the sandbox’s build.

// sandbox

    entryPoints: ['src/plugin.ts'],
    bundle: true,
    platform: 'node',
    target: ['node10.4'],
    outfile: 'dist/plugin.js'
  .catch(() => process.exit(1));
Enter fullscreen mode Exit fullscreen mode

In the above script, we bundle the plugin.ts, the sandbox’s code, to its JavaScript counterpart plugin.js . As configuration, we tell esbuild to treat it as a NodeJS platform and we target the version 10.4.

I configured my plugin to handle sources from a folder src. I also bundle the outcome to another one called dist. If you do not have the same structure, modify the solution accordingly.


In comparison to the previous chapter, we are going to gather the results of the build instead of telling esbuild to write directly to a file. For such reason, we import NodeJS fs to interact with the file system.

const {readFile, writeFile} = require('fs').promises;
Enter fullscreen mode Exit fullscreen mode

We also install html-minifier-terser to minify the resulting HTML code.

npm i html-minifier-terser --save-dev
Enter fullscreen mode Exit fullscreen mode

Once installed, we add a related import to our build script too.

const minify = require('html-minifier-terser').minify;
Enter fullscreen mode Exit fullscreen mode

These imports set, we implement the bundling.

// iframe UI

(async () => {
  const script = esbuild.buildSync({
    entryPoints: ['src/ui.ts'],
    bundle: true,
    minify: true,
    write: false,
    target: ['chrome58', 'firefox57', 'safari11', 'edge16']

  const html = await readFile('src/ui.html', 'utf8');

  const minifyOptions = {
    collapseWhitespace: true,
    keepClosingSlash: true,
    removeComments: true,
    removeRedundantAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    useShortDoctype: true,
    minifyCSS: true

  await writeFile(
    `<script>${script.outputFiles[0].text}</script>${await minify(html, minifyOptions)}`
Enter fullscreen mode Exit fullscreen mode

In the above script, we compile the ui.ts , our TypeScript code related to the UI, with esbuild . We instruct it to inline any imported dependencies into the file itself with the option bundle, we minify the JavaScript code and, we do not write to the file system. Instead of such step, we gather the outcome in a variable I called script .

We read the ui.html source file, define some options for the HTML minification and, finally, write both compiled code and HTML to the output (dist/ui.html in this example).

Web Components

Of course, I had to create some Web Components for my projects 😉. Integrating these follow same logic as previously, except that we use the esm format.

const buildWebComponents = (entryPoints) =>
    .map((entryPoint) =>
        entryPoints: [entryPoint],
        bundle: true,
        minify: true,
        write: false,
        target: ['chrome58', 'firefox57', 'safari11', 'edge16'],
        format: 'esm'
    .map((componentScript) => componentScript.outputFiles[0].text)
(async () => {
  const componentsScript = buildWebComponents([

  // Same as previous chapter

  await writeFile(
    `<script>${script.outputFiles[0].text}</script><script type="module">${componentsScript}</script>${await minify(html, minifyOptions)}`
Enter fullscreen mode Exit fullscreen mode

I created more than one Web Component (checkbox.ts, button.ts, etc.), that’s why the buildWebComponents function. It takes an array, a list of files, as parameter and, concat all bundle together to a single value.

And...that’s it 😃. Sandbox, UI and Web Components are bundled faster than ever ⚡️.


You can find above solution and, other fun stuff in the open source repo of my plugin:


Setting up a project with esbuild was a pleasant developer experience. Writing a JS script to bundle my project, without many dependencies and with much flexibility, definitely matches my current inspiration. In addition, the outcome, the bundling itself, is faaaaaaaaaaaaaast! I am looking forward to use this compiler in other projects 👍.

To infinity and beyond!


Cover photo by Uillian Vargas on Unsplash

You can reach me on Twitter or my website.

Give a try to DeckDeckGo for your next slides!


Discussion (0)