@@ -29,6 +29,8 @@ const exportAssignment = (
2929
3030const defaultInteropName = '__interopDefault'
3131const interopHelper = `const ${ defaultInteropName } = mod => (mod && mod.__esModule ? mod.default : mod);\n`
32+ const requireInteropName = '__requireDefault'
33+ const requireInteropHelper = `const ${ requireInteropName } = mod => (mod && typeof mod === 'object' && 'default' in mod ? mod.default : mod);\n`
3234
3335const isRequireCallee = ( callee : any , shadowed : Set < string > ) => {
3436 if (
@@ -76,8 +78,15 @@ const lowerCjsRequireToImports = (
7678) => {
7779 const transforms : RequireTransform [ ] = [ ]
7880 const imports : string [ ] = [ ]
81+ const hoisted : string [ ] = [ ]
7982 let nsIndex = 0
8083 let needsCreateRequire = false
84+ let needsInteropHelper = false
85+
86+ const isJsonSpecifier = ( value : string ) => {
87+ const base = value . split ( / [ ? # ] / ) [ 0 ] ?? value
88+ return base . endsWith ( '.json' )
89+ }
8190
8291 for ( const stmt of program . body as any [ ] ) {
8392 if ( stmt . type === 'VariableDeclaration' ) {
@@ -89,15 +98,43 @@ const lowerCjsRequireToImports = (
8998 if ( allStatic ) {
9099 for ( const decl of decls ) {
91100 const init = decl . init !
92- const source = code . slice ( init . arguments [ 0 ] . start , init . arguments [ 0 ] . end )
101+ const arg = init . arguments [ 0 ]
102+ const source = code . slice ( arg . start , arg . end )
103+ const value = ( arg as any ) . value as string
104+ const isJson = typeof value === 'string' && isJsonSpecifier ( value )
105+
106+ const ns = `__cjsImport${ nsIndex ++ } `
107+
108+ const jsonImport = isJson ? `${ source } with { type: "json" }` : source
93109
94110 if ( decl . id . type === 'Identifier' ) {
95- imports . push ( `import * as ${ decl . id . name } from ${ source } ;\n` )
96- } else if ( decl . id . type === 'ObjectPattern' ) {
97- const ns = `__cjsImport${ nsIndex ++ } `
111+ imports . push (
112+ isJson
113+ ? `import ${ ns } from ${ jsonImport } ;\n`
114+ : `import * as ${ ns } from ${ jsonImport } ;\n` ,
115+ )
116+ hoisted . push (
117+ isJson
118+ ? `const ${ decl . id . name } = ${ ns } ;\n`
119+ : `const ${ decl . id . name } = ${ requireInteropName } (${ ns } );\n` ,
120+ )
121+ needsInteropHelper ||= ! isJson
122+ } else if (
123+ decl . id . type === 'ObjectPattern' ||
124+ decl . id . type === 'ArrayPattern'
125+ ) {
98126 const pattern = code . slice ( decl . id . start , decl . id . end )
99- imports . push ( `import * as ${ ns } from ${ source } ;\n` )
100- imports . push ( `const ${ pattern } = ${ ns } ;\n` )
127+ imports . push (
128+ isJson
129+ ? `import ${ ns } from ${ jsonImport } ;\n`
130+ : `import * as ${ ns } from ${ jsonImport } ;\n` ,
131+ )
132+ hoisted . push (
133+ isJson
134+ ? `const ${ pattern } = ${ ns } ;\n`
135+ : `const ${ pattern } = ${ requireInteropName } (${ ns } );\n` ,
136+ )
137+ needsInteropHelper ||= ! isJson
101138 } else {
102139 needsCreateRequire = true
103140 }
@@ -119,8 +156,14 @@ const lowerCjsRequireToImports = (
119156 const expr = stmt . expression
120157
121158 if ( expr && isStaticRequire ( expr , shadowed ) ) {
122- const source = code . slice ( expr . arguments [ 0 ] . start , expr . arguments [ 0 ] . end )
123- imports . push ( `import ${ source } ;\n` )
159+ const arg = expr . arguments [ 0 ]
160+ const source = code . slice ( arg . start , arg . end )
161+ const value = ( arg as any ) . value as string
162+ const isJson = typeof value === 'string' && isJsonSpecifier ( value )
163+
164+ const jsonImport = isJson ? `${ source } with { type: "json" }` : source
165+
166+ imports . push ( `import ${ jsonImport } ;\n` )
124167 transforms . push ( { start : stmt . start , end : stmt . end , code : ';\n' } )
125168 continue
126169 }
@@ -131,7 +174,7 @@ const lowerCjsRequireToImports = (
131174 }
132175 }
133176
134- return { transforms, imports, needsCreateRequire }
177+ return { transforms, imports, hoisted , needsCreateRequire, needsInteropHelper }
135178}
136179
137180const isRequireMainMember = ( node : any , shadowed : Set < string > ) =>
@@ -187,6 +230,26 @@ const hasTopLevelAwait = (program: any) => {
187230 return found
188231}
189232
233+ const isAsyncContext = ( ancestors : any [ ] ) => {
234+ for ( let i = ancestors . length - 1 ; i >= 0 ; i -= 1 ) {
235+ const node = ancestors [ i ]
236+ if (
237+ node . type === 'FunctionDeclaration' ||
238+ node . type === 'FunctionExpression' ||
239+ node . type === 'ArrowFunctionExpression'
240+ ) {
241+ return ! ! node . async
242+ }
243+
244+ if ( node . type === 'ClassDeclaration' || node . type === 'ClassExpression' ) {
245+ return false
246+ }
247+ }
248+
249+ // Program scope (top-level) supports await in ESM.
250+ return true
251+ }
252+
190253const lowerEsmToCjs = (
191254 program : any ,
192255 code : MagicString ,
@@ -454,12 +517,18 @@ const format = async (src: string, ast: ParseResult, opts: FormatterOptions) =>
454517 const containsTopLevelAwait = shouldCheckTopLevelAwait
455518 ? hasTopLevelAwait ( ast . program )
456519 : false
520+ const requireMainStrategy = opts . requireMainStrategy ?? 'import-meta-main'
521+ let requireMainNeedsRealpath = false
522+ let needsRequireResolveHelper = false
523+ const nestedRequireStrategy = opts . nestedRequireStrategy ?? 'create-require'
457524
458525 const shouldLowerCjs = opts . target === 'commonjs' && opts . transformSyntax
459526 const shouldRaiseEsm = opts . target === 'module' && opts . transformSyntax
460527 let hoistedImports : string [ ] = [ ]
528+ let hoistedStatements : string [ ] = [ ]
461529 let pendingRequireTransforms : RequireTransform [ ] = [ ]
462530 let needsCreateRequire = false
531+ let needsImportInterop = false
463532 let pendingCjsTransforms : {
464533 transforms : Array < ImportTransform | ExportTransform >
465534 needsInterop : boolean
@@ -475,12 +544,16 @@ const format = async (src: string, ast: ParseResult, opts: FormatterOptions) =>
475544 const {
476545 transforms,
477546 imports,
547+ hoisted,
478548 needsCreateRequire : reqCreate ,
549+ needsInteropHelper : reqInteropHelper ,
479550 } = lowerCjsRequireToImports ( ast . program , code , shadowedBindings )
480551
481552 pendingRequireTransforms = transforms
482553 hoistedImports = imports
554+ hoistedStatements = hoisted
483555 needsCreateRequire = reqCreate
556+ needsImportInterop = reqInteropHelper
484557 }
485558
486559 await ancestorWalk ( ast . program , {
@@ -505,11 +578,14 @@ const format = async (src: string, ast: ParseResult, opts: FormatterOptions) =>
505578
506579 if ( ( leftMain && rightModule ) || ( rightMain && leftModule ) ) {
507580 const negate = op === '!==' || op === '!='
508- code . update (
509- node . start ,
510- node . end ,
511- negate ? '!import.meta.main' : 'import.meta.main' ,
512- )
581+ const mainExpr =
582+ requireMainStrategy === 'import-meta-main'
583+ ? 'import.meta.main'
584+ : 'import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href'
585+ if ( requireMainStrategy === 'realpath' ) {
586+ requireMainNeedsRealpath = true
587+ }
588+ code . update ( node . start , node . end , negate ? `!(${ mainExpr } )` : mainExpr )
513589 return
514590 }
515591 }
@@ -549,6 +625,24 @@ const format = async (src: string, ast: ParseResult, opts: FormatterOptions) =>
549625 const hoistableTopLevel = isStatic && ( topLevelExprStmt || topLevelVarDecl )
550626
551627 if ( ! isStatic || ! hoistableTopLevel ) {
628+ if ( nestedRequireStrategy === 'dynamic-import' ) {
629+ const asyncCapable = isAsyncContext ( ancestors )
630+
631+ if ( asyncCapable ) {
632+ const arg = node . arguments [ 0 ]
633+ const argSrc = arg ? code . slice ( arg . start , arg . end ) : 'undefined'
634+ const literalVal = ( arg as any ) ?. value
635+ const isJson =
636+ arg ?. type === 'Literal' &&
637+ typeof literalVal === 'string' &&
638+ ( literalVal . split ( / [ ? # ] / ) [ 0 ] ?? literalVal ) . endsWith ( '.json' )
639+ const importTarget = isJson ? `${ argSrc } with { type: "json" }` : argSrc
640+
641+ code . update ( node . start , node . end , `(await import(${ importTarget } ))` )
642+ return
643+ }
644+ }
645+
552646 needsCreateRequire = true
553647 }
554648 }
@@ -643,7 +737,31 @@ const format = async (src: string, ast: ParseResult, opts: FormatterOptions) =>
643737 }
644738
645739 if ( node . type === 'MemberExpression' ) {
646- memberExpression ( node , parent , code , opts , shadowedBindings )
740+ memberExpression ( node , parent , code , opts , shadowedBindings , {
741+ onRequireResolve : ( ) => {
742+ if ( shouldRaiseEsm ) needsRequireResolveHelper = true
743+ } ,
744+ requireResolveName : '__requireResolve' ,
745+ } )
746+ }
747+
748+ if ( shouldRaiseEsm && node . type === 'ThisExpression' ) {
749+ const bindsThis = ( ancestor : any ) => {
750+ return (
751+ ancestor . type === 'FunctionDeclaration' ||
752+ ancestor . type === 'FunctionExpression' ||
753+ ancestor . type === 'ClassDeclaration' ||
754+ ancestor . type === 'ClassExpression'
755+ )
756+ }
757+
758+ const bindingAncestor = ancestors . find ( ancestor => bindsThis ( ancestor ) )
759+ const isTopLevel = ! bindingAncestor
760+
761+ if ( isTopLevel ) {
762+ code . update ( node . start , node . end , exportsRename )
763+ return
764+ }
647765 }
648766
649767 if ( isIdentifierName ( node ) ) {
@@ -733,21 +851,53 @@ const format = async (src: string, ast: ParseResult, opts: FormatterOptions) =>
733851 if ( shouldRaiseEsm && opts . transformSyntax ) {
734852 const importPrelude : string [ ] = [ ]
735853
736- if ( needsCreateRequire ) {
854+ if ( needsCreateRequire || needsRequireResolveHelper ) {
737855 importPrelude . push ( 'import { createRequire } from "node:module";\n' )
738856 }
739857
858+ if ( needsRequireResolveHelper ) {
859+ importPrelude . push ( 'import { fileURLToPath } from "node:url";\n' )
860+ }
861+
862+ if ( requireMainNeedsRealpath ) {
863+ importPrelude . push ( 'import { realpathSync } from "node:fs";\n' )
864+ importPrelude . push ( 'import { pathToFileURL } from "node:url";\n' )
865+ }
866+
740867 if ( hoistedImports . length ) {
741868 importPrelude . push ( ...hoistedImports )
742869 }
743870
871+ const setupPrelude : string [ ] = [ ]
872+
873+ if ( needsImportInterop ) {
874+ setupPrelude . push ( requireInteropHelper )
875+ }
876+
877+ if ( hoistedStatements . length ) {
878+ setupPrelude . push ( ...hoistedStatements )
879+ }
880+
744881 const requireInit = needsCreateRequire
745882 ? 'const require = createRequire(import.meta.url);\n'
746883 : ''
747884
885+ const requireResolveInit = needsRequireResolveHelper
886+ ? needsCreateRequire
887+ ? `const __requireResolve = (id, parent) => {
888+ const resolved = require.resolve(id, parent);
889+ return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
890+ };\n`
891+ : `const __requireResolve = (id, parent) => {
892+ const req = createRequire(parent ?? import.meta.url);
893+ const resolved = req.resolve(id, parent);
894+ return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
895+ };\n`
896+ : ''
897+
748898 const prelude = `${ importPrelude . join ( '' ) } ${
749899 importPrelude . length ? '\n' : ''
750- } ${ requireInit } let ${ exportsRename } = {};
900+ } ${ setupPrelude . join ( '' ) } ${ setupPrelude . length ? '\n' : '' } ${ requireInit } ${ requireResolveInit } let ${ exportsRename } = {};
751901void import.meta.filename;
752902`
753903
0 commit comments