Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Server polyfills are not user-controllable: no way to opt out of `@angular/platform-server/init` or add server-only polyfills

已关闭
#34,515 0 条评论 3 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
48/100
Issue 类型
功能
描述清晰度
描述清楚
活跃度
活跃
领域
build-system

调研方向

Start with application-code-bundle.ts at createServerPolyfillBundleOptions and trace how options.polyfills is consumed in options.ts, execute-build.ts, dev-server/vite/index.ts, and browser-esbuild. The issue proposes a normalization step and separate browser and server lists; check the existing normalization patterns and add the server polyfills spec mentioned under “Anything else relevant?”. Done means the object form works while array form preserves current behavior, with tests covering the server bundle.

由索引模型根据 Issue 内容生成。

描述

area: @angular/build
Command

build, serve

Description

@angular/build:application derives the server polyfills list entirely on its own and offers no option to change it. In createServerPolyfillBundleOptions (application-code-bundle.ts#L164-L184):

  • zone.js/node (or zone.js on platform: 'neutral') is added when the browser polyfills list contains zone.js,
  • @angular/localize/init is added when the browser list contains it,
  • @angular/platform-server/init is always added.

So the browser polyfills list is fully explicit, and the server polyfills list is fully implicit. Two concrete gaps follow from that:

1. There is no opt-out of the init global patch

@angular/platform-server/init is a side-effectful module whose only job is Object.assign(globalThis, domino.impl) plus globalThis.KeyboardEvent = domino.impl.Event. The framework introduced it as something an app may import before bootstrap for code that touches DOM globals at module-evaluation time (angular/angular#40559). Most apps do not need it: platform-server runs the identical shim itself in DominoAdapter.makeCurrent() → setDomTypes() on every platform init, so for everything that runs after bootstrap the init patch is redundant and is overwritten on the first render anyway.

Since v15 the CLI adds it to every server build with no way to say no. Evidence that people need the opt-out:

  • The framework's own SSR benchmarks patch @angular/build to strip the platform-server/init import from polyfills.server.mjs (modules/ssr-benchmarks/README.md in angular/angular), and the main.server.ts there notes that ɵENABLE_DOM_EMULATION "won't prevent the monkey patching of DOM apis as this is introduced by the CLI".
  • Anyone running the server bundle outside Node with a real DOM (the benchmark case, or ENABLE_DOM_EMULATION: false) gets Domino's classes forced onto globalThis regardless.
  • Apps that want to keep globalThis clean on the server (no HTMLElement, Node, Event, … leaking into third-party code that feature-detects the environment with typeof window/typeof HTMLElement) have no option either.

Measured on a default ng new --ssr app (details in #34514): the init module costs ~235 kB on disk, ~1.1 MB of retained heap for the life of the process and ~7 ms of startup, while the Object.assign itself is negligible (~7 KB, 0.03 ms). Prerendered and SSR output are byte-identical with and without it.

2. There is nowhere to put a server-only polyfill

The ssr.platform option's own description says "Please note that this feature does not provide polyfills for Node.js modules." With platform: 'neutral' (Cloudflare Workers and similar), users are expected to bring polyfills themselves, yet the only polyfills option feeds the browser bundle. Today a server-only polyfill has to be imported manually at the top of main.server.ts, where it runs after zone.js and init and after any shared chunks.

Describe the solution you'd like

Let polyfills take either the current array or a browser/server object, mirroring the existing outputPath shape (string | { base, browser, server, media }):

"polyfills": ["zone.js"]                                 // today, unchanged
"polyfills": {
  "browser": ["zone.js"],
  "server": ["zone.js/node", "@angular/platform-server/init"]
}

Semantics:

  • Array form keeps today's behaviour exactly: browser = the array, server = derived as now (zone.js/node, localize, init). Non-breaking; no migration needed.
  • An explicit server list is the server polyfills list, the same way the browser list already is: the builder adds nothing on top. To drop the init global patch, omit it. To keep today's output, list it. The ng new / ssr schematics can emit the explicit form so new projects see what they get.
  • isZonelessApp is evaluated per list (browser list for the browser bundle, server list for the server bundle), so zone.js vs zone.js/node and the async/await support flags fall out naturally.
  • getEsBuildCommonPolyfillsOptions and the polyfills.server bundle are unchanged; only the input list is. Normalization follows the existing normalizeOptimization / normalizeSourceMaps pattern (a small normalizePolyfills in options.ts, folding in the string-to-array shim that browser-esbuild still relies on).
  • Consumers that read options.polyfills and would switch to .browser / .server: application-code-bundle.ts (browser bundle, server bundle, isZonelessApp checks), execute-build.ts (top-level-await hint), dev-server/vite/index.ts (es2016 target), and browser-esbuild's string handling.

This solves both gaps with one option and no new concept: server polyfills become as explicit as browser polyfills.

I am happy to implement this and open the PR once the shape is agreed; the single-copy fix in #34516 is independent of it and already covers the duplicate.

Describe alternatives you've considered

A boolean under ssr, next to ssr.platform, which already shapes the server polyfill list (zone.js vs zone.js/node), the createRequire banner and the xhr2 external:

"ssr": { "entry": "src/server.ts", "platform": "node", "domShims": false }

One if around line 184. It only addresses gap 1, and the name needs care so it is not confused with the framework's private ɵENABLE_DOM_EMULATION, which controls the adapter and does not affect the global shims.

Leaving it manual: users who want init could import it in main.server.ts as the v12 schematic did. That would be a breaking change for apps that rely on the implicit import, and it still leaves gap 2.

Not an alternative, but related: the same unconditional init import is also why the server build currently ships two copies of Domino (#34514). That has a bundling-level fix that does not need this option; this request is about control, not about the duplicate.

Anything else relevant?
  • No spec or e2e currently asserts anything about polyfills.server.mjs, zone.js/node or @angular/platform-server/init; tests/options/polyfills_spec.ts only covers dist/browser/polyfills.js. Either solution should add a server polyfills spec.
  • The webpack server builder only adds init when @angular/platform-server is installed (isPackageInstalled in webpack/configs/common.ts); the esbuild path has no equivalent guard.
  • Repro and measurements: https://github.com/ChristopherPHolder/ng-double-domino
主要语言
TypeScript
星标
27k
派生
11.8k
平均合并
1 天 2 小时
30 天内合并 PR
241

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

angular/angular-cli 的其他 Issue

查看 angular/angular-cli 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。