@@ -94,6 +94,9 @@ export class BundlerCompilerService
9494 prepareData ,
9595 ) ;
9696
97+ // Handle Vite differently from webpack
98+ const isVite = this . getBundler ( ) === "vite" ;
99+
97100 childProcess . stdout . on ( "data" , function ( data ) {
98101 process . stdout . write ( data ) ;
99102 } ) ;
@@ -102,9 +105,124 @@ export class BundlerCompilerService
102105 process . stderr . write ( data ) ;
103106 } ) ;
104107
108+ // For both Vite and webpack, we wait for the first build to complete
109+ // Don't resolve immediately for Vite - wait for first IPC message
110+
105111 childProcess . on ( "message" , ( message : string | IBundlerEmitMessage ) => {
106112 this . $logger . trace ( `Message from ${ projectData . bundler } ` , message ) ;
107113
114+ // Handle Vite messages
115+ if (
116+ isVite &&
117+ message &&
118+ ( message as IBundlerEmitMessage ) . emittedFiles
119+ ) {
120+ message = message as IBundlerEmitMessage ;
121+ console . log ( "Received Vite IPC message:" , message ) ;
122+
123+ // Copy Vite output files directly to platform destination
124+ const distOutput = path . join ( projectData . projectDir , "dist" ) ;
125+ const destDir = path . join (
126+ platformData . appDestinationDirectoryPath ,
127+ this . $options . hostProjectModuleName ,
128+ ) ;
129+
130+ console . log ( `🔥 Copying from ${ distOutput } to ${ destDir } ` ) ;
131+
132+ // For HMR updates, only copy changed files; for full builds, copy everything
133+ if (
134+ message . isHMR &&
135+ message . changedFiles &&
136+ message . changedFiles . length > 0
137+ ) {
138+ console . log (
139+ "🔥 HMR update - copying only changed files for:" ,
140+ message . changedFiles ,
141+ ) ;
142+
143+ // For HTML template changes, we need to copy the component files that were rebuilt
144+ let filesToCopy = message . emittedFiles ;
145+
146+ // If we have HTML changes, identify which component files need copying
147+ const hasHTMLChanges = message . changedFiles . some ( ( f ) =>
148+ f . endsWith ( ".html" ) ,
149+ ) ;
150+ if ( hasHTMLChanges ) {
151+ // Copy component-related files (the ones that would have been rebuilt due to template changes)
152+ filesToCopy = message . emittedFiles . filter (
153+ ( f ) =>
154+ f . includes ( ".component" ) ||
155+ f === "bundle.mjs" ||
156+ f === "bundle.mjs.map" ,
157+ ) ;
158+ console . log (
159+ "🔥 HTML change detected - copying component files:" ,
160+ filesToCopy ,
161+ ) ;
162+ }
163+
164+ this . copyViteBundleToNative ( distOutput , destDir , filesToCopy ) ;
165+ } else {
166+ console . log ( "🔥 Full build - copying all files" ) ;
167+ this . copyViteBundleToNative ( distOutput , destDir ) ;
168+ }
169+
170+ // Resolve the promise on first build completion
171+ if ( isFirstBundlerWatchCompilation ) {
172+ isFirstBundlerWatchCompilation = false ;
173+ console . log (
174+ "Vite first build completed, resolving compileWithWatch" ,
175+ ) ;
176+ resolve ( childProcess ) ;
177+ }
178+
179+ // Transform Vite message to match webpack format
180+ const files = ( message as IBundlerEmitMessage ) . emittedFiles . map (
181+ ( file ) =>
182+ path . join (
183+ platformData . appDestinationDirectoryPath ,
184+ this . $options . hostProjectModuleName ,
185+ file ,
186+ ) ,
187+ ) ;
188+
189+ const data = {
190+ files,
191+ hasOnlyHotUpdateFiles : message . isHMR || false ,
192+ hmrData : {
193+ hash : ( message as IBundlerEmitMessage ) . hash || "" ,
194+ fallbackFiles : [ ] as string [ ] ,
195+ } ,
196+ platform : platformData . platformNameLowerCase ,
197+ } ;
198+
199+ this . $logger . info (
200+ `Vite build completed! Files copied to native platform.` ,
201+ ) ;
202+ // Send HMR notification to connected WebSocket clients first
203+ this . notifyHMRClients ( {
204+ type : message . isHMR ? "js-update" : "build-complete" ,
205+ timestamp : Date . now ( ) ,
206+ changedFiles : message . changedFiles || [ ] ,
207+ buildType : message . buildType || "incremental" ,
208+ isHMR : message . isHMR || false ,
209+ } ) ;
210+
211+ if ( message . isHMR ) {
212+ console . log (
213+ "🔥 Skipping BUNDLER_COMPILATION_COMPLETE for HMR update - app will not restart" ,
214+ ) ;
215+ } else {
216+ // Only emit BUNDLER_COMPILATION_COMPLETE for non-HMR builds
217+ // This prevents the CLI from restarting the app during HMR updates
218+ console . log (
219+ "🔥 Emitting BUNDLER_COMPILATION_COMPLETE for full build" ,
220+ ) ;
221+ this . emit ( BUNDLER_COMPILATION_COMPLETE , data ) ;
222+ }
223+ return ;
224+ }
225+
108226 // if we are on webpack5 - we handle HMR in a slightly different way
109227 if (
110228 typeof message === "object" &&
@@ -228,23 +346,6 @@ export class BundlerCompilerService
228346 this . $logger . trace (
229347 `${ capitalizeFirstLetter ( projectData . bundler ) } process exited with code ${ exitCode } when we expected it to be long living with watch.` ,
230348 ) ;
231- if ( this . getBundler ( ) === "vite" && exitCode === 0 ) {
232- // note experimental: investigate watch mode
233- const bundlePath = path . join (
234- projectData . projectDir ,
235- "dist/bundle.js" ,
236- ) ;
237- console . log ( "bundlePath:" , bundlePath ) ;
238- const data = {
239- files : [ bundlePath ] ,
240- hasOnlyHotUpdateFiles : false ,
241- hmrData : { } ,
242- platform : platformData . platformNameLowerCase ,
243- } ;
244- this . emit ( BUNDLER_COMPILATION_COMPLETE , data ) ;
245- resolve ( 1 ) ;
246- return ;
247- }
248349
249350 await this . $cleanupService . removeKillProcess (
250351 childProcess . pid . toString ( ) ,
@@ -367,7 +468,12 @@ export class BundlerCompilerService
367468 ) ;
368469 // Note: With Vite, we need `--` to prevent vite cli from erroring on unknown options.
369470 const envParams = isVite
370- ? [ `--mode=${ platformData . platformNameLowerCase } ` , "--" , ...cliArgs ]
471+ ? [
472+ `--mode=${ platformData . platformNameLowerCase } ` ,
473+ `--watch` ,
474+ "--" ,
475+ ...cliArgs ,
476+ ]
371477 : cliArgs ;
372478 const additionalNodeArgs =
373479 semver . major ( process . version ) <= 8 ? [ "--harmony" ] : [ ] ;
@@ -383,7 +489,7 @@ export class BundlerCompilerService
383489 const args = [
384490 ...additionalNodeArgs ,
385491 this . getBundlerExecutablePath ( projectData ) ,
386- this . isModernBundler ( projectData ) ? `build` : null ,
492+ isVite ? "build" : this . isModernBundler ( projectData ) ? `build` : null ,
387493 `--config=${ projectData . bundlerConfigPath } ` ,
388494 ...envParams ,
389495 ] . filter ( Boolean ) ;
@@ -394,7 +500,7 @@ export class BundlerCompilerService
394500 }
395501 }
396502
397- const stdio = prepareData . watch ? [ "ipc" ] : "inherit" ;
503+ const stdio = prepareData . watch || isVite ? [ "ipc" ] : "inherit" ;
398504 const options : { [ key : string ] : any } = {
399505 cwd : projectData . projectDir ,
400506 stdio,
@@ -753,6 +859,100 @@ export class BundlerCompilerService
753859 public getBundler ( ) : BundlerType {
754860 return this . $projectConfigService . getValue ( `bundler` , "webpack" ) ;
755861 }
862+
863+ private copyViteBundleToNative (
864+ distOutput : string ,
865+ destDir : string ,
866+ specificFiles : string [ ] = null ,
867+ ) {
868+ // Clean and copy Vite output to native platform folder
869+ console . log ( `Copying Vite bundle from "${ distOutput } " to "${ destDir } "` ) ;
870+
871+ const fs = require ( "fs" ) ;
872+
873+ try {
874+ if ( specificFiles ) {
875+ // HMR mode: only copy specific files
876+ console . log ( "🔥 HMR: Copying specific files:" , specificFiles ) ;
877+
878+ // Ensure destination directory exists
879+ fs . mkdirSync ( destDir , { recursive : true } ) ;
880+
881+ // Copy only the specified files
882+ for ( const file of specificFiles ) {
883+ const srcPath = path . join ( distOutput , file ) ;
884+ const destPath = path . join ( destDir , file ) ;
885+
886+ if ( ! fs . existsSync ( srcPath ) ) continue ;
887+
888+ // create parent dirs
889+ fs . mkdirSync ( path . dirname ( destPath ) , { recursive : true } ) ;
890+
891+ fs . copyFileSync ( srcPath , destPath ) ;
892+
893+ console . log ( `🔥 HMR: Copied ${ file } ` ) ;
894+ }
895+ } else {
896+ // Full build mode: clean and copy everything
897+ console . log ( "🔥 Full build: Copying all files" ) ;
898+
899+ // Clean destination directory
900+ if ( fs . existsSync ( destDir ) ) {
901+ fs . rmSync ( destDir , { recursive : true , force : true } ) ;
902+ }
903+ fs . mkdirSync ( destDir , { recursive : true } ) ;
904+
905+ // Copy all files from dist to platform destination
906+ if ( fs . existsSync ( distOutput ) ) {
907+ this . copyRecursiveSync ( distOutput , destDir , fs ) ;
908+ } else {
909+ this . $logger . warn (
910+ `Vite output directory does not exist: ${ distOutput } ` ,
911+ ) ;
912+ }
913+ }
914+ } catch ( error ) {
915+ this . $logger . warn ( `Failed to copy Vite bundle: ${ error . message } ` ) ;
916+ }
917+ }
918+
919+ private notifyHMRClients ( message : any ) {
920+ // Send WebSocket notification to HMR clients
921+ try {
922+ const WebSocket = require ( "ws" ) ;
923+
924+ // Try to connect to HMR bridge and send notification
925+ const ws = new WebSocket ( "ws://localhost:24678" ) ;
926+
927+ ws . on ( "open" , ( ) => {
928+ console . log ( "🔥 Sending HMR notification to bridge:" , message . type ) ;
929+ ws . send ( JSON . stringify ( message ) ) ;
930+ ws . close ( ) ;
931+ } ) ;
932+
933+ ws . on ( "error" , ( ) => {
934+ // HMR bridge not available, which is fine
935+ console . log ( "🔥 HMR bridge not available (this is normal without HMR)" ) ;
936+ } ) ;
937+ } catch ( error ) {
938+ // WebSocket not available, which is fine
939+ console . log ( "🔥 WebSocket not available for HMR notifications" ) ;
940+ }
941+ }
942+
943+ private copyRecursiveSync ( src : string , dest : string , fs : any ) {
944+ for ( const entry of fs . readdirSync ( src , { withFileTypes : true } ) ) {
945+ const srcPath = path . join ( src , entry . name ) ;
946+ const destPath = path . join ( dest , entry . name ) ;
947+
948+ if ( entry . isDirectory ( ) ) {
949+ fs . mkdirSync ( destPath , { recursive : true } ) ;
950+ this . copyRecursiveSync ( srcPath , destPath , fs ) ;
951+ } else if ( entry . isFile ( ) || entry . isSymbolicLink ( ) ) {
952+ fs . copyFileSync ( srcPath , destPath ) ;
953+ }
954+ }
955+ }
756956}
757957
758958function capitalizeFirstLetter ( val : string ) {
0 commit comments