Skip to content

Commit cdfcb77

Browse files
devknollatscott
authored andcommitted
refactor(core): add internal API to enable i18n hydration (#54784)
Add an internal API to enable and use i18n hydration for testing and development. This helps ensure that we don't accidentally break the current behavior until we are completely ready to roll out i18n support. PR Close #54784
1 parent ac395d0 commit cdfcb77

File tree

7 files changed

+328
-192
lines changed

7 files changed

+328
-192
lines changed

packages/core/src/core_private_export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export {INJECTOR_SCOPE as ɵINJECTOR_SCOPE} from './di/scope';
2525
export {XSS_SECURITY_URL as ɵXSS_SECURITY_URL} from './error_details_base_url';
2626
export {formatRuntimeError as ɵformatRuntimeError, RuntimeError as ɵRuntimeError, RuntimeErrorCode as ɵRuntimeErrorCode} from './errors';
2727
export {annotateForHydration as ɵannotateForHydration} from './hydration/annotate';
28-
export {withDomHydration as ɵwithDomHydration} from './hydration/api';
28+
export {withDomHydration as ɵwithDomHydration, withI18nHydration as ɵwithI18nHydration} from './hydration/api';
2929
export {IS_HYDRATION_DOM_REUSE_ENABLED as ɵIS_HYDRATION_DOM_REUSE_ENABLED} from './hydration/tokens';
3030
export {HydratedNode as ɵHydratedNode, HydrationInfo as ɵHydrationInfo, readHydrationInfo as ɵreadHydrationInfo, SSR_CONTENT_INTEGRITY_MARKER as ɵSSR_CONTENT_INTEGRITY_MARKER} from './hydration/utils';
3131
export {CurrencyIndex as ɵCurrencyIndex, ExtraLocaleDataIndex as ɵExtraLocaleDataIndex, findLocaleData as ɵfindLocaleData, getLocaleCurrencyCode as ɵgetLocaleCurrencyCode, getLocalePluralCase as ɵgetLocalePluralCase, LocaleDataIndex as ɵLocaleDataIndex, registerLocaleData as ɵregisterLocaleData, unregisterAllLocaleData as ɵunregisterLocaleData} from './i18n/locale_data_api';

packages/core/src/hydration/annotate.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {CONTEXT, HEADER_OFFSET, HOST, LView, PARENT, RENDERER, TView, TVIEW, TVi
1919
import {unwrapLView, unwrapRNode} from '../render3/util/view_utils';
2020
import {TransferState} from '../transfer_state';
2121

22+
import {isI18nHydrationSupportEnabled} from './api';
2223
import {unsupportedProjectionOfDomNodes} from './error_handling';
2324
import {CONTAINERS, DISCONNECTED_NODES, ELEMENT_CONTAINERS, MULTIPLIER, NODES, NUM_ROOT_NODES, SerializedContainerView, SerializedView, TEMPLATE_ID, TEMPLATES} from './interfaces';
2425
import {calcPathForNode, isDisconnectedNode} from './node_lookup_utils';
@@ -526,7 +527,8 @@ function componentUsesShadowDomEncapsulation(lView: LView): boolean {
526527
function annotateHostElementForHydration(
527528
element: RElement, lView: LView, context: HydrationContext): number|null {
528529
const renderer = lView[RENDERER];
529-
if (hasI18n(lView) || componentUsesShadowDomEncapsulation(lView)) {
530+
if ((hasI18n(lView) && !isI18nHydrationSupportEnabled()) ||
531+
componentUsesShadowDomEncapsulation(lView)) {
530532
// Attach the skip hydration attribute if this component:
531533
// - either has i18n blocks, since hydrating such blocks is not yet supported
532534
// - or uses ShadowDom view encapsulation, since Domino doesn't support

packages/core/src/hydration/api.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {ENVIRONMENT_INITIALIZER, EnvironmentProviders, Injector, makeEnvironment
1212
import {inject} from '../di/injector_compatibility';
1313
import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../errors';
1414
import {enableLocateOrCreateContainerRefImpl} from '../linker/view_container_ref';
15+
import {enableLocateOrCreateI18nNodeImpl} from '../render3/i18n/i18n_apply';
1516
import {enableLocateOrCreateElementNodeImpl} from '../render3/instructions/element';
1617
import {enableLocateOrCreateElementContainerNodeImpl} from '../render3/instructions/element_container';
1718
import {enableApplyRootElementTransformImpl} from '../render3/instructions/shared';
@@ -24,7 +25,7 @@ import {performanceMarkFeature} from '../util/performance';
2425
import {NgZone} from '../zone';
2526

2627
import {cleanupDehydratedViews} from './cleanup';
27-
import {IS_HYDRATION_DOM_REUSE_ENABLED, PRESERVE_HOST_CONTENT} from './tokens';
28+
import {IS_HYDRATION_DOM_REUSE_ENABLED, IS_I18N_HYDRATION_ENABLED, PRESERVE_HOST_CONTENT} from './tokens';
2829
import {enableRetrieveHydrationInfoImpl, NGH_DATA_KEY, SSR_CONTENT_INTEGRITY_MARKER} from './utils';
2930
import {enableFindMatchingDehydratedViewImpl} from './views';
3031

@@ -34,6 +35,11 @@ import {enableFindMatchingDehydratedViewImpl} from './views';
3435
*/
3536
let isHydrationSupportEnabled = false;
3637

38+
/**
39+
* Indicates whether support for hydrating i18n blocks is enabled.
40+
*/
41+
let _isI18nHydrationSupportEnabled = false;
42+
3743
/**
3844
* Defines a period of time that Angular waits for the `ApplicationRef.isStable` to emit `true`.
3945
* If there was no event with the `true` value during this time, Angular reports a warning.
@@ -62,6 +68,7 @@ function enableHydrationRuntimeSupport() {
6268
enableLocateOrCreateContainerRefImpl();
6369
enableFindMatchingDehydratedViewImpl();
6470
enableApplyRootElementTransformImpl();
71+
enableLocateOrCreateI18nNodeImpl();
6572
}
6673
}
6774

@@ -145,6 +152,8 @@ export function withDomHydration(): EnvironmentProviders {
145152
{
146153
provide: ENVIRONMENT_INITIALIZER,
147154
useValue: () => {
155+
_isI18nHydrationSupportEnabled = !!inject(IS_I18N_HYDRATION_ENABLED, {optional: true});
156+
148157
// Since this function is used across both server and client,
149158
// make sure that the runtime code is only added when invoked
150159
// on the client. Moving forward, the `isPlatformBrowser` check should
@@ -198,6 +207,26 @@ export function withDomHydration(): EnvironmentProviders {
198207
]);
199208
}
200209

210+
/**
211+
* Returns a set of providers required to setup support for i18n hydration.
212+
* Requires hydration to be enabled separately.
213+
*/
214+
export function withI18nHydration(): EnvironmentProviders {
215+
return makeEnvironmentProviders([
216+
{
217+
provide: IS_I18N_HYDRATION_ENABLED,
218+
useValue: true,
219+
},
220+
]);
221+
}
222+
223+
/**
224+
* Returns whether i18n hydration support is enabled.
225+
*/
226+
export function isI18nHydrationSupportEnabled(): boolean {
227+
return _isI18nHydrationSupportEnabled;
228+
}
229+
201230
/**
202231
*
203232
* @param time The time in ms until the stable timedout warning message is logged

packages/core/src/hydration/tokens.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ export const PRESERVE_HOST_CONTENT = new InjectionToken<boolean>(
2828
providedIn: 'root',
2929
factory: () => PRESERVE_HOST_CONTENT_DEFAULT,
3030
});
31+
32+
/**
33+
* Internal token that indicates whether hydration support for i18n
34+
* is enabled.
35+
*/
36+
export const IS_I18N_HYDRATION_ENABLED = new InjectionToken<boolean>(
37+
(typeof ngDevMode === 'undefined' || !!ngDevMode ? 'IS_I18N_HYDRATION_ENABLED' : ''));

packages/core/src/render3/i18n/i18n_apply.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {RElement, RNode, RText} from '../interfaces/renderer_dom';
1818
import {SanitizerFn} from '../interfaces/sanitization';
1919
import {HEADER_OFFSET, LView, RENDERER, TView} from '../interfaces/view';
2020
import {createCommentNode, createElementNode, createTextNode, nativeInsertBefore, nativeParentNode, nativeRemoveNode, updateTextNode} from '../node_manipulation';
21-
import {getBindingIndex, lastNodeWasCreated} from '../state';
21+
import {getBindingIndex, lastNodeWasCreated, wasLastNodeCreated} from '../state';
2222
import {renderStringify} from '../util/stringify_utils';
2323
import {getNativeByIndex, unwrapRNode} from '../util/view_utils';
2424

@@ -78,12 +78,9 @@ export function applyI18n(tView: TView, lView: LView, index: number) {
7878
changeMaskCounter = 0;
7979
}
8080

81-
function locateOrCreateNode(
82-
lView: LView, index: number, textOrName: string,
81+
function createNodeWithoutHydration(
82+
lView: LView, textOrName: string,
8383
nodeType: typeof Node.COMMENT_NODE|typeof Node.TEXT_NODE|typeof Node.ELEMENT_NODE) {
84-
// TODO: Add support for hydration
85-
lastNodeWasCreated(true);
86-
8784
const renderer = lView[RENDERER];
8885

8986
switch (nodeType) {
@@ -98,6 +95,23 @@ function locateOrCreateNode(
9895
}
9996
}
10097

98+
let _locateOrCreateNode: typeof locateOrCreateNodeImpl = (lView, index, textOrName, nodeType) => {
99+
lastNodeWasCreated(true);
100+
return createNodeWithoutHydration(lView, textOrName, nodeType);
101+
};
102+
103+
function locateOrCreateNodeImpl(
104+
lView: LView, index: number, textOrName: string,
105+
nodeType: typeof Node.COMMENT_NODE|typeof Node.TEXT_NODE|typeof Node.ELEMENT_NODE) {
106+
// TODO: Add support for hydration
107+
lastNodeWasCreated(true);
108+
return createNodeWithoutHydration(lView, textOrName, nodeType);
109+
}
110+
111+
export function enableLocateOrCreateI18nNodeImpl() {
112+
_locateOrCreateNode = locateOrCreateNodeImpl;
113+
}
114+
101115
/**
102116
* Apply `I18nCreateOpCodes` op-codes as stored in `TI18n.create`.
103117
*
@@ -121,13 +135,15 @@ export function applyCreateOpCodes(
121135
(opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY;
122136
const index = opCode >>> I18nCreateOpCode.SHIFT;
123137
let rNode = lView[index];
138+
let lastNodeWasCreated = false;
124139
if (rNode === null) {
125140
// We only create new DOM nodes if they don't already exist: If ICU switches case back to a
126141
// case which was already instantiated, no need to create new DOM nodes.
127142
rNode = lView[index] =
128-
locateOrCreateNode(lView, index, text, isComment ? Node.COMMENT_NODE : Node.TEXT_NODE);
143+
_locateOrCreateNode(lView, index, text, isComment ? Node.COMMENT_NODE : Node.TEXT_NODE);
144+
lastNodeWasCreated = wasLastNodeCreated();
129145
}
130-
if (appendNow && parentRNode !== null) {
146+
if (appendNow && parentRNode !== null && lastNodeWasCreated) {
131147
nativeInsertBefore(renderer, parentRNode, rNode, insertInFrontOf, false);
132148
}
133149
}
@@ -160,7 +176,7 @@ export function applyMutableOpCodes(
160176
if (lView[textNodeIndex] === null) {
161177
ngDevMode && ngDevMode.rendererCreateTextNode++;
162178
ngDevMode && assertIndexInRange(lView, textNodeIndex);
163-
lView[textNodeIndex] = locateOrCreateNode(lView, textNodeIndex, opCode, Node.TEXT_NODE);
179+
lView[textNodeIndex] = _locateOrCreateNode(lView, textNodeIndex, opCode, Node.TEXT_NODE);
164180
}
165181
} else if (typeof opCode == 'number') {
166182
switch (opCode & IcuCreateOpCode.MASK_INSTRUCTION) {
@@ -238,7 +254,7 @@ export function applyMutableOpCodes(
238254
ngDevMode && ngDevMode.rendererCreateComment++;
239255
ngDevMode && assertIndexInExpandoRange(lView, commentNodeIndex);
240256
const commentRNode = lView[commentNodeIndex] =
241-
locateOrCreateNode(lView, commentNodeIndex, commentValue, Node.COMMENT_NODE);
257+
_locateOrCreateNode(lView, commentNodeIndex, commentValue, Node.COMMENT_NODE);
242258
// FIXME(misko): Attaching patch data is only needed for the root (Also add tests)
243259
attachPatchData(commentRNode, lView);
244260
}
@@ -255,7 +271,7 @@ export function applyMutableOpCodes(
255271
ngDevMode && ngDevMode.rendererCreateElement++;
256272
ngDevMode && assertIndexInExpandoRange(lView, elementNodeIndex);
257273
const elementRNode = lView[elementNodeIndex] =
258-
locateOrCreateNode(lView, elementNodeIndex, tagName, Node.ELEMENT_NODE);
274+
_locateOrCreateNode(lView, elementNodeIndex, tagName, Node.ELEMENT_NODE);
259275
// FIXME(misko): Attaching patch data is only needed for the root (Also add tests)
260276
attachPatchData(elementRNode, lView);
261277
}

packages/core/test/bundling/hydration/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@
215215
{
216216
"name": "IS_HYDRATION_DOM_REUSE_ENABLED"
217217
},
218+
{
219+
"name": "IS_I18N_HYDRATION_ENABLED"
220+
},
218221
{
219222
"name": "InjectFlags"
220223
},

0 commit comments

Comments
 (0)