diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index d5ab77879497df..e67a687b2144ef 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -2726,10 +2726,28 @@ settings set when the Node.js binary was compiled. However, the property has been mutable by user code making it impossible to rely on. The ability to change the value has been deprecated and will be disabled in the future. +### DEP0XXX: Main index lookup and extension searching + + +Type: Documentation-only (supports [`--pending-deprecation`][]) + +Previously, `index.js` and extension searching lookups would apply to +`import 'pkg'` main entry point resolution, even when resolving ES modules. + +With this deprecation, all ES module main entry point resolutions require +an explicit [`"exports"` or `"main"` entry][] with the exact file extension. + [Legacy URL API]: url.md#url_legacy_url_api [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3 [WHATWG URL API]: url.md#url_the_whatwg_url_api +[`"exports"` or `"main"` entry]: packages.md#packages_main_entry_point_export [`--pending-deprecation`]: cli.md#cli_pending_deprecation [`--throw-deprecation`]: cli.md#cli_throw_deprecation [`--unhandled-rejections`]: cli.md#cli_unhandled_rejections_mode @@ -2852,7 +2870,7 @@ change the value has been deprecated and will be disabled in the future. [from_string_encoding]: buffer.md#buffer_static_method_buffer_from_string_encoding [legacy `urlObject`]: url.md#url_legacy_urlobject [static methods of `crypto.Certificate()`]: crypto.md#crypto_class_certificate -[subpath exports]: #packages_subpath_exports -[subpath folder mappings]: #packages_subpath_folder_mappings -[subpath imports]: #packages_subpath_imports -[subpath patterns]: #packages_subpath_patterns +[subpath exports]: packages.md#packages_subpath_exports +[subpath folder mappings]: packages.md#packages_subpath_folder_mappings +[subpath imports]: packages.md#packages_subpath_imports +[subpath patterns]: packages.md#packages_subpath_patterns diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 5a578902cbb01a..d3867a07b1336b 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -90,6 +90,36 @@ function emitFolderMapDeprecation(match, pjsonUrl, isExports, base) { ); } +function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { + if (!pendingDeprecation) + return; + const { format } = defaultGetFormat(url); + if (format !== 'module') + return; + const path = fileURLToPath(url); + const pkgPath = fileURLToPath(new URL('.', packageJSONUrl)); + const basePath = fileURLToPath(base); + if (main) + process.emitWarning( + `Package ${pkgPath} has a "main" field set to ${JSONStringify(main)}, ` + + `excluding the full filename and extension to the resolved file at "${ + StringPrototypeSlice(path, pkgPath.length)}", imported from ${ + basePath}.\n Automatic extension resolution of the "main" field is` + + 'deprecated for ES modules.', + 'DeprecationWarning', + 'DEP0150' + ); + else + process.emitWarning( + `No "main" or "exports" field defined in the package.json for ${pkgPath + } resolving the main entry point "${ + StringPrototypeSlice(path, pkgPath.length)}", imported from ${basePath + }.\nDefault "index" lookups for the main are deprecated for ES modules.`, + 'DeprecationWarning', + 'DEP0150' + ); +} + function getConditionsSet(conditions) { if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) { if (!ArrayIsArray(conditions)) { @@ -209,41 +239,33 @@ function legacyMainResolve(packageJSONUrl, packageConfig, base) { if (fileExists(guess = new URL(`./${packageConfig.main}`, packageJSONUrl))) { return guess; - } - if (fileExists(guess = new URL(`./${packageConfig.main}.js`, - packageJSONUrl))) { - return guess; - } - if (fileExists(guess = new URL(`./${packageConfig.main}.json`, - packageJSONUrl))) { - return guess; - } - if (fileExists(guess = new URL(`./${packageConfig.main}.node`, - packageJSONUrl))) { - return guess; - } - if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`, - packageJSONUrl))) { - return guess; - } - if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`, - packageJSONUrl))) { - return guess; - } - if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`, - packageJSONUrl))) { + } else if (fileExists(guess = new URL(`./${packageConfig.main}.js`, + packageJSONUrl))); + else if (fileExists(guess = new URL(`./${packageConfig.main}.json`, + packageJSONUrl))); + else if (fileExists(guess = new URL(`./${packageConfig.main}.node`, + packageJSONUrl))); + else if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`, + packageJSONUrl))); + else if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`, + packageJSONUrl))); + else if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`, + packageJSONUrl))); + else guess = undefined; + if (guess) { + emitLegacyIndexDeprecation(guess, packageJSONUrl, base, + packageConfig.main); return guess; } // Fallthrough. } - if (fileExists(guess = new URL('./index.js', packageJSONUrl))) { - return guess; - } + if (fileExists(guess = new URL('./index.js', packageJSONUrl))); // So fs. - if (fileExists(guess = new URL('./index.json', packageJSONUrl))) { - return guess; - } - if (fileExists(guess = new URL('./index.node', packageJSONUrl))) { + else if (fileExists(guess = new URL('./index.json', packageJSONUrl))); + else if (fileExists(guess = new URL('./index.node', packageJSONUrl))); + else guess = undefined; + if (guess) { + emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main); return guess; } // Not found. @@ -891,3 +913,6 @@ module.exports = { packageExportsResolve, packageImportsResolve }; + +// cycle +const { defaultGetFormat } = require('internal/modules/esm/get_format'); diff --git a/test/es-module/test-esm-exports-pending-deprecations.mjs b/test/es-module/test-esm-exports-pending-deprecations.mjs index edc9ae814a59a8..ea7665fceb430d 100644 --- a/test/es-module/test-esm-exports-pending-deprecations.mjs +++ b/test/es-module/test-esm-exports-pending-deprecations.mjs @@ -6,7 +6,9 @@ let curWarning = 0; const expectedWarnings = [ '"./sub/"', '"./fallbackdir/"', - '"./subpath/"' + '"./subpath/"', + 'no_exports', + 'default_index' ]; process.addListener('warning', mustCall((warning) => { diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index d234099732e3aa..f153b46e321638 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -35,7 +35,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; ['pkgexports-sugar', { default: 'main' }], // Path patterns ['pkgexports/subpath/sub-dir1', { default: 'main' }], - ['pkgexports/features/dir1', { default: 'main' }] + ['pkgexports/features/dir1', { default: 'main' }], ]); if (isRequire) { @@ -44,6 +44,11 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; validSpecifiers.set('pkgexports/subpath/dir1/', { default: 'main' }); validSpecifiers.set('pkgexports/subpath/dir2', { default: 'index' }); validSpecifiers.set('pkgexports/subpath/dir2/', { default: 'index' }); + } else { + // No exports or main field + validSpecifiers.set('no_exports', { default: 'index' }); + // Main field without extension + validSpecifiers.set('default_index', { default: 'main' }); } for (const [validSpecifier, expected] of validSpecifiers) { diff --git a/test/fixtures/node_modules/default_index/index.js b/test/fixtures/node_modules/default_index/index.js new file mode 100644 index 00000000000000..748e47637e97fa --- /dev/null +++ b/test/fixtures/node_modules/default_index/index.js @@ -0,0 +1 @@ +export default 'main' diff --git a/test/fixtures/node_modules/default_index/package.json b/test/fixtures/node_modules/default_index/package.json new file mode 100644 index 00000000000000..7665d7a8037428 --- /dev/null +++ b/test/fixtures/node_modules/default_index/package.json @@ -0,0 +1,4 @@ +{ + "main": "index", + "type": "module" +} diff --git a/test/fixtures/node_modules/no_exports/index.js b/test/fixtures/node_modules/no_exports/index.js new file mode 100644 index 00000000000000..8d97dd4b70ce59 --- /dev/null +++ b/test/fixtures/node_modules/no_exports/index.js @@ -0,0 +1 @@ +export default 'index' diff --git a/test/fixtures/node_modules/no_exports/package.json b/test/fixtures/node_modules/no_exports/package.json new file mode 100644 index 00000000000000..3dbc1ca591c055 --- /dev/null +++ b/test/fixtures/node_modules/no_exports/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +}