11import { promises as fs } from 'node:fs'
22import path from 'node:path'
3- import { build } from 'esbuild'
3+ import { build , context } from 'esbuild'
44import { globby } from 'globby'
55
66const ROUTE_PREFIX = '/mcp-use/widgets'
@@ -43,8 +43,62 @@ function htmlTemplate({ title, scriptPath }: { title: string, scriptPath: string
4343</html>`
4444}
4545
46- export async function buildWidgets ( projectPath : string ) {
47- console . log ( '🔨 Building UI widgets with esbuild...' )
46+ async function buildWidget ( entry : string , projectPath : string , minify = true ) {
47+ const relativePath = path . relative ( projectPath , entry )
48+ const route = toRoute ( relativePath )
49+ const pageOutDir = path . join ( projectPath , outDirForRoute ( route ) )
50+ const baseName = path . parse ( entry ) . name
51+
52+ // Build JS/CSS chunks for this page
53+ await build ( {
54+ entryPoints : [ entry ] ,
55+ bundle : true ,
56+ splitting : true ,
57+ format : 'esm' ,
58+ platform : 'browser' ,
59+ target : 'es2018' ,
60+ sourcemap : ! minify ,
61+ minify,
62+ outdir : path . join ( pageOutDir , 'assets' ) ,
63+ logLevel : 'silent' ,
64+ loader : {
65+ '.svg' : 'file' ,
66+ '.png' : 'file' ,
67+ '.jpg' : 'file' ,
68+ '.jpeg' : 'file' ,
69+ '.gif' : 'file' ,
70+ '.css' : 'css' ,
71+ } ,
72+ entryNames : `[name]-[hash]` ,
73+ chunkNames : `chunk-[hash]` ,
74+ assetNames : `asset-[hash]` ,
75+ define : {
76+ 'process.env.NODE_ENV' : minify ? '"production"' : '"development"' ,
77+ } ,
78+ } )
79+
80+ // Find the main entry file name
81+ const files = await fs . readdir ( path . join ( pageOutDir , 'assets' ) )
82+ const mainJs = files . find ( f => f . startsWith ( `${ baseName } -` ) && f . endsWith ( '.js' ) )
83+ if ( ! mainJs )
84+ throw new Error ( `Failed to locate entry JS for ${ entry } ` )
85+
86+ // Write an index.html that points to the entry
87+ await fs . mkdir ( pageOutDir , { recursive : true } )
88+ await fs . writeFile (
89+ path . join ( pageOutDir , 'index.html' ) ,
90+ htmlTemplate ( {
91+ title : baseName ,
92+ scriptPath : `./assets/${ mainJs } ` ,
93+ } ) ,
94+ 'utf8' ,
95+ )
96+
97+ return { baseName, route }
98+ }
99+
100+ export async function buildWidgets ( projectPath : string , watch = false ) {
101+ console . log ( `🔨 Building UI widgets with esbuild${ watch ? ' (watch mode)' : '' } ...` )
48102
49103 const srcDir = path . join ( projectPath , SRC_DIR )
50104 const outDir = path . join ( projectPath , OUT_DIR )
@@ -56,63 +110,81 @@ export async function buildWidgets(projectPath: string) {
56110 const entries = await globby ( [ `${ srcDir } /**/*.tsx` ] )
57111 console . log ( `📦 Found ${ entries . length } widget files` )
58112
59- // Build each entry as an isolated page with hashed output
60- for ( const entry of entries ) {
61- const relativePath = path . relative ( projectPath , entry )
62- const route = toRoute ( relativePath )
63- const pageOutDir = path . join ( projectPath , outDirForRoute ( route ) )
64- const baseName = path . parse ( entry ) . name
65-
66- console . log ( `🔨 Building ${ baseName } ...` )
67-
68- // Build JS/CSS chunks for this page
69- await build ( {
70- entryPoints : [ entry ] ,
71- bundle : true ,
72- splitting : true ,
73- format : 'esm' ,
74- platform : 'browser' ,
75- target : 'es2018' ,
76- sourcemap : false ,
77- minify : true ,
78- outdir : path . join ( pageOutDir , 'assets' ) ,
79- logLevel : 'silent' ,
80- loader : {
81- '.svg' : 'file' ,
82- '.png' : 'file' ,
83- '.jpg' : 'file' ,
84- '.jpeg' : 'file' ,
85- '.gif' : 'file' ,
86- '.css' : 'css' ,
87- } ,
88- entryNames : `[name]-[hash]` ,
89- chunkNames : `chunk-[hash]` ,
90- assetNames : `asset-[hash]` ,
91- define : {
92- 'process.env.NODE_ENV' : '"production"' ,
93- } ,
94- } )
95-
96- // Find the main entry file name
97- const files = await fs . readdir ( path . join ( pageOutDir , 'assets' ) )
98- const mainJs = files . find ( f => f . startsWith ( `${ baseName } -` ) && f . endsWith ( '.js' ) )
99- if ( ! mainJs )
100- throw new Error ( `Failed to locate entry JS for ${ entry } ` )
101-
102- // Write an index.html that points to the entry
103- await fs . mkdir ( pageOutDir , { recursive : true } )
104- await fs . writeFile (
105- path . join ( pageOutDir , 'index.html' ) ,
106- htmlTemplate ( {
107- title : baseName ,
108- scriptPath : `./assets/${ mainJs } ` ,
109- } ) ,
110- 'utf8' ,
111- )
112-
113- console . log ( `✅ Built ${ baseName } -> ${ route } ` )
113+ if ( watch ) {
114+ // Watch mode
115+ for ( const entry of entries ) {
116+ const relativePath = path . relative ( projectPath , entry )
117+ const route = toRoute ( relativePath )
118+ const pageOutDir = path . join ( projectPath , outDirForRoute ( route ) )
119+ const baseName = path . parse ( entry ) . name
120+
121+ const ctx = await context ( {
122+ entryPoints : [ entry ] ,
123+ bundle : true ,
124+ splitting : true ,
125+ format : 'esm' ,
126+ platform : 'browser' ,
127+ target : 'es2018' ,
128+ sourcemap : true ,
129+ minify : false ,
130+ outdir : path . join ( pageOutDir , 'assets' ) ,
131+ logLevel : 'info' ,
132+ loader : {
133+ '.svg' : 'file' ,
134+ '.png' : 'file' ,
135+ '.jpg' : 'file' ,
136+ '.jpeg' : 'file' ,
137+ '.gif' : 'file' ,
138+ '.css' : 'css' ,
139+ } ,
140+ entryNames : `[name]-[hash]` ,
141+ chunkNames : `chunk-[hash]` ,
142+ assetNames : `asset-[hash]` ,
143+ define : {
144+ 'process.env.NODE_ENV' : '"development"' ,
145+ } ,
146+ plugins : [ {
147+ name : 'html-writer' ,
148+ setup ( build ) {
149+ build . onEnd ( async ( ) => {
150+ try {
151+ const files = await fs . readdir ( path . join ( pageOutDir , 'assets' ) )
152+ const mainJs = files . find ( f => f . startsWith ( `${ baseName } -` ) && f . endsWith ( '.js' ) )
153+ if ( mainJs ) {
154+ await fs . mkdir ( pageOutDir , { recursive : true } )
155+ await fs . writeFile (
156+ path . join ( pageOutDir , 'index.html' ) ,
157+ htmlTemplate ( {
158+ title : baseName ,
159+ scriptPath : `./assets/${ mainJs } ` ,
160+ } ) ,
161+ 'utf8' ,
162+ )
163+ }
164+ } catch ( err ) {
165+ console . error ( `Error writing HTML for ${ baseName } :` , err )
166+ }
167+ } )
168+ } ,
169+ } ] ,
170+ } )
171+
172+ await ctx . watch ( )
173+ console . log ( `👀 Watching ${ baseName } ...` )
174+ }
175+
176+ console . log ( '👀 Watching for changes... Press Ctrl+C to stop.' )
177+ }
178+ else {
179+ // Build once
180+ for ( const entry of entries ) {
181+ console . log ( `🔨 Building ${ path . parse ( entry ) . name } ...` )
182+ const { baseName, route } = await buildWidget ( entry , projectPath )
183+ console . log ( `✅ Built ${ baseName } -> ${ route } ` )
184+ }
185+
186+ console . log ( '🎉 Build complete!' )
114187 }
115-
116- console . log ( '🎉 Build complete!' )
117188}
118189
190+
0 commit comments