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 <duhamelantoine1995@gmail.com>
  • Loading branch information
guybedford committed Jan 30, 2021
1 parent fd02dac commit 255d633
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 @@ -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
Expand Down Expand Up @@ -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
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 255d633

Please sign in to comment.