@@ -6,20 +6,14 @@ import type { TransformOptions } from 'esbuild';
66import { transform , transformSync } from '../../utils/transform/index.js' ;
77import { transformDynamicImport } from '../../utils/transform/transform-dynamic-import.js' ;
88import { inlineSourceMap } from '../../source-map.js' ;
9- import {
10- isFeatureSupported ,
11- importAttributes ,
12- esmLoadReadFile ,
13- requireEsm ,
14- loadReadFromSource ,
15- } from '../../utils/node-features.js' ;
9+ import { isFeatureSupported , importAttributes , esmLoadReadFile } from '../../utils/node-features.js' ;
1610import { parent } from '../../utils/ipc/client.js' ;
1711import type { Message } from '../types.js' ;
1812import { fileMatcher } from '../../utils/tsconfig.js' ;
1913import { isJsonPattern , tsExtensionsPattern , fileUrlPrefix } from '../../utils/path-utils.js' ;
2014import { isESM } from '../../utils/es-module-lexer.js' ;
2115import { logEsm as log , debugEnabled } from '../../utils/debug.js' ;
22- import { getNamespace , decodeCjsQuery } from './utils.js' ;
16+ import { getNamespace } from './utils.js' ;
2317import { data } from './initialize.js' ;
2418
2519const importAttributesProperty = (
@@ -38,9 +32,6 @@ let load: LoadHook = async (
3832 return nextLoad ( url , context ) ;
3933 }
4034
41- // TODO: Add version
42- url = decodeCjsQuery ( url ) ;
43-
4435 const urlNamespace = getNamespace ( url ) ;
4536 if ( data . namespace && data . namespace !== urlNamespace ) {
4637 return nextLoad ( url , context ) ;
@@ -66,73 +57,36 @@ let load: LoadHook = async (
6657 } ) ;
6758 }
6859
69- const filePath = url . startsWith ( fileUrlPrefix ) ? fileURLToPath ( url ) : url ;
60+ if ( isJsonPattern . test ( url ) ) {
61+ let contextAttributes = context [ importAttributesProperty ] ;
62+ if ( ! contextAttributes ) {
63+ contextAttributes = { } ;
64+ context [ importAttributesProperty ] = contextAttributes ;
65+ }
7066
71- if ( context . format === 'module-json' ) {
72- const code = await readFile ( new URL ( url ) , 'utf8' ) ;
73- const transformed = await transform (
74- code ,
75- filePath ,
76- {
77- tsconfigRaw : (
78- path . isAbsolute ( filePath )
79- ? fileMatcher ?.( filePath ) as TransformOptions [ 'tsconfigRaw' ]
80- : undefined
81- ) ,
82- } ,
83- ) ;
84- return {
85- shortCircuit : true ,
86- format : 'module' ,
87- source : inlineSourceMap ( transformed ) ,
88- } ;
67+ if ( ! contextAttributes . type ) {
68+ contextAttributes . type = 'json' ;
69+ }
8970 }
9071
91- if ( context . format === 'commonjs-json' ) {
92- const code = await readFile ( new URL ( url ) , 'utf8' ) ;
93- const transformed = transformSync (
94- code ,
95- filePath ,
96- {
97- tsconfigRaw : (
98- path . isAbsolute ( filePath )
99- ? fileMatcher ?.( filePath ) as TransformOptions [ 'tsconfigRaw' ]
100- : undefined
101- ) ,
102- } ,
103- ) ;
104-
105- transformed . code += `\n0 && (module.exports = ${
106- JSON . stringify ( Object . fromEntries ( Object . keys ( JSON . parse ( code ) ) . map ( key => [ key , 0 ] ) ) )
107- } );`;
72+ const loaded = await nextLoad ( url , context ) ;
73+ log ( 3 , 'loaded by next loader' , {
74+ url,
75+ loaded,
76+ } ) ;
10877
109- return {
110- shortCircuit : true ,
111- format : 'commonjs' ,
112- source : inlineSourceMap ( transformed ) ,
113- } ;
114- }
78+ const filePath = url . startsWith ( fileUrlPrefix ) ? fileURLToPath ( url ) : url ;
11579
116- /**
117- * For compiling ambiguous ESM (ESM syntax in a package without type)
118- * So Node.js can kind of do this now, but it tries CommonJS first, and if it fails,
119- * it uses NATIVE ESM. This means ESM code that uses mixed syntax (e.g. __dirname)
120- * wil not work.
121- */
12280 if (
123- (
124- context . format === undefined
125- || context . format === 'commonjs'
126- || context . format === 'commonjs-typescript'
127- )
81+ loaded . format === 'commonjs'
12882 && isFeatureSupported ( esmLoadReadFile )
129- && url . startsWith ( 'file:' ) // Could be data:
130- && filePath . endsWith ( '.js' )
83+ && loaded . responseURL ? .startsWith ( 'file:' ) // Could be data:
84+ && ! filePath . endsWith ( '.cjs' ) // CJS syntax doesn't need to be transformed for interop
13185 ) {
13286 const code = await readFile ( new URL ( url ) , 'utf8' ) ;
13387
13488 // if the file extension is .js, only transform if using esm syntax
135- if ( isESM ( code ) ) {
89+ if ( ! filePath . endsWith ( '.js' ) || isESM ( code ) ) {
13690 /**
13791 * es or cjs module lexer unfortunately cannot be used because it doesn't support
13892 * typescript syntax
@@ -150,172 +104,30 @@ let load: LoadHook = async (
150104 */
151105 const transformed = transformSync (
152106 code ,
153- url ,
107+ filePath ,
154108 {
155109 tsconfigRaw : fileMatcher ?.( filePath ) as TransformOptions [ 'tsconfigRaw' ] ,
156110 } ,
157111 ) ;
158112
159- if ( isFeatureSupported ( loadReadFromSource ) ) {
160- return {
161- shortCircuit : true ,
162- format : 'commonjs' ,
163-
164- // This is necessary for CJS exports to be parsed correctly
165- // Returning a `source` makes the ESM translator to handle
166- // the CJS compilation and skips the CJS loader
167- source : inlineSourceMap ( transformed ) ,
168- } ;
169- }
170-
171113 const filePathWithNamespace = urlNamespace ? `${ filePath } ?namespace=${ encodeURIComponent ( urlNamespace ) } ` : filePath ;
172- return {
173- shortCircuit : true ,
174- format : 'commonjs' ,
175- responseURL : `data:text/javascript,${ encodeURIComponent ( transformed . code ) } ?filePath=${ encodeURIComponent ( filePathWithNamespace ) } ` ,
176- } ;
177- }
178- }
179-
180- if ( isJsonPattern . test ( url ) ) {
181- let contextAttributes = context [ importAttributesProperty ] ;
182- if ( ! contextAttributes ) {
183- contextAttributes = { } ;
184- context [ importAttributesProperty ] = contextAttributes ;
185- }
186114
187- if ( ! contextAttributes . type ) {
188- contextAttributes . type = 'json' ;
189- }
190- }
191-
192- const loaded = await nextLoad ( url , context ) ;
193- log ( 3 , 'loaded by next loader' , {
194- url,
195- loaded,
196- } ) ;
115+ loaded . responseURL = `data:text/javascript,${ encodeURIComponent ( transformed . code ) } ?filePath=${ encodeURIComponent ( filePathWithNamespace ) } ` ;
197116
198- if ( loaded . format === 'commonjs' ) {
199- if (
200- isFeatureSupported ( loadReadFromSource )
201- && filePath . endsWith ( '.cjs' )
202- ) {
203- let code = await readFile ( new URL ( url ) , 'utf8' ) ;
204- // Contains native ESM check
205- const transformed = transformDynamicImport ( filePath , code ) ;
206- if ( transformed ) {
207- code = inlineSourceMap ( transformed ) ;
208- }
209- loaded . source = code ;
210- loaded . shortCircuit = true ;
117+ log ( 3 , 'returning CJS export annotation' , loaded ) ;
211118 return loaded ;
212119 }
213-
214- if (
215- isFeatureSupported ( esmLoadReadFile )
216- && loaded . responseURL ?. startsWith ( 'file:' ) // Could be data:
217- && ! filePath . endsWith ( '.cjs' ) // CJS syntax doesn't need to be transformed for interop
218- ) {
219- const code = await readFile ( new URL ( url ) , 'utf8' ) ;
220-
221- // if the file extension is .js, only transform if using esm syntax
222- if (
223- // TypeScript files
224- ! filePath . endsWith ( '.js' )
225-
226- // ESM syntax in CommonJS type package
227- || isESM ( code )
228- ) {
229- /**
230- * es or cjs module lexer unfortunately cannot be used because it doesn't support
231- * typescript syntax
232- *
233- * While the full code is transformed, only the exports are used for parsing.
234- * In fact, the code can't even run because imports cannot be resolved relative
235- * from the data: URL.
236- *
237- * This should pre-compile for the CJS loader to have a cache hit
238- *
239- * I considered extracting the CJS exports from esbuild via (0&&(module.exports={})
240- * to minimize the data URL size but this only works for ESM->CJS and not CTS files
241- * which are already in CJS syntax.
242- * In CTS, module.exports can be written in any pattern.
243- */
244- const transformed = transformSync (
245- code ,
246- url ,
247- {
248- tsconfigRaw : fileMatcher ?.( filePath ) as TransformOptions [ 'tsconfigRaw' ] ,
249- } ,
250- ) ;
251-
252- if ( isFeatureSupported ( loadReadFromSource ) ) {
253- /**
254- * Compile ESM to CJS
255- * In v22.15, the CJS loader logic is now moved to the ESM loader
256- */
257- loaded . source = inlineSourceMap ( transformed ) ;
258- } else {
259- /**
260- * This tricks Node into thinking the file is a data URL so it doesn't try to
261- * read from disk to parse the CJS exports
262- */
263- const filePathWithNamespace = urlNamespace ? `${ filePath } ?namespace=${ encodeURIComponent ( urlNamespace ) } ` : filePath ;
264- loaded . responseURL = `data:text/javascript,${ encodeURIComponent ( transformed . code ) } ?filePath=${ encodeURIComponent ( filePathWithNamespace ) } ` ;
265- }
266-
267- return loaded ;
268- }
269-
270- if ( ! loaded . source ) {
271- loaded . source = code ;
272- return loaded ;
273- }
274- }
275120 }
276121
277- // Internal modules (e.g. node:*)
122+ // CommonJS and Internal modules (e.g. node:*)
278123 if ( ! loaded . source ) {
279124 return loaded ;
280125 }
281126
282127 const code = loaded . source . toString ( ) ;
283128
284- // Since CJS can now require ESM, JSONs are now handled by the
285- // ESM loader as ESM in module contexts
286- // TODO: If we can detect whether this was "required",
287- // we can let the CJS loader handler it by returning an empty source
288- // Support named imports in JSON modules
289- /**
290- * In versions of Node that supports require'ing ESM,
291- */
292- if (
293- isFeatureSupported ( requireEsm )
294- && loaded . format === 'json'
295- ) {
296- const transformed = transformSync (
297- code ,
298- filePath ,
299- {
300- tsconfigRaw : (
301- path . isAbsolute ( filePath )
302- ? fileMatcher ?.( filePath ) as TransformOptions [ 'tsconfigRaw' ]
303- : undefined
304- ) ,
305- } ,
306- ) ;
307-
308- transformed . code += `\n0 && (module.exports = ${
309- JSON . stringify ( Object . fromEntries ( Object . keys ( JSON . parse ( code ) ) . map ( key => [ key , 0 ] ) ) )
310- } );`;
311-
312- return {
313- format : 'commonjs' ,
314- source : inlineSourceMap ( transformed ) ,
315- } ;
316- }
317-
318129 if (
130+ // Support named imports in JSON modules
319131 loaded . format === 'json'
320132 || tsExtensionsPattern . test ( url )
321133 ) {
0 commit comments