GeezerVison is showing github.com — module: implement the "module-sync" exports condition · nodejs/node@0b9249e
File treeExpand file treeCollapse file tree Open diff view settings fixtures/es-modules/module-condition
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/modules.md +12-6Lines changed: 12 additions & 6 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -335,9 +335,12 @@ LOAD_PACKAGE_IMPORTS(X, DIR) | 335 | 335 | 1. Find the closest package scope SCOPE to DIR. | 336 | 336 | 2. If no scope was found, return. | 337 | 337 | 3. If the SCOPE/package.json "imports" is null or undefined, return. | 338 | | -4. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), | 339 | | - ["node", "require"]) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>. | 340 | | -5. RESOLVE_ESM_MATCH(MATCH). | | 338 | +4. If `--experimental-require-module` is enabled | | 339 | + a. let CONDITIONS = ["node", "require", "module-sync"] | | 340 | + b. Else, let CONDITIONS = ["node", "require"] | | 341 | +5. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), | | 342 | + CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>. | | 343 | +6. RESOLVE_ESM_MATCH(MATCH). | 341 | 344 |
| 342 | 345 | LOAD_PACKAGE_EXPORTS(X, DIR) | 343 | 346 | 1. Try to interpret X as a combination of NAME and SUBPATH where the name | @@ -346,9 +349,12 @@ LOAD_PACKAGE_EXPORTS(X, DIR) | 346 | 349 | return. | 347 | 350 | 3. Parse DIR/NAME/package.json, and look for "exports" field. | 348 | 351 | 4. If "exports" is null or undefined, return. | 349 | | -5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, | 350 | | - `package.json` "exports", ["node", "require"]) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>. | 351 | | -6. RESOLVE_ESM_MATCH(MATCH) | | 352 | +5. If `--experimental-require-module` is enabled | | 353 | + a. let CONDITIONS = ["node", "require", "module-sync"] | | 354 | + b. Else, let CONDITIONS = ["node", "require"] | | 355 | +6. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, | | 356 | + `package.json` "exports", CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>. | | 357 | +7. RESOLVE_ESM_MATCH(MATCH) | 352 | 358 |
| 353 | 359 | LOAD_PACKAGE_SELF(X, DIR) | 354 | 360 | 1. Find the closest package scope SCOPE to DIR. | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/packages.md +17-2Lines changed: 17 additions & 2 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -665,6 +665,10 @@ specific to least specific as conditions should be defined: | 665 | 665 | formats include CommonJS, JSON, native addons, and ES modules | 666 | 666 | if `--experimental-require-module` is enabled. _Always mutually | 667 | 667 | exclusive with `"import"`._ | | 668 | +* `"module-sync"` - matches no matter the package is loaded via `import`, | | 669 | + `import()` or `require()`. The format is expected to be ES modules that does | | 670 | + not contain top-level await in its module graph - if it does, | | 671 | + `ERR_REQUIRE_ASYNC_MODULE` will be thrown when the module is `require()`-ed. | 668 | 672 | * `"default"` - the generic fallback that always matches. Can be a CommonJS | 669 | 673 | or ES module file. _This condition should always come last._ | 670 | 674 |
| @@ -755,7 +759,7 @@ Any number of custom conditions can be set with repeat flags. | 755 | 759 |
| 756 | 760 | ### Community Conditions Definitions | 757 | 761 |
| 758 | | -Condition strings other than the `"import"`, `"require"`, `"node"`, | | 762 | +Condition strings other than the `"import"`, `"require"`, `"node"`, `"module-sync"`, | 759 | 763 | `"node-addons"` and `"default"` conditions | 760 | 764 | [implemented in Node.js core](#conditional-exports) are ignored by default. | 761 | 765 |
| @@ -886,6 +890,17 @@ $ node other.js | 886 | 890 |
| 887 | 891 | ## Dual CommonJS/ES module packages | 888 | 892 |
| | 893 | +<!-- This section should not be in the API documentation: | | 894 | +
| | 895 | +1. It teaches opinionated practices that some consider dangerous, see | | 896 | + https://github.com/nodejs/node/issues/52174 | | 897 | +2. It will soon be obsolete when we unflag --experimental-require-module. | | 898 | +3. It's difficult to understand a multi-file structure via long texts and snippets in | | 899 | + a markdown document. | | 900 | +
| | 901 | +TODO(?): Move this section to its own repository with example folders. | | 902 | +--> | | 903 | + | 889 | 904 | Prior to the introduction of support for ES modules in Node.js, it was a common | 890 | 905 | pattern for package authors to include both CommonJS and ES module JavaScript | 891 | 906 | sources in their package, with `package.json` [`"main"`][] specifying the | @@ -898,7 +913,7 @@ ignores) the top-level `"module"` field. | 898 | 913 | Node.js can now run ES module entry points, and a package can contain both | 899 | 914 | CommonJS and ES module entry points (either via separate specifiers such as | 900 | 915 | `'pkg'` and `'pkg/es-module'`, or both at the same specifier via [Conditional | 901 | | -exports][]). Unlike in the scenario where `"module"` is only used by bundlers, | | 916 | +exports][]). Unlike in the scenario where top-level `"module"` field is only used by bundlers, | 902 | 917 | or ES module files are transpiled into CommonJS on the fly before evaluation by | 903 | 918 | Node.js, the files referenced by the ES module entry point are evaluated as ES | 904 | 919 | modules. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/get_format.js +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -91,7 +91,7 @@ function underNodeModules(url) { | 91 | 91 | let typelessPackageJsonFilesWarnedAbout; | 92 | 92 | function warnTypelessPackageJsonFile(pjsonPath, url) { | 93 | 93 | typelessPackageJsonFilesWarnedAbout ??= new SafeSet(); | 94 | | - if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { | | 94 | + if (!underNodeModules(url) && !typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { | 95 | 95 | const warning = `Module type of ${url} is not specified and it doesn't parse as CommonJS.\n` + | 96 | 96 | 'Reparsing as ES module because module syntax was detected. This incurs a performance overhead.\n' + | 97 | 97 | `To eliminate this warning, add "type": "module" to ${pjsonPath}.`; | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/utils.js +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -83,6 +83,9 @@ function initializeDefaultConditions() { | 83 | 83 | ...userConditions, | 84 | 84 | ]); | 85 | 85 | defaultConditionsSet = new SafeSet(defaultConditions); | | 86 | + if (getOptionValue('--experimental-require-module')) { | | 87 | + defaultConditionsSet.add('module-sync'); | | 88 | + } | 86 | 89 | } | 87 | 90 |
| 88 | 91 | /** | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/helpers.js +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -82,6 +82,9 @@ function initializeCjsConditions() { | 82 | 82 | ...addonConditions, | 83 | 83 | ...userConditions, | 84 | 84 | ]); | | 85 | + if (getOptionValue('--experimental-require-module')) { | | 86 | + cjsConditions.add('module-sync'); | | 87 | + } | 85 | 88 | } | 86 | 89 |
| 87 | 90 | /** | |
Collapse file Copy file name to clipboard +29Lines changed: 29 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Flags: --experimental-require-module | | 2 | + | | 3 | +import '../common/index.mjs'; | | 4 | +import assert from 'node:assert'; | | 5 | +import * as staticImport from '../fixtures/es-modules/module-condition/import.mjs'; | | 6 | +import { import as _import } from '../fixtures/es-modules/module-condition/dynamic_import.js'; | | 7 | + | | 8 | +async function dynamicImport(id) { | | 9 | + const result = await _import(id); | | 10 | + return result.resolved; | | 11 | +} | | 12 | + | | 13 | +assert.deepStrictEqual({ ...staticImport }, { | | 14 | + import_module_require: 'import', | | 15 | + module_and_import: 'module', | | 16 | + module_and_require: 'module', | | 17 | + module_import_require: 'module', | | 18 | + module_only: 'module', | | 19 | + module_require_import: 'module', | | 20 | + require_module_import: 'module', | | 21 | +}); | | 22 | + | | 23 | +assert.strictEqual(await dynamicImport('import-module-require'), 'import'); | | 24 | +assert.strictEqual(await dynamicImport('module-and-import'), 'module'); | | 25 | +assert.strictEqual(await dynamicImport('module-and-require'), 'module'); | | 26 | +assert.strictEqual(await dynamicImport('module-import-require'), 'module'); | | 27 | +assert.strictEqual(await dynamicImport('module-only'), 'module'); | | 28 | +assert.strictEqual(await dynamicImport('module-require-import'), 'module'); | | 29 | +assert.strictEqual(await dynamicImport('require-module-import'), 'module'); |
Collapse file Copy file name to clipboard +15Lines changed: 15 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Flags: --experimental-require-module | | 2 | +'use strict'; | | 3 | + | | 4 | +require('../common'); | | 5 | +const assert = require('assert'); | | 6 | + | | 7 | +const loader = require('../fixtures/es-modules/module-condition/require.cjs'); | | 8 | + | | 9 | +assert.strictEqual(loader.require('import-module-require').resolved, 'module'); | | 10 | +assert.strictEqual(loader.require('module-and-import').resolved, 'module'); | | 11 | +assert.strictEqual(loader.require('module-and-require').resolved, 'module'); | | 12 | +assert.strictEqual(loader.require('module-import-require').resolved, 'module'); | | 13 | +assert.strictEqual(loader.require('module-only').resolved, 'module'); | | 14 | +assert.strictEqual(loader.require('module-require-import').resolved, 'module'); | | 15 | +assert.strictEqual(loader.require('require-module-import').resolved, 'require'); |
Collapse file Copy file name to clipboard +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +function load(id) { | | 2 | + return import(id); | | 3 | +} | | 4 | + | | 5 | +export { load as import }; |
Collapse file Copy file name to clipboard +7Lines changed: 7 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +export { resolved as import_module_require } from 'import-module-require'; | | 2 | +export { resolved as module_and_import } from 'module-and-import'; | | 3 | +export { resolved as module_and_require } from 'module-and-require'; | | 4 | +export { resolved as module_import_require } from 'module-import-require'; | | 5 | +export { resolved as module_only } from 'module-only'; | | 6 | +export { resolved as module_require_import } from 'module-require-import'; | | 7 | +export { resolved as require_module_import } from 'require-module-import'; |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/es-modules/module-condition/node_modules/import-module-require/import.js +1Lines changed: 1 addition & 0 deletions
|