From 56b0b0f5dbd226ddb647eae23697187cce3cbeb4 Mon Sep 17 00:00:00 2001 From: David Michon Date: Fri, 10 Jan 2025 19:57:53 +0000 Subject: [PATCH 1/2] [workspace-resolve] Allow configuration Monomorphic request object. --- ...resolver-cache-fixes_2025-01-10-19-44.json | 10 +++ .../webpack-workspace-resolve-plugin.api.md | 3 +- .../src/KnownDescriptionFilePlugin.ts | 21 ++++- .../src/KnownPackageDependenciesPlugin.ts | 19 +++-- .../src/WorkspaceResolvePlugin.ts | 85 +++++++++++-------- .../tsconfig.json | 2 +- 6 files changed, 93 insertions(+), 47 deletions(-) create mode 100644 common/changes/@rushstack/webpack-workspace-resolve-plugin/resolver-cache-fixes_2025-01-10-19-44.json diff --git a/common/changes/@rushstack/webpack-workspace-resolve-plugin/resolver-cache-fixes_2025-01-10-19-44.json b/common/changes/@rushstack/webpack-workspace-resolve-plugin/resolver-cache-fixes_2025-01-10-19-44.json new file mode 100644 index 00000000000..75823229005 --- /dev/null +++ b/common/changes/@rushstack/webpack-workspace-resolve-plugin/resolver-cache-fixes_2025-01-10-19-44.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/webpack-workspace-resolve-plugin", + "comment": "(BREAKING CHANGE) Switch constructor to an options object. Add option to specify which webpack resolvers to apply the plugin to. Improve performance by using an object literal instead of the spread operator when updating the resolve request. Upgrade compilation target to not polyfill optional chaining.", + "type": "minor" + } + ], + "packageName": "@rushstack/webpack-workspace-resolve-plugin" +} \ No newline at end of file diff --git a/common/reviews/api/webpack-workspace-resolve-plugin.api.md b/common/reviews/api/webpack-workspace-resolve-plugin.api.md index cfa818c83ca..ef0216174c9 100644 --- a/common/reviews/api/webpack-workspace-resolve-plugin.api.md +++ b/common/reviews/api/webpack-workspace-resolve-plugin.api.md @@ -41,6 +41,7 @@ export interface IWorkspaceLayoutCacheOptions { // @beta export interface IWorkspaceResolvePluginOptions { cache: WorkspaceLayoutCache; + resolverNames?: Iterable; } // @beta @@ -58,7 +59,7 @@ export class WorkspaceLayoutCache { // @beta export class WorkspaceResolvePlugin implements WebpackPluginInstance { - constructor(cache: WorkspaceLayoutCache); + constructor(options: IWorkspaceResolvePluginOptions); // (undocumented) apply(compiler: Compiler): void; } diff --git a/webpack/webpack-workspace-resolve-plugin/src/KnownDescriptionFilePlugin.ts b/webpack/webpack-workspace-resolve-plugin/src/KnownDescriptionFilePlugin.ts index 3c3eb6a5bc0..9fd920cb6a0 100644 --- a/webpack/webpack-workspace-resolve-plugin/src/KnownDescriptionFilePlugin.ts +++ b/webpack/webpack-workspace-resolve-plugin/src/KnownDescriptionFilePlugin.ts @@ -96,12 +96,27 @@ export class KnownDescriptionFilePlugin { // Store the resolver context since a WeakMap lookup is cheaper than walking the tree again contextForPackage.set(descriptionFileData, match); + // Using the object literal is an order of magnitude faster, at least on node 18.19.1 const obj: ResolveRequest = { - ...request, - descriptionFileRoot, + path: request.path, + context: request.context, descriptionFilePath, + descriptionFileRoot, descriptionFileData, - relativePath + relativePath, + ignoreSymlinks: request.ignoreSymlinks, + fullySpecified: request.fullySpecified, + __innerRequest: request.__innerRequest, + __innerRequest_request: request.__innerRequest_request, + __innerRequest_relativePath: request.__innerRequest_relativePath, + + request: request.request, + query: request.query, + fragment: request.fragment, + module: request.module, + directory: request.directory, + file: request.file, + internal: request.internal }; // Delegate to the resolver step at `target`. diff --git a/webpack/webpack-workspace-resolve-plugin/src/KnownPackageDependenciesPlugin.ts b/webpack/webpack-workspace-resolve-plugin/src/KnownPackageDependenciesPlugin.ts index d5ad882d338..cfbf33abd5c 100644 --- a/webpack/webpack-workspace-resolve-plugin/src/KnownPackageDependenciesPlugin.ts +++ b/webpack/webpack-workspace-resolve-plugin/src/KnownPackageDependenciesPlugin.ts @@ -80,16 +80,25 @@ export class KnownPackageDependenciesPlugin { (remainingPath.length > 1 && cache.normalizeToSlash?.(remainingPath)) || remainingPath; const { descriptionFileRoot } = dependency.value; const obj: ResolveRequest = { - ...request, path: descriptionFileRoot, + context: request.context, + descriptionFilePath: `${descriptionFileRoot}${cache.resolverPathSeparator}package.json`, descriptionFileRoot, descriptionFileData: undefined, - descriptionFilePath: `${descriptionFileRoot}${cache.resolverPathSeparator}package.json`, + relativePath, + ignoreSymlinks: request.ignoreSymlinks, + fullySpecified, + __innerRequest: request.__innerRequest, + __innerRequest_request: request.__innerRequest_request, + __innerRequest_relativePath: request.__innerRequest_relativePath, - relativePath: relativePath, request: relativePath, - fullySpecified, - module: false + query: request.query, + fragment: request.fragment, + module: false, + directory: request.directory, + file: request.file, + internal: request.internal }; // eslint-disable-next-line @rushstack/no-new-null resolver.doResolve(target, obj, null, resolveContext, callback); diff --git a/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts b/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts index 6cbd3be6ca9..1b993acc420 100644 --- a/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts +++ b/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { WebpackPluginInstance, Compiler } from 'webpack'; +import type { WebpackPluginInstance, Compiler, ResolveOptions } from 'webpack'; import type { WorkspaceLayoutCache } from './WorkspaceLayoutCache'; import { KnownDescriptionFilePlugin } from './KnownDescriptionFilePlugin'; @@ -17,6 +17,12 @@ export interface IWorkspaceResolvePluginOptions { * The cache of workspace layout information. */ cache: WorkspaceLayoutCache; + + /** + * Which webpack resolvers to apply the plugin to. + * @default ['normal'] + */ + resolverNames?: Iterable; } /** @@ -26,48 +32,53 @@ export interface IWorkspaceResolvePluginOptions { */ export class WorkspaceResolvePlugin implements WebpackPluginInstance { private readonly _cache: WorkspaceLayoutCache; + private readonly _resolverNames: Set; - public constructor(cache: WorkspaceLayoutCache) { - this._cache = cache; + public constructor(options: IWorkspaceResolvePluginOptions) { + this._cache = options.cache; + this._resolverNames = new Set(options.resolverNames ?? ['normal']); } public apply(compiler: Compiler): void { - compiler.resolverFactory.hooks.resolveOptions - .for('normal') - .tap(WorkspaceResolvePlugin.name, (resolveOptions) => { - // Omit default `node_modules` - if (resolveOptions.modules) { - resolveOptions.modules = resolveOptions.modules.filter((modulePath: string) => { - return modulePath !== 'node_modules'; - }); - } else { - resolveOptions.modules = []; - } + const cache: WorkspaceLayoutCache = this._cache; - const cache: WorkspaceLayoutCache = this._cache; + function handler(resolveOptions: ResolveOptions): ResolveOptions { + // Omit default `node_modules` + if (resolveOptions.modules) { + resolveOptions.modules = resolveOptions.modules.filter((modulePath: string) => { + return modulePath !== 'node_modules'; + }); + } else { + resolveOptions.modules = []; + } - resolveOptions.plugins ??= []; - resolveOptions.plugins.push( - // Optimize identifying the package.json file for the issuer - new KnownDescriptionFilePlugin(cache, 'before-parsed-resolve', 'described-resolve'), - // Optimize locating the installed dependencies of the current package - new KnownPackageDependenciesPlugin(cache, 'before-raw-module', 'resolve-as-module'), - // Optimize loading the package.json file for the destination package (bare specifier) - new KnownDescriptionFilePlugin(cache, 'before-resolve-as-module', 'resolve-in-package'), - // Optimize loading the package.json file for the destination package (relative path) - new KnownDescriptionFilePlugin(cache, 'before-relative', 'described-relative'), - // Optimize locating and loading nested package.json for a directory - new KnownDescriptionFilePlugin( - cache, - 'before-undescribed-existing-directory', - 'existing-directory', - true - ), - // Optimize locating and loading nested package.json for a file - new KnownDescriptionFilePlugin(cache, 'before-undescribed-raw-file', 'raw-file') - ); + resolveOptions.plugins ??= []; + resolveOptions.plugins.push( + // Optimize identifying the package.json file for the issuer + new KnownDescriptionFilePlugin(cache, 'before-parsed-resolve', 'described-resolve'), + // Optimize locating the installed dependencies of the current package + new KnownPackageDependenciesPlugin(cache, 'before-raw-module', 'resolve-as-module'), + // Optimize loading the package.json file for the destination package (bare specifier) + new KnownDescriptionFilePlugin(cache, 'before-resolve-as-module', 'resolve-in-package'), + // Optimize loading the package.json file for the destination package (relative path) + new KnownDescriptionFilePlugin(cache, 'before-relative', 'described-relative'), + // Optimize locating and loading nested package.json for a directory + new KnownDescriptionFilePlugin( + cache, + 'before-undescribed-existing-directory', + 'existing-directory', + true + ), + // Optimize locating and loading nested package.json for a file + new KnownDescriptionFilePlugin(cache, 'before-undescribed-raw-file', 'raw-file') + ); - return resolveOptions; - }); + return resolveOptions; + } + for (const resolverName of this._resolverNames) { + compiler.resolverFactory.hooks.resolveOptions + .for(resolverName) + .tap(WorkspaceResolvePlugin.name, handler); + } } } diff --git a/webpack/webpack-workspace-resolve-plugin/tsconfig.json b/webpack/webpack-workspace-resolve-plugin/tsconfig.json index 97c868d4e4d..c4bf22522fe 100644 --- a/webpack/webpack-workspace-resolve-plugin/tsconfig.json +++ b/webpack/webpack-workspace-resolve-plugin/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json", "compilerOptions": { - "target": "ES2019", + "target": "ES2020", "types": ["heft-jest", "node"] } } From 1aadc8e8ede935966bada36a09430b25dcb06eef Mon Sep 17 00:00:00 2001 From: David Michon Date: Fri, 10 Jan 2025 20:48:25 +0000 Subject: [PATCH 2/2] Update default --- .../src/WorkspaceResolvePlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts b/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts index 1b993acc420..a68a97aa5db 100644 --- a/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts +++ b/webpack/webpack-workspace-resolve-plugin/src/WorkspaceResolvePlugin.ts @@ -20,7 +20,7 @@ export interface IWorkspaceResolvePluginOptions { /** * Which webpack resolvers to apply the plugin to. - * @default ['normal'] + * @defaultValue ['normal', 'context', 'loader'] */ resolverNames?: Iterable; } @@ -36,7 +36,7 @@ export class WorkspaceResolvePlugin implements WebpackPluginInstance { public constructor(options: IWorkspaceResolvePluginOptions) { this._cache = options.cache; - this._resolverNames = new Set(options.resolverNames ?? ['normal']); + this._resolverNames = new Set(options.resolverNames ?? ['normal', 'context', 'loader']); } public apply(compiler: Compiler): void {