Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e88aea6

Browse files
committedMar 30, 2023
perf(@angular-devkit/build-angular): avoid unnessary iterations
This commit reduces the number of child node visited when adding the nonce attribute. This changes have been ported form angular/universal.
1 parent a76228d commit e88aea6

File tree

1 file changed

+28
-28
lines changed

1 file changed

+28
-28
lines changed
 

‎packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css.ts

+28-28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
2020
*/
2121
const CSP_MEDIA_ATTR = 'ngCspMedia';
2222

23+
/**
24+
* Script text used to change the media value of the link tags.
25+
*/
26+
const LINK_LOAD_SCRIPT_CONTENT = [
27+
`(() => {`,
28+
// Save the `children` in a variable since they're a live DOM node collection.
29+
// We iterate over the direct descendants, instead of going through a `querySelectorAll`,
30+
// because we know that the tags will be directly inside the `head`.
31+
` const children = document.head.children;`,
32+
// Declare `onLoad` outside the loop to avoid leaking memory.
33+
// Can't be an arrow function, because we need `this` to refer to the DOM node.
34+
` function onLoad() {this.media = this.getAttribute('${CSP_MEDIA_ATTR}');}`,
35+
// Has to use a plain for loop, because some browsers don't support
36+
// `forEach` on `children` which is a `HTMLCollection`.
37+
` for (let i = 0; i < children.length; i++) {`,
38+
` const child = children[i];`,
39+
` child.hasAttribute('${CSP_MEDIA_ATTR}') && child.addEventListener('load', onLoad);`,
40+
` }`,
41+
`})();`,
42+
].join('\n');
43+
2344
export interface InlineCriticalCssProcessOptions {
2445
outputPath: string;
2546
}
@@ -40,6 +61,8 @@ interface PartialHTMLElement {
4061
textContent: string;
4162
tagName: string | null;
4263
children: PartialHTMLElement[];
64+
next: PartialHTMLElement | null;
65+
prev: PartialHTMLElement | null;
4366
}
4467

4568
/** Partial representation of an HTML `Document`. */
@@ -123,15 +146,7 @@ class CrittersExtended extends Critters {
123146
this.conditionallyInsertCspLoadingScript(document, cspNonce);
124147
}
125148

126-
// Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
127-
// a way of doing that at the moment so we fall back to doing it any time a `link` tag is
128-
// inserted. We mitigate it by only iterating the direct children of the `<head>` which
129-
// should be pretty shallow.
130-
document.head.children.forEach((child) => {
131-
if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
132-
child.setAttribute('nonce', cspNonce);
133-
}
134-
});
149+
link.prev?.setAttribute('nonce', cspNonce);
135150
}
136151

137152
return returnValue;
@@ -142,7 +157,8 @@ class CrittersExtended extends Critters {
142157
*/
143158
private findCspNonce(document: PartialDocument): string | null {
144159
if (this.documentNonces.has(document)) {
145-
return this.documentNonces.get(document) ?? null;
160+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
161+
return this.documentNonces.get(document)!;
146162
}
147163

148164
// HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
@@ -159,30 +175,14 @@ class CrittersExtended extends Critters {
159175
* Inserts the `script` tag that swaps the critical CSS at runtime,
160176
* if one hasn't been inserted into the document already.
161177
*/
162-
private conditionallyInsertCspLoadingScript(document: PartialDocument, nonce: string) {
178+
private conditionallyInsertCspLoadingScript(document: PartialDocument, nonce: string): void {
163179
if (this.addedCspScriptsDocuments.has(document)) {
164180
return;
165181
}
166182

167183
const script = document.createElement('script');
168184
script.setAttribute('nonce', nonce);
169-
script.textContent = [
170-
`(() => {`,
171-
// Save the `children` in a variable since they're a live DOM node collection.
172-
// We iterate over the direct descendants, instead of going through a `querySelectorAll`,
173-
// because we know that the tags will be directly inside the `head`.
174-
` const children = document.head.children;`,
175-
// Declare `onLoad` outside the loop to avoid leaking memory.
176-
// Can't be an arrow function, because we need `this` to refer to the DOM node.
177-
` function onLoad() {this.media = this.getAttribute('${CSP_MEDIA_ATTR}');}`,
178-
// Has to use a plain for loop, because some browsers don't support
179-
// `forEach` on `children` which is a `HTMLCollection`.
180-
` for (let i = 0; i < children.length; i++) {`,
181-
` const child = children[i];`,
182-
` child.hasAttribute('${CSP_MEDIA_ATTR}') && child.addEventListener('load', onLoad);`,
183-
` }`,
184-
`})();`,
185-
].join('\n');
185+
script.textContent = LINK_LOAD_SCRIPT_CONTENT;
186186
// Append the script to the head since it needs to
187187
// run as early as possible, after the `link` tags.
188188
document.head.appendChild(script);

0 commit comments

Comments
 (0)
Please sign in to comment.