Skip to content

Commit cf84acf

Browse files
alan-agius4AndrewKushnir
authored andcommitted
fix(platform-server): remove event dispatch script from HTML when hydration is disabled (angular#55681)
Prior to this commit, the included event dispatcher remained in the HTML even when hydration was disabled. PR Close angular#55681
1 parent 7898957 commit cf84acf

File tree

2 files changed

+70
-29
lines changed

2 files changed

+70
-29
lines changed

packages/platform-server/src/utils.ts

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,38 @@ function removeEventDispatchScript(doc: Document) {
7171
findEventDispatchScript(doc)?.remove();
7272
}
7373

74+
/**
75+
* Annotate nodes for hydration and remove event dispatch script when not needed.
76+
*/
77+
function prepareForHydration(platformState: PlatformState, applicationRef: ApplicationRef): void {
78+
const environmentInjector = applicationRef.injector;
79+
const doc = platformState.getDocument();
80+
81+
if (!environmentInjector.get(IS_HYDRATION_DOM_REUSE_ENABLED, false)) {
82+
// Hydration is diabled, remove inlined event dispatch script.
83+
// (which was injected by the build process) from the HTML.
84+
removeEventDispatchScript(doc);
85+
86+
return;
87+
}
88+
89+
appendSsrContentIntegrityMarker(doc);
90+
91+
const eventTypesToBeReplayed = annotateForHydration(applicationRef, doc);
92+
if (eventTypesToBeReplayed) {
93+
insertEventRecordScript(
94+
environmentInjector.get(APP_ID),
95+
doc,
96+
eventTypesToBeReplayed,
97+
environmentInjector.get(CSP_NONCE, null),
98+
);
99+
} else {
100+
// No events to replay, we should remove inlined event dispatch script
101+
// (which was injected by the build process) from the HTML.
102+
removeEventDispatchScript(doc);
103+
}
104+
}
105+
74106
/**
75107
* Creates a marker comment node and append it into the `<body>`.
76108
* Some CDNs have mechanisms to remove all comment node from HTML.
@@ -124,31 +156,14 @@ function insertEventRecordScript(
124156
}
125157

126158
async function _render(platformRef: PlatformRef, applicationRef: ApplicationRef): Promise<string> {
127-
const environmentInjector = applicationRef.injector;
128-
129159
// Block until application is stable.
130160
await whenStable(applicationRef);
131161

132162
const platformState = platformRef.injector.get(PlatformState);
133-
if (environmentInjector.get(IS_HYDRATION_DOM_REUSE_ENABLED, false)) {
134-
const doc = platformState.getDocument();
135-
appendSsrContentIntegrityMarker(doc);
136-
const eventTypesToBeReplayed = annotateForHydration(applicationRef, doc);
137-
if (eventTypesToBeReplayed) {
138-
insertEventRecordScript(
139-
environmentInjector.get(APP_ID),
140-
doc,
141-
eventTypesToBeReplayed,
142-
environmentInjector.get(CSP_NONCE, null),
143-
);
144-
} else {
145-
// No events to replay, we should remove inlined event dispatch script
146-
// (which was injected by the build process) from the HTML.
147-
removeEventDispatchScript(doc);
148-
}
149-
}
163+
prepareForHydration(platformState, applicationRef);
150164

151165
// Run any BEFORE_APP_SERIALIZED callbacks just before rendering to string.
166+
const environmentInjector = applicationRef.injector;
152167
const callbacks = environmentInjector.get(BEFORE_APP_SERIALIZED, null);
153168
if (callbacks) {
154169
const asyncCallbacks: Promise<void>[] = [];

packages/platform-server/test/event_replay_spec.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,27 @@ describe('event replay', () => {
5454
*/
5555
async function ssr(
5656
component: Type<unknown>,
57-
options?: {doc?: string; enableEventReplay?: boolean},
57+
options: {doc?: string; enableEventReplay?: boolean; hydrationDisabled?: boolean} = {},
5858
): Promise<string> {
59-
const enableEventReplay = options?.enableEventReplay ?? true;
60-
const defaultHtml = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
61-
const hydrationProviders = enableEventReplay
62-
? provideClientHydration(withEventReplay())
63-
: provideClientHydration();
64-
const providers = [provideServerRendering(), hydrationProviders];
65-
66-
const bootstrap = () => bootstrapApplication(component, {providers});
59+
const {
60+
enableEventReplay = true,
61+
hydrationDisabled,
62+
doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`,
63+
} = options;
64+
65+
const hydrationProviders = hydrationDisabled
66+
? []
67+
: enableEventReplay
68+
? provideClientHydration(withEventReplay())
69+
: provideClientHydration();
70+
71+
const bootstrap = () =>
72+
bootstrapApplication(component, {
73+
providers: [provideServerRendering(), hydrationProviders],
74+
});
6775

6876
return renderApplication(bootstrap, {
69-
document: options?.doc ?? defaultHtml,
77+
document: doc,
7078
});
7179
}
7280

@@ -208,6 +216,24 @@ describe('event replay', () => {
208216
});
209217

210218
describe('event dispatch script', () => {
219+
it('should not be present on a page when hydration is disabled', async () => {
220+
@Component({
221+
standalone: true,
222+
selector: 'app',
223+
template: '<input (click)="onClick()" />',
224+
})
225+
class SimpleComponent {
226+
onClick() {}
227+
}
228+
229+
const doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
230+
const html = await ssr(SimpleComponent, {doc, hydrationDisabled: true});
231+
const ssrContents = getAppContents(html);
232+
233+
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
234+
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
235+
});
236+
211237
it('should not be present on a page if there are no events to replay', async () => {
212238
@Component({
213239
standalone: true,

0 commit comments

Comments
 (0)