@angular/build 19.0.0-next.9 → 19.0.0-rc.1

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.
Files changed (90) hide show
  1. package/package.json +21 -19
  2. package/src/builders/application/build-action.js +22 -10
  3. package/src/builders/application/chunk-optimizer.js +1 -4
  4. package/src/builders/application/execute-build.js +59 -24
  5. package/src/builders/application/execute-post-bundle.js +28 -5
  6. package/src/builders/application/index.d.ts +0 -16
  7. package/src/builders/application/index.js +15 -10
  8. package/src/builders/application/options.d.ts +14 -2
  9. package/src/builders/application/options.js +25 -10
  10. package/src/builders/application/results.d.ts +5 -3
  11. package/src/builders/application/schema.d.ts +86 -0
  12. package/src/builders/application/schema.js +19 -1
  13. package/src/builders/application/schema.json +73 -4
  14. package/src/builders/application/setup-bundling.d.ts +6 -1
  15. package/src/builders/application/setup-bundling.js +47 -13
  16. package/src/builders/dev-server/options.d.ts +2 -2
  17. package/src/builders/dev-server/options.js +2 -2
  18. package/src/builders/dev-server/schema.d.ts +2 -1
  19. package/src/builders/dev-server/schema.json +1 -2
  20. package/src/builders/dev-server/vite-server.d.ts +3 -2
  21. package/src/builders/dev-server/vite-server.js +123 -61
  22. package/src/index.d.ts +1 -0
  23. package/src/tools/angular/angular-host.d.ts +1 -1
  24. package/src/tools/angular/angular-host.js +14 -6
  25. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  26. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  27. package/src/tools/angular/compilation/aot-compilation.js +39 -0
  28. package/src/tools/angular/compilation/parallel-compilation.js +2 -2
  29. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  30. package/src/tools/angular/compilation/parallel-worker.js +5 -2
  31. package/src/tools/esbuild/angular/compiler-plugin.d.ts +3 -4
  32. package/src/tools/esbuild/angular/compiler-plugin.js +58 -33
  33. package/src/tools/esbuild/angular/component-stylesheets.d.ts +18 -18
  34. package/src/tools/esbuild/angular/component-stylesheets.js +66 -38
  35. package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
  36. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +11 -3
  37. package/src/tools/esbuild/angular/source-file-cache.d.ts +1 -1
  38. package/src/tools/esbuild/angular/source-file-cache.js +6 -2
  39. package/src/tools/esbuild/application-code-bundle.d.ts +7 -5
  40. package/src/tools/esbuild/application-code-bundle.js +280 -249
  41. package/src/tools/esbuild/bundler-context.d.ts +2 -1
  42. package/src/tools/esbuild/bundler-context.js +10 -12
  43. package/src/tools/esbuild/bundler-execution-result.d.ts +14 -3
  44. package/src/tools/esbuild/bundler-execution-result.js +15 -8
  45. package/src/tools/esbuild/commonjs-checker.js +2 -2
  46. package/src/tools/esbuild/compiler-plugin-options.d.ts +2 -4
  47. package/src/tools/esbuild/compiler-plugin-options.js +15 -37
  48. package/src/tools/esbuild/global-scripts.js +1 -1
  49. package/src/tools/esbuild/global-styles.js +4 -1
  50. package/src/tools/esbuild/index-html-generator.js +8 -0
  51. package/src/tools/esbuild/javascript-transformer.js +3 -0
  52. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
  53. package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
  54. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  55. package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
  56. package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
  57. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
  58. package/src/tools/esbuild/utils.js +13 -31
  59. package/src/tools/sass/worker.js +19 -0
  60. package/src/tools/vite/middlewares/assets-middleware.d.ts +6 -1
  61. package/src/tools/vite/middlewares/assets-middleware.js +42 -22
  62. package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
  63. package/src/tools/vite/middlewares/component-middleware.js +33 -0
  64. package/src/tools/vite/middlewares/index.d.ts +2 -1
  65. package/src/tools/vite/middlewares/index.js +3 -1
  66. package/src/tools/vite/middlewares/ssr-middleware.js +11 -8
  67. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -0
  68. package/src/tools/vite/plugins/angular-memory-plugin.js +5 -13
  69. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +3 -1
  70. package/src/tools/vite/plugins/setup-middlewares-plugin.js +12 -3
  71. package/src/tools/vite/utils.d.ts +1 -0
  72. package/src/typings.d.ts +1 -1
  73. package/src/utils/environment-options.d.ts +1 -0
  74. package/src/utils/environment-options.js +4 -2
  75. package/src/utils/index-file/auto-csp.d.ts +23 -0
  76. package/src/utils/index-file/auto-csp.js +283 -0
  77. package/src/utils/index-file/html-rewriting-stream.d.ts +5 -1
  78. package/src/utils/index-file/index-html-generator.d.ts +4 -0
  79. package/src/utils/index-file/index-html-generator.js +11 -0
  80. package/src/utils/index-file/inline-critical-css.js +17 -18
  81. package/src/utils/normalize-cache.js +1 -1
  82. package/src/utils/server-rendering/esm-in-memory-loader/utils.d.ts +8 -0
  83. package/src/utils/server-rendering/esm-in-memory-loader/utils.js +13 -0
  84. package/src/utils/server-rendering/launch-server.js +5 -5
  85. package/src/utils/server-rendering/load-esm-from-memory.d.ts +1 -1
  86. package/src/utils/server-rendering/manifest.d.ts +9 -8
  87. package/src/utils/server-rendering/manifest.js +17 -23
  88. package/src/utils/server-rendering/prerender.js +30 -19
  89. package/src/utils/server-rendering/render-worker.js +4 -2
  90. package/src/utils/supported-browsers.js +1 -0
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || function (mod) {
26
+ if (mod && mod.__esModule) return mod;
27
+ var result = {};
28
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29
+ __setModuleDefault(result, mod);
30
+ return result;
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.hashTextContent = hashTextContent;
34
+ exports.autoCsp = autoCsp;
35
+ const crypto = __importStar(require("node:crypto"));
36
+ const html_rewriting_stream_1 = require("./html-rewriting-stream");
37
+ /**
38
+ * The hash function to use for hash directives to use in the CSP.
39
+ */
40
+ const HASH_FUNCTION = 'sha256';
41
+ /**
42
+ * Get the specified attribute or return undefined if the tag doesn't have that attribute.
43
+ *
44
+ * @param tag StartTag of the <script>
45
+ * @returns
46
+ */
47
+ function getScriptAttributeValue(tag, attrName) {
48
+ return tag.attrs.find((attr) => attr.name === attrName)?.value;
49
+ }
50
+ /**
51
+ * Checks whether a particular string is a MIME type associated with JavaScript, according to
52
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types#textjavascript
53
+ *
54
+ * @param mimeType a string that may be a MIME type
55
+ * @returns whether the string is a MIME type that is associated with JavaScript
56
+ */
57
+ function isJavascriptMimeType(mimeType) {
58
+ return mimeType.split(';')[0] === 'text/javascript';
59
+ }
60
+ /**
61
+ * Which of the type attributes on the script tag we should try passing along
62
+ * based on https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
63
+ * @param scriptType the `type` attribute on the `<script>` tag under question
64
+ * @returns whether to add the script tag to the dynamically loaded script tag
65
+ */
66
+ function shouldDynamicallyLoadScriptTagBasedOnType(scriptType) {
67
+ return (scriptType === undefined ||
68
+ scriptType === '' ||
69
+ scriptType === 'module' ||
70
+ isJavascriptMimeType(scriptType));
71
+ }
72
+ /**
73
+ * Calculates a CSP compatible hash of an inline script.
74
+ * @param scriptText Text between opening and closing script tag. Has to
75
+ * include whitespaces and newlines!
76
+ * @returns The hash of the text formatted appropriately for CSP.
77
+ */
78
+ function hashTextContent(scriptText) {
79
+ const hash = crypto.createHash(HASH_FUNCTION).update(scriptText, 'utf-8').digest('base64');
80
+ return `'${HASH_FUNCTION}-${hash}'`;
81
+ }
82
+ /**
83
+ * Finds all `<script>` tags and creates a dynamic script loading block for consecutive `<script>` with `src` attributes.
84
+ * Hashes all scripts, both inline and generated dynamic script loading blocks.
85
+ * Inserts a `<meta>` tag at the end of the `<head>` of the document with the generated hash-based CSP.
86
+ *
87
+ * @param html Markup that should be processed.
88
+ * @returns The transformed HTML that contains the `<meta>` tag CSP and dynamic loader scripts.
89
+ */
90
+ async function autoCsp(html, unsafeEval = false) {
91
+ const { rewriter, transformedContent } = await (0, html_rewriting_stream_1.htmlRewritingStream)(html);
92
+ let openedScriptTag = undefined;
93
+ let scriptContent = [];
94
+ const hashes = [];
95
+ /**
96
+ * Generates the dynamic loading script and puts it in the rewriter and adds the hash of the dynamic
97
+ * loader script to the collection of hashes to add to the <meta> tag CSP.
98
+ */
99
+ function emitLoaderScript() {
100
+ const loaderScript = createLoaderScript(scriptContent);
101
+ hashes.push(hashTextContent(loaderScript));
102
+ rewriter.emitRaw(`<script>${loaderScript}</script>`);
103
+ scriptContent = [];
104
+ }
105
+ rewriter.on('startTag', (tag, html) => {
106
+ if (tag.tagName === 'script') {
107
+ openedScriptTag = tag;
108
+ const src = getScriptAttributeValue(tag, 'src');
109
+ if (src) {
110
+ // If there are any interesting attributes, note them down.
111
+ const scriptType = getScriptAttributeValue(tag, 'type');
112
+ if (shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) {
113
+ scriptContent.push({
114
+ src: src,
115
+ type: scriptType,
116
+ async: getScriptAttributeValue(tag, 'async') !== undefined,
117
+ defer: getScriptAttributeValue(tag, 'defer') !== undefined,
118
+ });
119
+ return; // Skip writing my script tag until we've read it all.
120
+ }
121
+ }
122
+ }
123
+ // We are encountering the first start tag that's not <script src="..."> after a string of
124
+ // consecutive <script src="...">. <script> tags without a src attribute will also end a chain
125
+ // of src attributes that can be loaded in a single loader script, so those will end up here.
126
+ //
127
+ // The first place when we can determine this to be the case is
128
+ // during the first opening tag that's not <script src="...">, where we need to insert the
129
+ // dynamic loader script before continuing on with writing the rest of the tags.
130
+ // (One edge case is where there are no more opening tags after the last <script src="..."> is
131
+ // closed, but this case is handled below with the final </body> tag.)
132
+ if (scriptContent.length > 0) {
133
+ emitLoaderScript();
134
+ }
135
+ rewriter.emitStartTag(tag);
136
+ });
137
+ rewriter.on('text', (tag, html) => {
138
+ if (openedScriptTag && !getScriptAttributeValue(openedScriptTag, 'src')) {
139
+ hashes.push(hashTextContent(html));
140
+ }
141
+ rewriter.emitText(tag);
142
+ });
143
+ rewriter.on('endTag', (tag, html) => {
144
+ if (openedScriptTag && tag.tagName === 'script') {
145
+ const src = getScriptAttributeValue(openedScriptTag, 'src');
146
+ const scriptType = getScriptAttributeValue(openedScriptTag, 'type');
147
+ openedScriptTag = undefined;
148
+ // Return early to avoid writing the closing </script> tag if it's a part of the
149
+ // dynamic loader script.
150
+ if (src && shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) {
151
+ return;
152
+ }
153
+ }
154
+ if (tag.tagName === 'body' || tag.tagName === 'html') {
155
+ // Write the loader script if a string of <script>s were the last opening tag of the document.
156
+ if (scriptContent.length > 0) {
157
+ emitLoaderScript();
158
+ }
159
+ }
160
+ rewriter.emitEndTag(tag);
161
+ });
162
+ const rewritten = await transformedContent();
163
+ // Second pass to add the header
164
+ const secondPass = await (0, html_rewriting_stream_1.htmlRewritingStream)(rewritten);
165
+ secondPass.rewriter.on('startTag', (tag, _) => {
166
+ secondPass.rewriter.emitStartTag(tag);
167
+ if (tag.tagName === 'head') {
168
+ // See what hashes we came up with!
169
+ secondPass.rewriter.emitRaw(`<meta http-equiv="Content-Security-Policy" content="${getStrictCsp(hashes, {
170
+ enableBrowserFallbacks: true,
171
+ enableTrustedTypes: false,
172
+ enableUnsafeEval: unsafeEval,
173
+ })}">`);
174
+ }
175
+ });
176
+ return secondPass.transformedContent();
177
+ }
178
+ /**
179
+ * Returns a strict Content Security Policy for mitigating XSS.
180
+ * For more details read csp.withgoogle.com.
181
+ * If you modify this CSP, make sure it has not become trivially bypassable by
182
+ * checking the policy using csp-evaluator.withgoogle.com.
183
+ *
184
+ * @param hashes A list of sha-256 hashes of trusted inline scripts.
185
+ * @param enableTrustedTypes If Trusted Types should be enabled for scripts.
186
+ * @param enableBrowserFallbacks If fallbacks for older browsers should be
187
+ * added. This is will not weaken the policy as modern browsers will ignore
188
+ * the fallbacks.
189
+ * @param enableUnsafeEval If you cannot remove all uses of eval(), you can
190
+ * still set a strict CSP, but you will have to use the 'unsafe-eval'
191
+ * keyword which will make your policy slightly less secure.
192
+ */
193
+ function getStrictCsp(hashes,
194
+ // default CSP options
195
+ cspOptions = {
196
+ enableBrowserFallbacks: true,
197
+ enableTrustedTypes: false,
198
+ enableUnsafeEval: false,
199
+ }) {
200
+ hashes = hashes || [];
201
+ const strictCspTemplate = {
202
+ // 'strict-dynamic' allows hashed scripts to create new scripts.
203
+ 'script-src': [`'strict-dynamic'`, ...hashes],
204
+ // Restricts `object-src` to disable dangerous plugins like Flash.
205
+ 'object-src': [`'none'`],
206
+ // Restricts `base-uri` to block the injection of `<base>` tags. This
207
+ // prevents attackers from changing the locations of scripts loaded from
208
+ // relative URLs.
209
+ 'base-uri': [`'self'`],
210
+ };
211
+ // Adds fallbacks for browsers not compatible to CSP3 and CSP2.
212
+ // These fallbacks are ignored by modern browsers in presence of hashes,
213
+ // and 'strict-dynamic'.
214
+ if (cspOptions.enableBrowserFallbacks) {
215
+ // Fallback for Safari. All modern browsers supporting strict-dynamic will
216
+ // ignore the 'https:' fallback.
217
+ strictCspTemplate['script-src'].push('https:');
218
+ // 'unsafe-inline' is only ignored in presence of a hash or nonce.
219
+ if (hashes.length > 0) {
220
+ strictCspTemplate['script-src'].push(`'unsafe-inline'`);
221
+ }
222
+ }
223
+ // If enabled, dangerous DOM sinks will only accept typed objects instead of
224
+ // strings.
225
+ if (cspOptions.enableTrustedTypes) {
226
+ strictCspTemplate['require-trusted-types-for'] = ['script'];
227
+ }
228
+ // If enabled, `eval()`-calls will be allowed, making the policy slightly
229
+ // less secure.
230
+ if (cspOptions.enableUnsafeEval) {
231
+ strictCspTemplate['script-src'].push(`'unsafe-eval'`);
232
+ }
233
+ return Object.entries(strictCspTemplate)
234
+ .map(([directive, values]) => {
235
+ return `${directive} ${values.join(' ')};`;
236
+ })
237
+ .join('');
238
+ }
239
+ /**
240
+ * Returns JS code for dynamically loading sourced (external) scripts.
241
+ * @param srcList A list of paths for scripts that should be loaded.
242
+ */
243
+ function createLoaderScript(srcList, enableTrustedTypes = false) {
244
+ if (!srcList.length) {
245
+ throw new Error('Cannot create a loader script with no scripts to load.');
246
+ }
247
+ const srcListFormatted = srcList
248
+ .map((s) => {
249
+ // URI encoding means value can't escape string, JS, or HTML context.
250
+ const srcAttr = encodeURI(s.src).replaceAll("'", "\\'");
251
+ // Can only be 'module' or a JS MIME type or an empty string.
252
+ const typeAttr = s.type ? "'" + s.type + "'" : undefined;
253
+ const asyncAttr = s.async ? 'true' : 'false';
254
+ const deferAttr = s.defer ? 'true' : 'false';
255
+ return `['${srcAttr}', ${typeAttr}, ${asyncAttr}, ${deferAttr}]`;
256
+ })
257
+ .join();
258
+ return enableTrustedTypes
259
+ ? `
260
+ var scripts = [${srcListFormatted}];
261
+ var policy = self.trustedTypes && self.trustedTypes.createPolicy ?
262
+ self.trustedTypes.createPolicy('angular#auto-csp', {createScriptURL: function(u) {
263
+ return scripts.includes(u) ? u : null;
264
+ }}) : { createScriptURL: function(u) { return u; } };
265
+ scripts.forEach(function(scriptUrl) {
266
+ var s = document.createElement('script');
267
+ s.src = policy.createScriptURL(scriptUrl[0]);
268
+ s.type = scriptUrl[1];
269
+ s.async = !!scriptUrl[2];
270
+ s.defer = !!scriptUrl[3];
271
+ document.body.appendChild(s);
272
+ });\n`
273
+ : `
274
+ var scripts = [${srcListFormatted}];
275
+ scripts.forEach(function(scriptUrl) {
276
+ var s = document.createElement('script');
277
+ s.src = scriptUrl[0];
278
+ s.type = scriptUrl[1];
279
+ s.async = !!scriptUrl[2];
280
+ s.defer = !!scriptUrl[3];
281
+ document.body.appendChild(s);
282
+ });\n`;
283
+ }
@@ -5,7 +5,11 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
+ import type { RewritingStream } from 'parse5-html-rewriting-stream';
9
+ export type StartTag = Parameters<RewritingStream['emitStartTag']>[0];
10
+ export type EndTag = Parameters<RewritingStream['emitEndTag']>[0];
11
+ export type { RewritingStream };
8
12
  export declare function htmlRewritingStream(content: string): Promise<{
9
- rewriter: import('parse5-html-rewriting-stream').RewritingStream;
13
+ rewriter: RewritingStream;
10
14
  transformedContent: () => Promise<string>;
11
15
  }>;
@@ -20,6 +20,9 @@ export interface IndexHtmlGeneratorProcessOptions {
20
20
  as?: string;
21
21
  }[];
22
22
  }
23
+ export interface AutoCspOptions {
24
+ unsafeEval: boolean;
25
+ }
23
26
  export interface IndexHtmlGeneratorOptions {
24
27
  indexPath: string;
25
28
  deployUrl?: string;
@@ -31,6 +34,7 @@ export interface IndexHtmlGeneratorOptions {
31
34
  cache?: NormalizedCachedOptions;
32
35
  imageDomains?: string[];
33
36
  generateDedicatedSSRContent?: boolean;
37
+ autoCsp?: AutoCspOptions;
34
38
  }
35
39
  export type IndexHtmlTransform = (content: string) => Promise<string>;
36
40
  export interface IndexHtmlPluginTransformResult {
@@ -12,6 +12,7 @@ const promises_1 = require("node:fs/promises");
12
12
  const node_path_1 = require("node:path");
13
13
  const add_event_dispatch_contract_1 = require("./add-event-dispatch-contract");
14
14
  const augment_index_html_1 = require("./augment-index-html");
15
+ const auto_csp_1 = require("./auto-csp");
15
16
  const inline_critical_css_1 = require("./inline-critical-css");
16
17
  const inline_fonts_1 = require("./inline-fonts");
17
18
  const ngcm_attribute_1 = require("./ngcm-attribute");
@@ -39,6 +40,13 @@ class IndexHtmlGenerator {
39
40
  this.csrPlugins.push(addNgcmAttributePlugin());
40
41
  this.ssrPlugins.push(addEventDispatchContractPlugin(), addNoncePlugin());
41
42
  }
43
+ // Auto-CSP (as the last step)
44
+ if (options.autoCsp) {
45
+ if (options.generateDedicatedSSRContent) {
46
+ throw new Error('Cannot set both SSR and auto-CSP at the same time.');
47
+ }
48
+ this.csrPlugins.push(autoCspPlugin(options.autoCsp.unsafeEval));
49
+ }
42
50
  }
43
51
  async process(options) {
44
52
  let content = await this.readIndex(this.options.indexPath);
@@ -130,6 +138,9 @@ function inlineCriticalCssPlugin(generator) {
130
138
  function addNoncePlugin() {
131
139
  return (html) => (0, nonce_1.addNonce)(html);
132
140
  }
141
+ function autoCspPlugin(unsafeEval) {
142
+ return (html) => (0, auto_csp_1.autoCsp)(html, unsafeEval);
143
+ }
133
144
  function postTransformPlugin({ options }) {
134
145
  return async (html) => (options.postTransform ? options.postTransform(html) : html);
135
146
  }
@@ -11,14 +11,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.InlineCriticalCssProcessor = void 0;
14
- const critters_1 = __importDefault(require("critters"));
14
+ const beasties_1 = __importDefault(require("beasties"));
15
15
  const promises_1 = require("node:fs/promises");
16
16
  /**
17
- * Pattern used to extract the media query set by Critters in an `onload` handler.
17
+ * Pattern used to extract the media query set by Beasties in an `onload` handler.
18
18
  */
19
19
  const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
20
20
  /**
21
- * Name of the attribute used to save the Critters media query so it can be re-assigned on load.
21
+ * Name of the attribute used to save the Beasties media query so it can be re-assigned on load.
22
22
  */
23
23
  const CSP_MEDIA_ATTR = 'ngCspMedia';
24
24
  /**
@@ -60,12 +60,11 @@ const LINK_LOAD_SCRIPT_CONTENT = `
60
60
  };
61
61
 
62
62
  documentElement.addEventListener('load', listener, true);
63
- })();
64
- `.trim();
65
- class CrittersBase extends critters_1.default {
63
+ })();`;
64
+ class BeastiesBase extends beasties_1.default {
66
65
  }
67
66
  /* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */
68
- class CrittersExtended extends CrittersBase {
67
+ class BeastiesExtended extends BeastiesBase {
69
68
  optionsExtended;
70
69
  warnings = [];
71
70
  errors = [];
@@ -98,7 +97,7 @@ class CrittersExtended extends CrittersBase {
98
97
  return readAsset ? readAsset(path) : (0, promises_1.readFile)(path, 'utf-8');
99
98
  }
100
99
  /**
101
- * Override of the Critters `embedLinkedStylesheet` method
100
+ * Override of the Beasties `embedLinkedStylesheet` method
102
101
  * that makes it work with Angular's CSP APIs.
103
102
  */
104
103
  async embedLinkedStylesheet(link, document) {
@@ -115,17 +114,17 @@ class CrittersExtended extends CrittersBase {
115
114
  const returnValue = await super.embedLinkedStylesheet(link, document);
116
115
  const cspNonce = this.findCspNonce(document);
117
116
  if (cspNonce) {
118
- const crittersMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
119
- if (crittersMedia) {
120
- // If there's a Critters-generated `onload` handler and the file has an Angular CSP nonce,
117
+ const beastiesMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
118
+ if (beastiesMedia) {
119
+ // If there's a Beasties-generated `onload` handler and the file has an Angular CSP nonce,
121
120
  // we have to remove the handler, because it's incompatible with CSP. We save the value
122
121
  // in a different attribute and we generate a script tag with the nonce that uses
123
122
  // `addEventListener` to apply the media query instead.
124
123
  link.removeAttribute('onload');
125
- link.setAttribute(CSP_MEDIA_ATTR, crittersMedia[1]);
124
+ link.setAttribute(CSP_MEDIA_ATTR, beastiesMedia[1]);
126
125
  this.conditionallyInsertCspLoadingScript(document, cspNonce, link);
127
126
  }
128
- // Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
127
+ // Ideally we would hook in at the time Beasties inserts the `style` tags, but there isn't
129
128
  // a way of doing that at the moment so we fall back to doing it any time a `link` tag is
130
129
  // inserted. We mitigate it by only iterating the direct children of the `<head>` which
131
130
  // should be pretty shallow.
@@ -145,7 +144,7 @@ class CrittersExtended extends CrittersBase {
145
144
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
146
145
  return this.documentNonces.get(document);
147
146
  }
148
- // HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
147
+ // HTML attribute are case-insensitive, but the parser used by Beasties is case-sensitive.
149
148
  const nonceElement = document.querySelector('[ngCspNonce], [ngcspnonce]');
150
149
  const cspNonce = nonceElement?.getAttribute('ngCspNonce') || nonceElement?.getAttribute('ngcspnonce') || null;
151
150
  this.documentNonces.set(document, cspNonce);
@@ -174,15 +173,15 @@ class InlineCriticalCssProcessor {
174
173
  this.options = options;
175
174
  }
176
175
  async process(html, options) {
177
- const critters = new CrittersExtended({ ...this.options, ...options });
178
- const content = await critters.process(html);
176
+ const beasties = new BeastiesExtended({ ...this.options, ...options });
177
+ const content = await beasties.process(html);
179
178
  return {
180
179
  // Clean up value from value less attributes.
181
180
  // This is caused because parse5 always requires attributes to have a string value.
182
181
  // nomodule="" defer="" -> nomodule defer.
183
182
  content: content.replace(/(\s(?:defer|nomodule))=""/g, '$1'),
184
- errors: critters.errors,
185
- warnings: critters.warnings,
183
+ errors: beasties.errors,
184
+ warnings: beasties.warnings,
186
185
  };
187
186
  }
188
187
  }
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.normalizeCacheOptions = normalizeCacheOptions;
11
11
  const node_path_1 = require("node:path");
12
12
  /** Version placeholder is replaced during the build process with actual package version */
13
- const VERSION = '19.0.0-next.9';
13
+ const VERSION = '19.0.0-rc.1';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -0,0 +1,8 @@
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 declare const IMPORT_EXEC_ARGV: string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.IMPORT_EXEC_ARGV = void 0;
11
+ const node_path_1 = require("node:path");
12
+ const node_url_1 = require("node:url");
13
+ exports.IMPORT_EXEC_ARGV = '--import=' + (0, node_url_1.pathToFileURL)((0, node_path_1.join)(__dirname, 'register-hooks.js')).href;
@@ -24,21 +24,21 @@ exports.DEFAULT_URL = new URL('http://ng-localhost/');
24
24
  * @returns A promise that resolves to the URL of the running server.
25
25
  */
26
26
  async function launchServer() {
27
- const { default: handler } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./server.mjs');
27
+ const { reqHandler } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./server.mjs');
28
28
  const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
29
- if (!(0, utils_1.isSsrNodeRequestHandler)(handler) && !(0, utils_1.isSsrRequestHandler)(handler)) {
29
+ if (!(0, utils_1.isSsrNodeRequestHandler)(reqHandler) && !(0, utils_1.isSsrRequestHandler)(reqHandler)) {
30
30
  return exports.DEFAULT_URL;
31
31
  }
32
32
  const server = (0, node_http_1.createServer)((req, res) => {
33
33
  (async () => {
34
34
  // handle request
35
- if ((0, utils_1.isSsrNodeRequestHandler)(handler)) {
36
- await handler(req, res, (e) => {
35
+ if ((0, utils_1.isSsrNodeRequestHandler)(reqHandler)) {
36
+ await reqHandler(req, res, (e) => {
37
37
  throw e;
38
38
  });
39
39
  }
40
40
  else {
41
- const webRes = await handler(createWebRequestFromNodeRequest(req));
41
+ const webRes = await reqHandler(createWebRequestFromNodeRequest(req));
42
42
  if (webRes) {
43
43
  await writeResponseToNodeResponse(webRes, res);
44
44
  }
@@ -19,7 +19,7 @@ interface MainServerBundleExports {
19
19
  * Represents the exports available from the server bundle.
20
20
  */
21
21
  interface ServerBundleExports {
22
- default: unknown;
22
+ reqHandler?: unknown;
23
23
  }
24
24
  export declare function loadEsmModuleFromMemory(path: './main.server.mjs'): Promise<MainServerBundleExports>;
25
25
  export declare function loadEsmModuleFromMemory(path: './server.mjs'): Promise<ServerBundleExports>;
@@ -6,8 +6,7 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
9
- import type { BuildOutputFile } from '../../tools/esbuild/bundler-context';
10
- import { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
9
+ import { type BuildOutputFile } from '../../tools/esbuild/bundler-context';
11
10
  export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
12
11
  export declare const SERVER_APP_ENGINE_MANIFEST_FILENAME = "angular-app-engine-manifest.mjs";
13
12
  /**
@@ -21,10 +20,8 @@ export declare const SERVER_APP_ENGINE_MANIFEST_FILENAME = "angular-app-engine-m
21
20
  * includes settings for inlining locales and determining the output structure.
22
21
  * @param baseHref - The base HREF for the application. This is used to set the base URL
23
22
  * for all relative URLs in the application.
24
- * @param perenderedRoutes - A record mapping static paths to their associated data.
25
- * @returns A string representing the content of the SSR server manifest for App Engine.
26
23
  */
27
- export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined, perenderedRoutes?: PrerenderedRoutesRecord | undefined): string;
24
+ export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined): string;
28
25
  /**
29
26
  * Generates the server manifest for the standard Node.js environment.
30
27
  *
@@ -44,7 +41,11 @@ export declare function generateAngularServerAppEngineManifest(i18nOptions: Norm
44
41
  * @param locale - An optional string representing the locale or language code to be used for
45
42
  * the application, helping with localization and rendering content specific to the locale.
46
43
  *
47
- * @returns A string representing the content of the SSR server manifest for the Node.js
48
- * environment.
44
+ * @returns An object containing:
45
+ * - `manifestContent`: A string of the SSR manifest content.
46
+ * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
49
47
  */
50
- export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined): string;
48
+ export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined): {
49
+ manifestContent: string;
50
+ serverAssetsChunks: BuildOutputFile[];
51
+ };
@@ -10,7 +10,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = exports.SERVER_APP_MANIFEST_FILENAME = void 0;
11
11
  exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
12
12
  exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
13
+ const node_path_1 = require("node:path");
13
14
  const options_1 = require("../../builders/application/options");
15
+ const bundler_context_1 = require("../../tools/esbuild/bundler-context");
16
+ const utils_1 = require("../../tools/esbuild/utils");
14
17
  exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
15
18
  exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
16
19
  const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
@@ -43,10 +46,8 @@ function escapeUnsafeChars(str) {
43
46
  * includes settings for inlining locales and determining the output structure.
44
47
  * @param baseHref - The base HREF for the application. This is used to set the base URL
45
48
  * for all relative URLs in the application.
46
- * @param perenderedRoutes - A record mapping static paths to their associated data.
47
- * @returns A string representing the content of the SSR server manifest for App Engine.
48
49
  */
49
- function generateAngularServerAppEngineManifest(i18nOptions, baseHref, perenderedRoutes = {}) {
50
+ function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
50
51
  const entryPointsContent = [];
51
52
  if (i18nOptions.shouldInline) {
52
53
  for (const locale of i18nOptions.inlineLocales) {
@@ -62,22 +63,10 @@ function generateAngularServerAppEngineManifest(i18nOptions, baseHref, perendere
62
63
  else {
63
64
  entryPointsContent.push(`['', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
64
65
  }
65
- const staticHeaders = [];
66
- for (const [path, { headers }] of Object.entries(perenderedRoutes)) {
67
- if (!headers) {
68
- continue;
69
- }
70
- const headersValues = [];
71
- for (const [name, value] of Object.entries(headers)) {
72
- headersValues.push(`['${name}', '${encodeURIComponent(value)}']`);
73
- }
74
- staticHeaders.push(`['${path}', [${headersValues.join(', ')}]]`);
75
- }
76
66
  const manifestContent = `
77
67
  export default {
78
68
  basePath: '${baseHref ?? '/'}',
79
69
  entryPoints: new Map([${entryPointsContent.join(', \n')}]),
80
- staticPathsHeaders: new Map([${staticHeaders.join(', \n')}]),
81
70
  };
82
71
  `;
83
72
  return manifestContent;
@@ -101,16 +90,21 @@ export default {
101
90
  * @param locale - An optional string representing the locale or language code to be used for
102
91
  * the application, helping with localization and rendering content specific to the locale.
103
92
  *
104
- * @returns A string representing the content of the SSR server manifest for the Node.js
105
- * environment.
93
+ * @returns An object containing:
94
+ * - `manifestContent`: A string of the SSR manifest content.
95
+ * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
106
96
  */
107
97
  function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale) {
98
+ const serverAssetsChunks = [];
108
99
  const serverAssetsContent = [];
109
100
  for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
110
- if (file.path === options_1.INDEX_HTML_SERVER ||
111
- file.path === options_1.INDEX_HTML_CSR ||
112
- (inlineCriticalCss && file.path.endsWith('.css'))) {
113
- serverAssetsContent.push(`['${file.path}', async () => \`${escapeUnsafeChars(file.text)}\`]`);
101
+ const extension = (0, node_path_1.extname)(file.path);
102
+ if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
103
+ const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
104
+ const escapedContent = escapeUnsafeChars(file.text);
105
+ serverAssetsChunks.push((0, utils_1.createOutputFile)(jsChunkFilePath, `export default \`${escapedContent}\`;`, bundler_context_1.BuildOutputFileType.ServerApplication));
106
+ const contentLength = Buffer.byteLength(escapedContent);
107
+ serverAssetsContent.push(`['${file.path}', {size: ${contentLength}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}]`);
114
108
  }
115
109
  }
116
110
  const manifestContent = `
@@ -118,9 +112,9 @@ export default {
118
112
  bootstrap: () => import('./main.server.mjs').then(m => m.default),
119
113
  inlineCriticalCss: ${inlineCriticalCss},
120
114
  routes: ${JSON.stringify(routes, undefined, 2)},
121
- assets: new Map([${serverAssetsContent.join(', \n')}]),
115
+ assets: new Map([\n${serverAssetsContent.join(', \n')}\n]),
122
116
  locale: ${locale !== undefined ? `'${locale}'` : undefined},
123
117
  };
124
118
  `;
125
- return manifestContent;
119
+ return { manifestContent, serverAssetsChunks };
126
120
  }