Skip to content

Commit dadcf27

Browse files
committed
fix: revert v4.20 changes
1 parent 2d1aaee commit dadcf27

File tree

9 files changed

+48
-381
lines changed

9 files changed

+48
-381
lines changed

src/esm/hook/load.ts

Lines changed: 26 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,14 @@ import type { TransformOptions } from 'esbuild';
66
import { transform, transformSync } from '../../utils/transform/index.js';
77
import { transformDynamicImport } from '../../utils/transform/transform-dynamic-import.js';
88
import { 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';
1610
import { parent } from '../../utils/ipc/client.js';
1711
import type { Message } from '../types.js';
1812
import { fileMatcher } from '../../utils/tsconfig.js';
1913
import { isJsonPattern, tsExtensionsPattern, fileUrlPrefix } from '../../utils/path-utils.js';
2014
import { isESM } from '../../utils/es-module-lexer.js';
2115
import { logEsm as log, debugEnabled } from '../../utils/debug.js';
22-
import { getNamespace, decodeCjsQuery } from './utils.js';
16+
import { getNamespace } from './utils.js';
2317
import { data } from './initialize.js';
2418

2519
const 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

Comments
 (0)