1- import { dirname , resolve , sep , posix , join } from 'path' ;
2- import { readFileSync , writeFileSync , unlinkSync , existsSync } from 'fs' ;
1+ import { dirname , resolve , sep , posix , join , relative } from 'path' ;
2+ import {
3+ readFileSync ,
4+ writeFileSync ,
5+ existsSync ,
6+ mkdtempSync ,
7+ rmSync ,
8+ } from 'fs' ;
39import { debuglog } from 'util' ;
410import { PathToRegexpUtil } from './pathToRegexp' ;
511import { MidwayCommonError } from '../error' ;
@@ -16,7 +22,9 @@ import { CONFIGURATION_KEY, CONFIGURATION_OBJECT_KEY } from '../decorator';
1622
1723const debug = debuglog ( 'midway:debug' ) ;
1824
19- function resolveRelativeEsmSpecifierFallback (
25+ let cachedTypeScriptCompiler : any ;
26+
27+ function resolveRelativeEsmSpecifierPath (
2028 importerFile : string ,
2129 specifier : string
2230) : string | undefined {
@@ -28,7 +36,7 @@ function resolveRelativeEsmSpecifierFallback(
2836 }
2937
3038 const absolute = resolve ( dirname ( importerFile ) , specifier ) ;
31- const candidates : string [ ] = [ ] ;
39+ const candidates : string [ ] = [ absolute ] ;
3240
3341 if ( / \. ( m j s | c j s | j s ) $ / i. test ( specifier ) ) {
3442 candidates . push (
@@ -43,23 +51,44 @@ function resolveRelativeEsmSpecifierFallback(
4351 `${ absolute } .cts` ,
4452 `${ absolute } .ts` ,
4553 `${ absolute } .tsx` ,
54+ `${ absolute } .mjs` ,
55+ `${ absolute } .cjs` ,
56+ `${ absolute } .js` ,
57+ `${ absolute } .json` ,
4658 join ( absolute , 'index.mts' ) ,
4759 join ( absolute , 'index.cts' ) ,
4860 join ( absolute , 'index.ts' ) ,
49- join ( absolute , 'index.tsx' )
61+ join ( absolute , 'index.tsx' ) ,
62+ join ( absolute , 'index.mjs' ) ,
63+ join ( absolute , 'index.cjs' ) ,
64+ join ( absolute , 'index.js' ) ,
65+ join ( absolute , 'index.json' )
5066 ) ;
5167 }
5268
5369 for ( const item of candidates ) {
5470 if ( existsSync ( item ) ) {
55- const normalized = item . split ( sep ) . join ( '/' ) ;
56- const baseDir = dirname ( importerFile ) . split ( sep ) . join ( '/' ) ;
57- if ( normalized . startsWith ( baseDir + '/' ) ) {
58- return './' + normalized . slice ( baseDir . length + 1 ) ;
59- }
60- return specifier ;
71+ return item ;
72+ }
73+ }
74+
75+ return undefined ;
76+ }
77+
78+ function resolveRelativeEsmSpecifierFallback (
79+ importerFile : string ,
80+ specifier : string
81+ ) : string | undefined {
82+ const resolved = resolveRelativeEsmSpecifierPath ( importerFile , specifier ) ;
83+ if ( resolved ) {
84+ const normalized = resolved . split ( sep ) . join ( '/' ) ;
85+ const baseDir = dirname ( importerFile ) . split ( sep ) . join ( '/' ) ;
86+ if ( normalized . startsWith ( baseDir + '/' ) ) {
87+ return './' + normalized . slice ( baseDir . length + 1 ) ;
6188 }
89+ return specifier ;
6290 }
91+
6392 return undefined ;
6493}
6594
@@ -86,6 +115,137 @@ function rewriteEsmSourceWithSpecifierFallback(
86115 return changed ? output : source ;
87116}
88117
118+ function shouldUseEsmFallback (
119+ originErr : any ,
120+ filePath : string ,
121+ rewritten : string ,
122+ source : string
123+ ) {
124+ if ( rewritten !== source ) {
125+ return true ;
126+ }
127+
128+ return (
129+ originErr ?. code === 'ERR_UNKNOWN_FILE_EXTENSION' &&
130+ / \. ( m t s | c t s | t s | t s x ) $ / i. test ( filePath )
131+ ) ;
132+ }
133+
134+ function formatFallbackImportSpecifier ( fromFile : string , toFile : string ) {
135+ let specifier = relative ( dirname ( fromFile ) , toFile ) . split ( sep ) . join ( '/' ) ;
136+ if ( ! specifier . startsWith ( '.' ) ) {
137+ specifier = `./${ specifier } ` ;
138+ }
139+ return specifier ;
140+ }
141+
142+ function loadTypeScriptCompiler ( sourceFile : string ) {
143+ if ( cachedTypeScriptCompiler ) {
144+ return cachedTypeScriptCompiler ;
145+ }
146+
147+ const searchPaths = [ dirname ( sourceFile ) , process . cwd ( ) , __dirname ] ;
148+ for ( const item of searchPaths ) {
149+ try {
150+ cachedTypeScriptCompiler = require (
151+ require . resolve ( 'typescript' , {
152+ paths : [ item ] ,
153+ } )
154+ ) ;
155+ return cachedTypeScriptCompiler ;
156+ } catch {
157+ // try next path
158+ }
159+ }
160+ }
161+
162+ function createCompiledEsmFallbackGraph ( entryFile : string ) {
163+ const tempDir = mkdtempSync (
164+ join ( dirname ( entryFile ) , '.midway-esm-fallback-' )
165+ ) ;
166+ const compiledFileMap = new Map < string , string > ( ) ;
167+ const tsCompiler = loadTypeScriptCompiler ( entryFile ) ;
168+
169+ const compileFile = ( sourceFile : string ) : string => {
170+ const existed = compiledFileMap . get ( sourceFile ) ;
171+ if ( existed ) {
172+ return existed ;
173+ }
174+
175+ const compiledFile = join (
176+ tempDir ,
177+ `${ crypto . createHash ( 'sha1' ) . update ( sourceFile ) . digest ( 'hex' ) } .mjs`
178+ ) ;
179+ compiledFileMap . set ( sourceFile , compiledFile ) ;
180+
181+ if ( sourceFile . endsWith ( '.json' ) ) {
182+ const jsonSource = readFileSync ( sourceFile , { encoding : 'utf-8' } ) ;
183+ writeFileSync ( compiledFile , `export default ${ jsonSource } ;` , {
184+ encoding : 'utf-8' ,
185+ } ) ;
186+ return compiledFile ;
187+ }
188+
189+ const source = readFileSync ( sourceFile , { encoding : 'utf-8' } ) ;
190+ const rewriteByPattern = ( pattern : RegExp , input : string ) => {
191+ return input . replace ( pattern , ( full , head , spec , tail ) => {
192+ const resolved = resolveRelativeEsmSpecifierPath ( sourceFile , spec ) ;
193+ if ( ! resolved ) {
194+ return full ;
195+ }
196+ const compiledDependency = compileFile ( resolved ) ;
197+ const fallbackSpecifier = formatFallbackImportSpecifier (
198+ compiledFile ,
199+ compiledDependency
200+ ) ;
201+ return `${ head } ${ fallbackSpecifier } ${ tail } ` ;
202+ } ) ;
203+ } ;
204+
205+ let rewritten = source ;
206+ rewritten = rewriteByPattern ( / ( f r o m \s + [ ' " ] ) ( [ ^ ' " ] + ) ( [ ' " ] ) / g, rewritten ) ;
207+ rewritten = rewriteByPattern (
208+ / ( i m p o r t \s * \( \s * [ ' " ] ) ( [ ^ ' " ] + ) ( [ ' " ] \s * \) ) / g,
209+ rewritten
210+ ) ;
211+
212+ let output = rewritten ;
213+ if ( / \. ( m t s | c t s | t s | t s x ) $ / i. test ( sourceFile ) ) {
214+ if ( ! tsCompiler ) {
215+ throw new Error (
216+ `[core]: can not transpile esm typescript file "${ sourceFile } ", please install "typescript" in current project`
217+ ) ;
218+ }
219+
220+ output = tsCompiler . transpileModule ( rewritten , {
221+ fileName : sourceFile ,
222+ compilerOptions : {
223+ module : tsCompiler . ModuleKind . ESNext ,
224+ target : tsCompiler . ScriptTarget . ES2020 ,
225+ moduleResolution : tsCompiler . ModuleResolutionKind . NodeNext ,
226+ esModuleInterop : true ,
227+ allowSyntheticDefaultImports : true ,
228+ resolveJsonModule : true ,
229+ jsx : tsCompiler . JsxEmit . ReactJSX ,
230+ } ,
231+ } ) . outputText ;
232+ }
233+
234+ writeFileSync ( compiledFile , output , { encoding : 'utf-8' } ) ;
235+ return compiledFile ;
236+ } ;
237+
238+ return {
239+ entryFile : compileFile ( entryFile ) ,
240+ cleanup ( ) {
241+ rmSync ( tempDir , {
242+ recursive : true ,
243+ force : true ,
244+ } ) ;
245+ } ,
246+ } ;
247+ }
248+
89249async function importWithSpecifierFallback (
90250 p : string ,
91251 fileUrl : URL ,
@@ -96,26 +256,19 @@ async function importWithSpecifierFallback(
96256 } catch ( originErr ) {
97257 const source = readFileSync ( p , { encoding : 'utf-8' } ) ;
98258 const rewritten = rewriteEsmSourceWithSpecifierFallback ( p , source ) ;
99- if ( rewritten === source ) {
259+ if ( ! shouldUseEsmFallback ( originErr , p , rewritten , source ) ) {
100260 throw originErr ;
101261 }
102262
103- const tmpFile = `${ p } .mw-esm-fallback-${ Date . now ( ) } -${ Math . random ( )
104- . toString ( 36 )
105- . slice ( 2 ) } .ts`;
106- writeFileSync ( tmpFile , rewritten , { encoding : 'utf-8' } ) ;
263+ const fallbackGraph = createCompiledEsmFallbackGraph ( p ) ;
107264 try {
108- const tmpUrl = pathToFileURL ( tmpFile ) ;
265+ const tmpUrl = pathToFileURL ( fallbackGraph . entryFile ) ;
109266 if ( importQuery ) {
110267 tmpUrl . searchParams . set ( 'mwImportQuery' , importQuery ) ;
111268 }
112269 return await import ( tmpUrl . href ) ;
113270 } finally {
114- try {
115- unlinkSync ( tmpFile ) ;
116- } catch {
117- // ignore cleanup failure
118- }
271+ fallbackGraph . cleanup ( ) ;
119272 }
120273 }
121274}
@@ -193,14 +346,14 @@ export const loadModule = async (
193346 if ( options . loadMode === 'commonjs' ) {
194347 try {
195348 return require ( p ) ;
196- } catch ( _ ) {
349+ } catch {
197350 for ( const extraPath of [
198351 process . cwd ( ) ,
199352 ...( options . extraModuleRoot || [ ] ) ,
200353 ] ) {
201354 try {
202355 return require ( require . resolve ( p , { paths : [ extraPath ] } ) ) ;
203- } catch ( _ ) {
356+ } catch {
204357 // do nothing
205358 }
206359 }
@@ -485,7 +638,7 @@ export const transformRequestObjectByType = (originValue: any, targetType?) => {
485638
486639export function toPathMatch ( pattern ) {
487640 if ( typeof pattern === 'boolean' ) {
488- return ctx => pattern ;
641+ return ( ) => pattern ;
489642 }
490643 if ( typeof pattern === 'string' ) {
491644 const reg = PathToRegexpUtil . toRegexp ( pattern . replace ( '*' , '(.*)' ) ) ;
0 commit comments