@@ -158,6 +158,7 @@ module.exports = function (api) {
158158
159159 convertESM ? "@babel/proposal-export-namespace-from" : null ,
160160 convertESM ? "@babel/transform-modules-commonjs" : null ,
161+ convertESM ? pluginNodeImportInterop : null ,
161162
162163 pluginPackageJsonMacro ,
163164
@@ -413,3 +414,54 @@ function pluginPackageJsonMacro({ types: t }) {
413414 } ,
414415 } ;
415416}
417+
418+ // Match the Node.js behavior (the default import is module.exports)
419+ function pluginNodeImportInterop ( { template } ) {
420+ return {
421+ visitor : {
422+ ImportDeclaration ( path ) {
423+ const specifiers = path . get ( "specifiers" ) ;
424+ if ( specifiers . length === 0 ) {
425+ return ;
426+ }
427+
428+ const { source } = path . node ;
429+ if (
430+ source . value . startsWith ( "." ) ||
431+ source . value . startsWith ( "@babel/" ) ||
432+ source . value === "charcodes"
433+ ) {
434+ // For internal modules, it's either "all CJS" or "all ESM".
435+ // We don't need to worry about interop.
436+ return ;
437+ }
438+
439+ const defImport = specifiers . find ( s => s . isImportDefaultSpecifier ( ) ) ;
440+ const nsImport = specifiers . find ( s => s . isImportNamespaceSpecifier ( ) ) ;
441+
442+ if ( defImport ) {
443+ path . insertAfter (
444+ template . ast `
445+ const ${ defImport . node . local } = require(${ source } );
446+ `
447+ ) ;
448+ defImport . remove ( ) ;
449+ }
450+
451+ if ( nsImport ) {
452+ path . insertAfter (
453+ template . ast `
454+ const ${ nsImport . node . local } = {
455+ ...require(${ source } ),
456+ default: require(${ source } ),
457+ };
458+ `
459+ ) ;
460+ nsImport . remove ( ) ;
461+ }
462+
463+ if ( path . node . specifiers . length === 0 ) path . remove ( ) ;
464+ } ,
465+ } ,
466+ } ;
467+ }
0 commit comments