@@ -26,7 +26,7 @@ const { |
26 | 26 | const { BuiltinModule } = require('internal/bootstrap/realm'); |
27 | 27 | const assert = require('internal/assert'); |
28 | 28 | const { readFileSync } = require('fs'); |
29 | | -const { dirname, extname, isAbsolute } = require('path'); |
| 29 | +const { dirname, extname } = require('path'); |
30 | 30 | const { |
31 | 31 | assertBufferSource, |
32 | 32 | loadBuiltinModule, |
@@ -42,6 +42,9 @@ const { |
42 | 42 | kModuleSource, |
43 | 43 | kModuleExport, |
44 | 44 | kModuleExportNames, |
| 45 | + findLongestRegisteredExtension, |
| 46 | + resolveForCJSWithHooks, |
| 47 | + loadSourceForCJSWithHooks, |
45 | 48 | } = require('internal/modules/cjs/loader'); |
46 | 49 | const { fileURLToPath, pathToFileURL, URL } = require('internal/url'); |
47 | 50 | let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { |
@@ -171,17 +174,18 @@ const cjsCache = new SafeMap(); |
171 | 174 | * @param {string} url - The URL of the module. |
172 | 175 | * @param {string} source - The source code of the module. |
173 | 176 | * @param {boolean} isMain - Whether the module is the main module. |
| 177 | + * @param {string} format - Format of the module. |
174 | 178 | * @param {typeof loadCJSModule} [loadCJS=loadCJSModule] - The function to load the CommonJS module. |
175 | 179 | * @returns {ModuleWrap} The ModuleWrap object for the CommonJS module. |
176 | 180 | */ |
177 | | -function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { |
| 181 | +function createCJSModuleWrap(url, source, isMain, format, loadCJS = loadCJSModule) { |
178 | 182 | debug(`Translating CJSModule ${url}`); |
179 | 183 |
|
180 | 184 | const filename = urlToFilename(url); |
181 | 185 | // In case the source was not provided by the `load` step, we need fetch it now. |
182 | 186 | source = stringify(source ?? getSource(new URL(url)).source); |
183 | 187 |
|
184 | | - const { exportNames, module } = cjsPreparseModuleExports(filename, source); |
| 188 | + const { exportNames, module } = cjsPreparseModuleExports(filename, source, isMain, format); |
185 | 189 | cjsCache.set(url, module); |
186 | 190 |
|
187 | 191 | const wrapperNames = [...exportNames, 'module.exports']; |
@@ -228,7 +232,7 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { |
228 | 232 | translators.set('commonjs-sync', function requireCommonJS(url, source, isMain) { |
229 | 233 | initCJSParseSync(); |
230 | 234 |
|
231 | | - return createCJSModuleWrap(url, source, isMain, (module, source, url, filename, isMain) => { |
| 235 | + return createCJSModuleWrap(url, source, isMain, 'commonjs', (module, source, url, filename, isMain) => { |
232 | 236 | assert(module === CJSModule._cache[filename]); |
233 | 237 | wrapModuleLoad(filename, null, isMain); |
234 | 238 | }); |
@@ -240,7 +244,7 @@ translators.set('require-commonjs', (url, source, isMain) => { |
240 | 244 | initCJSParseSync(); |
241 | 245 | assert(cjsParse); |
242 | 246 |
|
243 | | - return createCJSModuleWrap(url, source); |
| 247 | + return createCJSModuleWrap(url, source, isMain, 'commonjs'); |
244 | 248 | }); |
245 | 249 |
|
246 | 250 | // Handle CommonJS modules referenced by `require` calls. |
@@ -249,7 +253,7 @@ translators.set('require-commonjs-typescript', (url, source, isMain) => { |
249 | 253 | emitExperimentalWarning('Type Stripping'); |
250 | 254 | assert(cjsParse); |
251 | 255 | const code = stripTypeScriptModuleTypes(stringify(source), url); |
252 | | - return createCJSModuleWrap(url, code); |
| 256 | + return createCJSModuleWrap(url, code, isMain, 'commonjs-typescript'); |
253 | 257 | }); |
254 | 258 |
|
255 | 259 | // Handle CommonJS modules referenced by `import` statements or expressions, |
@@ -273,16 +277,17 @@ translators.set('commonjs', function commonjsStrategy(url, source, isMain) { |
273 | 277 | } catch { |
274 | 278 | // Continue regardless of error. |
275 | 279 | } |
276 | | - return createCJSModuleWrap(url, source, isMain, cjsLoader); |
| 280 | + return createCJSModuleWrap(url, source, isMain, 'commonjs', cjsLoader); |
277 | 281 | }); |
278 | 282 |
|
279 | 283 | /** |
280 | 284 | * Pre-parses a CommonJS module's exports and re-exports. |
281 | 285 | * @param {string} filename - The filename of the module. |
282 | 286 | * @param {string} [source] - The source code of the module. |
| 287 | + * @param {boolean} isMain - Whether it is pre-parsing for the entry point. |
| 288 | + * @param {string} format |
283 | 289 | */ |
284 | | -function cjsPreparseModuleExports(filename, source) { |
285 | | - // TODO: Do we want to keep hitting the user mutable CJS loader here? |
| 290 | +function cjsPreparseModuleExports(filename, source, isMain, format) { |
286 | 291 | let module = CJSModule._cache[filename]; |
287 | 292 | if (module && module[kModuleExportNames] !== undefined) { |
288 | 293 | return { module, exportNames: module[kModuleExportNames] }; |
@@ -293,10 +298,15 @@ function cjsPreparseModuleExports(filename, source) { |
293 | 298 | module.filename = filename; |
294 | 299 | module.paths = CJSModule._nodeModulePaths(module.path); |
295 | 300 | module[kIsCachedByESMLoader] = true; |
296 | | - module[kModuleSource] = source; |
297 | 301 | CJSModule._cache[filename] = module; |
298 | 302 | } |
299 | 303 |
|
| 304 | + if (source === undefined) { |
| 305 | + ({ source } = loadSourceForCJSWithHooks(module, filename, format)); |
| 306 | + } |
| 307 | + module[kModuleSource] = source; |
| 308 | + |
| 309 | + debug(`Preparsing exports of ${filename}`); |
300 | 310 | let exports, reexports; |
301 | 311 | try { |
302 | 312 | ({ exports, reexports } = cjsParse(source || '')); |
@@ -310,34 +320,27 @@ function cjsPreparseModuleExports(filename, source) { |
310 | 320 | // Set first for cycles. |
311 | 321 | module[kModuleExportNames] = exportNames; |
312 | 322 |
|
| 323 | + // If there are any re-exports e.g. `module.exports = { ...require(...) }`, |
| 324 | + // pre-parse the dependencies to find transitively exported names. |
313 | 325 | if (reexports.length) { |
314 | | - module.filename = filename; |
315 | | - module.paths = CJSModule._nodeModulePaths(module.path); |
| 326 | + module.filename ??= filename; |
| 327 | + module.paths ??= CJSModule._nodeModulePaths(dirname(filename)); |
| 328 | + |
316 | 329 | for (let i = 0; i < reexports.length; i++) { |
| 330 | + debug(`Preparsing re-exports of '${filename}'`); |
317 | 331 | const reexport = reexports[i]; |
318 | 332 | let resolved; |
| 333 | + let format; |
319 | 334 | try { |
320 | | - // TODO: this should be calling the `resolve` hook chain instead. |
321 | | - // Doing so would mean dropping support for CJS in the loader thread, as |
322 | | - // this call needs to be sync from the perspective of the main thread, |
323 | | - // which we can do via HooksProxy and Atomics, but we can't do within |
324 | | - // the loaders thread. Until this is done, the lexer will use the |
325 | | - // monkey-patchable CJS loader to get the path to the module file to |
326 | | - // load (which may or may not be aligned with the URL that the `resolve` |
327 | | - // hook have returned). |
328 | | - resolved = CJSModule._resolveFilename(reexport, module); |
329 | | - } catch { |
| 335 | + ({ format, filename: resolved } = resolveForCJSWithHooks(reexport, module, false)); |
| 336 | + } catch (e) { |
| 337 | + debug(`Failed to resolve '${reexport}', skipping`, e); |
330 | 338 | continue; |
331 | 339 | } |
332 | | - // TODO: this should be calling the `load` hook chain and check if it returns |
333 | | - // `format: 'commonjs'` instead of relying on file extensions. |
334 | | - const ext = extname(resolved); |
335 | | - if ((ext === '.js' || ext === '.cjs' || !CJSModule._extensions[ext]) && |
336 | | - isAbsolute(resolved)) { |
337 | | - // TODO: this should be calling the `load` hook chain to get the source |
338 | | - // (and fallback to reading the FS only if the source is nullish). |
339 | | - const source = readFileSync(resolved, 'utf-8'); |
340 | | - const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved, source); |
| 340 | + |
| 341 | + if (format === 'commonjs' || |
| 342 | + (!BuiltinModule.normalizeRequirableId(resolved) && findLongestRegisteredExtension(resolved) === '.js')) { |
| 343 | + const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved, undefined, false, format); |
341 | 344 | for (const name of reexportNames) { |
342 | 345 | exportNames.add(name); |
343 | 346 | } |
|