Skip to content

feat: monomorphic ResolveRequest object#445

Open
dmichon-msft wants to merge 3 commits intowebpack:mainfrom
dmichon-msft:monomorphic-context
Open

feat: monomorphic ResolveRequest object#445
dmichon-msft wants to merge 3 commits intowebpack:mainfrom
dmichon-msft:monomorphic-context

Conversation

@dmichon-msft
Copy link
Copy Markdown
Contributor

@dmichon-msft dmichon-msft commented Jan 10, 2025

Improves performance all-up (especially in ParsePlugin, JoinRequestPlugin, JoinRequestPartPlugin) by ensuring that the ResolveRequest object always has a constant shape.

Note that this is technically a breaking change to plugin authors, if they took a dependency on the ability to attach arbitrary properties to the ResolveRequest object to pass them between plugins and out of the resolver, instead of using the context property.

Additionally, creating the object via a direct object literal is significantly faster than creating it via object spread.

Testing on NodeJS 18.19.1, this had the following impact for a local webpack build.
Before:
image
image

After:
image
image

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 5 out of 20 changed files in this pull request and generated 1 comment.

Files not reviewed (15)
  • lib/ResolverFactory.js: Evaluated as low risk
  • lib/ModulesInHierarchicalDirectoriesPlugin.js: Evaluated as low risk
  • lib/SymlinkPlugin.js: Evaluated as low risk
  • lib/ModulesInRootPlugin.js: Evaluated as low risk
  • lib/AppendPlugin.js: Evaluated as low risk
  • lib/DescriptionFilePlugin.js: Evaluated as low risk
  • lib/JoinRequestPartPlugin.js: Evaluated as low risk
  • lib/ExportsFieldPlugin.js: Evaluated as low risk
  • lib/AliasFieldPlugin.js: Evaluated as low risk
  • lib/ResultPlugin.js: Evaluated as low risk
  • lib/AliasPlugin.js: Evaluated as low risk
  • lib/RootsPlugin.js: Evaluated as low risk
  • lib/ImportsFieldPlugin.js: Evaluated as low risk
  • lib/CloneBasenamePlugin.js: Evaluated as low risk
  • lib/JoinRequestPlugin.js: Evaluated as low risk
Comments suppressed due to low confidence (1)

lib/ParsePlugin.js:55

  • The logic for setting the 'fragment' property might not correctly handle cases where both 'parsed.fragment' and 'request.fragment' are falsy. Consider using 'parsed.fragment ?? request.fragment' to handle these cases correctly.
fragment: parsed.fragment || request.fragment || parsed.fragment,

Comment thread lib/ParsePlugin.js
Copy link
Copy Markdown
Member

@alexander-akait alexander-akait left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmichon-msft Yeah, I am afraid it is a breaking change...

In webpack we have such things - https://github.com/webpack/webpack/blob/main/lib/cache/ResolverCachePlugin.js#L147 and https://github.com/webpack/webpack/blob/main/lib/cache/ResolverCachePlugin.js#L150

Technically we can solve this and add a special place to store such things.

I have only remembered one place where we use it, maybe somewhere else, but it is quite possible that this is the only place

So let's try to solve it step by step

  1. Implement special place to store custom values for authors (maybe just Map?)
  2. Make internal helper for request generation and implement an option to back compat
  3. Rewrite it in webpack and test it

Alternative solution, maybe even better, but need to check perf

  1. Check request in factory (we can use Symbol isPure) - if no unknown properties it is pure (we known them), otherwise false
  2. Make internal helper for creating request - if it isPure request we can use your logic and get perf improvement, if no just use olg logic
  3. (Optional) And also provide a place to store custom values for authors, because to bring such improvements to webpack

So we will keep old behaviour and have perf improvement for some places

@dmichon-msft
Copy link
Copy Markdown
Contributor Author

@dmichon-msft Yeah, I am afraid it is a breaking change...

In webpack we have such things - https://github.com/webpack/webpack/blob/main/lib/cache/ResolverCachePlugin.js#L147 and https://github.com/webpack/webpack/blob/main/lib/cache/ResolverCachePlugin.js#L150

Technically we can solve this and add a special place to store such things.

I have only remembered one place where we use it, maybe somewhere else, but it is quite possible that this is the only place

So let's try to solve it step by step

  1. Implement special place to store custom values for authors (maybe just Map?)
  2. Make internal helper for request generation and implement an option to back compat
  3. Rewrite it in webpack and test it

Alternative solution, maybe even better, but need to check perf

  1. Check request in factory (we can use Symbol isPure) - if no unknown properties it is pure (we known them), otherwise false
  2. Make internal helper for creating request - if it isPure request we can use your logic and get perf improvement, if no just use olg logic
  3. (Optional) And also provide a place to store custom values for authors, because to bring such improvements to webpack

So we will keep old behaviour and have perf improvement for some places

The context property is already a completely opaque object that is forwarded through the request chain without being inspected or modified in any way, so we ought to be able to just use that for the ResolverCachePlugin scenario.

@dmichon-msft
Copy link
Copy Markdown
Contributor Author

I have some thoughts on ways to make this behavior be a resolver option, will refactor.

@alexander-akait
Copy link
Copy Markdown
Member

Yeah, an option is a good way here, also maybe we should add a place where we can store custom values, so after merge this we can update logic inside webpack and get perf (we need a place where to store custom values inside webpack for caches)

alexander-akait pushed a commit that referenced this pull request Apr 16, 2026
Rewrites the `{ ...request, ... }` spread pattern used across every
request-producing plugin into a shared `cloneRequest` helper that writes
the canonical ResolveRequest fields as a fixed-order object literal.
V8 can then cache a single hidden class for the result, matching the
perf win from #445 (ParsePlugin, JoinRequestPlugin, JoinRequestPartPlugin)
without dropping unknown own properties — after the literal, any extra
string or symbol keys on the original request are copied over, so
plugin authors (e.g. webpack's ResolverCachePlugin) that attach custom
state to the request keep working unchanged.
alexander-akait pushed a commit that referenced this pull request Apr 17, 2026
…atible)

Re-implements the perf wins from PR #445 (constant-shape ResolveRequest
objects so V8 keeps a single hidden class on the resolver hot path)
without the breaking change for plugin authors that attach custom
properties directly to a request.

Each plugin now builds the new ResolveRequest as an explicit object
literal in a fixed property order. A new `lib/util/cloneRequest.js`
helper exports `copyExtras(target, source)` which copies any
non-canonical own string keys plus any symbol keys from the source
request, preserving the historical behavior of `{ ...request, ... }`
for callers that pass through values via custom keys (e.g. webpack's
ResolverCachePlugin).

ParsePlugin keeps its public `(source, requestOptions, target)`
constructor; the partial overrides are applied via `Object.assign`
after the canonical literal so the `{ fullySpecified }` use case used
by ResolverFactory does not transition the hidden class. MainFieldPlugin
keeps its `alreadyTriedMainField` Symbol-based recursion guard, set
after `copyExtras` so the current attempt's value always wins.

Adds a regression test in `test/plugins.test.js` that taps the resolve
hook to attach both a string-keyed and a Symbol-keyed property and
asserts both survive through the plugin chain to `hooks.result`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants