@angular/ssr 19.0.0-next.1 → 19.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- export { getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig } from './src/routes/ng-routes';
9
- export { ServerRenderContext as ɵServerRenderContext, getOrCreateAngularServerApp as ɵgetOrCreateAngularServerApp, destroyAngularServerApp as ɵdestroyAngularServerApp, } from './src/app';
10
- export { setAngularAppManifest as ɵsetAngularAppManifest, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, } from './src/manifest';
@@ -1,9 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- export { CommonEngine, } from './src/common-engine/common-engine';
9
- export * from './private_export';
@@ -1,84 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- import { Hooks } from './hooks';
9
- import { getPotentialLocaleIdFromUrl } from './i18n';
10
- import { getAngularAppEngineManifest } from './manifest';
11
- /**
12
- * Angular server application engine.
13
- * Manages Angular server applications (including localized ones), handles rendering requests,
14
- * and optionally transforms index HTML before rendering.
15
- */
16
- export class AngularAppEngine {
17
- /**
18
- * Hooks for extending or modifying the behavior of the server application.
19
- * These hooks are used by the Angular CLI when running the development server and
20
- * provide extensibility points for the application lifecycle.
21
- *
22
- * @internal
23
- */
24
- static hooks = new Hooks();
25
- /**
26
- * Provides access to the hooks for extending or modifying the server application's behavior.
27
- * This allows attaching custom functionality to various server application lifecycle events.
28
- *
29
- * @internal
30
- */
31
- get hooks() {
32
- return AngularAppEngine.hooks;
33
- }
34
- /**
35
- * The manifest for the server application.
36
- */
37
- manifest = getAngularAppEngineManifest();
38
- /**
39
- * Renders a response for the given HTTP request using the server application.
40
- *
41
- * This method processes the request, determines the appropriate route and rendering context,
42
- * and returns an HTTP response.
43
- *
44
- * If the request URL appears to be for a file (excluding `/index.html`), the method returns `null`.
45
- * A request to `https://www.example.com/page/index.html` will render the Angular route
46
- * corresponding to `https://www.example.com/page`.
47
- *
48
- * @param request - The incoming HTTP request object to be rendered.
49
- * @param requestContext - Optional additional context for the request, such as metadata.
50
- * @returns A promise that resolves to a Response object, or `null` if the request URL represents a file (e.g., `./logo.png`)
51
- * rather than an application route.
52
- */
53
- async render(request, requestContext) {
54
- // Skip if the request looks like a file but not `/index.html`.
55
- const url = new URL(request.url);
56
- const entryPoint = this.getEntryPointFromUrl(url);
57
- if (!entryPoint) {
58
- return null;
59
- }
60
- const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await entryPoint();
61
- const serverApp = getOrCreateAngularServerApp();
62
- serverApp.hooks = this.hooks;
63
- return serverApp.render(request, requestContext);
64
- }
65
- /**
66
- * Retrieves the entry point path and locale for the Angular server application based on the provided URL.
67
- *
68
- * This method determines the appropriate entry point and locale for rendering the application by examining the URL.
69
- * If there is only one entry point available, it is returned regardless of the URL.
70
- * Otherwise, the method extracts a potential locale identifier from the URL and looks up the corresponding entry point.
71
- *
72
- * @param url - The URL used to derive the locale and determine the appropriate entry point.
73
- * @returns A function that returns a promise resolving to an object with the `EntryPointExports` type,
74
- * or `undefined` if no matching entry point is found for the extracted locale.
75
- */
76
- getEntryPointFromUrl(url) {
77
- const { entryPoints, basePath } = this.manifest;
78
- if (entryPoints.size === 1) {
79
- return entryPoints.values().next().value;
80
- }
81
- const potentialLocale = getPotentialLocaleIdFromUrl(url, basePath);
82
- return entryPoints.get(potentialLocale);
83
- }
84
- }
@@ -1,167 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- import { ɵConsole, ɵresetCompiledComponents } from '@angular/core';
9
- import { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server';
10
- import { ServerAssets } from './assets';
11
- import { Console } from './console';
12
- import { Hooks } from './hooks';
13
- import { getAngularAppManifest } from './manifest';
14
- import { ServerRouter } from './routes/router';
15
- import { REQUEST, REQUEST_CONTEXT, RESPONSE_INIT } from './tokens';
16
- import { renderAngular } from './utils/ng';
17
- /**
18
- * Enum representing the different contexts in which server rendering can occur.
19
- */
20
- export var ServerRenderContext;
21
- (function (ServerRenderContext) {
22
- ServerRenderContext["SSR"] = "ssr";
23
- ServerRenderContext["SSG"] = "ssg";
24
- ServerRenderContext["AppShell"] = "app-shell";
25
- })(ServerRenderContext || (ServerRenderContext = {}));
26
- /**
27
- * Represents a locale-specific Angular server application managed by the server application engine.
28
- *
29
- * The `AngularServerApp` class handles server-side rendering and asset management for a specific locale.
30
- */
31
- export class AngularServerApp {
32
- /**
33
- * Hooks for extending or modifying the behavior of the server application.
34
- * This instance can be used to attach custom functionality to various events in the server application lifecycle.
35
- */
36
- hooks = new Hooks();
37
- /**
38
- * The manifest associated with this server application.
39
- */
40
- manifest = getAngularAppManifest();
41
- /**
42
- * An instance of ServerAsset that handles server-side asset.
43
- */
44
- assets = new ServerAssets(this.manifest);
45
- /**
46
- * The router instance used for route matching and handling.
47
- */
48
- router;
49
- /**
50
- * Renders a response for the given HTTP request using the server application.
51
- *
52
- * This method processes the request and returns a response based on the specified rendering context.
53
- *
54
- * @param request - The incoming HTTP request to be rendered.
55
- * @param requestContext - Optional additional context for rendering, such as request metadata.
56
- * @param serverContext - The rendering context.
57
- *
58
- * @returns A promise that resolves to the HTTP response object resulting from the rendering, or null if no match is found.
59
- */
60
- render(request, requestContext, serverContext = ServerRenderContext.SSR) {
61
- return Promise.race([
62
- this.createAbortPromise(request),
63
- this.handleRendering(request, requestContext, serverContext),
64
- ]);
65
- }
66
- /**
67
- * Creates a promise that rejects when the request is aborted.
68
- *
69
- * @param request - The HTTP request to monitor for abortion.
70
- * @returns A promise that never resolves but rejects with an `AbortError` if the request is aborted.
71
- */
72
- createAbortPromise(request) {
73
- return new Promise((_, reject) => {
74
- request.signal.addEventListener('abort', () => {
75
- const abortError = new Error(`Request for: ${request.url} was aborted.\n${request.signal.reason}`);
76
- abortError.name = 'AbortError';
77
- reject(abortError);
78
- }, { once: true });
79
- });
80
- }
81
- /**
82
- * Handles the server-side rendering process for the given HTTP request.
83
- * This method matches the request URL to a route and performs rendering if a matching route is found.
84
- *
85
- * @param request - The incoming HTTP request to be processed.
86
- * @param requestContext - Optional additional context for rendering, such as request metadata.
87
- * @param serverContext - The rendering context. Defaults to server-side rendering (SSR).
88
- *
89
- * @returns A promise that resolves to the rendered response, or null if no matching route is found.
90
- */
91
- async handleRendering(request, requestContext, serverContext = ServerRenderContext.SSR) {
92
- const url = new URL(request.url);
93
- this.router ??= await ServerRouter.from(this.manifest, url);
94
- const matchedRoute = this.router.match(url);
95
- if (!matchedRoute) {
96
- // Not a known Angular route.
97
- return null;
98
- }
99
- const { redirectTo } = matchedRoute;
100
- if (redirectTo !== undefined) {
101
- // 302 Found is used by default for redirections
102
- // See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
103
- return Response.redirect(new URL(redirectTo, url), 302);
104
- }
105
- const platformProviders = [
106
- {
107
- provide: SERVER_CONTEXT,
108
- useValue: serverContext,
109
- },
110
- {
111
- // An Angular Console Provider that does not print a set of predefined logs.
112
- provide: ɵConsole,
113
- // Using `useClass` would necessitate decorating `Console` with `@Injectable`,
114
- // which would require switching from `ts_library` to `ng_module`. This change
115
- // would also necessitate various patches of `@angular/bazel` to support ESM.
116
- useFactory: () => new Console(),
117
- },
118
- ];
119
- const isSsrMode = serverContext === ServerRenderContext.SSR;
120
- const responseInit = {};
121
- if (isSsrMode) {
122
- platformProviders.push({
123
- provide: REQUEST,
124
- useValue: request,
125
- }, {
126
- provide: REQUEST_CONTEXT,
127
- useValue: requestContext,
128
- }, {
129
- provide: RESPONSE_INIT,
130
- useValue: responseInit,
131
- });
132
- }
133
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
134
- // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
135
- // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
136
- // See: https://github.com/angular/angular-cli/issues/25924
137
- ɵresetCompiledComponents();
138
- }
139
- const { manifest, hooks, assets } = this;
140
- let html = await assets.getIndexServerHtml();
141
- // Skip extra microtask if there are no pre hooks.
142
- if (hooks.has('html:transform:pre')) {
143
- html = await hooks.run('html:transform:pre', { html });
144
- }
145
- return new Response(await renderAngular(html, manifest.bootstrap(), new URL(request.url), platformProviders), responseInit);
146
- }
147
- }
148
- let angularServerApp;
149
- /**
150
- * Retrieves or creates an instance of `AngularServerApp`.
151
- * - If an instance of `AngularServerApp` already exists, it will return the existing one.
152
- * - If no instance exists, it will create a new one with the provided options.
153
- * @returns The existing or newly created instance of `AngularServerApp`.
154
- */
155
- export function getOrCreateAngularServerApp() {
156
- return (angularServerApp ??= new AngularServerApp());
157
- }
158
- /**
159
- * Destroys the existing `AngularServerApp` instance, releasing associated resources and resetting the
160
- * reference to `undefined`.
161
- *
162
- * This function is primarily used to enable the recreation of the `AngularServerApp` instance,
163
- * typically when server configuration or application state needs to be refreshed.
164
- */
165
- export function destroyAngularServerApp() {
166
- angularServerApp = undefined;
167
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- /**
9
- * Manages server-side assets.
10
- */
11
- export class ServerAssets {
12
- manifest;
13
- /**
14
- * Creates an instance of ServerAsset.
15
- *
16
- * @param manifest - The manifest containing the server assets.
17
- */
18
- constructor(manifest) {
19
- this.manifest = manifest;
20
- }
21
- /**
22
- * Retrieves the content of a server-side asset using its path.
23
- *
24
- * @param path - The path to the server asset.
25
- * @returns A promise that resolves to the asset content as a string.
26
- * @throws Error If the asset path is not found in the manifest, an error is thrown.
27
- */
28
- async getServerAsset(path) {
29
- const asset = this.manifest.assets.get(path);
30
- if (!asset) {
31
- throw new Error(`Server asset '${path}' does not exist.`);
32
- }
33
- return asset();
34
- }
35
- /**
36
- * Retrieves and caches the content of 'index.server.html'.
37
- *
38
- * @returns A promise that resolves to the content of 'index.server.html'.
39
- * @throws Error If there is an issue retrieving the asset.
40
- */
41
- getIndexServerHtml() {
42
- return this.getServerAsset('index.server.html');
43
- }
44
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- import { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
9
- import * as fs from 'node:fs';
10
- import { dirname, join, normalize, resolve } from 'node:path';
11
- import { URL } from 'node:url';
12
- import { InlineCriticalCssProcessor } from './inline-css-processor';
13
- import { noopRunMethodAndMeasurePerf, printPerformanceLogs, runMethodAndMeasurePerf, } from './peformance-profiler';
14
- const SSG_MARKER_REGEXP = /ng-server-context=["']\w*\|?ssg\|?\w*["']/;
15
- /**
16
- * A common engine to use to server render an application.
17
- */
18
- export class CommonEngine {
19
- options;
20
- templateCache = new Map();
21
- inlineCriticalCssProcessor;
22
- pageIsSSG = new Map();
23
- constructor(options) {
24
- this.options = options;
25
- this.inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
26
- minify: false,
27
- });
28
- }
29
- /**
30
- * Render an HTML document for a specific URL with specified
31
- * render options
32
- */
33
- async render(opts) {
34
- const enablePerformanceProfiler = this.options?.enablePerformanceProfiler;
35
- const runMethod = enablePerformanceProfiler
36
- ? runMethodAndMeasurePerf
37
- : noopRunMethodAndMeasurePerf;
38
- let html = await runMethod('Retrieve SSG Page', () => this.retrieveSSGPage(opts));
39
- if (html === undefined) {
40
- html = await runMethod('Render Page', () => this.renderApplication(opts));
41
- if (opts.inlineCriticalCss !== false) {
42
- const { content, errors, warnings } = await runMethod('Inline Critical CSS', () =>
43
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
44
- this.inlineCriticalCss(html, opts));
45
- html = content;
46
- // eslint-disable-next-line no-console
47
- warnings?.forEach((m) => console.warn(m));
48
- // eslint-disable-next-line no-console
49
- errors?.forEach((m) => console.error(m));
50
- }
51
- }
52
- if (enablePerformanceProfiler) {
53
- printPerformanceLogs();
54
- }
55
- return html;
56
- }
57
- inlineCriticalCss(html, opts) {
58
- return this.inlineCriticalCssProcessor.process(html, {
59
- outputPath: opts.publicPath ?? (opts.documentFilePath ? dirname(opts.documentFilePath) : ''),
60
- });
61
- }
62
- async retrieveSSGPage(opts) {
63
- const { publicPath, documentFilePath, url } = opts;
64
- if (!publicPath || !documentFilePath || url === undefined) {
65
- return undefined;
66
- }
67
- const { pathname } = new URL(url, 'resolve://');
68
- // Do not use `resolve` here as otherwise it can lead to path traversal vulnerability.
69
- // See: https://portswigger.net/web-security/file-path-traversal
70
- const pagePath = join(publicPath, pathname, 'index.html');
71
- if (this.pageIsSSG.get(pagePath)) {
72
- // Serve pre-rendered page.
73
- return fs.promises.readFile(pagePath, 'utf-8');
74
- }
75
- if (!pagePath.startsWith(normalize(publicPath))) {
76
- // Potential path traversal detected.
77
- return undefined;
78
- }
79
- if (pagePath === resolve(documentFilePath) || !(await exists(pagePath))) {
80
- // View matches with prerender path or file does not exist.
81
- this.pageIsSSG.set(pagePath, false);
82
- return undefined;
83
- }
84
- // Static file exists.
85
- const content = await fs.promises.readFile(pagePath, 'utf-8');
86
- const isSSG = SSG_MARKER_REGEXP.test(content);
87
- this.pageIsSSG.set(pagePath, isSSG);
88
- return isSSG ? content : undefined;
89
- }
90
- async renderApplication(opts) {
91
- const moduleOrFactory = this.options?.bootstrap ?? opts.bootstrap;
92
- if (!moduleOrFactory) {
93
- throw new Error('A module or bootstrap option must be provided.');
94
- }
95
- const extraProviders = [
96
- { provide: ɵSERVER_CONTEXT, useValue: 'ssr' },
97
- ...(opts.providers ?? []),
98
- ...(this.options?.providers ?? []),
99
- ];
100
- let document = opts.document;
101
- if (!document && opts.documentFilePath) {
102
- document = await this.getDocument(opts.documentFilePath);
103
- }
104
- const commonRenderingOptions = {
105
- url: opts.url,
106
- document,
107
- };
108
- return isBootstrapFn(moduleOrFactory)
109
- ? renderApplication(moduleOrFactory, {
110
- platformProviders: extraProviders,
111
- ...commonRenderingOptions,
112
- })
113
- : renderModule(moduleOrFactory, { extraProviders, ...commonRenderingOptions });
114
- }
115
- /** Retrieve the document from the cache or the filesystem */
116
- async getDocument(filePath) {
117
- let doc = this.templateCache.get(filePath);
118
- if (!doc) {
119
- doc = await fs.promises.readFile(filePath, 'utf-8');
120
- this.templateCache.set(filePath, doc);
121
- }
122
- return doc;
123
- }
124
- }
125
- async function exists(path) {
126
- try {
127
- await fs.promises.access(path, fs.constants.F_OK);
128
- return true;
129
- }
130
- catch {
131
- return false;
132
- }
133
- }
134
- function isBootstrapFn(value) {
135
- // We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
136
- return typeof value === 'function' && !('ɵmod' in value);
137
- }
@@ -1,181 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.dev/license
7
- */
8
- import Critters from 'critters';
9
- import { readFile } from 'node:fs/promises';
10
- /**
11
- * Pattern used to extract the media query set by Critters in an `onload` handler.
12
- */
13
- const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
14
- /**
15
- * Name of the attribute used to save the Critters media query so it can be re-assigned on load.
16
- */
17
- const CSP_MEDIA_ATTR = 'ngCspMedia';
18
- /**
19
- * Script text used to change the media value of the link tags.
20
- *
21
- * NOTE:
22
- * We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
23
- * because this does not always fire on Chome.
24
- * See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
25
- */
26
- const LINK_LOAD_SCRIPT_CONTENT = [
27
- '(() => {',
28
- ` const CSP_MEDIA_ATTR = '${CSP_MEDIA_ATTR}';`,
29
- ' const documentElement = document.documentElement;',
30
- ' const listener = (e) => {',
31
- ' const target = e.target;',
32
- ` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {`,
33
- ' return;',
34
- ' }',
35
- ' target.media = target.getAttribute(CSP_MEDIA_ATTR);',
36
- ' target.removeAttribute(CSP_MEDIA_ATTR);',
37
- // Remove onload listener when there are no longer styles that need to be loaded.
38
- ' if (!document.head.querySelector(`link[${CSP_MEDIA_ATTR}]`)) {',
39
- ` documentElement.removeEventListener('load', listener);`,
40
- ' }',
41
- ' };',
42
- // We use an event with capturing (the true parameter) because load events don't bubble.
43
- ` documentElement.addEventListener('load', listener, true);`,
44
- '})();',
45
- ].join('\n');
46
- class CrittersExtended extends Critters {
47
- optionsExtended;
48
- resourceCache;
49
- warnings = [];
50
- errors = [];
51
- initialEmbedLinkedStylesheet;
52
- addedCspScriptsDocuments = new WeakSet();
53
- documentNonces = new WeakMap();
54
- constructor(optionsExtended, resourceCache) {
55
- super({
56
- logger: {
57
- warn: (s) => this.warnings.push(s),
58
- error: (s) => this.errors.push(s),
59
- info: () => { },
60
- },
61
- logLevel: 'warn',
62
- path: optionsExtended.outputPath,
63
- publicPath: optionsExtended.deployUrl,
64
- compress: !!optionsExtended.minify,
65
- pruneSource: false,
66
- reduceInlineStyles: false,
67
- mergeStylesheets: false,
68
- // Note: if `preload` changes to anything other than `media`, the logic in
69
- // `embedLinkedStylesheetOverride` will have to be updated.
70
- preload: 'media',
71
- noscriptFallback: true,
72
- inlineFonts: true,
73
- });
74
- this.optionsExtended = optionsExtended;
75
- this.resourceCache = resourceCache;
76
- // We can't use inheritance to override `embedLinkedStylesheet`, because it's not declared in
77
- // the `Critters` .d.ts which means that we can't call the `super` implementation. TS doesn't
78
- // allow for `super` to be cast to a different type.
79
- this.initialEmbedLinkedStylesheet = this.embedLinkedStylesheet;
80
- this.embedLinkedStylesheet = this.embedLinkedStylesheetOverride;
81
- }
82
- async readFile(path) {
83
- let resourceContent = this.resourceCache.get(path);
84
- if (resourceContent === undefined) {
85
- resourceContent = await readFile(path, 'utf-8');
86
- this.resourceCache.set(path, resourceContent);
87
- }
88
- return resourceContent;
89
- }
90
- /**
91
- * Override of the Critters `embedLinkedStylesheet` method
92
- * that makes it work with Angular's CSP APIs.
93
- */
94
- embedLinkedStylesheetOverride = async (link, document) => {
95
- if (link.getAttribute('media') === 'print' && link.next?.name === 'noscript') {
96
- // Workaround for https://github.com/GoogleChromeLabs/critters/issues/64
97
- // NB: this is only needed for the webpack based builders.
98
- const media = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
99
- if (media) {
100
- link.removeAttribute('onload');
101
- link.setAttribute('media', media[1]);
102
- link?.next?.remove();
103
- }
104
- }
105
- const returnValue = await this.initialEmbedLinkedStylesheet(link, document);
106
- const cspNonce = this.findCspNonce(document);
107
- if (cspNonce) {
108
- const crittersMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
109
- if (crittersMedia) {
110
- // If there's a Critters-generated `onload` handler and the file has an Angular CSP nonce,
111
- // we have to remove the handler, because it's incompatible with CSP. We save the value
112
- // in a different attribute and we generate a script tag with the nonce that uses
113
- // `addEventListener` to apply the media query instead.
114
- link.removeAttribute('onload');
115
- link.setAttribute(CSP_MEDIA_ATTR, crittersMedia[1]);
116
- this.conditionallyInsertCspLoadingScript(document, cspNonce, link);
117
- }
118
- // Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
119
- // a way of doing that at the moment so we fall back to doing it any time a `link` tag is
120
- // inserted. We mitigate it by only iterating the direct children of the `<head>` which
121
- // should be pretty shallow.
122
- document.head.children.forEach((child) => {
123
- if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
124
- child.setAttribute('nonce', cspNonce);
125
- }
126
- });
127
- }
128
- return returnValue;
129
- };
130
- /**
131
- * Finds the CSP nonce for a specific document.
132
- */
133
- findCspNonce(document) {
134
- if (this.documentNonces.has(document)) {
135
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
136
- return this.documentNonces.get(document);
137
- }
138
- // HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
139
- const nonceElement = document.querySelector('[ngCspNonce], [ngcspnonce]');
140
- const cspNonce = nonceElement?.getAttribute('ngCspNonce') || nonceElement?.getAttribute('ngcspnonce') || null;
141
- this.documentNonces.set(document, cspNonce);
142
- return cspNonce;
143
- }
144
- /**
145
- * Inserts the `script` tag that swaps the critical CSS at runtime,
146
- * if one hasn't been inserted into the document already.
147
- */
148
- conditionallyInsertCspLoadingScript(document, nonce, link) {
149
- if (this.addedCspScriptsDocuments.has(document)) {
150
- return;
151
- }
152
- if (document.head.textContent.includes(LINK_LOAD_SCRIPT_CONTENT)) {
153
- // Script was already added during the build.
154
- this.addedCspScriptsDocuments.add(document);
155
- return;
156
- }
157
- const script = document.createElement('script');
158
- script.setAttribute('nonce', nonce);
159
- script.textContent = LINK_LOAD_SCRIPT_CONTENT;
160
- // Prepend the script to the head since it needs to
161
- // run as early as possible, before the `link` tags.
162
- document.head.insertBefore(script, link);
163
- this.addedCspScriptsDocuments.add(document);
164
- }
165
- }
166
- export class InlineCriticalCssProcessor {
167
- options;
168
- resourceCache = new Map();
169
- constructor(options) {
170
- this.options = options;
171
- }
172
- async process(html, options) {
173
- const critters = new CrittersExtended({ ...this.options, ...options }, this.resourceCache);
174
- const content = await critters.process(html);
175
- return {
176
- content,
177
- errors: critters.errors.length ? critters.errors : undefined,
178
- warnings: critters.warnings.length ? critters.warnings : undefined,
179
- };
180
- }
181
- }