@@ -367,6 +367,7 @@ export class DenoBundlerBackend implements Bundler {
367367
368368 /**
369369 * Find which chunks an entrypoint depends on by analyzing proxy files.
370+ * Also searches all chunks to find where the component is actually exported.
370371 */
371372 private async findEntrypointChunks (
372373 entrypointPath : string ,
@@ -379,28 +380,103 @@ export class DenoBundlerBackend implements Bundler {
379380 const relativePath = entrypointPath . replace ( / \. t s x ? $ / , ".js" ) ;
380381 const proxyFilePath = posix . join ( scanDir , relativePath ) ;
381382
383+ // Try to read proxy file
384+ let proxyContent : string | null = null ;
382385 try {
383- const content = await runtime . fs . readTextFile ( proxyFilePath ) ;
384- return this . extractChunksFromProxyFile ( content , outputs ) ;
386+ proxyContent = await runtime . fs . readTextFile ( proxyFilePath ) ;
385387 } catch {
386388 // Try in the nested directory structure
387389 try {
388390 const nestedProxyPath = posix . join ( outputDir , "dist" , relativePath ) ;
389- const content = await runtime . fs . readTextFile ( nestedProxyPath ) ;
390- return this . extractChunksFromProxyFile ( content , outputs ) ;
391+ proxyContent = await runtime . fs . readTextFile ( nestedProxyPath ) ;
391392 } catch {
392- // No proxy file found - this might be the main entry
393- return [ ] ;
393+ // No proxy file found
394394 }
395395 }
396+
397+ // If we have a proxy file, use it
398+ if ( proxyContent !== null ) {
399+ return this . extractChunksFromProxyFile ( proxyContent , outputs ) ;
400+ }
401+
402+ // No proxy file - search all chunks for the component export
403+ // This handles cases where Deno.bundle doesn't create proxy files
404+ return this . findChunksForComponentName ( entrypointPath , outputs ) ;
405+ }
406+
407+ /**
408+ * Find chunks containing a component by searching all chunk exports.
409+ * Used when no proxy file exists for an entrypoint.
410+ */
411+ private findChunksForComponentName (
412+ entrypointPath : string ,
413+ outputs : Map < string , BundleOutput > ,
414+ ) : string [ ] {
415+ const chunks : string [ ] = [ ] ;
416+
417+ // Extract expected export name from file path
418+ // e.g., "src/app/icon.tsx" -> "Icon" (PascalCase of basename)
419+ const basename = posix . basename ( entrypointPath ) . replace ( / \. [ ^ . ] + $ / , "" ) ;
420+ const expectedExport = basename . charAt ( 0 ) . toUpperCase ( ) +
421+ basename . slice ( 1 ) . replace ( / - ( [ a - z ] ) / g, ( _ , c ) => c . toUpperCase ( ) ) ;
422+
423+ // Search all chunks for the exported component
424+ for ( const [ chunkFile , chunkOutput ] of outputs ) {
425+ // Only check chunk files
426+ if ( ! chunkFile . startsWith ( "chunk-" ) || ! chunkFile . endsWith ( ".js" ) ) {
427+ continue ;
428+ }
429+
430+ const chunkContent = new TextDecoder ( ) . decode ( chunkOutput . code ) ;
431+
432+ // Check if this chunk exports the expected symbol
433+ const exportPatterns = [
434+ // export { Symbol } or export { Symbol, ... } or export { X as Symbol }
435+ new RegExp ( `export\\s*\\{[^}]*\\b${ expectedExport } \\b[^}]*\\}` ) ,
436+ // export function Symbol or export const Symbol
437+ new RegExp (
438+ `export\\s+(?:function|const|let|var|class)\\s+${ expectedExport } \\b` ,
439+ ) ,
440+ // minified pattern: Symbol2 as Symbol
441+ new RegExp ( `\\b\\w+\\s+as\\s+${ expectedExport } \\b` ) ,
442+ ] ;
443+
444+ const exportsSymbol = exportPatterns . some ( ( pattern ) =>
445+ pattern . test ( chunkContent )
446+ ) ;
447+
448+ if ( exportsSymbol ) {
449+ // Found the main chunk - add it to the front
450+ chunks . unshift ( chunkFile ) ;
451+
452+ // Also find dependency chunks by looking at what this chunk imports
453+ const importPattern =
454+ / f r o m \s * [ " ' ] \. ? \/ ? ( [ ^ " ' ] * c h u n k - [ A - Z 0 - 9 ] + \. j s ) [ " ' ] / gi;
455+ let match : RegExpExecArray | null ;
456+ while ( ( match = importPattern . exec ( chunkContent ) ) !== null ) {
457+ const depChunk = posix . basename ( match [ 1 ] ?? "" ) ;
458+ if ( depChunk && ! chunks . includes ( depChunk ) ) {
459+ chunks . push ( depChunk ) ;
460+ }
461+ }
462+ break ;
463+ }
464+ }
465+
466+ return chunks ;
396467 }
397468
398469 /**
399470 * Extract chunk dependencies from a proxy file's import statements.
471+ * Returns chunks with the main chunk (containing the exported symbol) first.
472+ *
473+ * IMPORTANT: Due to code splitting, the proxy file may not import from the chunk
474+ * that actually contains the exported component. We must search ALL chunks to find
475+ * the one that exports the symbol.
400476 */
401477 private extractChunksFromProxyFile (
402478 content : string ,
403- _outputs : Map < string , BundleOutput > ,
479+ outputs : Map < string , BundleOutput > ,
404480 ) : string [ ] {
405481 const chunks : string [ ] = [ ] ;
406482
@@ -411,7 +487,7 @@ export class DenoBundlerBackend implements Bundler {
411487
412488 let match : RegExpExecArray | null ;
413489
414- // Find all chunk imports
490+ // Find all chunk imports from proxy file
415491 while ( ( match = chunkImportPattern . exec ( content ) ) !== null ) {
416492 const importPath = match [ 1 ] ;
417493 if ( importPath !== undefined ) {
@@ -433,32 +509,64 @@ export class DenoBundlerBackend implements Bundler {
433509 }
434510 }
435511
436- // Determine main chunk (the one that exports the component)
512+ // Determine main chunk by finding which chunk actually exports the symbol
513+ // IMPORTANT: Search ALL chunks in the bundle, not just the ones imported by proxy
437514 const exportMatch = content . match ( / e x p o r t \s * \{ ( [ ^ } ] + ) \} / ) ;
438- if ( exportMatch !== null && chunks . length > 0 ) {
515+ if ( exportMatch !== null ) {
439516 const exportStatement = exportMatch [ 1 ] ;
440- const symbolMatch = exportStatement ?. match ( / ( \w + ) \s + a s \s + / ) ;
441- const exportedSymbol = symbolMatch !== null && symbolMatch !== undefined
442- ? symbolMatch [ 1 ]
443- : null ;
517+ // Match either "Symbol as ExportName" or just "Symbol"
518+ const symbolMatch = exportStatement ?. match ( / ( \w + ) (?: \s + a s \s + \w + ) ? / ) ;
519+ const exportedSymbol = symbolMatch ?. [ 1 ] ?? null ;
444520
445521 if ( exportedSymbol !== null ) {
446- // Find which chunk exports this symbol
447- for ( const chunkFile of chunks ) {
448- const chunkName = chunkFile . replace ( / \. j s $ / , "" ) ;
449- const importPattern = new RegExp (
450- `import\\s*\\{[^}]*\\b${ exportedSymbol } \\b[^}]*\\}\\s*from\\s*["'][^"']*${ chunkName } ` ,
522+ // Search ALL chunks in the bundle to find which one exports the symbol
523+ // This handles code splitting where the actual component ends up in a shared chunk
524+ let mainChunkFile : string | null = null ;
525+
526+ for ( const [ chunkFile , chunkOutput ] of outputs ) {
527+ // Only check chunk files (not main entry or other files)
528+ if ( ! chunkFile . startsWith ( "chunk-" ) || ! chunkFile . endsWith ( ".js" ) ) {
529+ continue ;
530+ }
531+
532+ const chunkContent = new TextDecoder ( ) . decode ( chunkOutput . code ) ;
533+
534+ // Check if this chunk exports the symbol directly
535+ const exportPatterns = [
536+ // export { Symbol } or export { Symbol, ... } or export { X as Symbol }
537+ new RegExp ( `export\\s*\\{[^}]*\\b${ exportedSymbol } \\b[^}]*\\}` ) ,
538+ // export function Symbol or export const Symbol
539+ new RegExp (
540+ `export\\s+(?:function|const|let|var|class)\\s+${ exportedSymbol } \\b` ,
541+ ) ,
542+ // minified pattern: Symbol2 as Symbol (common in bundled code)
543+ new RegExp ( `\\b\\w+\\s+as\\s+${ exportedSymbol } \\b` ) ,
544+ ] ;
545+
546+ const exportsSymbol = exportPatterns . some ( ( pattern ) =>
547+ pattern . test ( chunkContent )
451548 ) ;
452- if ( importPattern . test ( content ) ) {
453- // Move main chunk to front
454- const mainIndex = chunks . indexOf ( chunkFile ) ;
455- if ( mainIndex > 0 ) {
456- chunks . splice ( mainIndex , 1 ) ;
457- chunks . unshift ( chunkFile ) ;
458- }
549+
550+ if ( exportsSymbol ) {
551+ mainChunkFile = chunkFile ;
459552 break ;
460553 }
461554 }
555+
556+ if ( mainChunkFile !== null ) {
557+ // Ensure main chunk is in the list and at the front
558+ const mainIndex = chunks . indexOf ( mainChunkFile ) ;
559+ if ( mainIndex > 0 ) {
560+ // Already in list but not first - move to front
561+ chunks . splice ( mainIndex , 1 ) ;
562+ chunks . unshift ( mainChunkFile ) ;
563+ } else if ( mainIndex === - 1 ) {
564+ // Not in list from proxy imports - add to front
565+ // This happens when code splitting puts the component in a shared chunk
566+ chunks . unshift ( mainChunkFile ) ;
567+ }
568+ // mainIndex === 0 means already first, no change needed
569+ }
462570 }
463571 }
464572
0 commit comments