You're correct that Figma plugins have strict file constraints - they can only accept a single JavaScript file and a single HTML file, with no ability to load external files[10]. Here are the main approaches to bundle TypeScript, CSS, and HTML files into the required single-file format:
Webpack is the most commonly recommended solution for Figma plugin bundling[1]. Here's how to set it up:
Install dependencies:
npm install --save-dev typescript ts-loader webpack webpack-cli
npm install --save-dev html-webpack-plugin style-loader css-loader
Basic webpack.config.js:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: {
ui: './src/ui.ts',
code: './src/code.ts'
},
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.(png|jpg|gif|webp|svg)$/, loader: 'url-loader' }
]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/ui.html',
filename: 'ui.html',
chunks: ['ui']
})
]
};
For inlining JavaScript directly into HTML (eliminating the separate JS file), you can use the html-webpack-inline-source-plugin
[9].
Rollup offers another approach, particularly good for Figma plugins[4]:
Install dependencies:
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
npm install --save-dev rollup-plugin-postcss rollup-plugin-html-bundle rollup-plugin-terser
Rollup configuration example:
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
import htmlBundle from 'rollup-plugin-html-bundle';
import { terser } from 'rollup-plugin-terser';
export default [
{
input: 'src/main.js',
output: {
format: 'umd',
name: 'ui',
file: 'public/bundle.js'
},
plugins: [
resolve({ browser: true }),
commonjs(),
postcss({ extensions: ['.css'] }),
htmlBundle({
template: 'src/template.html',
target: 'public/index.html',
inline: true // This inlines JS into HTML
}),
terser()
]
},
{
input: 'src/code.js',
output: {
file: 'public/code.js',
format: 'iife'
},
plugins: [resolve(), commonjs(), terser()]
}
];
The newer HTML Bundler Plugin simplifies the entire process[8]:
npm install --save-dev html-bundler-webpack-plugin
This plugin allows you to use HTML as the entry point and automatically processes all referenced CSS and JS files, bundling everything together.
Several boilerplates handle this complexity for you:
Thomas Lowry's Boilerplate[7]:
- Pre-configured with Gulp build process
- Write HTML, SCSS, and vanilla JavaScript normally
- Automatically inlines and bundles everything into single files
- Handles image assets by converting them to data URIs
Advanced Boilerplate[10]:
- Supports TypeScript, React, Sass/SCSS
- Handles SVG imports as React components
- Includes communication patterns between plugin code and UI
- Pre-configured bundling with Vite/Rollup
Your typical project structure should look like this[1]:
src/
├── code.ts # Main plugin logic
├── ui.html # UI template
├── ui.ts # UI JavaScript
├── styles/
│ └── main.css # Stylesheets
└── components/ # Additional modules
dist/
├── code.js # Bundled plugin code
└── ui.html # Bundled UI with inline CSS/JS
The key is that your build process must inline all CSS and JavaScript into the final HTML file, as Figma plugins cannot reference external assets[7][10]. Choose Webpack if you prefer a mature ecosystem, or Rollup for simpler configuration and smaller bundle sizes.
[1] https://www.figma.com/plugin-docs/libraries-and-bundling/ [2] https://www.fabiofranchino.com/log/use-rollup-to-inject-bundled-javascript-into-an-html-file/ [3] https://stackoverflow.com/questions/46866228/how-to-bundle-html-js-and-css-in-one-html-file-with-webpack [4] https://tomquinonero.com/blog/write-a-figma-plugin-using-svelte/ [5] https://dev.to/geekygeeky/how-we-built-a-figma-plugin-in-300-lines-of-code-4l24 [6] https://www.figma.com/plugin-docs/typescript/ [7] https://github.com/thomas-lowry/figma-plugin-boilerplate/blob/master/README.md [8] https://hackernoon.com/the-right-way-to-utilize-webpack-for-bundling-a-html-page-with-css-and-js [9] https://stackoverflow.com/questions/68512734/how-to-bundle-js-inline-into-html-file [10] https://github.com/brreg/designsystem-figma-plugin [11] https://forum.figma.com/ask-the-community-7/solved-use-functions-from-bundled-js-files-in-ui-split-ui-js-into-multiple-files-11818 [12] https://evilmartians.com/chronicles/how-to-make-next-level-figma-plugins-auth-routing-storage-and-more [13] https://forum.figma.com/ask-the-community-7/external-javascript-file-for-my-plugin-36295 [14] https://stackoverflow.com/questions/78399749/how-can-i-bundle-my-full-stack-ts-application-with-html-all-into-one-html-js-and [15] webpack/webpack#15163 [16] https://www.coryetzkorn.com/writing/building-a-figma-plugin [17] https://github.com/aarongarciah/figma-plugin-typescript-boilerplate [18] https://www.youtube.com/watch?v=3rT4tHsbC1k [19] https://www.youtube.com/watch?v=fCAZ5JuUygw [20] https://dev.to/markliu2013/multiple-html-files-using-webpack-1116 [21] https://stackoverflow.com/questions/47546300/webpack-bundle-multiple-vendor-css-in-one-separate-file/47549457 [22] https://tripleten.com/blog/posts/merging-a-web-project-into-a-single-file-the-magic-of-webpack [23] https://www.matthewsimo.com/posts/2024-03-05-figma-plugin-template
You're right that Webpack can be bloated for simple use cases. Here are several lighter, faster alternatives that work perfectly for Figma plugin bundling:
ESBuild is extremely fast and well-suited for Figma plugins. It's written in Go and is 10-100x faster than Webpack[1][3].
Installation:
npm install --save-dev esbuild
For Figma plugins specifically, here's a proven setup:
tsconfig.json configuration:
{
"compilerOptions": {
"allowImportingTsExtensions": true,
"isolatedModules": true,
"noEmit": true
}
}
Build commands:
# Bundle your plugin code
./node_modules/.bin/esbuild code.ts --bundle --outfile=code.js
# For UI with CSS inlining
esbuild ui.ts --bundle --outfile=ui.js --loader:.css=text
Auto-watch setup in package.json:
{
"scripts": {
"typecheck": "tsc --project tsconfig.json",
"bundle": "./node_modules/.bin/esbuild code.ts --bundle --outfile=code.js",
"watch_typecheck": "npm run typecheck -- --watch --preserveWatchOutput",
"watch_bundle": "npm run bundle -- --watch=forever",
"watch": "parallel --ungroup 'npm run' ::: watch_typecheck watch_bundle"
}
}
ESBuild supports TypeScript and JSX syntax natively, performs tree shaking, and doesn't require a cache[1][2].
Vite is modern, fast, and much simpler than Webpack. It uses ESBuild under the hood for development and Rollup for production builds[3].
Installation:
npm install --save-dev vite
vite.config.js:
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
input: {
code: 'src/code.ts',
ui: 'src/ui.html'
},
output: {
entryFileNames: '[name].js'
}
}
}
})
There's a proven Figma plugin boilerplate using Vite and ESBuild that separates the plugin logic and UI into different folders with their own configurations[5].
Parcel offers zero configuration and is much faster than Webpack[6]. It automatically handles different file types including CSS, images, and HTML without any setup.
Installation:
npm install --save-dev parcel
Usage:
# Start with your HTML file as entry point
parcel build src/ui.html --dist-dir dist
# For the plugin code
parcel build src/code.ts --dist-dir dist
Parcel automatically bundles any assets referenced in your HTML file. It supports advanced features without heavy configuration and has an extensible plugin system[6].
Rollup excels at producing smaller bundles through excellent tree-shaking. It's perfect for libraries and lightweight projects[3].
Installation:
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript
rollup.config.js:
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/code.ts',
output: {
file: 'dist/code.js',
format: 'iife'
},
plugins: [
resolve(),
commonjs(),
typescript()
]
};
FuseBox is designed to be a simpler, faster alternative to Webpack. It allows importing non-JavaScript files without extra workarounds and includes built-in plugins like WebIndexPlugin for auto-generating HTML files[4].
For Figma plugin development, ESBuild is likely your best choice due to its speed, TypeScript support, and the existing community solutions specifically for Figma plugins[2]. It provides the minimal setup you want while handling all the bundling requirements effectively.
[1] https://blog.openreplay.com/top-five-alternatives-to-webpack/ [2] https://gist.github.com/lzell/bcedfd754f2d8015fbd850a56f1eae92 [3] https://dev.to/mohamedlaminef/which-javascript-bundler-is-right-for-you-a-deep-dive-into-webpack-vite-and-more-50bc [4] https://www.sitepoint.com/fusebox-faster-webpack-alternative/ [5] https://github.com/gnchrv/figma-plugin-boilerplate [6] https://blixtdev.com/you-dont-need-webpack-3-better-alternatives-for-building-your-javascript/ [7] https://css-tricks.com/comparing-the-new-generation-of-build-tools/ [8] https://stackoverflow.com/questions/58945488/bundle-ts-and-css-into-single-files-with-webpack [9] https://nomadicsoft.io/top-5-webpack-alternatives-revolutionize-your-javascript-build-tools/ [10] https://www.antforfigma.com/theme-buddy-figma-plugin [11] https://www.reddit.com/r/typescript/comments/a7z2we/can_i_import_css_into_typescript_without_webpack/ [12] https://www.figma.com/plugin-docs/libraries-and-bundling/ [13] https://webpack.js.org/guides/typescript/ [14] https://esbuild.github.io/plugins/ [15] https://esbuild.github.io/api/ [16] https://auth0.com/blog/introducing-fusebox-an-alternative-to-webpack/ [17] https://yuanqing.github.io/create-figma-plugin/
Based on the search results and your specific needs for a Figma plugin with substantial JavaScript logic, here's my analysis of the main alternatives:
Pros:
- Extremely fast performance (10-100x faster than Webpack)[1][5]
- Minimal configuration required[8]
- Built-in TypeScript and JSX support[5]
- Specifically mentioned as good for Figma plugins[5]
- Simple learning curve[8]
Cons:
- Limited CSS bundling capabilities - "there may be problems with bundling css files"[1]
- Smaller plugin ecosystem compared to other tools[8]
- Less mature for complex asset handling
Pros:
- Exceptional development experience with fast HMR[2]
- Uses ESBuild for development (speed) and Rollup for production (optimization)[6]
- Excellent tree-shaking and code splitting[2]
- Strong TypeScript support out of the box[2]
- Well-documented and growing community[2]
- Proven Figma plugin boilerplate available[7]
Cons:
- More complex than pure ESBuild[2]
- Potential overkill for very simple projects[2]
- Learning curve for developers new to modern tooling[2]
Pros:
- Excellent tree-shaking creates smaller bundles[3][8]
- ES6 module support encourages modern JavaScript[3]
- Optimized output perfect for production[8]
Cons:
- No native hot module replacement support[3]
- More complex configuration[8]
- Smaller plugin ecosystem[3]
- Less ideal for development workflow
For your Figma plugin with substantial JavaScript logic, I recommend Vite for these reasons:
Best fit for your use case:
- The "lots of JavaScript logic" requirement benefits from Vite's excellent tree-shaking and code splitting[2]
- Fast development iteration is crucial when building complex logic - Vite's HMR is consistently fast regardless of application size[2]
- There's already a proven Figma plugin boilerplate using Vite + React that handles the single-file bundling requirement[7]
Balanced approach:
- You get ESBuild's speed during development and Rollup's optimization for production[6]
- This dual approach gives you the best of both worlds without the complexity of managing separate tools
Future-proofing:
- Vite has strong momentum and community support[2]
- It's designed to handle everything from simple to complex applications[2]
- The configuration is intuitive while still allowing advanced customization when needed[2]
Alternative recommendation: If you prioritize absolute simplicity and your CSS needs are minimal, ESBuild would be my second choice due to its speed and minimal setup, especially since it's specifically mentioned as good for Figma plugins[5].
Avoid Rollup for this project since the lack of HMR would significantly slow down development of complex JavaScript logic[3].
[1] https://elixirforum.com/t/is-it-worth-using-esbuild/47881 [2] https://blog.logrocket.com/vite-adoption-guide/ [3] https://proxify.io/articles/webpack-vs-rollup [4] https://snipcart.com/blog/javascript-module-bundler [5] https://www.figma.com/plugin-docs/libraries-and-bundling/ [6] https://www.reddit.com/r/AskProgramming/comments/13x94zl/vite_vs_rollup_vs_webpack_vs_esbuild_difference/ [7] https://github.com/CoconutGoodie/figma-plugin-react-vite [8] https://npm-compare.com/@swc/core,esbuild,rollup,vite,webpack [9] https://news.ycombinator.com/item?id=28862454 [10] https://forum.figma.com/archive-21/which-is-the-best-structure-for-spliting-code-26793 [11] https://css-tricks.com/comparing-the-new-generation-of-build-tools/ [12] https://www.telerik.com/blogs/introduction-esbuild [13] https://www.reddit.com/r/Frontend/comments/1fjuphq/best_free_plugin_to_convert_figma_designs_to_code/ [14] https://github.com/gnchrv/figma-plugin-boilerplate [15] https://news.ycombinator.com/item?id=21961963 [16] https://evilmartians.com/chronicles/how-to-make-next-level-figma-plugins-auth-routing-storage-and-more [17] https://blog.khaleelgibran.com/posts/chromakey/ [18] https://www.wearedevelopers.com/magazine/figma-plugins-for-frontend-developers [19] https://www.reddit.com/r/FigmaDesign/comments/1itc5vd/what_are_the_best_figma_plugins_and_tools_for_ui/
Based on the search results, yes, Rollup is an excellent choice for CSS import structures, especially for your Figma plugin needs. Here are the main approaches:
rollup-plugin-import-css (Recommended)
This plugin specifically handles CSS import structures and supports three different import methods:
Installation:
Configuration:
rollup-plugin-postcss (Popular Alternative)
This plugin processes CSS files and supports extraction to separate files:
For Figma Plugin (Single File Requirement)
Since you need everything bundled into single files, here's a proven Rollup setup from the search results that handles CSS imports and inlines everything:
CSS Import Structure Benefits with Rollup
Import order preservation: All these plugins respect the import order of your CSS files[1][2][7], which is crucial for CSS specificity.
Modular structure: You can organize CSS like this:
Nested imports: CSS files can also use
@import
statements:Why Rollup Over Other Options
Given your earlier preference against Webpack's complexity, Rollup is perfect because:
For your Figma plugin with substantial JavaScript logic and CSS import structures, rollup-plugin-import-css with the
inject: true
option would be ideal, as it allows you to use normal CSS import syntax while ensuring everything gets bundled into your single required files.[1] https://www.npmjs.com/package/rollup-plugin-import-css
[2] https://classic.yarnpkg.com/en/package/rollup-plugin-css-bundle
[3] https://dev.to/bhargab/rollupjs-made-easy-a-step-by-step-guide-to-building-and-publishing-npm-packages-1c1k
[4] https://github.com/Evercoder/rollup-plugin-css-bundle
[5] https://github.com/jleeson/rollup-plugin-import-css
[6] https://tomquinonero.com/blog/write-a-figma-plugin-using-svelte/
[7] https://github.com/thgh/rollup-plugin-css-only
[8] https://codetv.dev/blog/learn-rollup-css
[9] https://www.dhiwise.com/post/the-ultimate-guide-to-bundling-css-with-rollup
[10] https://anidetrix.github.io/rollup-plugin-styles/
[11] https://rollupjs.org/configuration-options/
[12] rollup/rollup#5307
[13] https://github.com/LekoArts/figma-theme-ui/blob/main/rollup.config.mjs
[14] https://github.com/the-dataface/figma2html/blob/main/rollup.config.mjs
[15] https://forum.figma.com/ask-the-community-7/linking-css-to-ui-html-31363
[16] https://fabulousdesignspro.hashnode.dev/a-beginners-guide-to-rollup-module-bundler