diff --git a/doc/api/modules.md b/doc/api/modules.md index 56d435115929d8..c60a334a52143d 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -335,9 +335,12 @@ LOAD_PACKAGE_IMPORTS(X, DIR) 1. Find the closest package scope SCOPE to DIR. 2. If no scope was found, return. 3. If the SCOPE/package.json "imports" is null or undefined, return. -4. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), - ["node", "require"]) defined in the ESM resolver. -5. RESOLVE_ESM_MATCH(MATCH). +4. If `--experimental-require-module` is enabled + a. let CONDITIONS = ["node", "require", "module-sync"] + b. Else, let CONDITIONS = ["node", "require"] +5. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), + CONDITIONS) defined in the ESM resolver. +6. RESOLVE_ESM_MATCH(MATCH). LOAD_PACKAGE_EXPORTS(X, DIR) 1. Try to interpret X as a combination of NAME and SUBPATH where the name @@ -346,9 +349,12 @@ LOAD_PACKAGE_EXPORTS(X, DIR) return. 3. Parse DIR/NAME/package.json, and look for "exports" field. 4. If "exports" is null or undefined, return. -5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, - `package.json` "exports", ["node", "require"]) defined in the ESM resolver. -6. RESOLVE_ESM_MATCH(MATCH) +5. If `--experimental-require-module` is enabled + a. let CONDITIONS = ["node", "require", "module-sync"] + b. Else, let CONDITIONS = ["node", "require"] +6. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, + `package.json` "exports", CONDITIONS) defined in the ESM resolver. +7. RESOLVE_ESM_MATCH(MATCH) LOAD_PACKAGE_SELF(X, DIR) 1. Find the closest package scope SCOPE to DIR. diff --git a/doc/api/packages.md b/doc/api/packages.md index 9e55b053e75993..5648f56df68996 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -665,6 +665,10 @@ specific to least specific as conditions should be defined: formats include CommonJS, JSON, native addons, and ES modules if `--experimental-require-module` is enabled. _Always mutually exclusive with `"import"`._ +* `"module-sync"` - matches no matter the package is loaded via `import`, + `import()` or `require()`. The format is expected to be ES modules that does + not contain top-level await in its module graph - if it does, + `ERR_REQUIRE_ASYNC_MODULE` will be thrown when the module is `require()`-ed. * `"default"` - the generic fallback that always matches. Can be a CommonJS or ES module file. _This condition should always come last._ @@ -755,7 +759,7 @@ Any number of custom conditions can be set with repeat flags. ### Community Conditions Definitions -Condition strings other than the `"import"`, `"require"`, `"node"`, +Condition strings other than the `"import"`, `"require"`, `"node"`, `"module-sync"`, `"node-addons"` and `"default"` conditions [implemented in Node.js core](#conditional-exports) are ignored by default. @@ -886,6 +890,17 @@ $ node other.js ## Dual CommonJS/ES module packages + + Prior to the introduction of support for ES modules in Node.js, it was a common pattern for package authors to include both CommonJS and ES module JavaScript sources in their package, with `package.json` [`"main"`][] specifying the @@ -898,7 +913,7 @@ ignores) the top-level `"module"` field. Node.js can now run ES module entry points, and a package can contain both CommonJS and ES module entry points (either via separate specifiers such as `'pkg'` and `'pkg/es-module'`, or both at the same specifier via [Conditional -exports][]). Unlike in the scenario where `"module"` is only used by bundlers, +exports][]). Unlike in the scenario where top-level `"module"` field is only used by bundlers, or ES module files are transpiled into CommonJS on the fly before evaluation by Node.js, the files referenced by the ES module entry point are evaluated as ES modules. diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 0eedf2a728858b..26d0bace6cdd39 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -91,7 +91,7 @@ function underNodeModules(url) { let typelessPackageJsonFilesWarnedAbout; function warnTypelessPackageJsonFile(pjsonPath, url) { typelessPackageJsonFilesWarnedAbout ??= new SafeSet(); - if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { + if (!underNodeModules(url) && !typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { const warning = `Module type of ${url} is not specified and it doesn't parse as CommonJS.\n` + 'Reparsing as ES module because module syntax was detected. This incurs a performance overhead.\n' + `To eliminate this warning, add "type": "module" to ${pjsonPath}.`; diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index d393d4336a0c1e..2799af0f8dd492 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -83,6 +83,9 @@ function initializeDefaultConditions() { ...userConditions, ]); defaultConditionsSet = new SafeSet(defaultConditions); + if (getOptionValue('--experimental-require-module')) { + defaultConditionsSet.add('module-sync'); + } } /** diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index a31c2a74d88b36..156ce76fd0db1a 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -81,6 +81,9 @@ function initializeCjsConditions() { ...addonConditions, ...userConditions, ]); + if (getOptionValue('--experimental-require-module')) { + cjsConditions.add('module-sync'); + } } /** diff --git a/test/es-module/test-import-module-conditional-exports-module.mjs b/test/es-module/test-import-module-conditional-exports-module.mjs new file mode 100644 index 00000000000000..c751996c804529 --- /dev/null +++ b/test/es-module/test-import-module-conditional-exports-module.mjs @@ -0,0 +1,29 @@ +// Flags: --experimental-require-module + +import '../common/index.mjs'; +import assert from 'node:assert'; +import * as staticImport from '../fixtures/es-modules/module-condition/import.mjs'; +import { import as _import } from '../fixtures/es-modules/module-condition/dynamic_import.js'; + +async function dynamicImport(id) { + const result = await _import(id); + return result.resolved; +} + +assert.deepStrictEqual({ ...staticImport }, { + import_module_require: 'import', + module_and_import: 'module', + module_and_require: 'module', + module_import_require: 'module', + module_only: 'module', + module_require_import: 'module', + require_module_import: 'module', +}); + +assert.strictEqual(await dynamicImport('import-module-require'), 'import'); +assert.strictEqual(await dynamicImport('module-and-import'), 'module'); +assert.strictEqual(await dynamicImport('module-and-require'), 'module'); +assert.strictEqual(await dynamicImport('module-import-require'), 'module'); +assert.strictEqual(await dynamicImport('module-only'), 'module'); +assert.strictEqual(await dynamicImport('module-require-import'), 'module'); +assert.strictEqual(await dynamicImport('require-module-import'), 'module'); diff --git a/test/es-module/test-require-module-conditional-exports-module.js b/test/es-module/test-require-module-conditional-exports-module.js new file mode 100644 index 00000000000000..2a9e83869053d3 --- /dev/null +++ b/test/es-module/test-require-module-conditional-exports-module.js @@ -0,0 +1,15 @@ +// Flags: --experimental-require-module +'use strict'; + +require('../common'); +const assert = require('assert'); + +const loader = require('../fixtures/es-modules/module-condition/require.cjs'); + +assert.strictEqual(loader.require('import-module-require').resolved, 'module'); +assert.strictEqual(loader.require('module-and-import').resolved, 'module'); +assert.strictEqual(loader.require('module-and-require').resolved, 'module'); +assert.strictEqual(loader.require('module-import-require').resolved, 'module'); +assert.strictEqual(loader.require('module-only').resolved, 'module'); +assert.strictEqual(loader.require('module-require-import').resolved, 'module'); +assert.strictEqual(loader.require('require-module-import').resolved, 'require'); diff --git a/test/fixtures/es-modules/module-condition/dynamic_import.js b/test/fixtures/es-modules/module-condition/dynamic_import.js new file mode 100644 index 00000000000000..7c4cd42d7037f4 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/dynamic_import.js @@ -0,0 +1,5 @@ +function load(id) { + return import(id); +} + +export { load as import }; diff --git a/test/fixtures/es-modules/module-condition/import.mjs b/test/fixtures/es-modules/module-condition/import.mjs new file mode 100644 index 00000000000000..ae12fbf1429424 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/import.mjs @@ -0,0 +1,7 @@ +export { resolved as import_module_require } from 'import-module-require'; +export { resolved as module_and_import } from 'module-and-import'; +export { resolved as module_and_require } from 'module-and-require'; +export { resolved as module_import_require } from 'module-import-require'; +export { resolved as module_only } from 'module-only'; +export { resolved as module_require_import } from 'module-require-import'; +export { resolved as require_module_import } from 'require-module-import'; diff --git a/test/fixtures/es-modules/module-condition/node_modules/import-module-require/import.js b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/import.js new file mode 100644 index 00000000000000..58f10e20bf3041 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/import.js @@ -0,0 +1 @@ +export const resolved = 'import'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/import-module-require/module.js b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/import-module-require/package.json b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/package.json new file mode 100644 index 00000000000000..46a096c2e7396d --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/package.json @@ -0,0 +1,11 @@ +{ + "type": "module", + "exports": { + "node": { + "import": "./import.js", + "module-sync": "./module.js", + "require": "./require.cjs" + }, + "default": "./module.js" + } +} \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/import-module-require/require.cjs b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/require.cjs new file mode 100644 index 00000000000000..6dd2c2ec97abd6 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/import-module-require/require.cjs @@ -0,0 +1 @@ +exports.resolved = 'require'; diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-and-import/import.js b/test/fixtures/es-modules/module-condition/node_modules/module-and-import/import.js new file mode 100644 index 00000000000000..58f10e20bf3041 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-and-import/import.js @@ -0,0 +1 @@ +export const resolved = 'import'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-and-import/module.js b/test/fixtures/es-modules/module-condition/node_modules/module-and-import/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-and-import/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-and-import/package.json b/test/fixtures/es-modules/module-condition/node_modules/module-and-import/package.json new file mode 100644 index 00000000000000..4425147f7dab2c --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-and-import/package.json @@ -0,0 +1,10 @@ +{ + "type": "module", + "exports": { + "node": { + "module-sync": "./module.js", + "import": "./import.js" + }, + "default": "./module.js" + } +} diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-and-require/module.js b/test/fixtures/es-modules/module-condition/node_modules/module-and-require/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-and-require/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-and-require/package.json b/test/fixtures/es-modules/module-condition/node_modules/module-and-require/package.json new file mode 100644 index 00000000000000..57598fb5014bb0 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-and-require/package.json @@ -0,0 +1,10 @@ +{ + "type": "module", + "exports": { + "node": { + "module-sync": "./module.js", + "require": "./require.cjs" + }, + "default": "./module.js" + } +} \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-and-require/require.cjs b/test/fixtures/es-modules/module-condition/node_modules/module-and-require/require.cjs new file mode 100644 index 00000000000000..6dd2c2ec97abd6 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-and-require/require.cjs @@ -0,0 +1 @@ +exports.resolved = 'require'; diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-import-require/import.js b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/import.js new file mode 100644 index 00000000000000..58f10e20bf3041 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/import.js @@ -0,0 +1 @@ +export const resolved = 'import'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-import-require/module.js b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-import-require/package.json b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/package.json new file mode 100644 index 00000000000000..bbc8c0d286e656 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/package.json @@ -0,0 +1,11 @@ +{ + "type": "module", + "exports": { + "node": { + "module-sync": "./module.js", + "import": "./import.js", + "require": "./require.cjs" + }, + "default": "./module.js" + } +} \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-import-require/require.cjs b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/require.cjs new file mode 100644 index 00000000000000..6dd2c2ec97abd6 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-import-require/require.cjs @@ -0,0 +1 @@ +exports.resolved = 'require'; diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-only/module.js b/test/fixtures/es-modules/module-condition/node_modules/module-only/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-only/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-only/package.json b/test/fixtures/es-modules/module-condition/node_modules/module-only/package.json new file mode 100644 index 00000000000000..2d29bbdf325e1a --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-only/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "exports": { + "module-sync": "./module.js" + } +} diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-require-import/import.js b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/import.js new file mode 100644 index 00000000000000..58f10e20bf3041 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/import.js @@ -0,0 +1 @@ +export const resolved = 'import'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-require-import/module.js b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-require-import/package.json b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/package.json new file mode 100644 index 00000000000000..1490f04b27f9ad --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/package.json @@ -0,0 +1,11 @@ +{ + "type": "module", + "exports": { + "node": { + "module-sync": "./module.js", + "require": "./require.cjs", + "import": "./import.js" + }, + "default": "./module.js" + } +} diff --git a/test/fixtures/es-modules/module-condition/node_modules/module-require-import/require.cjs b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/require.cjs new file mode 100644 index 00000000000000..f5bf7ed32992e1 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/module-require-import/require.cjs @@ -0,0 +1 @@ +export const resolved = 'require'; diff --git a/test/fixtures/es-modules/module-condition/node_modules/require-module-import/import.js b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/import.js new file mode 100644 index 00000000000000..58f10e20bf3041 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/import.js @@ -0,0 +1 @@ +export const resolved = 'import'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/require-module-import/module.js b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/module.js new file mode 100644 index 00000000000000..136ec680a7e3a8 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/module.js @@ -0,0 +1 @@ +export const resolved = 'module'; \ No newline at end of file diff --git a/test/fixtures/es-modules/module-condition/node_modules/require-module-import/package.json b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/package.json new file mode 100644 index 00000000000000..b62f96c997ecb7 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/package.json @@ -0,0 +1,11 @@ +{ + "type": "module", + "exports": { + "node": { + "require": "./require.cjs", + "module-sync": "./module.js", + "import": "./module.js" + }, + "default": "./module.js" + } +} diff --git a/test/fixtures/es-modules/module-condition/node_modules/require-module-import/require.cjs b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/require.cjs new file mode 100644 index 00000000000000..6dd2c2ec97abd6 --- /dev/null +++ b/test/fixtures/es-modules/module-condition/node_modules/require-module-import/require.cjs @@ -0,0 +1 @@ +exports.resolved = 'require'; diff --git a/test/fixtures/es-modules/module-condition/require.cjs b/test/fixtures/es-modules/module-condition/require.cjs new file mode 100644 index 00000000000000..2457758a98f32b --- /dev/null +++ b/test/fixtures/es-modules/module-condition/require.cjs @@ -0,0 +1 @@ +exports.require = require;