DEV Community

loading...
Cover image for Bundling Figma Plugin With Esbuild

Bundling Figma Plugin With Esbuild

David Dal Busco
Creator of DeckDeckGo | Organizer of the Ionic Zürich Meetup
Originally published at daviddalbusco.Medium ・4 min read

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 ❤️.


Foreword

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 😉.


Setup

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

Sandbox

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

esbuild
  .build({
    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.


UI

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(
    'dist/ui.html',
    `<script>${script.outputFiles[0].text}</script>${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) =>
  entryPoints
    .map((entryPoint) =>
      esbuild.buildSync({
        entryPoints: [entryPoint],
        bundle: true,
        minify: true,
        write: false,
        target: ['chrome58', 'firefox57', 'safari11', 'edge16'],
        format: 'esm'
      })
    )
    .map((componentScript) => componentScript.outputFiles[0].text)
    .join('');
(async () => {
  const componentsScript = buildWebComponents([
    'src/components/checkbox.ts',
    'src/components/button.ts',
    'src/components/spinner.ts',
    'src/components/fonts.ts'
  ]);

  // Same as previous chapter

  await writeFile(
    'dist/ui.html',
    `<script>${script.outputFiles[0].text}</script><script type="module">${componentsScript}</script>${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 ⚡️.


Repo

You can find above solution and, other fun stuff in the open source repo of my plugin: https://github.com/deckgo/figma-deckdeckgo-plugin


Summary

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!

David

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!

DeckDeckGo

Discussion (0)