Creating a single-file build of Vue.js application for Figma plugin integration

Building Vue app as single file for Figma plugin

I’m working on a Vue application built with Vite that works perfectly when running npm run serve locally. The issue comes when I try to package it as a Figma plugin.

My current setup generates multiple files in the build folder:

build/
  static/
    css/
      app.b5d2c3e1.css
    js/
      app.f7e9a2b4.js
      chunk-vendors.c8d5f6a7.js
  index.html
  manifest.json
  figma-plugin.js

The generated HTML file contains:

<script type="module" crossorigin src="/static/js/app.f7e9a2b4.js"></script>
<link rel="modulepreload" href="/static/js/chunk-vendors.c8d5f6a7.js">
<link rel="stylesheet" href="/static/css/app.b5d2c3e1.css">
<div id="root" class="full-height full-width"></div>

When testing in Figma, the HTML loads but Vue doesn’t initialize. Console shows: <link rel=modulepreload> has no 'href' value

I need to bundle everything into one file for the plugin to work properly. My current vite config:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ command, mode }) => {
  return {
    plugins: [vue()],
    base: './',
    build: {
      outDir: 'build',
      rollupOptions: {
        input: './index.html'
      }
    }
  }
})

How can I configure Vite to create a single bundled file that works with Figma’s plugin system?

Yeah, this is super common with complex build setups. You could keep tweaking Vite configs manually, but you’re basically fighting the tool at that point.

I’ve hit this same issue with plugin builds before. Automation beats manual config changes every time. Instead of wrestling with Vite settings whenever you need different output formats, just set up a workflow that takes your standard Vue build and converts it to whatever format you need.

What’s great about this approach? Your dev setup stays untouched. The automation handles everything - inlines CSS, bundles JS into IIFE format, fixes path issues, optimizes assets - all without messing with your main build process.

No more maintaining separate configs or remembering which flags to flip when switching between local dev and plugin builds. The automation does the transformation every time, so your workflow stays consistent.

Latenode makes this build automation pretty straightforward. Trigger it after your normal build and it’ll automatically process everything into the single file format Figma wants.

The modulepreload issue happens because Figma’s plugin environment doesn’t support ES modules properly. I ran into this exact problem migrating a Vue dashboard to a Figma plugin last year. Besides the Vite config changes others mentioned, make sure your HTML template doesn’t use module preloading at all. Try adding build.target: 'es2015' to your config and set rollupOptions.output.format to ‘umd’ instead of ‘iife’ - worked way better for me with Vue’s reactivity system in Figma. Also double-check your manifest.json has the right script references and you’re not importing any Node.js modules that won’t work in Figma’s sandbox.

Try adding build.rollupOptions.external: [] to force everything internal. Also check your Figma manifest permissions for loading scripts - that’s usually the real issue, not the bundling.

Had this exact problem building my first Figma plugin with Vue. Figma’s sandboxed environment handles module imports differently than browsers, which triggers those modulepreload errors. Here’s what fixed it for me: modify your vite config with rollupOptions.output.format: 'iife' to bundle everything into one file that doesn’t need module loading. Also set build.minify: false while debugging - makes issues way easier to spot. One more gotcha: base path setup for Figma’s context. Even with base: './', relative paths can still break. You might need to try absolute paths or tweak how Figma loads your HTML file depending on your plugin setup.

To resolve the single-file build problem for your Figma plugin, you can adjust your Vite configuration as follows. Set rollupOptions.output.manualChunks to undefined. Also, enable rollupOptions.output.inlineDynamicImports and set build.assetsInlineLimit to a large number to ensure all assets are inlined. Additionally, set build.cssCodeSplit to false to prevent code splitting. These settings should bundle everything into one file, thus eliminating the module preload error you’ve encountered.