| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 14b53df commit 1d5ed72
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -474,18 +474,39 @@ See [Loading ECMAScript modules using `require()`][] for details. | |||
| 474 | 474 | ||
| 475 | 475 | ### CommonJS Namespaces | |
| 476 | 476 | ||
| 477 | + <!-- YAML | ||
| 478 | + added: v14.13.0 | ||
| 479 | + changes: | ||
| 480 | + - version: REPLACEME | ||
| 481 | + pr-url: https://github.com/nodejs/node/pull/53848 | ||
| 482 | + description: Added `'module.exports'` export marker to CJS namespaces. | ||
| 483 | + --> | ||
| 484 | + | ||
| 477 | 485 | CommonJS modules consist of a `module.exports` object which can be of any type. | |
| 478 | 486 | ||
| 487 | + To support this, when importing CommonJS from an ECMAScript module, a namespace | ||
| 488 | + wrapper for the CommonJS module is constructed, which always provides a | ||
| 489 | + `default` export key pointing to the CommonJS `module.exports` value. | ||
| 490 | + | ||
| 491 | + In addition, a heuristic static analysis is performed against the source text of | ||
| 492 | + the CommonJS module to get a best-effort static list of exports to provide on | ||
| 493 | + the namespace from values on `module.exports`. This is necessary since these | ||
| 494 | + namespaces must be constructed prior to the evaluation of the CJS module. | ||
| 495 | + | ||
| 496 | + These CommonJS namespace objects also provide the `default` export as a | ||
| 497 | + `'module.exports'` named export, in order to unambiguously indicate that their | ||
| 498 | + representation in CommonJS uses this value, and not the namespace value. This | ||
| 499 | + mirrors the semantics of the handling of the `'module.exports'` export name in | ||
| 500 | + [`require(esm)`][] interop support. | ||
| 501 | + | ||
| 479 | 502 | When importing a CommonJS module, it can be reliably imported using the ES | |
| 480 | 503 | module default import or its corresponding sugar syntax: | |
| 481 | 504 | ||
| 482 | 505 | <!-- eslint-disable no-duplicate-imports --> | |
| 483 | 506 | ||
| 484 | 507 | ```js | |
| 485 | 508 | import { default as cjs } from 'cjs'; | |
| 486 | - | ||
| 487 | - // The following import statement is "syntax sugar" (equivalent but sweeter) | ||
| 488 | - // for `{ default as cjsSugar }` in the above import statement: | ||
| 509 | + // identical to the above | ||
| 489 | 510 | import cjsSugar from 'cjs'; | |
| 490 | 511 | ||
| 491 | 512 | console.log(cjs); | |
@@ -495,10 +516,6 @@ console.log(cjs === cjsSugar); | |||
| 495 | 516 | // true | |
| 496 | 517 | ``` | |
| 497 | 518 | ||
| 498 | - The ECMAScript Module Namespace representation of a CommonJS module is always | ||
| 499 | - a namespace with a `default` export key pointing to the CommonJS | ||
| 500 | - `module.exports` value. | ||
| 501 | - | ||
| 502 | 519 | This Module Namespace Exotic Object can be directly observed either when using | |
| 503 | 520 | `import * as m from 'cjs'` or a dynamic import: | |
| 504 | 521 | ||
@@ -509,7 +526,7 @@ import * as m from 'cjs'; | |||
| 509 | 526 | console.log(m); | |
| 510 | 527 | console.log(m === await import('cjs')); | |
| 511 | 528 | // Prints: | |
| 512 | - // [Module] { default: <module.exports> } | ||
| 529 | + // [Module] { default: <module.exports>, 'module.exports': <module.exports> } | ||
| 513 | 530 | // true | |
| 514 | 531 | ``` | |
| 515 | 532 | ||
@@ -540,7 +557,12 @@ console.log(cjs); | |||
| 540 | 557 | ||
| 541 | 558 | import * as m from './cjs.cjs'; | |
| 542 | 559 | console.log(m); | |
| 543 | - // Prints: [Module] { default: { name: 'exported' }, name: 'exported' } | ||
| 560 | + // Prints: | ||
| 561 | + // [Module] { | ||
| 562 | + // default: { name: 'exported' }, | ||
| 563 | + // 'module.exports': { name: 'exported' }, | ||
| 564 | + // name: 'exported' | ||
| 565 | + // } | ||
| 544 | 566 | ``` | |
| 545 | 567 | ||
| 546 | 568 | As can be seen from the last example of the Module Namespace Exotic Object being | |
@@ -1103,6 +1125,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. | |||
| 1103 | 1125 | [`package.json`]: packages.md#nodejs-packagejson-field-definitions | |
| 1104 | 1126 | [`path.dirname()`]: path.md#pathdirnamepath | |
| 1105 | 1127 | [`process.dlopen`]: process.md#processdlopenmodule-filename-flags | |
| 1128 | + [`require(esm)`]: modules.md#loading-ecmascript-modules-using-require | ||
| 1106 | 1129 | [`url.fileURLToPath()`]: url.md#urlfileurltopathurl-options | |
| 1107 | 1130 | [cjs-module-lexer]: https://github.com/nodejs/cjs-module-lexer/tree/1.2.2 | |
| 1108 | 1131 | [commonjs-extension-resolution-loader]: https://github.com/nodejs/loaders-test/tree/main/commonjs-extension-resolution-loader | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | 4 | ArrayPrototypeMap, | |
| 5 | + ArrayPrototypePush, | ||
| 5 | 6 | Boolean, | |
| 6 | 7 | FunctionPrototypeCall, | |
| 7 | 8 | JSONParse, | |
@@ -182,14 +183,17 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { | |||
| 182 | 183 | ||
| 183 | 184 | const { exportNames, module } = cjsPreparseModuleExports(filename, source); | |
| 184 | 185 | cjsCache.set(url, module); | |
| 185 | - const namesWithDefault = exportNames.has('default') ? | ||
| 186 | - [...exportNames] : ['default', ...exportNames]; | ||
| 186 | + | ||
| 187 | + const wrapperNames = [...exportNames, 'module.exports']; | ||
| 188 | + if (!exportNames.has('default')) { | ||
| 189 | + ArrayPrototypePush(wrapperNames, 'default'); | ||
| 190 | + } | ||
| 187 | 191 | ||
| 188 | 192 | if (isMain) { | |
| 189 | 193 | setOwnProperty(process, 'mainModule', module); | |
| 190 | 194 | } | |
| 191 | 195 | ||
| 192 | - return new ModuleWrap(url, undefined, namesWithDefault, function() { | ||
| 196 | + return new ModuleWrap(url, undefined, wrapperNames, function() { | ||
| 193 | 197 | debug(`Loading CJSModule ${url}`); | |
| 194 | 198 | ||
| 195 | 199 | if (!module.loaded) { | |
@@ -204,8 +208,7 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { | |||
| 204 | 208 | ({ exports } = module); | |
| 205 | 209 | } | |
| 206 | 210 | for (const exportName of exportNames) { | |
| 207 | - if (!ObjectPrototypeHasOwnProperty(exports, exportName) || | ||
| 208 | - exportName === 'default') { | ||
| 211 | + if (!ObjectPrototypeHasOwnProperty(exports, exportName) || exportName === 'default') { | ||
| 209 | 212 | continue; | |
| 210 | 213 | } | |
| 211 | 214 | // We might trigger a getter -> dont fail. | |
@@ -218,6 +221,7 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { | |||
| 218 | 221 | this.setExport(exportName, value); | |
| 219 | 222 | } | |
| 220 | 223 | this.setExport('default', exports); | |
| 224 | + this.setExport('module.exports', exports); | ||
| 221 | 225 | }, module); | |
| 222 | 226 | } | |
| 223 | 227 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,50 +8,53 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; | |||
| 8 | 8 | [requireFixture, importFixture].forEach((loadFixture) => { | |
| 9 | 9 | const isRequire = loadFixture === requireFixture; | |
| 10 | 10 | ||
| 11 | + const maybeWrapped = isRequire ? (exports) => exports : | ||
| 12 | + (exports) => ({ ...exports, 'module.exports': exports.default }); | ||
| 13 | + | ||
| 11 | 14 | const validSpecifiers = new Map([ | |
| 12 | 15 | // A simple mapping of a path. | |
| 13 | - ['pkgexports/valid-cjs', { default: 'asdf' }], | ||
| 16 | + ['pkgexports/valid-cjs', maybeWrapped({ default: 'asdf' })], | ||
| 14 | 17 | // A mapping pointing to a file that needs special encoding (%20) in URLs. | |
| 15 | - ['pkgexports/space', { default: 'encoded path' }], | ||
| 18 | + ['pkgexports/space', maybeWrapped({ default: 'encoded path' })], | ||
| 16 | 19 | // Verifying that normal packages still work with exports turned on. | |
| 17 | 20 | isRequire ? ['baz/index', { default: 'eye catcher' }] : [null], | |
| 18 | 21 | // Fallbacks | |
| 19 | - ['pkgexports/fallbackdir/asdf.js', { default: 'asdf' }], | ||
| 20 | - ['pkgexports/fallbackfile', { default: 'asdf' }], | ||
| 22 | + ['pkgexports/fallbackdir/asdf.js', maybeWrapped({ default: 'asdf' })], | ||
| 23 | + ['pkgexports/fallbackfile', maybeWrapped({ default: 'asdf' })], | ||
| 21 | 24 | // Conditional split for require | |
| 22 | 25 | ['pkgexports/condition', isRequire ? { default: 'encoded path' } : | |
| 23 | - { default: 'asdf' }], | ||
| 26 | + maybeWrapped({ default: 'asdf' })], | ||
| 24 | 27 | // String exports sugar | |
| 25 | - ['pkgexports-sugar', { default: 'main' }], | ||
| 28 | + ['pkgexports-sugar', maybeWrapped({ default: 'main' })], | ||
| 26 | 29 | // Conditional object exports sugar | |
| 27 | 30 | ['pkgexports-sugar2', isRequire ? { default: 'not-exported' } : | |
| 28 | - { default: 'main' }], | ||
| 31 | + maybeWrapped({ default: 'main' })], | ||
| 29 | 32 | // Resolve self | |
| 30 | 33 | ['pkgexports/resolve-self', isRequire ? | |
| 31 | 34 | { default: 'self-cjs' } : { default: 'self-mjs' }], | |
| 32 | 35 | // Resolve self sugar | |
| 33 | - ['pkgexports-sugar', { default: 'main' }], | ||
| 36 | + ['pkgexports-sugar', maybeWrapped({ default: 'main' })], | ||
| 34 | 37 | // Path patterns | |
| 35 | - ['pkgexports/subpath/sub-dir1', { default: 'main' }], | ||
| 36 | - ['pkgexports/subpath/sub-dir1.js', { default: 'main' }], | ||
| 37 | - ['pkgexports/features/dir1', { default: 'main' }], | ||
| 38 | - ['pkgexports/dir1/dir1/trailer', { default: 'main' }], | ||
| 39 | - ['pkgexports/dir2/dir2/trailer', { default: 'index' }], | ||
| 40 | - ['pkgexports/a/dir1/dir1', { default: 'main' }], | ||
| 41 | - ['pkgexports/a/b/dir1/dir1', { default: 'main' }], | ||
| 38 | + ['pkgexports/subpath/sub-dir1', maybeWrapped({ default: 'main' })], | ||
| 39 | + ['pkgexports/subpath/sub-dir1.js', maybeWrapped({ default: 'main' })], | ||
| 40 | + ['pkgexports/features/dir1', maybeWrapped({ default: 'main' })], | ||
| 41 | + ['pkgexports/dir1/dir1/trailer', maybeWrapped({ default: 'main' })], | ||
| 42 | + ['pkgexports/dir2/dir2/trailer', maybeWrapped({ default: 'index' })], | ||
| 43 | + ['pkgexports/a/dir1/dir1', maybeWrapped({ default: 'main' })], | ||
| 44 | + ['pkgexports/a/b/dir1/dir1', maybeWrapped({ default: 'main' })], | ||
| 42 | 45 | ||
| 43 | 46 | // Deprecated: | |
| 44 | 47 | // Double slashes: | |
| 45 | - ['pkgexports/a//dir1/dir1', { default: 'main' }], | ||
| 48 | + ['pkgexports/a//dir1/dir1', maybeWrapped({ default: 'main' })], | ||
| 46 | 49 | // double slash target | |
| 47 | - ['pkgexports/doubleslash', { default: 'asdf' }], | ||
| 50 | + ['pkgexports/doubleslash', maybeWrapped({ default: 'asdf' })], | ||
| 48 | 51 | // Null target with several slashes | |
| 49 | - ['pkgexports/sub//internal/test.js', { default: 'internal only' }], | ||
| 50 | - ['pkgexports/sub//internal//test.js', { default: 'internal only' }], | ||
| 51 | - ['pkgexports/sub/////internal/////test.js', { default: 'internal only' }], | ||
| 52 | + ['pkgexports/sub//internal/test.js', maybeWrapped({ default: 'internal only' })], | ||
| 53 | + ['pkgexports/sub//internal//test.js', maybeWrapped({ default: 'internal only' })], | ||
| 54 | + ['pkgexports/sub/////internal/////test.js', maybeWrapped({ default: 'internal only' })], | ||
| 52 | 55 | // trailing slash | |
| 53 | 56 | ['pkgexports/trailing-pattern-slash/', | |
| 54 | - { default: 'trailing-pattern-slash' }], | ||
| 57 | + maybeWrapped({ default: 'trailing-pattern-slash' })], | ||
| 55 | 58 | ]); | |
| 56 | 59 | ||
| 57 | 60 | if (!isRequire) { | |
@@ -214,11 +217,15 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; | |||
| 214 | 217 | ||
| 215 | 218 | const { requireFromInside, importFromInside } = fromInside; | |
| 216 | 219 | [importFromInside, requireFromInside].forEach((loadFromInside) => { | |
| 220 | + const isRequire = loadFromInside === requireFromInside; | ||
| 221 | + const maybeWrapped = isRequire ? (exports) => exports : | ||
| 222 | + (exports) => ({ ...exports, 'module.exports': exports.default }); | ||
| 223 | + | ||
| 217 | 224 | const validSpecifiers = new Map([ | |
| 218 | 225 | // A file not visible from outside of the package | |
| 219 | - ['../not-exported.js', { default: 'not-exported' }], | ||
| 226 | + ['../not-exported.js', maybeWrapped({ default: 'not-exported' })], | ||
| 220 | 227 | // Part of the public interface | |
| 221 | - ['pkgexports/valid-cjs', { default: 'asdf' }], | ||
| 228 | + ['pkgexports/valid-cjs', maybeWrapped({ default: 'asdf' })], | ||
| 222 | 229 | ]); | |
| 223 | 230 | for (const [validSpecifier, expected] of validSpecifiers) { | |
| 224 | 231 | if (validSpecifier === null) continue; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,23 +9,26 @@ const { requireImport, importImport } = importer; | |||
| 9 | 9 | [requireImport, importImport].forEach((loadFixture) => { | |
| 10 | 10 | const isRequire = loadFixture === requireImport; | |
| 11 | 11 | ||
| 12 | + const maybeWrapped = isRequire ? (exports) => exports : | ||
| 13 | + (exports) => ({ ...exports, 'module.exports': exports.default }); | ||
| 14 | + | ||
| 12 | 15 | const internalImports = new Map([ | |
| 13 | 16 | // Base case | |
| 14 | - ['#test', { default: 'test' }], | ||
| 17 | + ['#test', maybeWrapped({ default: 'test' })], | ||
| 15 | 18 | // import / require conditions | |
| 16 | - ['#branch', { default: isRequire ? 'requirebranch' : 'importbranch' }], | ||
| 19 | + ['#branch', maybeWrapped({ default: isRequire ? 'requirebranch' : 'importbranch' })], | ||
| 17 | 20 | // Subpath imports | |
| 18 | - ['#subpath/x.js', { default: 'xsubpath' }], | ||
| 21 | + ['#subpath/x.js', maybeWrapped({ default: 'xsubpath' })], | ||
| 19 | 22 | // External imports | |
| 20 | - ['#external', { default: 'asdf' }], | ||
| 23 | + ['#external', maybeWrapped({ default: 'asdf' })], | ||
| 21 | 24 | // External subpath imports | |
| 22 | - ['#external/subpath/asdf.js', { default: 'asdf' }], | ||
| 25 | + ['#external/subpath/asdf.js', maybeWrapped({ default: 'asdf' })], | ||
| 23 | 26 | // Trailing pattern imports | |
| 24 | - ['#subpath/asdf.asdf', { default: 'test' }], | ||
| 27 | + ['#subpath/asdf.asdf', maybeWrapped({ default: 'test' })], | ||
| 25 | 28 | // Leading slash | |
| 26 | - ['#subpath//asdf.asdf', { default: 'test' }], | ||
| 29 | + ['#subpath//asdf.asdf', maybeWrapped({ default: 'test' })], | ||
| 27 | 30 | // Double slash | |
| 28 | - ['#subpath/as//df.asdf', { default: 'test' }], | ||
| 31 | + ['#subpath/as//df.asdf', maybeWrapped({ default: 'test' })], | ||
| 29 | 32 | ]); | |
| 30 | 33 | ||
| 31 | 34 | for (const [validSpecifier, expected] of internalImports) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,7 +3,8 @@ import { strictEqual, deepEqual } from 'assert'; | |||
| 3 | 3 | import m, { π } from './exports-cases.js'; | |
| 4 | 4 | import * as ns from './exports-cases.js'; | |
| 5 | 5 | ||
| 6 | - deepEqual(Object.keys(ns), ['?invalid', 'default', 'invalid identifier', 'isObject', 'package', 'z', 'π', '\u{d83c}\u{df10}']); | ||
| 6 | + deepEqual(Object.keys(ns), ['?invalid', 'default', 'invalid identifier', 'isObject', 'module.exports', 'package', 'z', 'π', '\u{d83c}\u{df10}']); | ||
| 7 | + strictEqual(ns['module.exports'], ns.default); | ||
| 7 | 8 | strictEqual(π, 'yes'); | |
| 8 | 9 | strictEqual(typeof m.isObject, 'undefined'); | |
| 9 | 10 | strictEqual(m.π, 'yes'); | |
@@ -21,7 +22,8 @@ strictEqual(typeof m2, 'object'); | |||
| 21 | 22 | strictEqual(m2.default, 'the default'); | |
| 22 | 23 | strictEqual(ns2.__esModule, true); | |
| 23 | 24 | strictEqual(ns2.name, 'name'); | |
| 24 | - deepEqual(Object.keys(ns2), ['__esModule', 'case2', 'default', 'name', 'pi']); | ||
| 25 | + strictEqual(ns2['module.exports'], ns2.default); | ||
| 26 | + deepEqual(Object.keys(ns2), ['__esModule', 'case2', 'default', 'module.exports', 'name', 'pi']); | ||
| 25 | 27 | ||
| 26 | 28 | import m3, { __esModule as __esModule3, name as name3 } from './exports-cases3.js'; | |
| 27 | 29 | import * as ns3 from './exports-cases3.js'; | |
@@ -32,5 +34,6 @@ deepEqual(Object.keys(m3), ['name', 'default', 'pi', 'case2']); | |||
| 32 | 34 | strictEqual(ns3.__esModule, true); | |
| 33 | 35 | strictEqual(ns3.name, 'name'); | |
| 34 | 36 | strictEqual(ns3.case2, 'case2'); | |
| 37 | + strictEqual(ns3['module.exports'], ns3.default); | ||
| 35 | 38 | ||
| 36 | 39 | console.log('ok'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments