@@ -135,9 +135,9 @@ export default function nativeFilePlugin(
135135 // Reverse mapping from hashed filename to original file path
136136 // Used to resolve transformed bindings/node-gyp-build calls
137137 const hashedFilenameToPath = new Map < string , string > ( ) ;
138- // Track module type (ES module vs CommonJS) for virtual modules
139- // Maps virtual module ID to whether it's an ES module
140- const virtualModuleTypes = new Map < string , boolean > ( ) ;
138+ // Track the output format from Vite config
139+ // This determines whether we generate ESM or CJS code in the load hook
140+ let outputFormat : "es" | "cjs" = "es" ; // Default to ESM (Vite's default)
141141 let command : "build" | "serve" = "build" ;
142142
143143 // Helper function to detect if a file is an ES module based on extension and content
@@ -581,6 +581,35 @@ export default function nativeFilePlugin(
581581 return {
582582 configResolved ( config ) {
583583 command = config . command ;
584+
585+ // Detect output format from Vite config
586+ // Priority: rollupOptions.output.format > lib.formats > default (es)
587+ //
588+ // LIMITATION: For multi-format builds (e.g., lib.formats: ['es', 'cjs']), this
589+ // only uses the first format. Rollup's load hook is called once per module, not
590+ // per output format. In practice, the ESM pattern (createRequire + import.meta.url)
591+ // works correctly in both ESM and CJS outputs because Rollup handles the conversion.
592+ const rollupOutput = config . build ?. rollupOptions ?. output ;
593+ if ( rollupOutput ) {
594+ // rollupOptions.output can be an object or array of objects
595+ const format = Array . isArray ( rollupOutput )
596+ ? rollupOutput [ 0 ] ?. format
597+ : rollupOutput . format ;
598+ if ( format === "cjs" || format === "commonjs" ) {
599+ outputFormat = "cjs" ;
600+ } else {
601+ outputFormat = "es" ;
602+ }
603+ } else if ( config . build ?. lib ) {
604+ // lib mode - use first format
605+ // lib can be false or LibraryOptions, check for formats property
606+ const lib = config . build . lib ;
607+ if ( typeof lib === "object" && lib . formats ) {
608+ const formats = lib . formats ;
609+ outputFormat = formats [ 0 ] === "cjs" ? "cjs" : "es" ;
610+ }
611+ }
612+ // Otherwise keep default 'es' (Vite's default for modern builds)
584613 } ,
585614
586615 generateBundle ( ) {
@@ -602,53 +631,23 @@ export default function nativeFilePlugin(
602631
603632 if ( ! info ) return null ;
604633
605- // Check if this virtual module is being loaded in an ES module context
606- // Try to get the tracked module type first
607- let isESModule = virtualModuleTypes . get ( id ) ;
608-
609- // If not tracked, try to detect from the original path or use getModuleInfo if available
610- if ( isESModule === undefined ) {
611- // Try to detect from file extension as fallback
612- isESModule = detectModuleType ( originalPath ) ;
613-
614- // If getModuleInfo is available, try to use it (though it may not work for virtual modules)
615- try {
616- if ( typeof this . getModuleInfo === "function" ) {
617- const moduleInfo = this . getModuleInfo ( id ) ;
618- const format = ( moduleInfo as { format ?: string } ) . format ;
619- if ( moduleInfo && format ) {
620- isESModule = format === "es" ;
621- }
622- }
623- } catch {
624- // Ignore errors, use fallback detection
625- }
626-
627- // If still undefined, default to CommonJS (safer than ES module)
628- // This prevents mixing require() with import.meta.url
629- if ( isESModule === undefined ) {
630- isESModule = false ;
631- }
632- }
633-
634- // Return proxy code that requires the hashed file
635- // The hashed file will be in the same directory as the output bundle
636- //
637- // We use syntheticNamedExports (set in resolveId) to tell Rollup to resolve
638- // any named export requests from the default export's properties.
639- // This way, `const { databaseOpen } = require(...)` works correctly
640- // because Rollup gets databaseOpen from default.databaseOpen.
641- if ( isESModule ) {
634+ // Generate code based on OUTPUT format, not the importer's format.
635+ // This is important because:
636+ // 1. Using CJS require() in an ESM output causes "Cannot determine intended
637+ // module format because both require() and top-level await are present"
638+ // 2. Using import.meta.url in a CJS output doesn't work
639+ // 3. The output format is what matters for the final bundled code
640+ if ( outputFormat === "es" ) {
642641 return `
643- import { createRequire } from 'node:module';
644- const createRequireLocal = createRequire(import.meta.url);
645- const nativeModule = createRequireLocal ('./${ info . hashedFilename } ');
646- export default nativeModule;
647- `;
642+ import { createRequire } from 'node:module';
643+ const __require = createRequire(import.meta.url);
644+ const nativeModule = __require ('./${ info . hashedFilename } ');
645+ export default nativeModule;
646+ ` ;
648647 } else {
649648 return `
650- module.exports = require('./${ info . hashedFilename } ');
651- `;
649+ module.exports = require('./${ info . hashedFilename } ');
650+ ` ;
652651 }
653652 } ,
654653
@@ -673,13 +672,15 @@ export default function nativeFilePlugin(
673672 // Check if this matches a hashed filename we've generated
674673 if ( hashedFilenameToPath . has ( basename ) ) {
675674 const originalPath = hashedFilenameToPath . get ( basename ) ! ;
676- const importingModuleType = detectModuleTypeWithContext ( this , importer ) ;
677675 const virtualId = `\0native:${ originalPath } ` ;
678- virtualModuleTypes . set ( virtualId , importingModuleType ) ;
679- // Return virtual module ID with syntheticNamedExports enabled
680- // This tells Rollup to resolve named exports from the default export's properties,
681- // which fixes the getAugmentedNamespace issue where destructuring fails
682- // because properties like databaseOpen aren't copied to the namespace.
676+
677+ // Use syntheticNamedExports to enable named import/destructuring patterns
678+ // like `const { databaseOpen } = require('native-module')` or `import { foo } from 'native'`.
679+ // This tells Rollup to derive named exports from the default export's properties.
680+ //
681+ // Note: This is incompatible with `export * from 'native-module'` patterns because
682+ // Rollup cannot enumerate synthetic exports at bundle time. If you encounter errors
683+ // with export * re-exports, consider restructuring to use named imports instead.
683684 return {
684685 id : virtualId ,
685686 syntheticNamedExports : true ,
@@ -698,11 +699,8 @@ export default function nativeFilePlugin(
698699 // Register the native file (generates hash, stores mapping)
699700 registerNativeFile ( resolved ) ;
700701
701- // Track module type and return virtual module ID
702- const importingModuleType = detectModuleTypeWithContext ( this , importer ) ;
702+ // Return virtual module ID
703703 const virtualId = `\0native:${ resolved } ` ;
704- virtualModuleTypes . set ( virtualId , importingModuleType ) ;
705-
706704 return virtualId ;
707705 } ,
708706
0 commit comments