Skip to content

Commit 9085a8f

Browse files
kirjsAndrewKushnir
authored andcommitted
fix(platform-server): Warn user when transfer state happens more than once (#58935)
This can happen if server providers are provided more than twice. We detect it on the state transfer phase by flagging app id as transferred in a set. Resolves #58531 PR Close #58935
1 parent d8aa201 commit 9085a8f

File tree

2 files changed

+210
-32
lines changed

2 files changed

+210
-32
lines changed

packages/platform-server/src/transfer_state.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import {DOCUMENT} from '@angular/common';
1010
import {
1111
APP_ID,
12+
inject,
13+
InjectionToken,
14+
Injector,
1215
Provider,
1316
TransferState,
1417
ɵstartMeasuring as startMeasuring,
@@ -17,11 +20,19 @@ import {
1720

1821
import {BEFORE_APP_SERIALIZED} from './tokens';
1922

23+
// Tracks whether the server-side application state for a given app ID has been serialized already.
24+
export const TRANSFER_STATE_SERIALIZED_FOR_APPID = new InjectionToken<Set<string>>(
25+
typeof ngDevMode === 'undefined' || ngDevMode ? 'TRANSFER_STATE_SERIALIZED_FOR_APPID' : '',
26+
{
27+
providedIn: 'platform',
28+
factory: () => new Set(),
29+
},
30+
);
31+
2032
export const TRANSFER_STATE_SERIALIZATION_PROVIDERS: Provider[] = [
2133
{
2234
provide: BEFORE_APP_SERIALIZED,
2335
useFactory: serializeTransferStateFactory,
24-
deps: [DOCUMENT, APP_ID, TransferState],
2536
multi: true,
2637
},
2738
];
@@ -41,7 +52,28 @@ export function createScript(
4152
return script;
4253
}
4354

44-
function serializeTransferStateFactory(doc: Document, appId: string, transferStore: TransferState) {
55+
export function warnIfStateTransferHappened(injector: Injector): void {
56+
const appId = injector.get(APP_ID);
57+
const appIdsWithTransferStateSerialized = injector.get(TRANSFER_STATE_SERIALIZED_FOR_APPID);
58+
59+
if (appIdsWithTransferStateSerialized.has(appId)) {
60+
console.warn(
61+
`Angular detected an incompatible configuration, which causes duplicate serialization of the server-side application state.\n\n` +
62+
`This can happen if the server providers have been provided more than once using different mechanisms. For example:\n\n` +
63+
` imports: [ServerModule], // Registers server providers\n` +
64+
` providers: [provideServerRendering()] // Also registers server providers\n\n` +
65+
`To fix this, ensure that the \`provideServerRendering()\` function is the only provider used and remove the other(s).`,
66+
);
67+
}
68+
appIdsWithTransferStateSerialized.add(appId);
69+
}
70+
71+
function serializeTransferStateFactory() {
72+
const doc = inject(DOCUMENT);
73+
const appId = inject(APP_ID);
74+
const transferStore = inject(TransferState);
75+
const injector = inject(Injector);
76+
4577
return () => {
4678
const measuringLabel = 'serializeTransferStateFactory';
4779
startMeasuring(measuringLabel);
@@ -55,6 +87,10 @@ function serializeTransferStateFactory(doc: Document, appId: string, transferSto
5587
return;
5688
}
5789

90+
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
91+
warnIfStateTransferHappened(injector);
92+
}
93+
5894
const script = createScript(
5995
doc,
6096
content,
@@ -68,7 +104,7 @@ function serializeTransferStateFactory(doc: Document, appId: string, transferSto
68104
script.setAttribute('type', 'application/json');
69105

70106
// It is intentional that we add the script at the very bottom. Angular CLI script tags for
71-
// bundles are always `type="module"`. These are deferred by default and cause the transfer
107+
// bundles are always `type="module"`. These are deferred by default and cause the
72108
// transfer data to be queried only after the browser has finished parsing the DOM.
73109
doc.body.appendChild(script);
74110
stopMeasuring(measuringLabel);

0 commit comments

Comments
 (0)