Skip to content

fix: support no-expression template literals in computed member access - #20889

Merged
alexander-akait merged 1 commit into
mainfrom
claude/fix-pr-20607-6FBRi
Apr 29, 2026
Merged

alexander-akait merged 1 commit into
mainfrom
claude/fix-pr-20607-6FBRi

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Apr 29, 2026 •

Copy link
Copy Markdown
Member

fixes #20607

`import.meta[`url`]` was falling through to runtime member access while
`import.meta["url"]` was statically resolved. Both are statically
analyzable strings — the only difference is the AST node type
(TemplateLiteral vs Literal).

In `extractMemberExpressionChain`, accept a TemplateLiteral with no
expressions in addition to a Literal, pushing `quasis[0].value.cooked`.
BinaryExpression and other arbitrary expressions are intentionally NOT
evaluated here — concatenations like `obj["c" + "lass"]` are sometimes
written specifically to opt out of webpack's static analysis (see
`test/configCases/css/css-modules/use-style.js`), so evaluating them
would change semantics.

In `walkMemberExpressionWithExpressionName`, read the property string
from the already-extracted `members` array. The previous code looked at
`expression.property.name || expression.property.value`, which doesn't
work for a TemplateLiteral node (it has neither field).
Copilot AI review requested due to automatic review settings April 29, 2026 09:10
@changeset-bot

changeset-bot Bot commented Apr 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2995319

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (5bcf0e2).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@5bcf0e2
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@5bcf0e2
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@5bcf0e2

@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/fix-pr-20607-6FBRi into main will be
98.93%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/typescript
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%37
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js98.72%100%100%98.72%206, 226, 382
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.55%100%100%98.55%1554, 1850, 1857, 1865, 1887, 2783, 3208, 3870, 3899, 3952–3953, 3957, 3962, 3978–3979, 3993–3994, 3999–4000, 4477, 4503, 493, 498, 5211, 5292, 5307, 5332–5333, 5335, 5659, 5664, 5670, 5673, 5685, 5687, 5691, 5707, 5722, 5754, 5808, 5832, 5946, 712–713
   Compiler.js99.55%100%100%99.55%1116–1117, 1125
   ConcatenationScope.js98.59%100%100%98.59%189
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js100%100%100%100%
   ContextModuleFactory.js97.75%100%100%97.75%258, 393, 418, 443, 447, 458
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js98.92%100%100%98.92%158–159, 175, 194, 268
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.13%100%100%98.13%371, 404
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js97.88%100%100%97.88%237, 378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.89%100%100%98.89%399–403, 542
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.49%100%100%99.49%182, 2213–2214, 2217, 2228, 2239, 2250, 278, 3596, 3611, 3635
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.74%100%100%98.74%399, 401, 405
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1304, 1309, 1370, 1384, 1446, 1455
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1004
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%645
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.78%100%100%97.78%1020, 1036, 1123, 1774, 1779–1789, 708, 711, 728, 745, 986
   NormalModuleFactory.js99.47%100%100%99.47%1067, 1376, 466, 478
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js98.75%100%100%98.75%446–447, 452, 454, 518
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js99.16%100%100%99.16%267–268, 610
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js98.86%100%100%98.86%134–135
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%325
   cli.js98.71%100%100%98.71%117, 469, 501, 543, 813
   index.js100%100%100%100%
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.22%100%100%97.22%196, 218, 220
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModulesPlugin.js97.77%100%100%97.77%285, 309, 312, 364, 40
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%71, 83, 91
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%106, 113–114, 122, 89
   PackFileCacheStrategy.js96.40%100%100%96.40%1250, 1350, 1354, 1416, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 722, 728, 762, 768, 774, 779, 790, 799, 804–805, 807, 824, 830–831, 833
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.13%100%100%99.13%1291–1293, 1301, 270, 273, 278, 282, 467
   normalization.js99%100%100%99%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.js100%100%100%100%
   ContainerEntryModuleFactory.js100%100%100%100%
   ContainerExposedDependency.js100%100%100%100%
   ContainerPlugin.js100%100%100%100%
   ContainerReferencePlugin.js100%100%100%100%
   FallbackDependency.js100%100%100%100%
   FallbackItemDependency.js100%100%100%100%
   FallbackModule.js100%100%100%100%
   FallbackModuleFactory.js100%100%100%100%
   HoistContainerReferencesPlugin.js100%100%100%100%
   ModuleFederationPlugin.js100%100%100%100%
   RemoteModule.js100%100%100%100%
   RemoteRuntimeModule.js100%100%100%100%
   

@codecov

codecov Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.33%. Comparing base (45b2358) to head (2995319).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #20889      +/-   ##
==========================================
+ Coverage   82.98%   91.33%   +8.35%     
==========================================
  Files         530      562      +32     
  Lines       53890    55609    +1719     
  Branches    14236    14700     +464     
==========================================
+ Hits        44721    50791    +6070     
+ Misses       9169     4818    -4351     
Flag Coverage Δ
integration 90.30% <100.00%> (+9.55%) ⬆️
test262 46.02% <75.00%> (+0.03%) ⬆️
unit 36.18% <66.66%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes webpack’s static member-chain analysis to treat no-expression template literals (e.g. obj[`prop`]) as statically known strings in computed member access, aligning behavior with quoted string literals (e.g. obj["prop"]).

Changes:

  • Extend extractMemberExpressionChain to accept TemplateLiteral properties with zero expressions and extract their cooked string value.
  • Update walkMemberExpressionWithExpressionName to derive the current property segment from the already-extracted members array (supports TemplateLiteral nodes).
  • Add a parsing config-case that asserts import.meta["url"] and import.meta[`url`] both resolve consistently.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
lib/javascript/JavascriptParser.js Enables static member-chain extraction for computed properties using no-expression template literals and fixes property derivation during member-walk optimization.
test/configCases/parsing/import-meta-computed/index.js Adds regression tests covering computed access via both string literal and template literal on import.meta.
test/configCases/parsing/import-meta-computed/webpack.config.js Adds the config-case harness for the new parsing test.
.changeset/fuzzy-pigs-hide.md Adds a patch changeset describing the behavior change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codspeed

codspeed Bot commented Apr 29, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 53.95%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 4 regressed benchmarks
✅ 138 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Memory benchmark "react", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 232 KB 163.8 KB +41.67%
⚡ Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development","mode":"development"}' 1,297.3 KB 809.8 KB +60.2%
❌ Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-development","mode":"development"}' 1.1 MB 1.4 MB -21.26%
❌ Memory benchmark "devtool-eval", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 261.5 KB 372.9 KB -29.87%
❌ Memory benchmark "json-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 211.4 KB 459.1 KB -53.95%
❌ Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 153.2 KB 219.8 KB -30.31%

Comparing claude/fix-pr-20607-6FBRi (2995319) with main (45b2358)

Open in CodSpeed

@alexander-akait
alexander-akait merged commit 5bcf0e2 into main Apr 29, 2026
66 checks passed
@alexander-akait
alexander-akait deleted the claude/fix-pr-20607-6FBRi branch April 29, 2026 09:52
bjohansebas pushed a commit to bjohansebas/webpack that referenced this pull request Jul 10, 2026
alexander-akait added a commit that referenced this pull request Aug 27, 2026
The entry for #20889 padded a double-backtick code span with spaces, which
`fmt:check` rejects, so `lint` is red on main and on every branch cut from it.
alexander-akait added a commit that referenced this pull request Aug 27, 2026
… speed up pack writes (#21843)

* perf(serialization): dispatch cache items on their type

`ObjectMiddleware`'s write loop is the hottest function webpack runs while
storing the persistent cache. It opened with `Buffer.isBuffer(item)` and only
reached numbers, booleans and `undefined` after four more failed comparisons —
and counted over a 1,500-module build, those three are 1,066,959 of the
1,649,896 items it sees (64.6%), against 46,549 buffers (2.8%).

Dispatch on `typeof` instead, so each item reaches its branch directly. The
object branch also keys its type lookup on the serializer registration rather
than on a `${request}/${name}` string, dropping another 254,319 string
allocations per build. The wire format is unchanged: one registration is one
`(request, name)` pair, so the same entries are emitted in the same order.

Self time of that function drops from 296-321 ms to 201-230 ms over a
1,897-module fixture (`--cpu-prof`, two runs each, interleaved), and the cache
store measured through `compiler.close()` from 1,179 ms to 1,056 ms (median of
6 interleaved runs). Emitted output is byte-identical.

* fix: accept a snapshot.managedPaths RegExp without a capture group

The schema documents a `snapshot.managedPaths` entry as "a RegExp matching a
managed directory", but `FileSystemInfo` read the managed directory out of
`match[1]`. A pattern written exactly as documented — with no capture group —
therefore reached `getManagedItem(undefined, path)` and failed the build with
`TypeError: Cannot read properties of undefined (reading 'length')` from inside
the snapshot layer, naming nothing the user wrote.

Fall back to the whole match, which is the directory the schema describes.

* test: type the ObjectMiddleware round-trip context

`ObjectMiddleware`'s serialize/deserialize take a context carrying a logger,
and deserialize returns a value that may be a promise, so `{}` and a bare `[0]`
failed `lint:types-test` on CI.

* chore: shorten the comments the review flagged

Both new comments ran past the two-line limit AGENTS.md sets for `lib/`, and
one narrated the code it replaced. The changeset entry now fits the 80-character
limit while still naming the option.

* style: run prettier over CHANGELOG.md

The entry for #20889 padded a double-backtick code span with spaces, which
`fmt:check` rejects, so `lint` is red on main and on every branch cut from it.

* test: pin the round-trip with strict equality

`toEqual` ignores an undefined property and a swapped prototype, so the two
cases the dispatch table exists for — `d: undefined` and the null-prototype
object — would have passed even if the format dropped them.

* test: use strict equality for the rest of the round-trip assertions

The buffer case is the one that mattered: `toEqual` would have accepted a
plain Uint8Array where the format restores a Buffer.
alexander-akait added a commit that referenced this pull request Aug 30, 2026
The released entry for #20889 fails `fmt:check` on prettier 3.9.6, which
stops the lint chain before spellcheck on every pull request.
alexander-akait added a commit that referenced this pull request Aug 31, 2026
…d namespace import assignment (#21867)

* test(test262): correct four recorded causes

The two jest cases are not a drain-timing problem: the abandoned rejection
reaches neither unhandledRejection nor uncaughtException while the test
body runs, so the runner cannot capture it at all.

The two deferred-namespace cases are not blocked on unknowable exports —
the call site has the module graph. Sealing the proxy target needs those
names emitted at each deferred import, which costs bytes.

* test(test262): assert the rejections two cases abandon on purpose

Both bundles pass every assertion; each drops a rejected promise by design,
which jest records against the running test. The rejection is not visible
through process events on the sandboxed process, but it is in jest's own
state, which the environment already exposes.

Assert the expected count and clear it the way rejectionHandled would, so a
change in what they abandon fails instead of passing quietly.

* test(test262): record what the two deferred-namespace cases really need

Extensibility is only the first assertion of exotic-object-behavior; it also
wants ownKeys to be the sorted exports plus @@toStringTag, so __esModule
would have to go. That is the namespace-object trade-off, not defer-local.

* test: unskip dynamic imports whose specifier names no module

`import()` stringifies its argument, so `import(import(...))` asks for
"[object Promise]" and `import(typeof x)` for "object". webpack builds and
runs both correctly and the promise rejects, which is what the spec requires
and what Node.js does unbundled — the only fallout is a rejection the test
abandons and jest attributes to it.

Assert those alongside the two already tracked, now by reason as well as
count, and drop the twelve files from knownBugs.

* fix: keep the options argument of a computed-request import()

`import(`./${name}.js`, options)` goes through the context-dependency path,
which replaced everything after the request with `)` and so dropped the
options expression. The spec evaluates it right after the request, and it can
have side effects, so keep its source in place as a second argument the
context module ignores.

Also run the test262 suite with `output.strictModuleErrorHandling`, which is
what a Cyclic Module Record's [[EvaluationError]] asks for, and drop the three
files the two changes fix.

* fix: parenthesize the preserved import() options argument

A comma expression spliced in bare splits into extra arguments the context
module ignores; wrapping it keeps the call shape the source had.

* fix: check the options of a computed-request import(), and keep a comma expression whole

The context-dependency path passed the options as a second argument the
context module ignores, which reads as if it took one and skipped the checks
the spec asks for. Wrap the call the way the static-request path already does
instead, with the request and the options as its two arguments so they are
evaluated in that order, and share the check between the two paths.

An options range excludes any parentheses the source had, so `import(req, (a,
b))` split into arguments of its own and bound the check's parameter to `a`.
That rejected valid code with a TypeError on the static path already.

* fix: let exportsPresence decide how a circular reexport is reported

The circular-reexport diagnostic was built off the export-presence path
entirely, so it could be neither escalated nor suppressed: setting
`reexportExportsPresence: "error"` still only warned, and setting it to
`false" still warned. The spec makes a cycle in ResolveExport a link error,
the same as the ambiguous star export next to it, which does honour the
option.

Keep the default as it is — a cycle warns, skipping the promotion to an error
that AUTO gives a strict harmony module — but let an explicit setting say
whether and how it is reported.

* fix: await an async entry in an ESM bundle

An entry using top-level await produces a promise the startup code kept for
itself: the bundle settled before the entry did, and a rejecting entry left
its rejection with no one to hand it to, so it surfaced as a process-level
unhandled rejection instead of failing the bundle.

Only `output.library.type: "module"` awaited it. Move that await into the
generic startup, where it covers plain `output.module` too, and drop the
library plugin's copy so it is still emitted once.

* test(test262): record why the two production-only skips are not bugs

Both fire only where the class or the export is unused, and development
observes the same as the spec either way.

* test(test262): run unscopables-ignored with the parsers off

It only needs a bare global reference left alone, which is what the eval
cases next to it already ask for.

* fix: report an assignment to a namespace import

`import * as ns from "./x"; ns = null;` silently succeeded and left every
later read of `ns` looking at the assigned value. A real ES module throws a
TypeError there, and webpack already keeps that throw for a named import by
bailing out of concatenation so the write still lands on the namespace's
getter-only property. A namespace import is the binding itself, and nothing
webpack can emit for it throws: the declaration has to stay a `var` because a
runtime-condition import must be function-scoped and HMR accept reassigns it.

Report it at build time instead, which is what Rollup and esbuild do.

* chore: fold the branch's changesets into one

* chore: format a CHANGELOG entry prettier rejects

The released entry for #20889 fails `fmt:check` on prettier 3.9.6, which
stops the lint chain before spellcheck on every pull request.

* fix: await every async entry, and keep options webpack cannot read

Two defects a review found.

The entry await was gated on the last entry and spliced into the startup
buffer, so an earlier async entry still settled after the bundle did, and the
`await` would have been a syntax error inside the synchronous startup function
that `RuntimeGlobals.startup` renders. Capture each async entry instead and
await them after the loop, only where the startup lands at the top level of an
ESM chunk.

`isStaticStringAttributes` returned true for a key that is not `with` or
`assert`, so `import(request, { unused: effect() })` dropped `effect()`. Only
those two keys can be read at build time; anything else keeps the argument.

* test: cover the import() options checks

The null, non-object `with` and non-string attribute branches had no test,
on either the static or the computed-request path.

* fix: observe every async entry together, and gate the cases on Node.js 14.8

Awaiting the entries one by one leaves the rest unobserved once one rejects,
so a second failing entry became an unhandled rejection. Await them in a
single `Promise.all`.

The two cases need top-level await, which the runner's `vm.SourceTextModule`
cannot parse before Node.js 14.8, and both fixtures now settle on a task so
they fail when startup returns too early.

* test: assert the TypeError, and check 'with' on a computed request

The option assertions matched only the message, so a change to a plain Error
would have passed, and only the static request exercised the 'with' check.

* perf: await a lone async entry without Promise.all

The code size report grew from 3 changed assets to 9 when the await became a
`Promise.all`, which every ESM bundle with one async entry pays for. A single
promise has nothing to orphan, so keep the short form for it.

* feat(config): add output.environment.topLevelAwait

Awaiting an async ESM entry emits top-level await, which a target can read
ESM without supporting: `target: "node14"` (added in v14.8) then produced a
bundle it cannot parse. Gate the await on a new environment flag, derived
from the node/deno/bun/electron/nwjs/esX presets and the browserslist data,
so such targets fall back to not awaiting.

Also classify the flag in the ecma-conformance helper, which needed the
walk to track function nesting to tell top-level await from nested await,
and drop `globalThis` from the namespace-assignment fixture so it runs on
Node 10.

* test: read electron 10 back as ES2021

Chromium 85 has no top-level await, so the new environment flag makes the
version ladder answer 2021. The case still shows `module: false` is ignored:
without that, only the ES5 rung would match.

* fix: keep awaiting an async entry a module library exports

The topLevelAwait gate also suppressed the await `ModuleLibraryPlugin` has
always emitted, so an async ESM library built for `target: "node14"` handed
its consumers unsettled exports instead of values. Restore that plugin to
what it was and let it own the await, gating only the startup await this
branch adds.

* test: drive the multi-entry await through Promise.all

The case had one async entry among two, so it took the single-await path and
nothing exercised the `Promise.all` the multiple-entry fix added. Make both
entries async and assert both settled.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants