Skip to content

Commit

Permalink
esm: deprecate legacy main lookup for modules
Browse files Browse the repository at this point in the history
PR-URL: #36918
Reviewed-By: Antoine du Hamel <[email protected]>
  • Loading branch information
guybedford authored and targos committed Feb 2, 2021
1 parent b79b82d commit 02f1d2f
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 36 deletions.
26 changes: 22 additions & 4 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2699,10 +2699,28 @@ resolutions not in `node_modules`. This means there will not be deprecation
warnings for `"exports"` in dependencies. With `--pending-deprecation`, a
runtime warning results no matter where the `"exports"` usage occurs.

### 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
Expand Down Expand Up @@ -2823,7 +2841,7 @@ runtime warning results no matter where the `"exports"` usage occurs.
[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
85 changes: 55 additions & 30 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -891,3 +913,6 @@ module.exports = {
packageExportsResolve,
packageImportsResolve
};

// cycle
const { defaultGetFormat } = require('internal/modules/esm/get_format');
4 changes: 3 additions & 1 deletion test/es-module/test-esm-exports-pending-deprecations.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ let curWarning = 0;
const expectedWarnings = [
'"./sub/"',
'"./fallbackdir/"',
'"./subpath/"'
'"./subpath/"',
'no_exports',
'default_index'
];

process.addListener('warning', mustCall((warning) => {
Expand Down
7 changes: 6 additions & 1 deletion test/es-module/test-esm-exports.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/node_modules/default_index/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/fixtures/node_modules/default_index/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/node_modules/no_exports/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/node_modules/no_exports/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 02f1d2f

Please sign in to comment.