@angular/build 19.0.0-next.2 → 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.
Files changed (36) hide show
  1. package/package.json +10 -10
  2. package/src/builders/application/execute-post-bundle.js +29 -18
  3. package/src/builders/application/i18n.js +2 -11
  4. package/src/builders/application/options.d.ts +11 -0
  5. package/src/builders/application/options.js +23 -1
  6. package/src/builders/application/setup-bundling.js +7 -7
  7. package/src/builders/dev-server/internal.d.ts +0 -1
  8. package/src/builders/dev-server/internal.js +1 -3
  9. package/src/builders/dev-server/vite-server.d.ts +2 -1
  10. package/src/builders/dev-server/vite-server.js +13 -2
  11. package/src/tools/esbuild/angular/file-reference-tracker.d.ts +1 -1
  12. package/src/tools/esbuild/application-code-bundle.d.ts +1 -6
  13. package/src/tools/esbuild/application-code-bundle.js +105 -70
  14. package/src/tools/esbuild/bundler-context.js +14 -10
  15. package/src/tools/esbuild/cache.d.ts +1 -1
  16. package/src/tools/esbuild/utils.d.ts +9 -0
  17. package/src/tools/esbuild/utils.js +14 -0
  18. package/src/tools/sass/sass-service.js +9 -4
  19. package/src/tools/vite/angular-memory-plugin.js +2 -2
  20. package/src/tools/vite/middlewares/ssr-middleware.d.ts +1 -4
  21. package/src/tools/vite/middlewares/ssr-middleware.js +25 -38
  22. package/src/utils/normalize-cache.js +1 -1
  23. package/src/utils/server-rendering/fetch-patch.js +4 -5
  24. package/src/utils/server-rendering/load-esm-from-memory.d.ts +12 -2
  25. package/src/utils/server-rendering/manifest.d.ts +44 -0
  26. package/src/utils/server-rendering/manifest.js +88 -0
  27. package/src/utils/server-rendering/prerender.d.ts +22 -2
  28. package/src/utils/server-rendering/prerender.js +51 -40
  29. package/src/utils/server-rendering/render-worker.d.ts +7 -8
  30. package/src/utils/server-rendering/render-worker.js +10 -13
  31. package/src/utils/server-rendering/routes-extractor-worker.d.ts +2 -6
  32. package/src/utils/server-rendering/routes-extractor-worker.js +3 -34
  33. package/src/utils/server-rendering/main-bundle-exports.d.ts +0 -27
  34. package/src/utils/server-rendering/main-bundle-exports.js +0 -9
  35. package/src/utils/server-rendering/render-page.d.ts +0 -26
  36. package/src/utils/server-rendering/render-page.js +0 -114
@@ -0,0 +1,88 @@
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.SERVER_APP_MANIFEST_FILENAME = void 0;
11
+ exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
12
+ exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
13
+ const options_1 = require("../../builders/application/options");
14
+ exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
15
+ const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
16
+ /**
17
+ * Generates the server manifest for the App Engine environment.
18
+ *
19
+ * This manifest is used to configure the server-side rendering (SSR) setup for the
20
+ * Angular application when deployed to Google App Engine. It includes the entry points
21
+ * for different locales and the base HREF for the application.
22
+ *
23
+ * @param i18nOptions - The internationalization options for the application build. This
24
+ * includes settings for inlining locales and determining the output structure.
25
+ * @param baseHref - The base HREF for the application. This is used to set the base URL
26
+ * for all relative URLs in the application.
27
+ * @returns A string representing the content of the SSR server manifest for App Engine.
28
+ */
29
+ function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
30
+ const entryPointsContent = [];
31
+ if (i18nOptions.shouldInline) {
32
+ for (const locale of i18nOptions.inlineLocales) {
33
+ const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
34
+ const localWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
35
+ entryPointsContent.push(`['${localWithBaseHref}', () => import('${importPath}')]`);
36
+ }
37
+ }
38
+ else {
39
+ entryPointsContent.push(`['/', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
40
+ }
41
+ const manifestContent = `
42
+ {
43
+ basePath: '${baseHref ?? '/'}',
44
+ entryPoints: new Map([${entryPointsContent.join(', \n')}]),
45
+ }
46
+ `;
47
+ return manifestContent;
48
+ }
49
+ /**
50
+ * Generates the server manifest for the standard Node.js environment.
51
+ *
52
+ * This manifest is used to configure the server-side rendering (SSR) setup for the
53
+ * Angular application when running in a standard Node.js environment. It includes
54
+ * information about the bootstrap module, whether to inline critical CSS, and any
55
+ * additional HTML and CSS output files.
56
+ *
57
+ * @param additionalHtmlOutputFiles - A map of additional HTML output files generated
58
+ * during the build process, keyed by their file paths.
59
+ * @param outputFiles - An array of all output files from the build process, including
60
+ * JavaScript and CSS files.
61
+ * @param inlineCriticalCss - A boolean indicating whether critical CSS should be inlined
62
+ * in the server-side rendered pages.
63
+ * @param routes - An optional array of route definitions for the application, used for
64
+ * server-side rendering and routing.
65
+ * @returns A string representing the content of the SSR server manifest for the Node.js
66
+ * environment.
67
+ */
68
+ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes) {
69
+ const serverAssetsContent = [];
70
+ for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
71
+ if (file.path === options_1.INDEX_HTML_SERVER ||
72
+ file.path === options_1.INDEX_HTML_CSR ||
73
+ file.path.endsWith('.css')) {
74
+ serverAssetsContent.push(`['${file.path}', async () => ${JSON.stringify(file.text)}]`);
75
+ }
76
+ }
77
+ const manifestContent = `
78
+ import bootstrap from './main.server.mjs';
79
+
80
+ export default {
81
+ bootstrap: () => bootstrap,
82
+ inlineCriticalCss: ${inlineCriticalCss},
83
+ routes: ${JSON.stringify(routes, undefined, 2)},
84
+ assets: new Map([${serverAssetsContent.join(', \n')}]),
85
+ };
86
+ `;
87
+ return manifestContent;
88
+ }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { BuildOutputFile } from '../../tools/esbuild/bundler-context';
9
9
  import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result';
10
+ import type { RoutersExtractorWorkerResult as SerializableRouteTreeNode } from './routes-extractor-worker';
10
11
  interface PrerenderOptions {
11
12
  routesFile?: string;
12
13
  discoverRoutes?: boolean;
@@ -14,10 +15,29 @@ interface PrerenderOptions {
14
15
  interface AppShellOptions {
15
16
  route?: string;
16
17
  }
17
- export declare function prerenderPages(workspaceRoot: string, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, outputFiles: Readonly<BuildOutputFile[]>, assets: Readonly<BuildOutputAsset[]>, document: string, sourcemap?: boolean, inlineCriticalCss?: boolean, maxThreads?: number, verbose?: boolean): Promise<{
18
- output: Record<string, string>;
18
+ /**
19
+ * Represents the output of a prerendering process.
20
+ *
21
+ * The key is the file path, and the value is an object containing the following properties:
22
+ *
23
+ * - `content`: The HTML content or output generated for the corresponding file path.
24
+ * - `appShellRoute`: A boolean flag indicating whether the content is an app shell.
25
+ *
26
+ * @example
27
+ * {
28
+ * '/index.html': { content: '<html>...</html>', appShell: false },
29
+ * '/shell/index.html': { content: '<html>...</html>', appShellRoute: true }
30
+ * }
31
+ */
32
+ type PrerenderOutput = Record<string, {
33
+ content: string;
34
+ appShellRoute: boolean;
35
+ }>;
36
+ export declare function prerenderPages(workspaceRoot: string, baseHref: string, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, outputFiles: Readonly<BuildOutputFile[]>, assets: Readonly<BuildOutputAsset[]>, sourcemap?: boolean, maxThreads?: number, verbose?: boolean): Promise<{
37
+ output: PrerenderOutput;
19
38
  warnings: string[];
20
39
  errors: string[];
21
40
  prerenderedRoutes: Set<string>;
41
+ serializableRouteTreeNode: SerializableRouteTreeNode;
22
42
  }>;
23
43
  export {};
@@ -16,19 +16,21 @@ const node_path_1 = require("node:path");
16
16
  const node_url_1 = require("node:url");
17
17
  const piscina_1 = __importDefault(require("piscina"));
18
18
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
19
- async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOptions = {}, outputFiles, assets, document, sourcemap = false, inlineCriticalCss = false, maxThreads = 1, verbose = false) {
19
+ const url_1 = require("../url");
20
+ async function prerenderPages(workspaceRoot, baseHref, appShellOptions = {}, prerenderOptions = {}, outputFiles, assets, sourcemap = false, maxThreads = 1, verbose = false) {
20
21
  const outputFilesForWorker = {};
21
22
  const serverBundlesSourceMaps = new Map();
22
23
  const warnings = [];
23
24
  const errors = [];
24
25
  for (const { text, path, type } of outputFiles) {
25
- const fileExt = (0, node_path_1.extname)(path);
26
- if (type === bundler_context_1.BuildOutputFileType.Server && fileExt === '.map') {
26
+ if (type !== bundler_context_1.BuildOutputFileType.Server) {
27
+ continue;
28
+ }
29
+ // Contains the server runnable application code
30
+ if ((0, node_path_1.extname)(path) === '.map') {
27
31
  serverBundlesSourceMaps.set(path.slice(0, -4), text);
28
32
  }
29
- else if (type === bundler_context_1.BuildOutputFileType.Server || // Contains the server runnable application code
30
- (type === bundler_context_1.BuildOutputFileType.Browser && fileExt === '.css') // Global styles for critical CSS inlining.
31
- ) {
33
+ else {
32
34
  outputFilesForWorker[path] = text;
33
35
  }
34
36
  }
@@ -49,7 +51,7 @@ async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOpti
49
51
  assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source;
50
52
  }
51
53
  // Get routes to prerender
52
- const { routes: allRoutes, warnings: routesWarnings, errors: routesErrors, } = await getAllRoutes(workspaceRoot, outputFilesForWorker, assetsReversed, document, appShellOptions, prerenderOptions, sourcemap, verbose);
54
+ const { routes: allRoutes, warnings: routesWarnings, errors: routesErrors, serializableRouteTreeNode, } = await getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetsReversed, appShellOptions, prerenderOptions, sourcemap, verbose);
53
55
  if (routesErrors?.length) {
54
56
  errors.push(...routesErrors);
55
57
  }
@@ -61,17 +63,18 @@ async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOpti
61
63
  errors,
62
64
  warnings,
63
65
  output: {},
66
+ serializableRouteTreeNode,
64
67
  prerenderedRoutes: allRoutes,
65
68
  };
66
69
  }
67
70
  // Render routes
68
- const { warnings: renderingWarnings, errors: renderingErrors, output, } = await renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, inlineCriticalCss, document, appShellOptions);
71
+ const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions);
69
72
  errors.push(...renderingErrors);
70
- warnings.push(...renderingWarnings);
71
73
  return {
72
74
  errors,
73
75
  warnings,
74
76
  output,
77
+ serializableRouteTreeNode,
75
78
  prerenderedRoutes: allRoutes,
76
79
  };
77
80
  }
@@ -80,9 +83,8 @@ class RoutesSet extends Set {
80
83
  return super.add(addLeadingSlash(value));
81
84
  }
82
85
  }
83
- async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, inlineCriticalCss, document, appShellOptions) {
86
+ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions) {
84
87
  const output = {};
85
- const warnings = [];
86
88
  const errors = [];
87
89
  const workerExecArgv = [
88
90
  '--import',
@@ -99,8 +101,6 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
99
101
  workspaceRoot,
100
102
  outputFiles: outputFilesForWorker,
101
103
  assetFiles: assetFilesForWorker,
102
- inlineCriticalCss,
103
- document,
104
104
  },
105
105
  execArgv: workerExecArgv,
106
106
  recordTiming: false,
@@ -108,27 +108,21 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
108
108
  try {
109
109
  const renderingPromises = [];
110
110
  const appShellRoute = appShellOptions.route && addLeadingSlash(appShellOptions.route);
111
+ const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
111
112
  for (const route of allRoutes) {
112
- const isAppShellRoute = appShellRoute === route;
113
- const serverContext = isAppShellRoute ? 'app-shell' : 'ssg';
114
- const render = renderWorker.run({ route, serverContext });
113
+ // Remove base href from file output path.
114
+ const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
115
+ const isAppShellRoute = appShellRoute === routeWithoutBaseHref;
116
+ const render = renderWorker.run({ url: route, isAppShellRoute });
115
117
  const renderResult = render
116
- .then(({ content, warnings, errors }) => {
117
- if (content !== undefined) {
118
- const outPath = isAppShellRoute
119
- ? 'index.html'
120
- : node_path_1.posix.join(removeLeadingSlash(route), 'index.html');
121
- output[outPath] = content;
122
- }
123
- if (warnings) {
124
- warnings.push(...warnings);
125
- }
126
- if (errors) {
127
- errors.push(...errors);
118
+ .then((content) => {
119
+ if (content !== null) {
120
+ const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
121
+ output[outPath] = { content, appShellRoute: isAppShellRoute };
128
122
  }
129
123
  })
130
124
  .catch((err) => {
131
- errors.push(`An error occurred while prerendering route '${route}'.\n\n${err.stack}`);
125
+ errors.push(`An error occurred while prerendering route '${route}'.\n\n${err.stack ?? err.message ?? err.code ?? err}`);
132
126
  void renderWorker.destroy();
133
127
  });
134
128
  renderingPromises.push(renderResult);
@@ -140,25 +134,24 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
140
134
  }
141
135
  return {
142
136
  errors,
143
- warnings,
144
137
  output,
145
138
  };
146
139
  }
147
- async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWorker, document, appShellOptions, prerenderOptions, sourcemap, verbose) {
140
+ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap, verbose) {
148
141
  const { routesFile, discoverRoutes } = prerenderOptions;
149
142
  const routes = new RoutesSet();
150
143
  const { route: appShellRoute } = appShellOptions;
151
144
  if (appShellRoute !== undefined) {
152
- routes.add(appShellRoute);
145
+ routes.add((0, url_1.urlJoin)(baseHref, appShellRoute));
153
146
  }
154
147
  if (routesFile) {
155
148
  const routesFromFile = (await (0, promises_1.readFile)(routesFile, 'utf8')).split(/\r?\n/);
156
149
  for (const route of routesFromFile) {
157
- routes.add(route.trim());
150
+ routes.add((0, url_1.urlJoin)(baseHref, route.trim()));
158
151
  }
159
152
  }
160
153
  if (!discoverRoutes) {
161
- return { routes };
154
+ return { routes, serializableRouteTreeNode: [] };
162
155
  }
163
156
  const workerExecArgv = [
164
157
  '--import',
@@ -175,14 +168,12 @@ async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWo
175
168
  workspaceRoot,
176
169
  outputFiles: outputFilesForWorker,
177
170
  assetFiles: assetFilesForWorker,
178
- document,
179
- verbose,
180
171
  },
181
172
  execArgv: workerExecArgv,
182
173
  recordTiming: false,
183
174
  });
184
175
  const errors = [];
185
- const { routes: extractedRoutes, warnings } = await renderWorker
176
+ const serializableRouteTreeNode = await renderWorker
186
177
  .run({})
187
178
  .catch((err) => {
188
179
  errors.push(`An error occurred while extracting routes.\n\n${err.stack}`);
@@ -190,10 +181,30 @@ async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWo
190
181
  .finally(() => {
191
182
  void renderWorker.destroy();
192
183
  });
193
- for (const route of extractedRoutes) {
194
- routes.add(route);
184
+ const skippedRedirects = [];
185
+ const skippedOthers = [];
186
+ for (const { route, redirectTo } of serializableRouteTreeNode) {
187
+ if (redirectTo) {
188
+ skippedRedirects.push(route);
189
+ }
190
+ else if (route.includes('*')) {
191
+ skippedOthers.push(route);
192
+ }
193
+ else {
194
+ routes.add(route);
195
+ }
196
+ }
197
+ let warnings;
198
+ if (verbose) {
199
+ if (skippedOthers.length) {
200
+ (warnings ??= []).push('The following routes were skipped from prerendering because they contain routes with dynamic parameters:\n' +
201
+ skippedOthers.join('\n'));
202
+ }
203
+ if (skippedRedirects.length) {
204
+ (warnings ??= []).push('The following routes were skipped from prerendering because they contain redirects:\n', skippedRedirects.join('\n'));
205
+ }
195
206
  }
196
- return { routes, warnings, errors };
207
+ return { routes, serializableRouteTreeNode, warnings };
197
208
  }
198
209
  function addLeadingSlash(value) {
199
210
  return value.charAt(0) === '/' ? value : '/' + value;
@@ -6,17 +6,16 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
9
- import { RenderResult, ServerContext } from './render-page';
10
9
  export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
11
- document: string;
12
- inlineCriticalCss?: boolean;
13
10
  assetFiles: Record</** Destination */ string, /** Source */ string>;
14
11
  }
15
12
  export interface RenderOptions {
16
- route: string;
17
- serverContext: ServerContext;
13
+ url: string;
14
+ isAppShellRoute: boolean;
18
15
  }
19
- /** Renders an application based on a provided options. */
20
- declare function render(options: RenderOptions): Promise<RenderResult>;
21
- declare const _default: typeof render;
16
+ /**
17
+ * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
18
+ */
19
+ declare function renderPage({ url, isAppShellRoute }: RenderOptions): Promise<string | null>;
20
+ declare const _default: typeof renderPage;
22
21
  export default _default;
@@ -7,24 +7,21 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- const node_worker_threads_1 = require("node:worker_threads");
11
10
  const fetch_patch_1 = require("./fetch-patch");
12
- const render_page_1 = require("./render-page");
11
+ const load_esm_from_memory_1 = require("./load-esm-from-memory");
13
12
  /**
14
- * This is passed as workerData when setting up the worker via the `piscina` package.
13
+ * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
15
14
  */
16
- const { outputFiles, document, inlineCriticalCss } = node_worker_threads_1.workerData;
17
- /** Renders an application based on a provided options. */
18
- function render(options) {
19
- return (0, render_page_1.renderPage)({
20
- ...options,
21
- outputFiles,
22
- document,
23
- inlineCriticalCss,
24
- });
15
+ async function renderPage({ url, isAppShellRoute }) {
16
+ const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp, ɵServerRenderContext: ServerRenderContext, } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
17
+ const angularServerApp = getOrCreateAngularServerApp();
18
+ const response = await angularServerApp.render(new Request(new URL(url, 'http://local-angular-prerender'), {
19
+ signal: AbortSignal.timeout(30_000),
20
+ }), undefined, isAppShellRoute ? ServerRenderContext.AppShell : ServerRenderContext.SSG);
21
+ return response ? response.text() : null;
25
22
  }
26
23
  function initialize() {
27
24
  (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
28
- return render;
25
+ return renderPage;
29
26
  }
30
27
  exports.default = initialize();
@@ -5,16 +5,12 @@
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 { ɵextractRoutesAndCreateRouteTree } from '@angular/ssr';
8
9
  import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
9
10
  export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData {
10
- document: string;
11
- verbose: boolean;
12
11
  assetFiles: Record</** Destination */ string, /** Source */ string>;
13
12
  }
14
- export interface RoutersExtractorWorkerResult {
15
- routes: string[];
16
- warnings?: string[];
17
- }
13
+ export type RoutersExtractorWorkerResult = ReturnType<Awaited<ReturnType<typeof ɵextractRoutesAndCreateRouteTree>>['toObject']>;
18
14
  /** Renders an application based on a provided options. */
19
15
  declare function extractRoutes(): Promise<RoutersExtractorWorkerResult>;
20
16
  declare const _default: typeof extractRoutes;
@@ -7,44 +7,13 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- const node_worker_threads_1 = require("node:worker_threads");
11
10
  const fetch_patch_1 = require("./fetch-patch");
12
11
  const load_esm_from_memory_1 = require("./load-esm-from-memory");
13
- /**
14
- * This is passed as workerData when setting up the worker via the `piscina` package.
15
- */
16
- const { document, verbose } = node_worker_threads_1.workerData;
17
12
  /** Renders an application based on a provided options. */
18
13
  async function extractRoutes() {
19
- const { ɵgetRoutesFromAngularRouterConfig: getRoutesFromAngularRouterConfig } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./render-utils.server.mjs');
20
- const { default: bootstrapAppFnOrModule } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
21
- const skippedRedirects = [];
22
- const skippedOthers = [];
23
- const routes = [];
24
- const { routes: extractRoutes } = await getRoutesFromAngularRouterConfig(bootstrapAppFnOrModule, document, new URL('http://localhost'));
25
- for (const { route, redirectTo } of extractRoutes) {
26
- if (redirectTo !== undefined) {
27
- skippedRedirects.push(route);
28
- }
29
- else if (/[:*]/.test(route)) {
30
- skippedOthers.push(route);
31
- }
32
- else {
33
- routes.push(route);
34
- }
35
- }
36
- if (!verbose) {
37
- return { routes };
38
- }
39
- let warnings;
40
- if (skippedOthers.length) {
41
- (warnings ??= []).push('The following routes were skipped from prerendering because they contain routes with dynamic parameters:\n' +
42
- skippedOthers.join('\n'));
43
- }
44
- if (skippedRedirects.length) {
45
- (warnings ??= []).push('The following routes were skipped from prerendering because they contain redirects:\n', skippedRedirects.join('\n'));
46
- }
47
- return { routes, warnings };
14
+ const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
15
+ const routeTree = await extractRoutesAndCreateRouteTree(new URL('http://local-angular-prerender/'));
16
+ return routeTree.toObject();
48
17
  }
49
18
  function initialize() {
50
19
  (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
@@ -1,27 +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 type { ApplicationRef, Type, ɵConsole } from '@angular/core';
9
- import type { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
10
- import type { ɵgetRoutesFromAngularRouterConfig } from '@angular/ssr';
11
- export interface MainServerBundleExports {
12
- /** Standalone application bootstrapping function. */
13
- default: (() => Promise<ApplicationRef>) | Type<unknown>;
14
- }
15
- export interface RenderUtilsServerBundleExports {
16
- /** An internal token that allows providing extra information about the server context. */
17
- ɵSERVER_CONTEXT: typeof ɵSERVER_CONTEXT;
18
- /** Render an NgModule application. */
19
- renderModule: typeof renderModule;
20
- /** Method to render a standalone application. */
21
- renderApplication: typeof renderApplication;
22
- /** Method to extract routes from the router config. */
23
- ɵgetRoutesFromAngularRouterConfig: typeof ɵgetRoutesFromAngularRouterConfig;
24
- ɵresetCompiledComponents?: () => void;
25
- /** Angular Console token/class. */
26
- ɵConsole: typeof ɵConsole;
27
- }
@@ -1,9 +0,0 @@
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 });
@@ -1,26 +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 { MainServerBundleExports, RenderUtilsServerBundleExports } from './main-bundle-exports';
9
- export interface RenderOptions {
10
- route: string;
11
- serverContext: ServerContext;
12
- outputFiles: Record<string, string>;
13
- document: string;
14
- inlineCriticalCss?: boolean;
15
- loadBundle?: ((path: './main.server.mjs') => Promise<MainServerBundleExports>) & ((path: './render-utils.server.mjs') => Promise<RenderUtilsServerBundleExports>);
16
- }
17
- export interface RenderResult {
18
- errors?: string[];
19
- warnings?: string[];
20
- content?: string;
21
- }
22
- export type ServerContext = 'app-shell' | 'ssg' | 'ssr';
23
- /**
24
- * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
25
- */
26
- export declare function renderPage({ route, serverContext, document, inlineCriticalCss, outputFiles, loadBundle, }: RenderOptions): Promise<RenderResult>;
@@ -1,114 +0,0 @@
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
- var __importDefault = (this && this.__importDefault) || function (mod) {
33
- return (mod && mod.__esModule) ? mod : { "default": mod };
34
- };
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.renderPage = renderPage;
37
- const node_assert_1 = __importDefault(require("node:assert"));
38
- const node_path_1 = require("node:path");
39
- const load_esm_from_memory_1 = require("./load-esm-from-memory");
40
- /**
41
- * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
42
- */
43
- async function renderPage({ route, serverContext, document, inlineCriticalCss, outputFiles, loadBundle = load_esm_from_memory_1.loadEsmModuleFromMemory, }) {
44
- const { default: bootstrapAppFnOrModule } = await loadBundle('./main.server.mjs');
45
- const { ɵSERVER_CONTEXT, renderModule, renderApplication, ɵresetCompiledComponents, ɵConsole } = await loadBundle('./render-utils.server.mjs');
46
- // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
47
- // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
48
- // See: https://github.com/angular/angular-cli/issues/25924
49
- ɵresetCompiledComponents?.();
50
- const platformProviders = [
51
- {
52
- provide: ɵSERVER_CONTEXT,
53
- useValue: serverContext,
54
- },
55
- {
56
- provide: ɵConsole,
57
- /** An Angular Console Provider that does not print a set of predefined logs. */
58
- useFactory: () => {
59
- class Console extends ɵConsole {
60
- ignoredLogs = new Set(['Angular is running in development mode.']);
61
- log(message) {
62
- if (!this.ignoredLogs.has(message)) {
63
- super.log(message);
64
- }
65
- }
66
- }
67
- return new Console();
68
- },
69
- },
70
- ];
71
- (0, node_assert_1.default)(bootstrapAppFnOrModule, 'The file "./main.server.mjs" does not have a default export for an AppServerModule or a bootstrapping function.');
72
- let renderAppPromise;
73
- if (isBootstrapFn(bootstrapAppFnOrModule)) {
74
- renderAppPromise = renderApplication(bootstrapAppFnOrModule, {
75
- document,
76
- url: route,
77
- platformProviders,
78
- });
79
- }
80
- else {
81
- renderAppPromise = renderModule(bootstrapAppFnOrModule, {
82
- document,
83
- url: route,
84
- extraProviders: platformProviders,
85
- });
86
- }
87
- // The below should really handled by the framework!!!.
88
- // See: https://github.com/angular/angular/issues/51549
89
- let timer;
90
- const renderingTimeout = new Promise((_, reject) => (timer = setTimeout(() => reject(new Error(`Page ${new URL(route, 'resolve://').pathname} did not render in 30 seconds.`)), 30_000)));
91
- const html = await Promise.race([renderAppPromise, renderingTimeout]).finally(() => clearTimeout(timer));
92
- if (inlineCriticalCss) {
93
- const { InlineCriticalCssProcessor } = await Promise.resolve().then(() => __importStar(require('../../utils/index-file/inline-critical-css')));
94
- const inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
95
- minify: false, // CSS has already been minified during the build.
96
- readAsset: async (filePath) => {
97
- filePath = (0, node_path_1.basename)(filePath);
98
- const content = outputFiles[filePath];
99
- if (content === undefined) {
100
- throw new Error(`Output file does not exist: ${filePath}`);
101
- }
102
- return content;
103
- },
104
- });
105
- return inlineCriticalCssProcessor.process(html, { outputPath: '' });
106
- }
107
- return {
108
- content: html,
109
- };
110
- }
111
- function isBootstrapFn(value) {
112
- // We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
113
- return typeof value === 'function' && !('ɵmod' in value);
114
- }