From dee6ef89c7e37713d7577244ea2042e9ded5157c Mon Sep 17 00:00:00 2001 From: arturovt Date: Wed, 17 Sep 2025 15:25:30 +0300 Subject: [PATCH] refactor(core): tree-shake `HOST_TAG_NAME` in prod (#63861) Previously, HOST_TAG_NAME had its __NG_ELEMENT_ID__ set at the top level. This prevented tree-shaking, since the bundler had to keep the assignment as a potential side effect even when the token was never used. This change moves the token creation and __NG_ELEMENT_ID__ assignment into a @__PURE__ IIFE. If HOST_TAG_NAME is not injected anywhere, the IIFE result is unused and can be dropped entirely by the optimizer. If it is used, the token still behaves the same at runtime. PR Close #63861 --- packages/core/src/di/host_tag_name_token.ts | 59 +++++++++++-------- .../bundle.golden_symbols.json | 1 - .../bundling/defer/bundle.golden_symbols.json | 7 +-- .../forms_reactive/bundle.golden_symbols.json | 1 - .../bundle.golden_symbols.json | 1 - .../hydration/bundle.golden_symbols.json | 1 - .../router/bundle.golden_symbols.json | 1 - .../bundle.golden_symbols.json | 1 - 8 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/core/src/di/host_tag_name_token.ts b/packages/core/src/di/host_tag_name_token.ts index 492d42adc8f..3e91e0ec1b2 100644 --- a/packages/core/src/di/host_tag_name_token.ts +++ b/packages/core/src/di/host_tag_name_token.ts @@ -34,36 +34,45 @@ import {InternalInjectFlags} from './interface/injector'; * ``` * @publicApi */ -export const HOST_TAG_NAME = new InjectionToken(ngDevMode ? 'HOST_TAG_NAME' : ''); +export const HOST_TAG_NAME: InjectionToken = /* @__PURE__ */ (() => { + // Wrapped in a `@__PURE__` IIFE so this token stays tree-shakable. + // If nothing ever injects `HOST_TAG_NAME`, the IIFE result is unused and + // the bundler can drop the whole block. If we set `__NG_ELEMENT_ID__` at + // the top level instead, the mutation would look like a side effect, + // forcing the bundler to keep it even when unused. + const HOST_TAG_NAME_TOKEN = new InjectionToken(ngDevMode ? 'HOST_TAG_NAME' : ''); -// HOST_TAG_NAME should be resolved at the current node, similar to e.g. ElementRef, -// so we manually specify __NG_ELEMENT_ID__ here, instead of using a factory. -// tslint:disable-next-line:no-toplevel-property-access -(HOST_TAG_NAME as any).__NG_ELEMENT_ID__ = (flags: InternalInjectFlags) => { - const tNode = getCurrentTNode(); - if (tNode === null) { + // HOST_TAG_NAME should be resolved at the current node, similar to e.g. ElementRef, + // so we manually specify __NG_ELEMENT_ID__ here, instead of using a factory. + // tslint:disable-next-line:no-toplevel-property-access + (HOST_TAG_NAME_TOKEN as any).__NG_ELEMENT_ID__ = (flags: InternalInjectFlags) => { + const tNode = getCurrentTNode(); + if (tNode === null) { + throw new RuntimeError( + RuntimeErrorCode.INVALID_INJECTION_TOKEN, + ngDevMode && + 'HOST_TAG_NAME can only be injected in directives and components ' + + 'during construction time (in a class constructor or as a class field initializer)', + ); + } + if (tNode.type & TNodeType.Element) { + return tNode.value; + } + if (flags & InternalInjectFlags.Optional) { + return null; + } throw new RuntimeError( RuntimeErrorCode.INVALID_INJECTION_TOKEN, ngDevMode && - 'HOST_TAG_NAME can only be injected in directives and components ' + - 'during construction time (in a class constructor or as a class field initializer)', + `HOST_TAG_NAME was used on ${getDevModeNodeName( + tNode, + )} which doesn't have an underlying element in the DOM. ` + + `This is invalid, and so the dependency should be marked as optional.`, ); - } - if (tNode.type & TNodeType.Element) { - return tNode.value; - } - if (flags & InternalInjectFlags.Optional) { - return null; - } - throw new RuntimeError( - RuntimeErrorCode.INVALID_INJECTION_TOKEN, - ngDevMode && - `HOST_TAG_NAME was used on ${getDevModeNodeName( - tNode, - )} which doesn't have an underlying element in the DOM. ` + - `This is invalid, and so the dependency should be marked as optional.`, - ); -}; + }; + + return HOST_TAG_NAME_TOKEN; +})(); function getDevModeNodeName(tNode: TNode) { if (tNode.type & TNodeType.ElementContainer) { diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index 9e887cdb6c5..c2abdc3d27c 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -118,7 +118,6 @@ "HEADER_OFFSET", "HOST", "HOST_ATTR", - "HOST_TAG_NAME", "HYDRATION", "ID", "INJECTOR", diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index 4f5a7a26e3d..dd802868d1c 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -67,6 +67,8 @@ "shimStylesContent" ], "lazy": [ + "DeferComponent", + "_DeferComponent", "AFTER_RENDER_SEQUENCES_TO_ADD", "ANIMATIONS", "APP_BOOTSTRAP_LISTENER", @@ -141,7 +143,6 @@ "FLAGS", "HEADER_OFFSET", "HOST", - "HOST_TAG_NAME", "HYDRATE_TRIGGER_CLEANUP_FNS", "HYDRATION", "ID", @@ -792,9 +793,7 @@ "wasLastNodeCreated", "writeDirectClass", "writeDirectStyle", - "writeToDirectiveInput", - "DeferComponent", - "_DeferComponent" + "writeToDirectiveInput" ] } } \ No newline at end of file diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 66b475e98ec..ff99997b5bc 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -120,7 +120,6 @@ "HEADER_OFFSET", "HOST", "HOST_ATTR", - "HOST_TAG_NAME", "HYDRATION", "ID", "INJECTOR", diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index afbd06a3759..b65037e710d 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -114,7 +114,6 @@ "HEADER_OFFSET", "HOST", "HOST_ATTR", - "HOST_TAG_NAME", "HYDRATION", "ID", "INJECTOR", diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index 85a090320cd..6d4cfbf8658 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -93,7 +93,6 @@ "HEADER_OFFSET", "HOST", "HOST_ATTR", - "HOST_TAG_NAME", "HTTP_ROOT_INTERCEPTOR_FNS", "HTTP_TRANSFER_CACHE_ORIGIN_MAP", "HYDRATION", diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 20c371e262b..b101340c70b 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -113,7 +113,6 @@ "HEADER_OFFSET", "HOST", "HOST_ATTR", - "HOST_TAG_NAME", "HYDRATION", "HistoryStateManager", "HostAttributeToken", diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 2bccee44295..5096f1ddd15 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -80,7 +80,6 @@ "HEADER_OFFSET", "HOST", "HOST_ATTR", - "HOST_TAG_NAME", "HYDRATION", "HelloWorld", "ID",