From 4744bab38ef2cf417df60df2f6f11e106db41de5 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Sun, 12 Apr 2026 17:03:21 +0200 Subject: [PATCH] refactor(core): Tree shake the `SimpleChanges` & co. Any application that doesn't use the `ngOnChanges` hook shouldn't pull its code. --- .../src/render3/features/ng_onchanges_feature.ts | 12 +++++++++++- packages/core/src/render3/hooks.ts | 6 +++--- .../animations-standalone/bundle.golden_symbols.json | 9 ++------- .../create_component/bundle.golden_symbols.json | 9 ++------- .../test/bundling/defer/bundle.golden_symbols.json | 9 ++------- .../forms_reactive/bundle.golden_symbols.json | 2 ++ .../forms_template_driven/bundle.golden_symbols.json | 2 ++ .../bundling/hydration/bundle.golden_symbols.json | 9 ++------- .../test/bundling/router/bundle.golden_symbols.json | 2 ++ .../standalone_bootstrap/bundle.golden_symbols.json | 9 ++------- 10 files changed, 30 insertions(+), 39 deletions(-) diff --git a/packages/core/src/render3/features/ng_onchanges_feature.ts b/packages/core/src/render3/features/ng_onchanges_feature.ts index 6f34be488bb..53ed56691c9 100644 --- a/packages/core/src/render3/features/ng_onchanges_feature.ts +++ b/packages/core/src/render3/features/ng_onchanges_feature.ts @@ -14,6 +14,8 @@ import {EMPTY_OBJ} from '../../util/empty'; import {applyValueToInputField} from '../apply_value_input_field'; import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition'; +let _ngOnChangesFeatureImpl: typeof NgOnChangesFeatureImpl | null = null; + /** * The NgOnChangesFeature decorates a component with support for the ngOnChanges * lifecycle hook, so it should be included in any component that implements @@ -37,6 +39,7 @@ import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition'; * @codeGenApi */ export const ɵɵNgOnChangesFeature: () => DirectiveDefFeature = /* @__PURE__ */ (() => { + _ngOnChangesFeatureImpl = NgOnChangesFeatureImpl; const ɵɵNgOnChangesFeatureImpl = () => NgOnChangesFeatureImpl; // This option ensures that the ngOnChanges lifecycle hook will be inherited @@ -47,7 +50,14 @@ export const ɵɵNgOnChangesFeature: () => DirectiveDefFeature = /* @__PURE__ */ return ɵɵNgOnChangesFeatureImpl; })(); -export function NgOnChangesFeatureImpl(definition: DirectiveDef) { +export function getNgOnChangesFeatureImpl(): typeof NgOnChangesFeatureImpl { + return _ngOnChangesFeatureImpl!; +} + +/** + * We don't expose the feature implementation directly in order to tree shake it when `ngOnChanges` isn't used. + */ +function NgOnChangesFeatureImpl(definition: DirectiveDef) { if (definition.type.prototype.ngOnChanges) { definition.setInput = ngOnChangesSetInput; } diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index a381586778d..065095d0430 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -20,8 +20,9 @@ import { } from '../change_detection/lifecycle_hooks'; import {assertDefined, assertEqual, assertNotEqual} from '../util/assert'; +import {ProfilerEvent} from '../../primitives/devtools'; import {assertFirstCreatePass} from './assert'; -import {NgOnChangesFeatureImpl} from './features/ng_onchanges_feature'; +import {getNgOnChangesFeatureImpl} from './features/ng_onchanges_feature'; import {DirectiveDef} from './interfaces/definition'; import {TNode} from './interfaces/node'; import { @@ -35,7 +36,6 @@ import { TView, } from './interfaces/view'; import {profiler} from './profiler'; -import {ProfilerEvent} from '../../primitives/devtools'; import {isInCheckNoChangesMode} from './state'; /** @@ -61,7 +61,7 @@ export function registerPreOrderHooks( DoCheck; if (ngOnChanges as Function | undefined) { - const wrappedOnChanges = NgOnChangesFeatureImpl(directiveDef); + const wrappedOnChanges = getNgOnChangesFeatureImpl()(directiveDef); (tView.preOrderHooks ??= []).push(directiveIndex, wrappedOnChanges); (tView.preOrderCheckHooks ??= []).push(directiveIndex, wrappedOnChanges); } 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 e6c094dc1c1..8b890c06e80 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -172,7 +172,6 @@ "NULL_REMOVAL_STATE", "NULL_REMOVED_QUERIED_STATE", "NgModuleRef", - "NgOnChangesFeatureImpl", "NgZone", "NodeInjector", "NodeInjectorDestroyRef", @@ -224,7 +223,6 @@ "SHARED_ANIMATION_PROVIDERS", "SHARED_STYLES_HOST", "SIGNAL", - "SIMPLE_CHANGES_STORE", "STABILITY_WARNING_THRESHOLD", "STAR_CLASSNAME", "STAR_SELECTOR", @@ -235,7 +233,6 @@ "Sanitizer", "ShadowDomRenderer", "SharedStylesHost", - "SimpleChange", "SpecialCasedStyles", "StandaloneService", "StateValue", @@ -309,6 +306,7 @@ "_isRefreshingViews", "_keyMap", "_locateOrCreateElementNode", + "_ngOnChangesFeatureImpl", "_platformInjector", "_processI18nInsertBefore", "_retrieveHydrationInfoImpl", @@ -525,6 +523,7 @@ "getNearestLContainer", "getNextLContainer", "getNgDirectiveDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", @@ -548,7 +547,6 @@ "getRuntimeErrorCode", "getSelectedIndex", "getSelectedTNode", - "getSimpleChangesStore", "getStyleHost", "getTNode", "getTNodeFromLView", @@ -706,7 +704,6 @@ "nextBindingIndex", "nextNgElementId", "nextNotification", - "ngOnChangesSetInput", "ngZoneInstanceId", "noSideEffects", "noop", @@ -763,7 +760,6 @@ "registerPostOrderHooks", "registerPreOrderHooks", "relativePath", - "rememberChangeHistoryAndInvokeOnChangesHook", "remove", "removeAnimationsFromQueue", "removeClass", @@ -821,7 +817,6 @@ "setRootDomAdapter", "setSelectedIndex", "setShadowStylingInputFlags", - "setSimpleChangesStore", "setStyles", "setThrowInvalidWriteToSignalError", "setUpAttributes", diff --git a/packages/core/test/bundling/create_component/bundle.golden_symbols.json b/packages/core/test/bundling/create_component/bundle.golden_symbols.json index 3a1eeaaad2b..8fa1b3e48f9 100644 --- a/packages/core/test/bundling/create_component/bundle.golden_symbols.json +++ b/packages/core/test/bundling/create_component/bundle.golden_symbols.json @@ -130,7 +130,6 @@ "NO_PARENT_INJECTOR", "NULL_INJECTOR", "NgModuleRef", - "NgOnChangesFeatureImpl", "NgZone", "NodeInjector", "NodeInjectorDestroyRef", @@ -172,14 +171,12 @@ "SHARED_STYLES_HOST", "SIGNAL", "SIGNAL_NODE", - "SIMPLE_CHANGES_STORE", "STABILITY_WARNING_THRESHOLD", "SVG_NAMESPACE", "SafeSubscriber", "Sanitizer", "ShadowDomRenderer", "SharedStylesHost", - "SimpleChange", "StandaloneService", "Subject", "Subscriber", @@ -235,6 +232,7 @@ "_keyMap", "_locateOrCreateAnchorNode", "_locateOrCreateTextNode", + "_ngOnChangesFeatureImpl", "_platformInjector", "_processI18nInsertBefore", "_retrieveHydrationInfoImpl", @@ -429,6 +427,7 @@ "getNearestLContainer", "getNextLContainer", "getNgDirectiveDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", @@ -453,7 +452,6 @@ "getRuntimeErrorCode", "getSelectedIndex", "getSelectedTNode", - "getSimpleChangesStore", "getStyleHost", "getTNode", "getTNodeFromLView", @@ -584,7 +582,6 @@ "nextBindingIndex", "nextNgElementId", "nextNotification", - "ngOnChangesSetInput", "ngZoneInstanceId", "noSideEffects", "noop", @@ -627,7 +624,6 @@ "registerPostOrderHooks", "registerPreOrderHooks", "relativePath", - "rememberChangeHistoryAndInvokeOnChangesHook", "remove", "removeAnimationsFromQueue", "removeElements", @@ -674,7 +670,6 @@ "setRootDomAdapter", "setSelectedIndex", "setShadowStylingInputFlags", - "setSimpleChangesStore", "setThrowInvalidWriteToSignalError", "setUpAttributes", "setupHostDirectiveInputsOrOutputs", diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index 94742698200..2d80dde6664 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -176,7 +176,6 @@ "NO_PARENT_INJECTOR", "NULL_INJECTOR", "NgModuleRef", - "NgOnChangesFeatureImpl", "NgZone", "NodeInjector", "NodeInjectorDestroyRef", @@ -213,14 +212,12 @@ "SCHEDULE_IN_ROOT_ZONE_DEFAULT", "SHARED_STYLES_HOST", "SIGNAL", - "SIMPLE_CHANGES_STORE", "SSR_BLOCK_STATE", "SSR_UNIQUE_ID", "STABILITY_WARNING_THRESHOLD", "SVG_NAMESPACE", "SafeSubscriber", "Sanitizer", - "SimpleChange", "StandaloneService", "Subject", "Subscriber", @@ -279,6 +276,7 @@ "_locateOrCreateContainerAnchor", "_locateOrCreateElementNode", "_locateOrCreateTextNode", + "_ngOnChangesFeatureImpl", "_platformInjector", "_populateDehydratedViewsInLContainer", "_processI18nInsertBefore", @@ -475,6 +473,7 @@ "getNearestLContainer", "getNextLContainer", "getNgDirectiveDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", @@ -498,7 +497,6 @@ "getRuntimeErrorCode", "getSelectedIndex", "getSelectedTNode", - "getSimpleChangesStore", "getStyleHost", "getTDeferBlockDetails", "getTNode", @@ -626,7 +624,6 @@ "nextBindingIndex", "nextNgElementId", "nextNotification", - "ngOnChangesSetInput", "ngZoneInstanceId", "noSideEffects", "noop", @@ -663,7 +660,6 @@ "registerLView", "registerPostOrderHooks", "registerPreOrderHooks", - "rememberChangeHistoryAndInvokeOnChangesHook", "remove", "removeAnimationsFromQueue", "removeFromArray", @@ -714,7 +710,6 @@ "setLocaleId", "setSelectedIndex", "setShadowStylingInputFlags", - "setSimpleChangesStore", "setTDeferBlockDetails", "setThrowInvalidWriteToSignalError", "setUpAttributes", 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 efcf8939057..2a0959fa6d1 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -349,6 +349,7 @@ "_locateOrCreateElementNode", "_locateOrCreateTextNode", "_moduleDoBootstrap", + "_ngOnChangesFeatureImpl", "_platformInjector", "_populateDehydratedViewsInLContainer", "_processI18nInsertBefore", @@ -620,6 +621,7 @@ "getNextLContainer", "getNgDirectiveDef", "getNgModuleDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", 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 beed18225ca..03d015997a4 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 @@ -350,6 +350,7 @@ "_locateOrCreateElementNode", "_locateOrCreateTextNode", "_moduleDoBootstrap", + "_ngOnChangesFeatureImpl", "_platformInjector", "_populateDehydratedViewsInLContainer", "_processI18nInsertBefore", @@ -616,6 +617,7 @@ "getNextLContainer", "getNgDirectiveDef", "getNgModuleDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index d26a1b3c08f..351b0162f29 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -198,7 +198,6 @@ "NULL_INJECTOR", "NUM_ROOT_NODES", "NgModuleRef", - "NgOnChangesFeatureImpl", "NgZone", "NodeInjector", "NodeInjectorDestroyRef", @@ -250,7 +249,6 @@ "SHA256_ROUND_CONSTANTS", "SHARED_STYLES_HOST", "SIGNAL", - "SIMPLE_CHANGES_STORE", "SKIP_HYDRATION_ATTR_NAME", "SKIP_HYDRATION_ATTR_NAME_LOWER_CASE", "SSR_BLOCK_STATE", @@ -263,7 +261,6 @@ "Sanitizer", "ShadowDomRenderer", "SharedStylesHost", - "SimpleChange", "StandaloneService", "Subject", "Subscriber", @@ -337,6 +334,7 @@ "_locateOrCreateElementContainerNode", "_locateOrCreateElementNode", "_locateOrCreateTextNode", + "_ngOnChangesFeatureImpl", "_platformInjector", "_populateDehydratedViewsInLContainer", "_processI18nInsertBefore", @@ -597,6 +595,7 @@ "getNextLContainer", "getNgContainerSize", "getNgDirectiveDef", + "getNgOnChangesFeatureImpl", "getNoOffsetIndex", "getNodeInjectable", "getNullInjector", @@ -626,7 +625,6 @@ "getSegmentHead", "getSelectedIndex", "getSerializedContainerViews", - "getSimpleChangesStore", "getStyleHost", "getSymbolIterator", "getTDeferBlockDetails", @@ -793,7 +791,6 @@ "nextNgElementId", "nextNotification", "nextRender", - "ngOnChangesSetInput", "ngZoneInstanceId", "noSideEffects", "noop", @@ -849,7 +846,6 @@ "registerPostOrderHooks", "registerPreOrderHooks", "relativePath", - "rememberChangeHistoryAndInvokeOnChangesHook", "remove", "removeAllEventListeners", "removeAnimationsFromQueue", @@ -926,7 +922,6 @@ "setSegmentHead", "setSelectedIndex", "setShadowStylingInputFlags", - "setSimpleChangesStore", "setStashFn", "setTargetElement", "setThrowInvalidWriteToSignalError", diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index a543ed73b37..a8e39cc0b77 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -381,6 +381,7 @@ "_locateOrCreateAnchorNode", "_locateOrCreateElementNode", "_locateOrCreateTextNode", + "_ngOnChangesFeatureImpl", "_platformInjector", "_processI18nInsertBefore", "_retrieveHydrationInfoImpl", @@ -694,6 +695,7 @@ "getNextLContainer", "getNgDirectiveDef", "getNgModuleDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", 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 3bf32b8c807..f50662bcafd 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -125,7 +125,6 @@ "NO_PARENT_INJECTOR", "NULL_INJECTOR", "NgModuleRef", - "NgOnChangesFeatureImpl", "NgZone", "NodeInjector", "NodeInjectorDestroyRef", @@ -163,14 +162,12 @@ "SCHEDULE_IN_ROOT_ZONE_DEFAULT", "SHARED_STYLES_HOST", "SIGNAL", - "SIMPLE_CHANGES_STORE", "STABILITY_WARNING_THRESHOLD", "SVG_NAMESPACE", "SafeSubscriber", "Sanitizer", "ShadowDomRenderer", "SharedStylesHost", - "SimpleChange", "StandaloneService", "Subject", "Subscriber", @@ -220,6 +217,7 @@ "_isRefreshingViews", "_keyMap", "_locateOrCreateTextNode", + "_ngOnChangesFeatureImpl", "_platformInjector", "_processI18nInsertBefore", "_retrieveHydrationInfoImpl", @@ -394,6 +392,7 @@ "getNearestLContainer", "getNextLContainer", "getNgDirectiveDef", + "getNgOnChangesFeatureImpl", "getNodeInjectable", "getNullInjector", "getOrBorrowReactiveLViewConsumer", @@ -414,7 +413,6 @@ "getRootTViewTemplate", "getRuntimeErrorCode", "getSelectedIndex", - "getSimpleChangesStore", "getStyleHost", "getTNode", "getTNodeFromLView", @@ -521,7 +519,6 @@ "nativeRemoveNode", "nextNgElementId", "nextNotification", - "ngOnChangesSetInput", "ngZoneInstanceId", "noSideEffects", "noop", @@ -559,7 +556,6 @@ "registerPostOrderHooks", "registerPreOrderHooks", "relativePath", - "rememberChangeHistoryAndInvokeOnChangesHook", "remove", "removeAnimationsFromQueue", "removeElements", @@ -604,7 +600,6 @@ "setRootDomAdapter", "setSelectedIndex", "setShadowStylingInputFlags", - "setSimpleChangesStore", "setThrowInvalidWriteToSignalError", "setUpAttributes", "setupHostDirectiveInputsOrOutputs",