Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 36.4k
modules: runtime deprecate subpath folder mappings#35747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
f258111dda53698a517bd0460eb44caaf606bf53bee33c7ac850772aFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -369,6 +369,45 @@ treating the right hand side target pattern as a `**` glob against the list of | ||
| files within the package. Because `node_modules` paths are forbidden in exports | ||
| targets, this expansion is dependent on only the files of the package itself. | ||
| ### Subpath folder mappings | ||
| <!-- YAML | ||
| changes: | ||
| - version: REPLACEME | ||
| pr-url: https://github.com/nodejs/node/pull/35746 | ||
| description: Runtime deprecation. | ||
| - version: v14.13.0 | ||
| pr-url: https://github.com/nodejs/node/pull/34718 | ||
| description: Documentation-only deprecation. | ||
| --> | ||
| > Stability: 0 - Deprecated: Use subpath patterns instead. | ||
| Before subpath patterns were supported, a trailing `"/"` suffix was used to | ||
| support folder mappings: | ||
| ```json | ||
| { | ||
| "exports": { | ||
| "./features/": "./features/" | ||
| } | ||
| } | ||
| ``` | ||
| _This feature will be removed in a future release._ | ||
| Instead, use direct [subpath patterns][]: | ||
guybedford marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ```json | ||
| { | ||
| "exports": { | ||
| "./features/*": "./features/*.js" | ||
| } | ||
| } | ||
| ``` | ||
| The benefit of patterns over folder exports is that packages can always be | ||
| imported by consumers without subpath file extensions being necessary. | ||
| ### Exports sugar | ||
| > Stability: 1 - Experimental | ||
| @@ -1040,5 +1079,6 @@ This field defines [subpath imports][] for the current package. | ||
| [self-reference]: #packages_self_referencing_a_package_using_its_name | ||
| [subpath exports]: #packages_subpath_exports | ||
| [subpath imports]: #packages_subpath_imports | ||
| [subpath patterns]: #packages_subpath_patterns | ||
| [the full specifier path]: esm.md#esm_mandatory_file_extensions | ||
| [the dual CommonJS/ES module packages section]: #packages_dual_commonjs_es_module_packages | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -16,6 +16,7 @@ const { | ||
| String, | ||
| StringPrototypeEndsWith, | ||
| StringPrototypeIndexOf, | ||
| StringPrototypeLastIndexOf, | ||
| StringPrototypeReplace, | ||
| StringPrototypeSlice, | ||
| StringPrototypeSplit, | ||
| @@ -59,6 +60,36 @@ const userConditions = getOptionValue('--conditions'); | ||
| const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]); | ||
| const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS); | ||
| const pendingDeprecation = getOptionValue('--pending-deprecation'); | ||
| const emittedPackageWarnings = new SafeSet(); | ||
| function emitFolderMapDeprecation(match, pjsonUrl, isExports, base) { | ||
| const pjsonPath = fileURLToPath(pjsonUrl); | ||
| if (!pendingDeprecation) { | ||
| const nodeModulesIndex = StringPrototypeLastIndexOf(pjsonPath, | ||
| '/node_modules/'); | ||
| if (nodeModulesIndex !== -1) { | ||
| const afterNodeModulesPath = StringPrototypeSlice(pjsonPath, | ||
| nodeModulesIndex + 14, | ||
| -13); | ||
| try { | ||
| const { packageSubpath } = parsePackageName(afterNodeModulesPath); | ||
| if (packageSubpath === '.') | ||
| return; | ||
| } catch {} | ||
| } | ||
| } | ||
| if (emittedPackageWarnings.has(pjsonPath + '|' + match)) | ||
| return; | ||
| emittedPackageWarnings.add(pjsonPath + '|' + match); | ||
| process.emitWarning( | ||
| `Use of deprecated folder mapping "${match}" in the ${isExports ? | ||
| '"exports"' : '"imports"'} field module resolution of the package at ${ | ||
| pjsonPath}${base ? ` imported from ${fileURLToPath(base)}` : ''}.\n` + | ||
| `Update this package.json to use a subpath pattern like "${match}*".`, | ||
Comment on lines
+85
to
+88
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be reasonable in this message to mention which node versions support subpath patterns, so package authors don't unknowingly make a breaking change? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have version information in the documentation for this under the exports field. Keeping it up to date here might be tricky though, especially with a backport still in progress. It's a good point though - we could possibly delay this landing until the 12 backport is released if you'd prefer. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should print version information here. It should be available in the docs and to the best of my knowledge we d not have that information available in any other deprecation warnings Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair. How likely are other deprecation warnings to cause package breakage compared to this one? Maybe we could at least say "Please check the docs for which node versions support subpath patterns" and link them there? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Subpath patterns are on 14.13, 15 and will be backported to 12 very soon so I'm just not sure what value there is in muddying the advice for users here? The deprecation has a clear action and that is to move away from the feature or upgrade to subpaths. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just because node doesn’t support a version doesn’t mean package authors don’t. It’s obv fine if you don’t want to take my suggestion, but i think the risk of causing breakage is far greater than the risk of someone remaining on slash patterns due to an extra sentence. ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed there is a risk, but my concern is that any advice with respect to being cautious about subpaths support will be outdated by the time most users read it. Even if we weren't backporting to Node.js 12, a deprecation of a feature on 15/16 that points to using a feature only available on 14 seems fine to me and I'm not sure there is precedent for warning users from using a feature (from the web to Node.js) just because it's not available 3 versions back. ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (within the platform warning itself) | ||
| 'DeprecationWarning', | ||
| 'DEP0148' | ||
| ); | ||
| } | ||
| function getConditionsSet(conditions) { | ||
| if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) { | ||
| @@ -507,6 +538,8 @@ function packageExportsResolve( | ||
| conditions); | ||
| if (resolved === null || resolved === undefined) | ||
| throwExportsNotFound(packageSubpath, packageJSONUrl, base); | ||
| if (!pattern) | ||
| emitFolderMapDeprecation(bestMatch, packageJSONUrl, true, base); | ||
| return { resolved, exact: pattern }; | ||
| } | ||
| @@ -556,8 +589,11 @@ function packageImportsResolve(name, base, conditions) { | ||
| const resolved = resolvePackageTarget( | ||
| packageJSONUrl, target, subpath, bestMatch, base, pattern, true, | ||
| conditions); | ||
| if (resolved !== null) | ||
| if (resolved !== null) { | ||
| if (!pattern) | ||
| emitFolderMapDeprecation(bestMatch, packageJSONUrl, false, base); | ||
| return { resolved, exact: pattern }; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| @@ -570,13 +606,7 @@ function getPackageType(url) { | ||
| return packageConfig.type; | ||
| } | ||
| /** | ||
| * @param {string} specifier | ||
| * @param {URL} base | ||
| * @param {Set<string>} conditions | ||
| * @returns {URL} | ||
| */ | ||
| function packageResolve(specifier, base, conditions) { | ||
| function parsePackageName(specifier, base) { | ||
| let separatorIndex = StringPrototypeIndexOf(specifier, '/'); | ||
| let validPackageName = true; | ||
| let isScoped = false; | ||
| @@ -610,6 +640,19 @@ function packageResolve(specifier, base, conditions) { | ||
| const packageSubpath = '.' + (separatorIndex === -1 ? '' : | ||
| StringPrototypeSlice(specifier, separatorIndex)); | ||
| return { packageName, packageSubpath, isScoped }; | ||
| } | ||
| /** | ||
| * @param {string} specifier | ||
| * @param {URL} base | ||
| * @param {Set<string>} conditions | ||
| * @returns {URL} | ||
| */ | ||
| function packageResolve(specifier, base, conditions) { | ||
| const { packageName, packageSubpath, isScoped } = | ||
| parsePackageName(specifier, base); | ||
| // ResolveSelf | ||
| const packageConfig = getPackageScopeConfig(base); | ||
| if (packageConfig.exists) { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Flags: --pending-deprecation | ||
| import { mustCall } from '../common/index.mjs'; | ||
| import assert from 'assert'; | ||
| let curWarning = 0; | ||
| const expectedWarnings = [ | ||
| '"./sub/"', | ||
| '"./fallbackdir/"', | ||
| '"./subpath/"' | ||
| ]; | ||
| process.addListener('warning', mustCall((warning) => { | ||
| assert(warning.stack.includes(expectedWarnings[curWarning++]), warning.stack); | ||
| }, expectedWarnings.length)); | ||
| (async () => { | ||
| await import('./test-esm-exports.mjs'); | ||
| })() | ||
| .catch((err) => console.error(err)); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { mustCall } from '../common/index.mjs'; | ||
| import assert from 'assert'; | ||
| import fixtures from '../common/fixtures.js'; | ||
| import { pathToFileURL } from 'url'; | ||
| const selfDeprecatedFolders = | ||
| fixtures.path('/es-modules/self-deprecated-folders/main.js'); | ||
| let curWarning = 0; | ||
| const expectedWarnings = [ | ||
| '"./" in the "exports" field', | ||
| '"#self/" in the "imports" field' | ||
| ]; | ||
| process.addListener('warning', mustCall((warning) => { | ||
| assert(warning.stack.includes(expectedWarnings[curWarning++]), warning.stack); | ||
| }, expectedWarnings.length)); | ||
| (async () => { | ||
| await import(pathToFileURL(selfDeprecatedFolders)); | ||
| })() | ||
| .catch((err) => console.error(err)); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import 'self/main.js'; | ||
| import '#self/main.js'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "name": "self", | ||
| "type": "module", | ||
| "exports": { | ||
| ".": "./main.js", | ||
| "./": "./" | ||
| }, | ||
| "imports": { | ||
| "#self/": "./" | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.