Skip to content

feat: reduce generated runtime code via method shorthand, spread, and Object.hasOwn#21188

Merged
alexander-akait merged 8 commits into
mainfrom
feat/env-code-reduction
Jun 16, 2026
Merged

feat: reduce generated runtime code via method shorthand, spread, and Object.hasOwn#21188
alexander-akait merged 8 commits into
mainfrom
feat/env-code-reduction

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #21186 (optional chaining in runtime code). This emits smaller runtime code on modern targets by using ECMAScript features webpack already models, and adds three new output.environment capabilities for features it didn't:

  • New RuntimeTemplate.method() helper uses object method shorthand (get(){…}) instead of get: function/arrow for object-literal methods (deferred-namespace Proxy handler, CommonJS harmony decorator, CSS HMR apply handler).
  • New output.environment.spread (ES2018) — { ...opts, type } instead of Object.assign({}, opts, { type }) for worker options, and [...a, x] instead of a.concat([x]) in the deferred-namespace ownKeys.
  • New output.environment.hasOwn (ES2022) — Object.hasOwn(obj, prop) instead of Object.prototype.hasOwnProperty.call(obj, prop) in __webpack_require__.o (nearly every bundle) and the HMR runtime.
  • New output.environment.symbol (ES2015) — drops the typeof Symbol !== 'undefined' && guard in __webpack_require__.r (makeNamespaceObject).
  • Also fills in the optional-chaining sites feat: use optional chaining in generated runtime code where supported #21186 left behind (CSS removed-chunks guard, css-modules and lazy-compilation HMR self-acceptance guards).

Each conversion falls back to the exact previous output when the target environment doesn't support the feature, so default/legacy builds are byte-identical. All new capability flags use target detection (config/target.js, browserslist) and are non-optimistic where the feature is new. No related issue.

What kind of change does this PR introduce?

feat

Did you add tests for your changes?

Yes — unit tests for RuntimeTemplate.method and RuntimeTemplate.objectHasOwn in test/RuntimeTemplate.unittest.js, plus updated Defaults/target-browserslist/Cli snapshots covering the new flags and target detection. The generated runtime is exercised end-to-end by the existing ConfigTestCases/HotTestCases suites in both the modern and fallback forms. The test harnesses force environment.hasOwn = false on Node < 16.9 (mirroring the existing optionalChaining gate) since the generated runtime runs in the Jest process.

Does this PR introduce a breaking change?

No. New options default conservatively (hasOwn is non-optimistic like importMetaDirnameAndFilename), so output is unchanged for targets that don't report support.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

The three new output.environment options — spread, hasOwn, and symbol — should be added to the output.environment documentation alongside the existing capability flags.

Use of AI

AI was used: Claude (Claude Code) helped locate candidate sites, write the helpers and the spread/hasOwn/symbol plumbing (schema, target detection, defaults), apply the edits, and run/compare the test suites. All changes were reviewed by me.

…n generated runtime code

Adds a RuntimeTemplate.method() helper (object-literal method shorthand with
a function/arrow-property fallback) and a new output.environment.spread
capability with target detection, then applies them where they shrink the
generated runtime: the deferred-namespace Proxy handler, the CommonJS harmony
module decorator, and the CSS HMR apply handler. Also converts the remaining
safe css removed-chunks guard to optional chaining.
…d runtime code

Adds a new output.environment.hasOwn capability (with target detection) and a
RuntimeTemplate.objectHasOwn() helper that emits Object.hasOwn(obj, prop) when
the target environment supports it, falling back to
Object.prototype.hasOwnProperty.call otherwise. Applies it to the
__webpack_require__.o helper (emitted in nearly every bundle) and the
deferred-namespace property copy.
… and HMR runtime

Extends the environment-gated code reduction to the remaining sites: object
spread for worker option merging (was Object.assign), Object.hasOwn in the HMR
runtime, and optional chaining for the css-modules and lazy-compilation HMR
self-acceptance guards. Each falls back to the previous output when the target
environment does not support the feature.
@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ff75786

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 Jun 15, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (8596a28).

Install it locally:

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

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.70%. Comparing base (877f3dc) to head (ff75786).

Files with missing lines Patch % Lines
lib/runtime/MakeDeferredNamespaceObjectRuntime.js 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21188      +/-   ##
==========================================
- Coverage   92.70%   92.70%   -0.01%     
==========================================
  Files         588      588              
  Lines       64091    64118      +27     
  Branches    17785    17797      +12     
==========================================
+ Hits        59416    59438      +22     
- Misses       4675     4680       +5     
Flag Coverage Δ
css-parsing 28.68% <20.00%> (-0.01%) ⬇️
html5lib 31.19% <20.00%> (-0.01%) ⬇️
integration 88.67% <93.33%> (-0.01%) ⬇️
test262 45.59% <70.00%> (+0.04%) ⬆️
unit 41.06% <53.33%> (+0.03%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

…sOwn

Regenerate the ecmaVersion browserslist inline environment snapshots to include
the new `spread` and `hasOwn` capabilities, and the es2022 html ConfigTest /
ConfigCacheTest snapshots where `__webpack_require__.o` now uses `Object.hasOwn`.
@codspeed-hq

codspeed-hq Bot commented Jun 15, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by ×2.2

⚠️ 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

⚡ 5 improved benchmarks
✅ 139 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 858.6 KB 129.2 KB ×6.6
Memory benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,048.3 KB 325.4 KB ×3.2
Memory benchmark "wasm-modules-sync", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 786.6 KB 563.9 KB +39.5%
Memory benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 488.8 KB 389 KB +25.65%
Memory benchmark "future-defaults", scenario '{"name":"mode-production","mode":"production"}' 9.1 MB 7.4 MB +23.7%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/env-code-reduction (ff75786) with main (877f3dc)

Open in CodSpeed

The generated runtime executes in the Jest process, so es2022/node16.9 fixtures
emitting `Object.hasOwn` crash on Node < 16.9. Mirror the existing
optionalChaining gate: force `environment.hasOwn = false` in the config/hot/
watch/cases harnesses on old Node (except `ecmaVersion`), and raise the html
snapshot fixtures' filters to require `Object.hasOwn` since their es2022
snapshots now use it.
Adds a new output.environment.symbol capability (with target detection) and a
RuntimeTemplate.supportsSymbol() check, then drops the
`typeof Symbol !== 'undefined' &&` guard in __webpack_require__.r
(makeNamespaceObject) when the target environment is known to have `Symbol`,
falling back to the guarded form otherwise.
…snapshots

The `supportsObjectHasOwn` helper referenced `Object.hasOwn` directly, which the
test tsconfig's pre-es2022 lib rejects; cast through EXPECTED_ANY. Also
regenerate the ecmaVersion browserslist inline environment snapshots to include
the new `symbol` key.
The makeNamespaceObject runtime is ~28 bytes smaller when the Symbol global
guard is dropped, shifting the entryB.js asset size in Stats.test.js.
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging feat/env-code-reduction into main will be
99.33%
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.js100%100%100%100%
examples/typescript-non-erasable
   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%39
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js99.15%100%100%99.15%206, 226
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.44%100%100%98.44%1609, 1905, 1912, 1920, 1942, 2838, 3317–3318, 3350, 4014, 4044, 4097–4098, 4102, 4107, 4123–4124, 4138–4139, 4144–4145, 4622, 4648, 513, 518, 5456, 5488, 5505, 5521, 5537, 5552, 5577–5578, 5580, 5908, 5913, 5919, 5922, 5934, 5936, 5940, 5956, 5971, 6003, 6057, 6081, 6195, 763–764
   Compiler.js99.56%100%100%99.56%1135–1136, 1144
   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.40%100%100%97.40%258, 395, 418, 420, 424, 433–434
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js99%100%100%99%171–172, 188, 207, 281
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.15%100%100%98.15%382, 428
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js98.41%100%100%98.41%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.67%100%100%98.67%1062, 1065, 448–452, 600
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.50%100%100%99.50%182, 2252–2253, 2256, 2267, 2278, 2289, 278, 3693, 3708, 3732
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.46%100%100%98.46%425, 434, 436, 440
   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%1285, 1290, 1350, 1364, 1426, 1435
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1005
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.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%659
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.90%100%100%97.90%1240, 1243, 1260, 1277, 1524, 1558, 1574, 1661, 2017, 2316, 2321–2331, 419, 423, 577
   NormalModuleFactory.js99.47%100%100%99.47%1083, 1392, 486, 498
   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.85%100%100%98.85%519–520, 525, 527, 591
   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.js98.62%100%100%98.62%220, 224, 226, 419, 430, 891
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.17%100%100%99.17%176–177
   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%371
   cli.js98.62%100%100%98.62%10, 119, 545, 577, 627, 897
   index.js99.72%100%100%99.72%179
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js96.33%100%100%96.33%10, 198, 220, 222
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.32%100%100%97.32%281, 305, 308, 36, 360, 41
   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.31%100%100%99.31%1439–1441, 1449, 274, 277, 282, 286
   normalization.js99.01%100%100%99.01%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%

@alexander-akait alexander-akait merged commit 8596a28 into main Jun 16, 2026
66 checks passed
@alexander-akait alexander-akait deleted the feat/env-code-reduction branch June 16, 2026 09:05
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.

1 participant