esm: deprecate legacy main lookup for modules

PR-URL: https://github.com/nodejs/node/pull/36918
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Guy Bedford 2021-01-13 16:09:17 -05:00
parent fd02dace6f
commit 255d6337cd
8 changed files with 95 additions and 36 deletions

View File

@ -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
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/36918
description: Documentation-only deprecation
with `--pending-deprecation` support.
-->
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

View File

@ -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');

View File

@ -6,7 +6,9 @@ let curWarning = 0;
const expectedWarnings = [
'"./sub/"',
'"./fallbackdir/"',
'"./subpath/"'
'"./subpath/"',
'no_exports',
'default_index'
];
process.addListener('warning', mustCall((warning) => {

View File

@ -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) {

1
test/fixtures/node_modules/default_index/index.js generated vendored Normal file
View File

@ -0,0 +1 @@
export default 'main'

View File

@ -0,0 +1,4 @@
{
"main": "index",
"type": "module"
}

1
test/fixtures/node_modules/no_exports/index.js generated vendored Normal file
View File

@ -0,0 +1 @@
export default 'index'

3
test/fixtures/node_modules/no_exports/package.json generated vendored Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}