Skip to content

Commit 17d3af3

Browse files
author
Angular Builds
committed
02d9d84 fix(@angular/ssr): handle handle load event for multiple stylesheets and CSP nonces
1 parent a077bc3 commit 17d3af3

File tree

3 files changed

+34
-26
lines changed

3 files changed

+34
-26
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"name": "@angular-devkit/build-angular",
3-
"version": "17.1.0+sha-35ebf1e",
3+
"version": "17.1.0+sha-02d9d84",
44
"description": "Angular Webpack Build Facade",
55
"main": "src/index.js",
66
"typings": "src/index.d.ts",
77
"builders": "builders.json",
88
"dependencies": {
99
"@ampproject/remapping": "2.2.1",
10-
"@angular-devkit/architect": "github:angular/angular-devkit-architect-builds#35ebf1e",
11-
"@angular-devkit/build-webpack": "github:angular/angular-devkit-build-webpack-builds#35ebf1e",
12-
"@angular-devkit/core": "github:angular/angular-devkit-core-builds#35ebf1e",
10+
"@angular-devkit/architect": "github:angular/angular-devkit-architect-builds#02d9d84",
11+
"@angular-devkit/build-webpack": "github:angular/angular-devkit-build-webpack-builds#02d9d84",
12+
"@angular-devkit/core": "github:angular/angular-devkit-core-builds#02d9d84",
1313
"@babel/core": "7.23.7",
1414
"@babel/generator": "7.23.6",
1515
"@babel/helper-annotate-as-pure": "7.22.5",
@@ -20,7 +20,7 @@
2020
"@babel/preset-env": "7.23.7",
2121
"@babel/runtime": "7.23.7",
2222
"@discoveryjs/json-ext": "0.5.7",
23-
"@ngtools/webpack": "github:angular/ngtools-webpack-builds#35ebf1e",
23+
"@ngtools/webpack": "github:angular/ngtools-webpack-builds#02d9d84",
2424
"@vitejs/plugin-basic-ssl": "1.0.2",
2525
"ansi-colors": "4.1.3",
2626
"autoprefixer": "10.4.16",

src/utils/index-file/inline-critical-css.js

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,31 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
2323
const CSP_MEDIA_ATTR = 'ngCspMedia';
2424
/**
2525
* Script text used to change the media value of the link tags.
26+
*
27+
* NOTE:
28+
* We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
29+
* because this does not always fire on Chome.
30+
* See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
2631
*/
2732
const LINK_LOAD_SCRIPT_CONTENT = [
28-
`(() => {`,
29-
// Save the `children` in a variable since they're a live DOM node collection.
30-
// We iterate over the direct descendants, instead of going through a `querySelectorAll`,
31-
// because we know that the tags will be directly inside the `head`.
32-
` const children = document.head.children;`,
33-
// Declare `onLoad` outside the loop to avoid leaking memory.
34-
// Can't be an arrow function, because we need `this` to refer to the DOM node.
35-
` function onLoad() {this.media = this.getAttribute('${CSP_MEDIA_ATTR}');}`,
36-
// Has to use a plain for loop, because some browsers don't support
37-
// `forEach` on `children` which is a `HTMLCollection`.
38-
` for (let i = 0; i < children.length; i++) {`,
39-
` const child = children[i];`,
40-
` child.hasAttribute('${CSP_MEDIA_ATTR}') && child.addEventListener('load', onLoad);`,
41-
` }`,
42-
`})();`,
33+
'(() => {',
34+
` const CSP_MEDIA_ATTR = '${CSP_MEDIA_ATTR}';`,
35+
' const documentElement = document.documentElement;',
36+
' const listener = (e) => {',
37+
' const target = e.target;',
38+
` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {`,
39+
' return;',
40+
' }',
41+
' target.media = target.getAttribute(CSP_MEDIA_ATTR);',
42+
' target.removeAttribute(CSP_MEDIA_ATTR);',
43+
// Remove onload listener when there are no longer styles that need to be loaded.
44+
' if (!document.head.querySelector(`link[${CSP_MEDIA_ATTR}]`)) {',
45+
` documentElement.removeEventListener('load', listener);`,
46+
' }',
47+
' };',
48+
// We use an event with capturing (the true parameter) because load events don't bubble.
49+
` documentElement.addEventListener('load', listener, true);`,
50+
'})();',
4351
].join('\n');
4452
class CrittersExtended extends critters_1.default {
4553
optionsExtended;
@@ -105,7 +113,7 @@ class CrittersExtended extends critters_1.default {
105113
// `addEventListener` to apply the media query instead.
106114
link.removeAttribute('onload');
107115
link.setAttribute(CSP_MEDIA_ATTR, crittersMedia[1]);
108-
this.conditionallyInsertCspLoadingScript(document, cspNonce);
116+
this.conditionallyInsertCspLoadingScript(document, cspNonce, link);
109117
}
110118
// Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
111119
// a way of doing that at the moment so we fall back to doing it any time a `link` tag is
@@ -137,16 +145,16 @@ class CrittersExtended extends critters_1.default {
137145
* Inserts the `script` tag that swaps the critical CSS at runtime,
138146
* if one hasn't been inserted into the document already.
139147
*/
140-
conditionallyInsertCspLoadingScript(document, nonce) {
148+
conditionallyInsertCspLoadingScript(document, nonce, link) {
141149
if (this.addedCspScriptsDocuments.has(document)) {
142150
return;
143151
}
144152
const script = document.createElement('script');
145153
script.setAttribute('nonce', nonce);
146154
script.textContent = LINK_LOAD_SCRIPT_CONTENT;
147-
// Append the script to the head since it needs to
148-
// run as early as possible, after the `link` tags.
149-
document.head.appendChild(script);
155+
// Prepend the script to the head since it needs to
156+
// run as early as possible, before the `link` tags.
157+
document.head.insertBefore(script, link);
150158
this.addedCspScriptsDocuments.add(document);
151159
}
152160
}

uniqueId

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Tue Jan 23 2024 17:39:24 GMT+0000 (Coordinated Universal Time)
1+
Wed Jan 24 2024 14:03:09 GMT+0000 (Coordinated Universal Time)

0 commit comments

Comments
 (0)