@angular/build 19.0.0-rc.0 → 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 (48) hide show
  1. package/package.json +6 -6
  2. package/src/builders/application/build-action.js +13 -1
  3. package/src/builders/application/execute-build.js +38 -31
  4. package/src/builders/application/execute-post-bundle.js +19 -4
  5. package/src/builders/application/options.d.ts +10 -1
  6. package/src/builders/application/options.js +9 -0
  7. package/src/builders/application/setup-bundling.d.ts +4 -1
  8. package/src/builders/application/setup-bundling.js +18 -11
  9. package/src/builders/dev-server/options.d.ts +2 -2
  10. package/src/builders/dev-server/options.js +2 -2
  11. package/src/builders/dev-server/schema.d.ts +2 -1
  12. package/src/builders/dev-server/schema.json +1 -2
  13. package/src/builders/dev-server/vite-server.d.ts +2 -1
  14. package/src/builders/dev-server/vite-server.js +94 -61
  15. package/src/tools/angular/angular-host.js +13 -2
  16. package/src/tools/esbuild/angular/compiler-plugin.d.ts +1 -0
  17. package/src/tools/esbuild/angular/compiler-plugin.js +5 -0
  18. package/src/tools/esbuild/angular/component-stylesheets.d.ts +1 -0
  19. package/src/tools/esbuild/angular/component-stylesheets.js +3 -0
  20. package/src/tools/esbuild/angular/source-file-cache.d.ts +1 -1
  21. package/src/tools/esbuild/angular/source-file-cache.js +6 -2
  22. package/src/tools/esbuild/application-code-bundle.d.ts +5 -4
  23. package/src/tools/esbuild/application-code-bundle.js +245 -239
  24. package/src/tools/esbuild/bundler-execution-result.d.ts +10 -2
  25. package/src/tools/esbuild/bundler-execution-result.js +12 -9
  26. package/src/tools/esbuild/compiler-plugin-options.d.ts +2 -1
  27. package/src/tools/esbuild/compiler-plugin-options.js +3 -2
  28. package/src/tools/esbuild/javascript-transformer.js +2 -1
  29. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +1 -1
  30. package/src/tools/esbuild/server-bundle-metadata-plugin.js +1 -1
  31. package/src/tools/vite/middlewares/assets-middleware.d.ts +6 -1
  32. package/src/tools/vite/middlewares/assets-middleware.js +27 -23
  33. package/src/tools/vite/middlewares/component-middleware.js +1 -1
  34. package/src/tools/vite/middlewares/index.d.ts +1 -1
  35. package/src/tools/vite/middlewares/ssr-middleware.js +5 -2
  36. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -0
  37. package/src/tools/vite/plugins/angular-memory-plugin.js +5 -13
  38. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +2 -1
  39. package/src/tools/vite/plugins/setup-middlewares-plugin.js +11 -3
  40. package/src/utils/environment-options.d.ts +1 -0
  41. package/src/utils/environment-options.js +3 -1
  42. package/src/utils/normalize-cache.js +1 -1
  43. package/src/utils/server-rendering/esm-in-memory-loader/utils.d.ts +8 -0
  44. package/src/utils/server-rendering/esm-in-memory-loader/utils.js +13 -0
  45. package/src/utils/server-rendering/manifest.d.ts +9 -8
  46. package/src/utils/server-rendering/manifest.js +17 -23
  47. package/src/utils/server-rendering/prerender.js +25 -16
  48. package/src/utils/server-rendering/render-worker.js +4 -2
@@ -8,6 +8,7 @@
8
8
  import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
9
9
  import type { createCompilerPlugin } from './angular/compiler-plugin';
10
10
  import type { SourceFileCache } from './angular/source-file-cache';
11
+ import type { LoadResultCache } from './load-result-cache';
11
12
  type CreateCompilerPluginParameters = Parameters<typeof createCompilerPlugin>;
12
- export declare function createCompilerPluginOptions(options: NormalizedApplicationBuildOptions, sourceFileCache?: SourceFileCache): CreateCompilerPluginParameters[0];
13
+ export declare function createCompilerPluginOptions(options: NormalizedApplicationBuildOptions, sourceFileCache: SourceFileCache, loadResultCache?: LoadResultCache, templateUpdates?: Map<string, string>): CreateCompilerPluginParameters[0];
13
14
  export {};
@@ -8,7 +8,7 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createCompilerPluginOptions = createCompilerPluginOptions;
11
- function createCompilerPluginOptions(options, sourceFileCache) {
11
+ function createCompilerPluginOptions(options, sourceFileCache, loadResultCache, templateUpdates) {
12
12
  const { sourcemapOptions, tsconfig, fileReplacements, advancedOptimizations, jit, externalRuntimeStyles, instrumentForCoverage, } = options;
13
13
  const incremental = !!options.watch;
14
14
  return {
@@ -19,9 +19,10 @@ function createCompilerPluginOptions(options, sourceFileCache) {
19
19
  advancedOptimizations,
20
20
  fileReplacements,
21
21
  sourceFileCache,
22
- loadResultCache: sourceFileCache?.loadResultCache,
22
+ loadResultCache,
23
23
  incremental,
24
24
  externalRuntimeStyles,
25
25
  instrumentForCoverage,
26
+ templateUpdates,
26
27
  };
27
28
  }
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.JavaScriptTransformer = void 0;
11
11
  const node_crypto_1 = require("node:crypto");
12
12
  const promises_1 = require("node:fs/promises");
13
+ const utils_1 = require("../../utils/server-rendering/esm-in-memory-loader/utils");
13
14
  const worker_pool_1 = require("../../utils/worker-pool");
14
15
  /**
15
16
  * A class that performs transformation of JavaScript files and raw data.
@@ -42,7 +43,7 @@ class JavaScriptTransformer {
42
43
  filename: require.resolve('./javascript-transformer-worker'),
43
44
  maxThreads: this.maxThreads,
44
45
  // Prevent passing `--import` (loader-hooks) from parent to child worker.
45
- execArgv: [],
46
+ execArgv: process.execArgv.filter((v) => v !== utils_1.IMPORT_EXEC_ARGV),
46
47
  });
47
48
  return this.#workerPool;
48
49
  }
@@ -13,7 +13,7 @@ import type { Plugin } from 'esbuild';
13
13
  * @param options Optional configuration object.
14
14
  * - `ssrEntryBundle`: If `true`, marks the bundle as an SSR entry point.
15
15
  *
16
- * @note We can't rely on `platform: node` or `platform: neutral`, as the latter
16
+ * @remarks We can't rely on `platform: node` or `platform: neutral`, as the latter
17
17
  * is used for non-SSR-related code too (e.g., global scripts).
18
18
  * @returns An esbuild plugin that injects SSR metadata into the build result's metafile.
19
19
  */
@@ -15,7 +15,7 @@ exports.createServerBundleMetadata = createServerBundleMetadata;
15
15
  * @param options Optional configuration object.
16
16
  * - `ssrEntryBundle`: If `true`, marks the bundle as an SSR entry point.
17
17
  *
18
- * @note We can't rely on `platform: node` or `platform: neutral`, as the latter
18
+ * @remarks We can't rely on `platform: node` or `platform: neutral`, as the latter
19
19
  * is used for non-SSR-related code too (e.g., global scripts).
20
20
  * @returns An esbuild plugin that injects SSR metadata into the build result's metafile.
21
21
  */
@@ -7,4 +7,9 @@
7
7
  */
8
8
  import type { Connect, ViteDevServer } from 'vite';
9
9
  import { AngularMemoryOutputFiles } from '../utils';
10
- export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles, usedComponentStyles: Map<string, Set<string>>): Connect.NextHandleFunction;
10
+ export interface ComponentStyleRecord {
11
+ rawContent: Uint8Array;
12
+ used?: Set<string>;
13
+ reload?: boolean;
14
+ }
15
+ export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles, componentStyles: Map<string, ComponentStyleRecord>, encapsulateStyle: (style: Uint8Array, componentId: string) => string): Connect.NextHandleFunction;
@@ -10,9 +10,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createAngularAssetsMiddleware = createAngularAssetsMiddleware;
11
11
  const mrmime_1 = require("mrmime");
12
12
  const node_path_1 = require("node:path");
13
- const load_esm_1 = require("../../../utils/load-esm");
14
13
  const utils_1 = require("../utils");
15
- function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponentStyles) {
14
+ function createAngularAssetsMiddleware(server, assets, outputFiles, componentStyles, encapsulateStyle) {
16
15
  return function angularAssetsMiddleware(req, res, next) {
17
16
  if (req.url === undefined || res.writableEnded) {
18
17
  return;
@@ -59,18 +58,25 @@ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponen
59
58
  if (extension !== '.js' && extension !== '.html') {
60
59
  const outputFile = outputFiles.get(pathname);
61
60
  if (outputFile?.servable) {
62
- const data = outputFile.contents;
63
- if (extension === '.css') {
61
+ let data = outputFile.contents;
62
+ const componentStyle = componentStyles.get(pathname);
63
+ if (componentStyle) {
64
64
  // Inject component ID for view encapsulation if requested
65
- const componentId = new URL(req.url, 'http://localhost').searchParams.get('ngcomp');
65
+ const searchParams = new URL(req.url, 'http://localhost').searchParams;
66
+ const componentId = searchParams.get('ngcomp');
66
67
  if (componentId !== null) {
68
+ // Track if the component uses ShadowDOM encapsulation (3 = ViewEncapsulation.ShadowDom)
69
+ // Shadow DOM components currently require a full reload.
70
+ // Vite's CSS hot replacement does not support shadow root searching.
71
+ if (searchParams.get('e') === '3') {
72
+ componentStyle.reload = true;
73
+ }
67
74
  // Record the component style usage for HMR updates
68
- const usedIds = usedComponentStyles.get(pathname);
69
- if (usedIds === undefined) {
70
- usedComponentStyles.set(pathname, new Set([componentId]));
75
+ if (componentStyle.used === undefined) {
76
+ componentStyle.used = new Set([componentId]);
71
77
  }
72
78
  else {
73
- usedIds.add(componentId);
79
+ componentStyle.used.add(componentId);
74
80
  }
75
81
  // Report if there are no changes to avoid reprocessing
76
82
  const etag = `W/"${outputFile.contents.byteLength}-${outputFile.hash}-${componentId}"`;
@@ -82,23 +88,21 @@ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponen
82
88
  // Shim the stylesheet if a component ID is provided
83
89
  if (componentId.length > 0) {
84
90
  // Validate component ID
85
- if (/^[_.\-\p{Letter}\d]+-c\d+$/u.test(componentId)) {
86
- (0, load_esm_1.loadEsmModule)('@angular/compiler')
87
- .then((compilerModule) => {
88
- const encapsulatedData = compilerModule.encapsulateStyle(new TextDecoder().decode(data), componentId);
89
- res.setHeader('Content-Type', 'text/css');
90
- res.setHeader('Cache-Control', 'no-cache');
91
- res.setHeader('ETag', etag);
92
- res.end(encapsulatedData);
93
- })
94
- .catch((e) => next(e));
95
- return;
96
- }
97
- else {
91
+ if (!/^[_.\-\p{Letter}\d]+-c\d+$/u.test(componentId)) {
92
+ const message = 'Invalid component stylesheet ID request: ' + componentId;
98
93
  // eslint-disable-next-line no-console
99
- console.error('Invalid component stylesheet ID request: ' + componentId);
94
+ console.error(message);
95
+ res.statusCode = 400;
96
+ res.end(message);
97
+ return;
100
98
  }
99
+ data = encapsulateStyle(data, componentId);
101
100
  }
101
+ res.setHeader('Content-Type', 'text/css');
102
+ res.setHeader('Cache-Control', 'no-cache');
103
+ res.setHeader('ETag', etag);
104
+ res.end(data);
105
+ return;
102
106
  }
103
107
  }
104
108
  // Avoid resending the content if it has not changed since last request
@@ -25,7 +25,7 @@ function createAngularComponentMiddleware(templateUpdates) {
25
25
  res.end();
26
26
  return;
27
27
  }
28
- const updateCode = templateUpdates.get(componentId) ?? '';
28
+ const updateCode = templateUpdates.get(encodeURIComponent(componentId)) ?? '';
29
29
  res.setHeader('Content-Type', 'text/javascript');
30
30
  res.setHeader('Cache-Control', 'no-cache');
31
31
  res.end(updateCode);
@@ -5,7 +5,7 @@
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
- export { createAngularAssetsMiddleware } from './assets-middleware';
8
+ export { type ComponentStyleRecord, createAngularAssetsMiddleware } from './assets-middleware';
9
9
  export { angularHtmlFallbackMiddleware } from './html-fallback-middleware';
10
10
  export { createAngularIndexHtmlMiddleware } from './index-html-middleware';
11
11
  export { createAngularSsrExternalMiddleware, createAngularSsrInternalMiddleware, } from './ssr-middleware';
@@ -23,7 +23,9 @@ function createAngularSsrInternalMiddleware(server, indexHtmlTransformer) {
23
23
  await (0, load_esm_1.loadEsmModule)('@angular/compiler');
24
24
  const { writeResponseToNodeResponse, createWebRequestFromNodeRequest } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
25
25
  const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
26
- const angularServerApp = ɵgetOrCreateAngularServerApp();
26
+ const angularServerApp = ɵgetOrCreateAngularServerApp({
27
+ allowStaticRouteRender: true,
28
+ });
27
29
  // Only Add the transform hook only if it's a different instance.
28
30
  if (cachedAngularServerApp !== angularServerApp) {
29
31
  angularServerApp.hooks.on('html:transform:pre', async ({ html, url }) => {
@@ -35,7 +37,7 @@ function createAngularSsrInternalMiddleware(server, indexHtmlTransformer) {
35
37
  const webReq = new Request(createWebRequestFromNodeRequest(req), {
36
38
  signal: AbortSignal.timeout(30_000),
37
39
  });
38
- const webRes = await angularServerApp.render(webReq);
40
+ const webRes = await angularServerApp.handle(webReq);
39
41
  if (!webRes) {
40
42
  return next();
41
43
  }
@@ -66,6 +68,7 @@ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer)
66
68
  return;
67
69
  }
68
70
  if (cachedAngularAppEngine !== AngularAppEngine) {
71
+ AngularAppEngine.ɵallowStaticRouteRender = true;
69
72
  AngularAppEngine.ɵhooks.on('html:transform:pre', async ({ html, url }) => {
70
73
  const processedHtml = await server.transformIndexHtml(url.pathname, html);
71
74
  return indexHtmlTransformer?.(processedHtml) ?? processedHtml;
@@ -11,6 +11,7 @@ interface AngularMemoryPluginOptions {
11
11
  virtualProjectRoot: string;
12
12
  outputFiles: AngularMemoryOutputFiles;
13
13
  external?: string[];
14
+ skipViteClient?: boolean;
14
15
  }
15
16
  export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Promise<Plugin>;
16
17
  export {};
@@ -18,8 +18,6 @@ const load_esm_1 = require("../../../utils/load-esm");
18
18
  async function createAngularMemoryPlugin(options) {
19
19
  const { virtualProjectRoot, outputFiles, external } = options;
20
20
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
21
- // See: https://github.com/vitejs/vite/blob/a34a73a3ad8feeacc98632c0f4c643b6820bbfda/packages/vite/src/node/server/pluginContainer.ts#L331-L334
22
- const defaultImporter = (0, node_path_1.join)(virtualProjectRoot, 'index.html');
23
21
  return {
24
22
  name: 'vite:angular-memory',
25
23
  // Ensures plugin hooks run before built-in Vite hooks
@@ -32,17 +30,10 @@ async function createAngularMemoryPlugin(options) {
32
30
  return source;
33
31
  }
34
32
  if (importer) {
35
- let normalizedSource;
36
33
  if (source[0] === '.' && normalizePath(importer).startsWith(virtualProjectRoot)) {
37
34
  // Remove query if present
38
35
  const [importerFile] = importer.split('?', 1);
39
- normalizedSource = (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source);
40
- }
41
- else if (source[0] === '/' && importer === defaultImporter) {
42
- normalizedSource = (0, node_path_1.basename)(source);
43
- }
44
- if (normalizedSource) {
45
- source = '/' + normalizePath(normalizedSource);
36
+ source = '/' + (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source);
46
37
  }
47
38
  }
48
39
  const [file] = source.split('?', 1);
@@ -55,9 +46,10 @@ async function createAngularMemoryPlugin(options) {
55
46
  const relativeFile = '/' + normalizePath((0, node_path_1.relative)(virtualProjectRoot, file));
56
47
  const codeContents = outputFiles.get(relativeFile)?.contents;
57
48
  if (codeContents === undefined) {
58
- return relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')
59
- ? loadViteClientCode(file)
60
- : undefined;
49
+ if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
50
+ return options.skipViteClient ? '' : loadViteClientCode(file);
51
+ }
52
+ return undefined;
61
53
  }
62
54
  const code = Buffer.from(codeContents).toString('utf-8');
63
55
  const mapContents = outputFiles.get(relativeFile + '.map')?.contents;
@@ -6,6 +6,7 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { Connect, Plugin } from 'vite';
9
+ import { ComponentStyleRecord } from '../middlewares';
9
10
  import { AngularMemoryOutputFiles } from '../utils';
10
11
  export declare enum ServerSsrMode {
11
12
  /**
@@ -34,7 +35,7 @@ interface AngularSetupMiddlewaresPluginOptions {
34
35
  assets: Map<string, string>;
35
36
  extensionMiddleware?: Connect.NextHandleFunction[];
36
37
  indexHtmlTransformer?: (content: string) => Promise<string>;
37
- usedComponentStyles: Map<string, Set<string>>;
38
+ componentStyles: Map<string, ComponentStyleRecord>;
38
39
  templateUpdates: Map<string, string>;
39
40
  ssrMode: ServerSsrMode;
40
41
  }
@@ -9,6 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.ServerSsrMode = void 0;
11
11
  exports.createAngularSetupMiddlewaresPlugin = createAngularSetupMiddlewaresPlugin;
12
+ const load_esm_1 = require("../../../utils/load-esm");
12
13
  const middlewares_1 = require("../middlewares");
13
14
  var ServerSsrMode;
14
15
  (function (ServerSsrMode) {
@@ -33,16 +34,23 @@ var ServerSsrMode;
33
34
  */
34
35
  ServerSsrMode[ServerSsrMode["ExternalSsrMiddleware"] = 2] = "ExternalSsrMiddleware";
35
36
  })(ServerSsrMode || (exports.ServerSsrMode = ServerSsrMode = {}));
37
+ async function createEncapsulateStyle() {
38
+ const { encapsulateStyle } = await (0, load_esm_1.loadEsmModule)('@angular/compiler');
39
+ const decoder = new TextDecoder('utf-8');
40
+ return (style, componentId) => {
41
+ return encapsulateStyle(decoder.decode(style), componentId);
42
+ };
43
+ }
36
44
  function createAngularSetupMiddlewaresPlugin(options) {
37
45
  return {
38
46
  name: 'vite:angular-setup-middlewares',
39
47
  enforce: 'pre',
40
- configureServer(server) {
41
- const { indexHtmlTransformer, outputFiles, extensionMiddleware, assets, usedComponentStyles, templateUpdates, ssrMode, } = options;
48
+ async configureServer(server) {
49
+ const { indexHtmlTransformer, outputFiles, extensionMiddleware, assets, componentStyles, templateUpdates, ssrMode, } = options;
42
50
  // Headers, assets and resources get handled first
43
51
  server.middlewares.use((0, middlewares_1.createAngularHeadersMiddleware)(server));
44
52
  server.middlewares.use((0, middlewares_1.createAngularComponentMiddleware)(templateUpdates));
45
- server.middlewares.use((0, middlewares_1.createAngularAssetsMiddleware)(server, assets, outputFiles, usedComponentStyles));
53
+ server.middlewares.use((0, middlewares_1.createAngularAssetsMiddleware)(server, assets, outputFiles, componentStyles, await createEncapsulateStyle()));
46
54
  extensionMiddleware?.forEach((middleware) => server.middlewares.use(middleware));
47
55
  // Returning a function, installs middleware after the main transform middleware but
48
56
  // before the built-in HTML middleware
@@ -16,4 +16,5 @@ export declare const useTypeChecking: boolean;
16
16
  export declare const useJSONBuildLogs: boolean;
17
17
  export declare const shouldOptimizeChunks: boolean;
18
18
  export declare const useComponentStyleHmr: boolean;
19
+ export declare const useComponentTemplateHmr: boolean;
19
20
  export declare const usePartialSsrBuild: boolean;
@@ -7,7 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.usePartialSsrBuild = exports.useComponentStyleHmr = exports.shouldOptimizeChunks = exports.useJSONBuildLogs = exports.useTypeChecking = exports.shouldWatchRoot = exports.debugPerformance = exports.useParallelTs = exports.maxWorkers = exports.allowMinify = exports.shouldBeautify = exports.allowMangle = void 0;
10
+ exports.usePartialSsrBuild = exports.useComponentTemplateHmr = exports.useComponentStyleHmr = exports.shouldOptimizeChunks = exports.useJSONBuildLogs = exports.useTypeChecking = exports.shouldWatchRoot = exports.debugPerformance = exports.useParallelTs = exports.maxWorkers = exports.allowMinify = exports.shouldBeautify = exports.allowMangle = void 0;
11
11
  const node_os_1 = require("node:os");
12
12
  function isDisabled(variable) {
13
13
  return variable === '0' || variable.toLowerCase() === 'false';
@@ -84,5 +84,7 @@ const optimizeChunksVariable = process.env['NG_BUILD_OPTIMIZE_CHUNKS'];
84
84
  exports.shouldOptimizeChunks = isPresent(optimizeChunksVariable) && isEnabled(optimizeChunksVariable);
85
85
  const hmrComponentStylesVariable = process.env['NG_HMR_CSTYLES'];
86
86
  exports.useComponentStyleHmr = !isPresent(hmrComponentStylesVariable) || !isDisabled(hmrComponentStylesVariable);
87
+ const hmrComponentTemplateVariable = process.env['NG_HMR_TEMPLATES'];
88
+ exports.useComponentTemplateHmr = isPresent(hmrComponentTemplateVariable) && isEnabled(hmrComponentTemplateVariable);
87
89
  const partialSsrBuildVariable = process.env['NG_BUILD_PARTIAL_SSR'];
88
90
  exports.usePartialSsrBuild = isPresent(partialSsrBuildVariable) && isEnabled(partialSsrBuildVariable);
@@ -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-rc.0';
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;
@@ -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 type { 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
  }
@@ -10,12 +10,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.prerenderPages = prerenderPages;
11
11
  const promises_1 = require("node:fs/promises");
12
12
  const node_path_1 = require("node:path");
13
- const node_url_1 = require("node:url");
14
13
  const schema_1 = require("../../builders/application/schema");
15
14
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
16
15
  const error_1 = require("../error");
17
16
  const url_1 = require("../url");
18
17
  const worker_pool_1 = require("../worker-pool");
18
+ const utils_1 = require("./esm-in-memory-loader/utils");
19
19
  const models_1 = require("./models");
20
20
  async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerenderOptions, outputFiles, assets, outputMode, sourcemap = false, maxThreads = 1) {
21
21
  const outputFilesForWorker = {};
@@ -104,11 +104,7 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerende
104
104
  async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions, outputMode) {
105
105
  const output = {};
106
106
  const errors = [];
107
- const workerExecArgv = [
108
- '--import',
109
- // Loader cannot be an absolute path on Windows.
110
- (0, node_url_1.pathToFileURL)((0, node_path_1.join)(__dirname, 'esm-in-memory-loader/register-hooks.js')).href,
111
- ];
107
+ const workerExecArgv = [utils_1.IMPORT_EXEC_ARGV];
112
108
  if (sourcemap) {
113
109
  workerExecArgv.push('--enable-source-maps');
114
110
  }
@@ -129,8 +125,10 @@ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxTh
129
125
  const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
130
126
  const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
131
127
  for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
132
- // Remove base href from file output path.
133
- const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
128
+ // Remove the base href from the file output path.
129
+ const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefWithLeadingSlash)
130
+ ? addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1))
131
+ : route;
134
132
  const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
135
133
  if (typeof redirectTo === 'string') {
136
134
  output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false };
@@ -167,6 +165,7 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
167
165
  const routes = [];
168
166
  if (appShellOptions) {
169
167
  routes.push({
168
+ renderMode: models_1.RouteRenderMode.AppShell,
170
169
  route: (0, url_1.urlJoin)(baseHref, appShellOptions.route),
171
170
  });
172
171
  }
@@ -174,6 +173,7 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
174
173
  const routesFromFile = (await (0, promises_1.readFile)(routesFile, 'utf8')).split(/\r?\n/);
175
174
  for (const route of routesFromFile) {
176
175
  routes.push({
176
+ renderMode: models_1.RouteRenderMode.Prerender,
177
177
  route: (0, url_1.urlJoin)(baseHref, route.trim()),
178
178
  });
179
179
  }
@@ -181,11 +181,7 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
181
181
  if (!discoverRoutes) {
182
182
  return { errors: [], serializedRouteTree: routes };
183
183
  }
184
- const workerExecArgv = [
185
- '--import',
186
- // Loader cannot be an absolute path on Windows.
187
- (0, node_url_1.pathToFileURL)((0, node_path_1.join)(__dirname, 'esm-in-memory-loader/register-hooks.js')).href,
188
- ];
184
+ const workerExecArgv = [utils_1.IMPORT_EXEC_ARGV];
189
185
  if (sourcemap) {
190
186
  workerExecArgv.push('--enable-source-maps');
191
187
  }
@@ -203,7 +199,17 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
203
199
  });
204
200
  try {
205
201
  const { serializedRouteTree, errors } = await renderWorker.run({});
206
- return { errors, serializedRouteTree: [...routes, ...serializedRouteTree] };
202
+ if (!routes.length) {
203
+ return { errors, serializedRouteTree };
204
+ }
205
+ // Merge the routing trees
206
+ const uniqueRoutes = new Map();
207
+ for (const item of [...routes, ...serializedRouteTree]) {
208
+ if (!uniqueRoutes.has(item.route)) {
209
+ uniqueRoutes.set(item.route, item);
210
+ }
211
+ }
212
+ return { errors, serializedRouteTree: Array.from(uniqueRoutes.values()) };
207
213
  }
208
214
  catch (err) {
209
215
  (0, error_1.assertIsError)(err);
@@ -219,10 +225,13 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
219
225
  }
220
226
  }
221
227
  function addLeadingSlash(value) {
222
- return value.charAt(0) === '/' ? value : '/' + value;
228
+ return value[0] === '/' ? value : '/' + value;
229
+ }
230
+ function addTrailingSlash(url) {
231
+ return url[url.length - 1] === '/' ? url : `${url}/`;
223
232
  }
224
233
  function removeLeadingSlash(value) {
225
- return value.charAt(0) === '/' ? value.slice(1) : value;
234
+ return value[0] === '/' ? value.slice(1) : value;
226
235
  }
227
236
  /**
228
237
  * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
@@ -21,8 +21,10 @@ let serverURL = launch_server_1.DEFAULT_URL;
21
21
  */
22
22
  async function renderPage({ url }) {
23
23
  const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
24
- const angularServerApp = getOrCreateAngularServerApp();
25
- const response = await angularServerApp.renderStatic(new URL(url, serverURL), AbortSignal.timeout(30_000));
24
+ const angularServerApp = getOrCreateAngularServerApp({
25
+ allowStaticRouteRender: true,
26
+ });
27
+ const response = await angularServerApp.handle(new Request(new URL(url, serverURL), { signal: AbortSignal.timeout(30_000) }));
26
28
  return response ? response.text() : null;
27
29
  }
28
30
  async function initialize() {