@@ -20,6 +20,27 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
20
20
*/
21
21
const CSP_MEDIA_ATTR = 'ngCspMedia' ;
22
22
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
+
23
44
export interface InlineCriticalCssProcessOptions {
24
45
outputPath : string ;
25
46
}
@@ -40,6 +61,8 @@ interface PartialHTMLElement {
40
61
textContent : string ;
41
62
tagName : string | null ;
42
63
children : PartialHTMLElement [ ] ;
64
+ next : PartialHTMLElement | null ;
65
+ prev : PartialHTMLElement | null ;
43
66
}
44
67
45
68
/** Partial representation of an HTML `Document`. */
@@ -123,15 +146,7 @@ class CrittersExtended extends Critters {
123
146
this . conditionallyInsertCspLoadingScript ( document , cspNonce ) ;
124
147
}
125
148
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 ) ;
135
150
}
136
151
137
152
return returnValue ;
@@ -142,7 +157,8 @@ class CrittersExtended extends Critters {
142
157
*/
143
158
private findCspNonce ( document : PartialDocument ) : string | null {
144
159
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 ) ! ;
146
162
}
147
163
148
164
// HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
@@ -159,30 +175,14 @@ class CrittersExtended extends Critters {
159
175
* Inserts the `script` tag that swaps the critical CSS at runtime,
160
176
* if one hasn't been inserted into the document already.
161
177
*/
162
- private conditionallyInsertCspLoadingScript ( document : PartialDocument , nonce : string ) {
178
+ private conditionallyInsertCspLoadingScript ( document : PartialDocument , nonce : string ) : void {
163
179
if ( this . addedCspScriptsDocuments . has ( document ) ) {
164
180
return ;
165
181
}
166
182
167
183
const script = document . createElement ( 'script' ) ;
168
184
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 ;
186
186
// Append the script to the head since it needs to
187
187
// run as early as possible, after the `link` tags.
188
188
document . head . appendChild ( script ) ;
0 commit comments