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/
Converting Conduit MCP Plugin to TypeScript: A Comprehensive Migration Guide
Based on the analysis of the Conduit project and current best practices for MCP server development with TypeScript, migrating this plugin from its current implementation to a modern TypeScript-based architecture presents both opportunities for improvement and specific challenges that require careful planning. The Conduit project is a sophisticated Model Context Protocol implementation that enables AI agents to communicate directly with Figma, and converting it to TypeScript will provide better type safety, improved developer experience, and enhanced maintainability.
Project Architecture and Setup Strategy
Initial Project Foundation
The recommended approach for starting your TypeScript conversion involves establishing a robust foundation with proper tooling and configuration. Create a new project directory and initialize it with modern Node.js and TypeScript tooling[21]. Begin by setting up your development environment with the essential dependencies including the official MCP TypeScript SDK, which provides comprehensive support for building MCP servers and clients[15].
Your initial project structure should follow established patterns for MCP server development. Create a
src
directory structure that separates concerns between the MCP server logic, Figma plugin interface, and WebSocket communication layer[2]. The TypeScript configuration should target ES2022 or later to take advantage of modern JavaScript features while maintaining compatibility with Node.js environments[21].Install the core dependencies including
@modelcontextprotocol/sdk
for MCP functionality,zod
for runtime type validation, and development tools likets-node
andnodemon
for efficient development workflows[4][21]. Configure yourtsconfig.json
with strict type checking enabled and proper module resolution settings to ensure robust type safety throughout the migration process.Development Environment Configuration
Establish a comprehensive development setup that supports both local development and testing with MCP clients. Configure your TypeScript project to support both CommonJS and ESM modules depending on your target environment[14]. The MCP TypeScript SDK provides excellent support for both stdio and HTTP-based transports, allowing flexibility in how your server communicates with clients[15].
Set up development scripts in your
package.json
that enable hot reloading during development. Usenodemon
withts-node
to automatically restart your server when source files change[21]. This setup is crucial for rapid iteration during the migration process, allowing you to test individual components as you port them from the original implementation.Migration Strategy and Component Breakdown
Incremental Migration Approach
The most effective strategy for migrating the Conduit plugin involves a systematic, component-by-component approach that maintains functionality throughout the process. Start by examining the existing codebase structure and identifying the core functional areas: MCP server implementation, Figma API integration, WebSocket communication, and plugin management[2].
Begin the migration with the foundational MCP server infrastructure. The original Conduit implementation includes sophisticated features like real-time Figma interaction, component management, and design token support[2]. Create TypeScript interfaces and types that represent the core data structures used throughout the application. This includes defining types for Figma nodes, design properties, color systems, and layout configurations.
Implement the basic MCP server skeleton using the official TypeScript SDK, establishing the core server instance and basic tool registration patterns[15]. This foundation will serve as the target architecture for migrating individual features from the original implementation. The MCP SDK provides excellent TypeScript support with proper type definitions for all protocol messages and server lifecycle events.
Core Component Migration Priority
Prioritize migrating components in order of dependency and complexity. Start with utility functions and data transformation logic, as these typically have fewer external dependencies and can be easily unit tested[12]. Move the basic Figma API interaction functions next, ensuring proper TypeScript typing for all Figma plugin API calls and responses.
The WebSocket communication layer should be migrated as a distinct module with clear interfaces for message handling and connection management[2]. This component is critical to the real-time collaboration features that make Conduit unique among MCP servers. Implement proper error handling and reconnection logic with TypeScript's strong typing to catch potential issues at compile time.
Migrate the advanced features like batch operations, component variants management, and design token systems after establishing the core functionality[2]. These sophisticated features benefit significantly from TypeScript's type system, particularly in ensuring consistency across different Figma element types and property structures.
TypeScript-Specific Enhancements
Type Safety and Interface Design
One of the primary benefits of migrating to TypeScript is the ability to create robust type definitions that prevent runtime errors and improve developer experience. Design comprehensive interfaces for all Figma-related data structures, including nodes, styles, components, and design tokens[2]. Create union types for the various Figma element types and use discriminated unions to ensure type safety when working with different node types.
Implement proper error handling with custom error types that extend the base Error class. This approach provides better debugging information and allows for more sophisticated error recovery strategies[15]. Use TypeScript's strict null checks and optional chaining features to handle the dynamic nature of Figma plugin environments safely.
Define clear interfaces for MCP tool parameters and responses, leveraging the
zod
library for runtime validation that aligns with TypeScript compile-time types[4]. This dual validation approach ensures that both the TypeScript compiler and runtime environment maintain consistency in data handling.Modern JavaScript Features Integration
Take advantage of modern JavaScript features that work well with TypeScript's type system. Use async/await patterns throughout the codebase for handling asynchronous operations, which are common in both MCP communication and Figma plugin interactions[15]. Implement proper Promise typing and error handling to maintain type safety across asynchronous boundaries.
Utilize ES6 modules with proper import/export patterns that support tree shaking and efficient bundling[20]. This is particularly important for the plugin component that needs to maintain a reasonable bundle size for Figma's plugin environment. Implement barrel exports through
index.ts
files judiciously, avoiding patterns that might prevent effective tree shaking[8][20].Development Workflow and Testing Strategy
Local Development and Testing
Establish a robust local development workflow that supports rapid iteration and testing. Configure the MCP server to work with development tools like the MCP Inspector for testing server functionality[15]. Set up proper debugging configurations in your IDE to step through both server and client code during development.
Create a comprehensive testing strategy that includes unit tests for individual components and integration tests for MCP protocol compliance[12]. The TypeScript ecosystem provides excellent testing tools that work well with the async nature of MCP servers. Use Jest or similar testing frameworks with proper TypeScript configuration to maintain type safety in your test code.
Implement proper logging and monitoring throughout the application to facilitate debugging during the migration process. Use structured logging that provides clear information about MCP message handling, Figma API interactions, and WebSocket communication status[11].
Plugin Development Integration
Configure your development environment to support both the MCP server and Figma plugin development workflows. The Figma plugin component requires compilation to JavaScript and proper manifest configuration[7][16]. Set up webpack or similar bundling tools to handle the plugin's build requirements while maintaining the TypeScript development experience.
Establish clear interfaces between the MCP server and Figma plugin components, ensuring that message passing and state synchronization work reliably. This separation of concerns is crucial for maintaining the real-time collaboration features that distinguish Conduit from simpler MCP implementations[2].
Conclusion
Converting the Conduit MCP plugin to TypeScript represents a significant opportunity to modernize the codebase while maintaining its sophisticated functionality. The migration strategy should prioritize establishing a solid TypeScript foundation with proper tooling and configuration, followed by systematic migration of components in order of dependency complexity. The resulting TypeScript implementation will provide better type safety, improved developer experience, and enhanced maintainability while preserving the innovative real-time Figma collaboration features that make Conduit valuable. Success in this migration depends on careful planning, comprehensive testing, and leveraging TypeScript's strengths in API design and error handling.
[1] https://github.com/eonist/conduit/blob/main/src/conduit_mcp_plugin
[2] https://github.com/eonist/conduit
[3] https://github.com/steipete/conduit-mcp
[4] https://dev.to/shadid12/how-to-build-mcp-servers-with-typescript-sdk-1c28
[5] https://stackoverflow.com/questions/40441252/typescript-typings-give-me-index-d-ts-is-not-a-module
[6] https://www.youtube.com/watch?v=odAIrlamMXc
[7] https://help.figma.com/hc/en-us/articles/4407531166743-BYFP-Plugin-Environment-Setup
[8] https://www.reddit.com/r/Angular2/comments/1amnupf/nx_monorepo_libs_and_indexts/
[9] https://stackoverflow.com/questions/56379875/how-to-write-a-plugin-loader-for-a-module-written-in-typescript
[10] https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins/adding-mcp-plugins
[11] https://learn.microsoft.com/en-us/microsoftteams/platform/teams-ai-library/typescript/in-depth-guides/ai/mcp/mcp-server
[12] https://dev.to/alexmercedcoder/building-a-basic-mcp-server-with-python-5ci7
[13] https://www.youtube.com/watch?v=L6yKk7EQYZc
[14] TypeStrong/ts-node#2100
[15] https://github.com/modelcontextprotocol/typescript-sdk
[16] https://github.com/aarongarciah/figma-plugin-typescript-boilerplate
[17] https://www.serverless.com/plugins/serverless-plugin-typescript
[18] https://www.a2e.ai/supercharge-your-vibe-coding-with-a2e-mcp-server/
[19] https://blog.platformatic.dev/accelerating-nodejs-development-with-mcp-node
[20] https://www.linkedin.com/pulse/best-practices-using-indexts-files-avoiding-tree-shaking-amin-atwi-bexmf
[21] https://www.infyways.com/build-your-first-mcp-server-with-typescript/
[22] https://mcp.pizza/mcp-server/jYB2/conduit
[23] https://conduit.io/docs/cli
[24] https://devblogs.microsoft.com/microsoft365dev/announcing-the-updated-teams-ai-library-and-mcp-support/
[25] TypeStrong/ts-node#935
[26] https://learn.microsoft.com/en-us/microsoftteams/platform/teams-ai-library/typescript/in-depth-guides/ai/mcp/mcp-client
[27] https://github.com/eonist/conduit/issues
[28] https://www.reddit.com/r/neovim/comments/189rqq7/home_made_plugin_better_formatted_typescript/
[29] https://nextjs.org/docs/app/api-reference/config/typescript
[30] https://docs.theconduit.dev/db/db_tools/
[31] https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-plugin-d-ts.html
[32] https://docs.pipecat.ai/server/base-classes/mcp/mcp
[33] https://code.visualstudio.com/api/references/vscode-api
[34] https://github.com/Heavybullets8/TT-Migration
[35] https://www.youtube.com/watch?v=AthL1lVrSv0
[36] https://dev.to/jneums/why-we-ditched-python-for-typescript-and-survived-oauth-in-our-ai-agent-mcp-server-45al