@angular/build 19.0.0-next.7 → 19.0.0-next.9
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.
- package/LICENSE +5 -5
- package/package.json +10 -9
- package/src/builders/application/execute-build.js +19 -2
- package/src/builders/application/execute-post-bundle.d.ts +2 -2
- package/src/builders/application/execute-post-bundle.js +30 -11
- package/src/builders/application/i18n.d.ts +2 -2
- package/src/builders/application/i18n.js +4 -5
- package/src/builders/application/index.js +8 -5
- package/src/builders/application/options.d.ts +25 -1
- package/src/builders/application/options.js +31 -2
- package/src/builders/application/schema.d.ts +15 -0
- package/src/builders/application/schema.js +11 -1
- package/src/builders/application/schema.json +5 -0
- package/src/builders/application/setup-bundling.js +6 -3
- package/src/builders/dev-server/vite-server.d.ts +2 -1
- package/src/builders/dev-server/vite-server.js +71 -53
- package/src/builders/extract-i18n/application-extraction.js +3 -3
- package/src/tools/angular/angular-host.d.ts +2 -1
- package/src/tools/angular/angular-host.js +20 -1
- package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/aot-compilation.js +9 -1
- package/src/tools/angular/compilation/parallel-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
- package/src/tools/angular/compilation/parallel-worker.js +2 -1
- package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
- package/src/tools/babel/plugins/add-code-coverage.js +44 -0
- package/src/tools/babel/plugins/types.d.ts +20 -0
- package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -0
- package/src/tools/esbuild/angular/compiler-plugin.js +44 -4
- package/src/tools/esbuild/angular/component-stylesheets.d.ts +8 -3
- package/src/tools/esbuild/angular/component-stylesheets.js +46 -11
- package/src/tools/esbuild/application-code-bundle.d.ts +1 -0
- package/src/tools/esbuild/application-code-bundle.js +109 -2
- package/src/tools/esbuild/budget-stats.js +1 -1
- package/src/tools/esbuild/bundler-context.d.ts +4 -3
- package/src/tools/esbuild/bundler-context.js +8 -4
- package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
- package/src/tools/esbuild/bundler-execution-result.js +7 -3
- package/src/tools/esbuild/cache.d.ts +5 -0
- package/src/tools/esbuild/cache.js +7 -0
- package/src/tools/esbuild/compiler-plugin-options.js +3 -1
- package/src/tools/esbuild/i18n-inliner.js +2 -1
- package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
- package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
- package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
- package/src/tools/esbuild/javascript-transformer.js +5 -3
- package/src/tools/esbuild/utils.js +7 -3
- package/src/tools/vite/middlewares/assets-middleware.js +2 -5
- package/src/tools/vite/middlewares/html-fallback-middleware.js +22 -6
- package/src/tools/vite/middlewares/index.d.ts +1 -1
- package/src/tools/vite/middlewares/index.js +3 -2
- package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -1
- package/src/tools/vite/middlewares/ssr-middleware.js +62 -15
- package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
- package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -41
- package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
- package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
- package/src/tools/vite/plugins/index.d.ts +12 -0
- package/src/tools/vite/plugins/index.js +21 -0
- package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +41 -0
- package/src/tools/vite/plugins/setup-middlewares-plugin.js +62 -0
- package/src/tools/vite/plugins/ssr-transform-plugin.d.ts +9 -0
- package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
- package/src/utils/environment-options.d.ts +2 -0
- package/src/utils/environment-options.js +5 -1
- package/src/utils/index-file/index-html-generator.js +5 -0
- package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
- package/src/utils/index-file/ngcm-attribute.js +37 -0
- package/src/utils/index-file/valid-self-closing-tags.js +28 -0
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
- package/src/utils/server-rendering/fetch-patch.js +2 -2
- package/src/utils/server-rendering/launch-server.d.ts +14 -0
- package/src/utils/server-rendering/launch-server.js +63 -0
- package/src/utils/server-rendering/load-esm-from-memory.d.ts +7 -0
- package/src/utils/server-rendering/manifest.d.ts +8 -2
- package/src/utils/server-rendering/manifest.js +52 -12
- package/src/utils/server-rendering/models.d.ts +27 -0
- package/src/utils/server-rendering/models.js +22 -0
- package/src/utils/server-rendering/prerender.d.ts +6 -10
- package/src/utils/server-rendering/prerender.js +102 -63
- package/src/utils/server-rendering/render-worker.d.ts +4 -1
- package/src/utils/server-rendering/render-worker.js +13 -3
- package/src/utils/server-rendering/routes-extractor-worker.d.ts +6 -10
- package/src/utils/server-rendering/routes-extractor-worker.js +14 -5
- package/src/utils/server-rendering/utils.d.ts +11 -0
- package/src/utils/server-rendering/utils.js +17 -0
- package/src/tools/vite/angular-memory-plugin.d.ts +0 -22
- /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
- /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
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 DEFAULT_URL: import("url").URL;
|
|
9
|
+
/**
|
|
10
|
+
* Launches a server that handles local requests.
|
|
11
|
+
*
|
|
12
|
+
* @returns A promise that resolves to the URL of the running server.
|
|
13
|
+
*/
|
|
14
|
+
export declare function launchServer(): Promise<URL>;
|
|
@@ -0,0 +1,63 @@
|
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DEFAULT_URL = void 0;
|
|
14
|
+
exports.launchServer = launchServer;
|
|
15
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
16
|
+
const node_http_1 = require("node:http");
|
|
17
|
+
const load_esm_1 = require("../load-esm");
|
|
18
|
+
const load_esm_from_memory_1 = require("./load-esm-from-memory");
|
|
19
|
+
const utils_1 = require("./utils");
|
|
20
|
+
exports.DEFAULT_URL = new URL('http://ng-localhost/');
|
|
21
|
+
/**
|
|
22
|
+
* Launches a server that handles local requests.
|
|
23
|
+
*
|
|
24
|
+
* @returns A promise that resolves to the URL of the running server.
|
|
25
|
+
*/
|
|
26
|
+
async function launchServer() {
|
|
27
|
+
const { default: handler } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./server.mjs');
|
|
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)) {
|
|
30
|
+
return exports.DEFAULT_URL;
|
|
31
|
+
}
|
|
32
|
+
const server = (0, node_http_1.createServer)((req, res) => {
|
|
33
|
+
(async () => {
|
|
34
|
+
// handle request
|
|
35
|
+
if ((0, utils_1.isSsrNodeRequestHandler)(handler)) {
|
|
36
|
+
await handler(req, res, (e) => {
|
|
37
|
+
throw e;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const webRes = await handler(createWebRequestFromNodeRequest(req));
|
|
42
|
+
if (webRes) {
|
|
43
|
+
await writeResponseToNodeResponse(webRes, res);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
res.statusCode = 501;
|
|
47
|
+
res.end('Not Implemented.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})().catch((e) => {
|
|
51
|
+
res.statusCode = 500;
|
|
52
|
+
res.end('Internal Server Error.');
|
|
53
|
+
// eslint-disable-next-line no-console
|
|
54
|
+
console.error(e);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
server.unref();
|
|
58
|
+
await new Promise((resolve) => server.listen(0, 'localhost', resolve));
|
|
59
|
+
const serverAddress = server.address();
|
|
60
|
+
(0, node_assert_1.default)(serverAddress, 'Server address should be defined.');
|
|
61
|
+
(0, node_assert_1.default)(typeof serverAddress !== 'string', 'Server address should not be a string.');
|
|
62
|
+
return new URL(`http://localhost:${serverAddress.port}/`);
|
|
63
|
+
}
|
|
@@ -15,5 +15,12 @@ interface MainServerBundleExports {
|
|
|
15
15
|
ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree;
|
|
16
16
|
ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Represents the exports available from the server bundle.
|
|
20
|
+
*/
|
|
21
|
+
interface ServerBundleExports {
|
|
22
|
+
default: unknown;
|
|
23
|
+
}
|
|
18
24
|
export declare function loadEsmModuleFromMemory(path: './main.server.mjs'): Promise<MainServerBundleExports>;
|
|
25
|
+
export declare function loadEsmModuleFromMemory(path: './server.mjs'): Promise<ServerBundleExports>;
|
|
19
26
|
export {};
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
|
|
9
9
|
import type { BuildOutputFile } from '../../tools/esbuild/bundler-context';
|
|
10
|
+
import { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
|
|
10
11
|
export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
|
|
12
|
+
export declare const SERVER_APP_ENGINE_MANIFEST_FILENAME = "angular-app-engine-manifest.mjs";
|
|
11
13
|
/**
|
|
12
14
|
* Generates the server manifest for the App Engine environment.
|
|
13
15
|
*
|
|
@@ -19,9 +21,10 @@ export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
|
|
|
19
21
|
* includes settings for inlining locales and determining the output structure.
|
|
20
22
|
* @param baseHref - The base HREF for the application. This is used to set the base URL
|
|
21
23
|
* for all relative URLs in the application.
|
|
24
|
+
* @param perenderedRoutes - A record mapping static paths to their associated data.
|
|
22
25
|
* @returns A string representing the content of the SSR server manifest for App Engine.
|
|
23
26
|
*/
|
|
24
|
-
export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined): string;
|
|
27
|
+
export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined, perenderedRoutes?: PrerenderedRoutesRecord | undefined): string;
|
|
25
28
|
/**
|
|
26
29
|
* Generates the server manifest for the standard Node.js environment.
|
|
27
30
|
*
|
|
@@ -38,7 +41,10 @@ export declare function generateAngularServerAppEngineManifest(i18nOptions: Norm
|
|
|
38
41
|
* in the server-side rendered pages.
|
|
39
42
|
* @param routes - An optional array of route definitions for the application, used for
|
|
40
43
|
* server-side rendering and routing.
|
|
44
|
+
* @param locale - An optional string representing the locale or language code to be used for
|
|
45
|
+
* the application, helping with localization and rendering content specific to the locale.
|
|
46
|
+
*
|
|
41
47
|
* @returns A string representing the content of the SSR server manifest for the Node.js
|
|
42
48
|
* environment.
|
|
43
49
|
*/
|
|
44
|
-
export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined): string;
|
|
50
|
+
export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined): string;
|
|
@@ -7,12 +7,31 @@
|
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.SERVER_APP_MANIFEST_FILENAME = void 0;
|
|
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
13
|
const options_1 = require("../../builders/application/options");
|
|
14
14
|
exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
|
|
15
|
+
exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
|
|
15
16
|
const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
|
|
17
|
+
/**
|
|
18
|
+
* A mapping of unsafe characters to their escaped Unicode equivalents.
|
|
19
|
+
*/
|
|
20
|
+
const UNSAFE_CHAR_MAP = {
|
|
21
|
+
'`': '\\`',
|
|
22
|
+
'$': '\\$',
|
|
23
|
+
'\\': '\\\\',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Escapes unsafe characters in a given string by replacing them with
|
|
27
|
+
* their Unicode escape sequences.
|
|
28
|
+
*
|
|
29
|
+
* @param str - The string to be escaped.
|
|
30
|
+
* @returns The escaped string where unsafe characters are replaced.
|
|
31
|
+
*/
|
|
32
|
+
function escapeUnsafeChars(str) {
|
|
33
|
+
return str.replace(/[$`\\]/g, (c) => UNSAFE_CHAR_MAP[c]);
|
|
34
|
+
}
|
|
16
35
|
/**
|
|
17
36
|
* Generates the server manifest for the App Engine environment.
|
|
18
37
|
*
|
|
@@ -24,26 +43,43 @@ const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
|
|
|
24
43
|
* includes settings for inlining locales and determining the output structure.
|
|
25
44
|
* @param baseHref - The base HREF for the application. This is used to set the base URL
|
|
26
45
|
* for all relative URLs in the application.
|
|
46
|
+
* @param perenderedRoutes - A record mapping static paths to their associated data.
|
|
27
47
|
* @returns A string representing the content of the SSR server manifest for App Engine.
|
|
28
48
|
*/
|
|
29
|
-
function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
|
|
49
|
+
function generateAngularServerAppEngineManifest(i18nOptions, baseHref, perenderedRoutes = {}) {
|
|
30
50
|
const entryPointsContent = [];
|
|
31
51
|
if (i18nOptions.shouldInline) {
|
|
32
52
|
for (const locale of i18nOptions.inlineLocales) {
|
|
33
53
|
const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
let localeWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
|
|
55
|
+
// Remove leading and trailing slashes.
|
|
56
|
+
const start = localeWithBaseHref[0] === '/' ? 1 : 0;
|
|
57
|
+
const end = localeWithBaseHref[localeWithBaseHref.length - 1] === '/' ? -1 : undefined;
|
|
58
|
+
localeWithBaseHref = localeWithBaseHref.slice(start, end);
|
|
59
|
+
entryPointsContent.push(`['${localeWithBaseHref}', () => import('${importPath}')]`);
|
|
36
60
|
}
|
|
37
61
|
}
|
|
38
62
|
else {
|
|
39
|
-
entryPointsContent.push(`['
|
|
63
|
+
entryPointsContent.push(`['', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
|
|
64
|
+
}
|
|
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(', ')}]]`);
|
|
40
75
|
}
|
|
41
76
|
const manifestContent = `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
77
|
+
export default {
|
|
78
|
+
basePath: '${baseHref ?? '/'}',
|
|
79
|
+
entryPoints: new Map([${entryPointsContent.join(', \n')}]),
|
|
80
|
+
staticPathsHeaders: new Map([${staticHeaders.join(', \n')}]),
|
|
81
|
+
};
|
|
82
|
+
`;
|
|
47
83
|
return manifestContent;
|
|
48
84
|
}
|
|
49
85
|
/**
|
|
@@ -62,16 +98,19 @@ function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
|
|
|
62
98
|
* in the server-side rendered pages.
|
|
63
99
|
* @param routes - An optional array of route definitions for the application, used for
|
|
64
100
|
* server-side rendering and routing.
|
|
101
|
+
* @param locale - An optional string representing the locale or language code to be used for
|
|
102
|
+
* the application, helping with localization and rendering content specific to the locale.
|
|
103
|
+
*
|
|
65
104
|
* @returns A string representing the content of the SSR server manifest for the Node.js
|
|
66
105
|
* environment.
|
|
67
106
|
*/
|
|
68
|
-
function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes) {
|
|
107
|
+
function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale) {
|
|
69
108
|
const serverAssetsContent = [];
|
|
70
109
|
for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
|
|
71
110
|
if (file.path === options_1.INDEX_HTML_SERVER ||
|
|
72
111
|
file.path === options_1.INDEX_HTML_CSR ||
|
|
73
112
|
(inlineCriticalCss && file.path.endsWith('.css'))) {
|
|
74
|
-
serverAssetsContent.push(`['${file.path}', async () =>
|
|
113
|
+
serverAssetsContent.push(`['${file.path}', async () => \`${escapeUnsafeChars(file.text)}\`]`);
|
|
75
114
|
}
|
|
76
115
|
}
|
|
77
116
|
const manifestContent = `
|
|
@@ -80,6 +119,7 @@ export default {
|
|
|
80
119
|
inlineCriticalCss: ${inlineCriticalCss},
|
|
81
120
|
routes: ${JSON.stringify(routes, undefined, 2)},
|
|
82
121
|
assets: new Map([${serverAssetsContent.join(', \n')}]),
|
|
122
|
+
locale: ${locale !== undefined ? `'${locale}'` : undefined},
|
|
83
123
|
};
|
|
84
124
|
`;
|
|
85
125
|
return manifestContent;
|
|
@@ -0,0 +1,27 @@
|
|
|
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 { RenderMode, ɵextractRoutesAndCreateRouteTree } from '@angular/ssr';
|
|
9
|
+
import { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
|
|
10
|
+
type Writeable<T extends readonly unknown[]> = T extends readonly (infer U)[] ? U[] : never;
|
|
11
|
+
export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData {
|
|
12
|
+
assetFiles: Record</** Destination */ string, /** Source */ string>;
|
|
13
|
+
}
|
|
14
|
+
export type SerializableRouteTreeNode = ReturnType<Awaited<ReturnType<typeof ɵextractRoutesAndCreateRouteTree>>['routeTree']['toObject']>;
|
|
15
|
+
export type WritableSerializableRouteTreeNode = Writeable<SerializableRouteTreeNode>;
|
|
16
|
+
export interface RoutersExtractorWorkerResult {
|
|
17
|
+
serializedRouteTree: SerializableRouteTreeNode;
|
|
18
|
+
errors: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Local copy of `RenderMode` exported from `@angular/ssr`.
|
|
22
|
+
* This constant is needed to handle interop between CommonJS (CJS) and ES Modules (ESM) formats.
|
|
23
|
+
*
|
|
24
|
+
* It maps `RenderMode` enum values to their corresponding numeric identifiers.
|
|
25
|
+
*/
|
|
26
|
+
export declare const RouteRenderMode: Record<keyof typeof RenderMode, RenderMode>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
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.RouteRenderMode = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Local copy of `RenderMode` exported from `@angular/ssr`.
|
|
13
|
+
* This constant is needed to handle interop between CommonJS (CJS) and ES Modules (ESM) formats.
|
|
14
|
+
*
|
|
15
|
+
* It maps `RenderMode` enum values to their corresponding numeric identifiers.
|
|
16
|
+
*/
|
|
17
|
+
exports.RouteRenderMode = {
|
|
18
|
+
AppShell: 0,
|
|
19
|
+
Server: 1,
|
|
20
|
+
Client: 2,
|
|
21
|
+
Prerender: 3,
|
|
22
|
+
};
|
|
@@ -5,16 +5,13 @@
|
|
|
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 { NormalizedApplicationBuildOptions } from '../../builders/application/options';
|
|
9
|
+
import { OutputMode } from '../../builders/application/schema';
|
|
8
10
|
import { BuildOutputFile } from '../../tools/esbuild/bundler-context';
|
|
9
11
|
import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result';
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
discoverRoutes?: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface AppShellOptions {
|
|
16
|
-
route?: string;
|
|
17
|
-
}
|
|
12
|
+
import { SerializableRouteTreeNode } from './models';
|
|
13
|
+
type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions'];
|
|
14
|
+
type AppShellOptions = NormalizedApplicationBuildOptions['appShellOptions'];
|
|
18
15
|
/**
|
|
19
16
|
* Represents the output of a prerendering process.
|
|
20
17
|
*
|
|
@@ -33,11 +30,10 @@ type PrerenderOutput = Record<string, {
|
|
|
33
30
|
content: string;
|
|
34
31
|
appShellRoute: boolean;
|
|
35
32
|
}>;
|
|
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
|
|
33
|
+
export declare function prerenderPages(workspaceRoot: string, baseHref: string, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, outputFiles: Readonly<BuildOutputFile[]>, assets: Readonly<BuildOutputAsset[]>, outputMode: OutputMode | undefined, sourcemap?: boolean, maxThreads?: number): Promise<{
|
|
37
34
|
output: PrerenderOutput;
|
|
38
35
|
warnings: string[];
|
|
39
36
|
errors: string[];
|
|
40
|
-
prerenderedRoutes: Set<string>;
|
|
41
37
|
serializableRouteTreeNode: SerializableRouteTreeNode;
|
|
42
38
|
}>;
|
|
43
39
|
export {};
|
|
@@ -11,16 +11,19 @@ exports.prerenderPages = prerenderPages;
|
|
|
11
11
|
const promises_1 = require("node:fs/promises");
|
|
12
12
|
const node_path_1 = require("node:path");
|
|
13
13
|
const node_url_1 = require("node:url");
|
|
14
|
+
const schema_1 = require("../../builders/application/schema");
|
|
14
15
|
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
|
|
16
|
+
const error_1 = require("../error");
|
|
15
17
|
const url_1 = require("../url");
|
|
16
18
|
const worker_pool_1 = require("../worker-pool");
|
|
17
|
-
|
|
19
|
+
const models_1 = require("./models");
|
|
20
|
+
async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerenderOptions, outputFiles, assets, outputMode, sourcemap = false, maxThreads = 1) {
|
|
18
21
|
const outputFilesForWorker = {};
|
|
19
22
|
const serverBundlesSourceMaps = new Map();
|
|
20
23
|
const warnings = [];
|
|
21
24
|
const errors = [];
|
|
22
25
|
for (const { text, path, type } of outputFiles) {
|
|
23
|
-
if (type !== bundler_context_1.BuildOutputFileType.
|
|
26
|
+
if (type !== bundler_context_1.BuildOutputFileType.ServerApplication && type !== bundler_context_1.BuildOutputFileType.ServerRoot) {
|
|
24
27
|
continue;
|
|
25
28
|
}
|
|
26
29
|
// Contains the server runnable application code
|
|
@@ -48,39 +51,57 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions = {}, pre
|
|
|
48
51
|
assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source;
|
|
49
52
|
}
|
|
50
53
|
// Get routes to prerender
|
|
51
|
-
const {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const { errors: extractionErrors, serializedRouteTree: serializableRouteTreeNode } = await getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetsReversed, appShellOptions, prerenderOptions, sourcemap, outputMode).catch((err) => {
|
|
55
|
+
return {
|
|
56
|
+
errors: [
|
|
57
|
+
`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`,
|
|
58
|
+
],
|
|
59
|
+
serializedRouteTree: [],
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
errors.push(...extractionErrors);
|
|
63
|
+
const serializableRouteTreeNodeForPrerender = [];
|
|
64
|
+
for (const metadata of serializableRouteTreeNode) {
|
|
65
|
+
if (outputMode !== schema_1.OutputMode.Static && metadata.redirectTo) {
|
|
66
|
+
// Skip redirects if output mode is not static.
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (metadata.route.includes('*')) {
|
|
70
|
+
// Skip catch all routes from prerender.
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
switch (metadata.renderMode) {
|
|
74
|
+
case undefined: /* Legacy building mode */
|
|
75
|
+
case models_1.RouteRenderMode.Prerender:
|
|
76
|
+
case models_1.RouteRenderMode.AppShell:
|
|
77
|
+
serializableRouteTreeNodeForPrerender.push(metadata);
|
|
78
|
+
break;
|
|
79
|
+
case models_1.RouteRenderMode.Server:
|
|
80
|
+
if (outputMode === schema_1.OutputMode.Static) {
|
|
81
|
+
errors.push(`Route '${metadata.route}' is configured with server render mode, but the build 'outputMode' is set to 'static'.`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
57
85
|
}
|
|
58
|
-
if (
|
|
86
|
+
if (!serializableRouteTreeNodeForPrerender.length || errors.length > 0) {
|
|
59
87
|
return {
|
|
60
88
|
errors,
|
|
61
89
|
warnings,
|
|
62
90
|
output: {},
|
|
63
91
|
serializableRouteTreeNode,
|
|
64
|
-
prerenderedRoutes: allRoutes,
|
|
65
92
|
};
|
|
66
93
|
}
|
|
67
94
|
// Render routes
|
|
68
|
-
const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap,
|
|
95
|
+
const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions, outputMode);
|
|
69
96
|
errors.push(...renderingErrors);
|
|
70
97
|
return {
|
|
71
98
|
errors,
|
|
72
99
|
warnings,
|
|
73
100
|
output,
|
|
74
101
|
serializableRouteTreeNode,
|
|
75
|
-
prerenderedRoutes: allRoutes,
|
|
76
102
|
};
|
|
77
103
|
}
|
|
78
|
-
|
|
79
|
-
add(value) {
|
|
80
|
-
return super.add(addLeadingSlash(value));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions) {
|
|
104
|
+
async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions, outputMode) {
|
|
84
105
|
const output = {};
|
|
85
106
|
const errors = [];
|
|
86
107
|
const workerExecArgv = [
|
|
@@ -93,27 +114,35 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
|
|
|
93
114
|
}
|
|
94
115
|
const renderWorker = new worker_pool_1.WorkerPool({
|
|
95
116
|
filename: require.resolve('./render-worker'),
|
|
96
|
-
maxThreads: Math.min(
|
|
117
|
+
maxThreads: Math.min(serializableRouteTreeNode.length, maxThreads),
|
|
97
118
|
workerData: {
|
|
98
119
|
workspaceRoot,
|
|
99
120
|
outputFiles: outputFilesForWorker,
|
|
100
121
|
assetFiles: assetFilesForWorker,
|
|
122
|
+
outputMode,
|
|
123
|
+
hasSsrEntry: !!outputFilesForWorker['/server.mjs'],
|
|
101
124
|
},
|
|
102
125
|
execArgv: workerExecArgv,
|
|
103
126
|
});
|
|
104
127
|
try {
|
|
105
128
|
const renderingPromises = [];
|
|
106
|
-
const appShellRoute = appShellOptions
|
|
129
|
+
const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
|
|
107
130
|
const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
|
|
108
|
-
for (const route of
|
|
131
|
+
for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
|
|
109
132
|
// Remove base href from file output path.
|
|
110
133
|
const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
|
|
111
|
-
const
|
|
134
|
+
const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
|
|
135
|
+
if (typeof redirectTo === 'string') {
|
|
136
|
+
output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false };
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const isAppShellRoute = renderMode === models_1.RouteRenderMode.AppShell ||
|
|
140
|
+
// Legacy handling
|
|
141
|
+
(renderMode === undefined && appShellRoute === routeWithoutBaseHref);
|
|
142
|
+
const render = renderWorker.run({ url: route, isAppShellRoute });
|
|
112
143
|
const renderResult = render
|
|
113
144
|
.then((content) => {
|
|
114
145
|
if (content !== null) {
|
|
115
|
-
const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
|
|
116
|
-
const isAppShellRoute = appShellRoute === routeWithoutBaseHref;
|
|
117
146
|
output[outPath] = { content, appShellRoute: isAppShellRoute };
|
|
118
147
|
}
|
|
119
148
|
})
|
|
@@ -133,21 +162,24 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
|
|
|
133
162
|
output,
|
|
134
163
|
};
|
|
135
164
|
}
|
|
136
|
-
async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap,
|
|
137
|
-
const { routesFile, discoverRoutes } = prerenderOptions;
|
|
138
|
-
const routes =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
165
|
+
async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap, outputMode) {
|
|
166
|
+
const { routesFile, discoverRoutes } = prerenderOptions ?? {};
|
|
167
|
+
const routes = [];
|
|
168
|
+
if (appShellOptions) {
|
|
169
|
+
routes.push({
|
|
170
|
+
route: (0, url_1.urlJoin)(baseHref, appShellOptions.route),
|
|
171
|
+
});
|
|
142
172
|
}
|
|
143
173
|
if (routesFile) {
|
|
144
174
|
const routesFromFile = (await (0, promises_1.readFile)(routesFile, 'utf8')).split(/\r?\n/);
|
|
145
175
|
for (const route of routesFromFile) {
|
|
146
|
-
routes.
|
|
176
|
+
routes.push({
|
|
177
|
+
route: (0, url_1.urlJoin)(baseHref, route.trim()),
|
|
178
|
+
});
|
|
147
179
|
}
|
|
148
180
|
}
|
|
149
181
|
if (!discoverRoutes) {
|
|
150
|
-
return {
|
|
182
|
+
return { errors: [], serializedRouteTree: routes };
|
|
151
183
|
}
|
|
152
184
|
const workerExecArgv = [
|
|
153
185
|
'--import',
|
|
@@ -164,42 +196,25 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
|
|
|
164
196
|
workspaceRoot,
|
|
165
197
|
outputFiles: outputFilesForWorker,
|
|
166
198
|
assetFiles: assetFilesForWorker,
|
|
199
|
+
outputMode,
|
|
200
|
+
hasSsrEntry: !!outputFilesForWorker['/server.mjs'],
|
|
167
201
|
},
|
|
168
202
|
execArgv: workerExecArgv,
|
|
169
203
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
.catch((err) => {
|
|
174
|
-
errors.push(`An error occurred while extracting routes.\n\n${err.stack}`);
|
|
175
|
-
})
|
|
176
|
-
.finally(() => {
|
|
177
|
-
void renderWorker.destroy();
|
|
178
|
-
});
|
|
179
|
-
const skippedRedirects = [];
|
|
180
|
-
const skippedOthers = [];
|
|
181
|
-
for (const { route, redirectTo } of serializableRouteTreeNode) {
|
|
182
|
-
if (redirectTo) {
|
|
183
|
-
skippedRedirects.push(route);
|
|
184
|
-
}
|
|
185
|
-
else if (route.includes('*')) {
|
|
186
|
-
skippedOthers.push(route);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
routes.add(route);
|
|
190
|
-
}
|
|
204
|
+
try {
|
|
205
|
+
const { serializedRouteTree, errors } = await renderWorker.run({});
|
|
206
|
+
return { errors, serializedRouteTree: [...routes, ...serializedRouteTree] };
|
|
191
207
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
catch (err) {
|
|
209
|
+
(0, error_1.assertIsError)(err);
|
|
210
|
+
return {
|
|
211
|
+
errors: [`An error occurred while extracting routes.\n\n${err.stack}`],
|
|
212
|
+
serializedRouteTree: [],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
void renderWorker.destroy();
|
|
201
217
|
}
|
|
202
|
-
return { routes, serializableRouteTreeNode, warnings };
|
|
203
218
|
}
|
|
204
219
|
function addLeadingSlash(value) {
|
|
205
220
|
return value.charAt(0) === '/' ? value : '/' + value;
|
|
@@ -207,3 +222,27 @@ function addLeadingSlash(value) {
|
|
|
207
222
|
function removeLeadingSlash(value) {
|
|
208
223
|
return value.charAt(0) === '/' ? value.slice(1) : value;
|
|
209
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
|
|
227
|
+
*
|
|
228
|
+
* This function creates a simple HTML page that performs a redirect using a meta tag.
|
|
229
|
+
* It includes a fallback link in case the meta-refresh doesn't work.
|
|
230
|
+
*
|
|
231
|
+
* @param url - The URL to which the page should redirect.
|
|
232
|
+
* @returns The HTML content of the static redirect page.
|
|
233
|
+
*/
|
|
234
|
+
function generateRedirectStaticPage(url) {
|
|
235
|
+
return `
|
|
236
|
+
<!DOCTYPE html>
|
|
237
|
+
<html>
|
|
238
|
+
<head>
|
|
239
|
+
<meta charset="utf-8">
|
|
240
|
+
<title>Redirecting</title>
|
|
241
|
+
<meta http-equiv="refresh" content="0; url=${url}">
|
|
242
|
+
</head>
|
|
243
|
+
<body>
|
|
244
|
+
<pre>Redirecting to <a href="${url}">${url}</a></pre>
|
|
245
|
+
</body>
|
|
246
|
+
</html>
|
|
247
|
+
`.trim();
|
|
248
|
+
}
|
|
@@ -5,9 +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 { OutputMode } from '../../builders/application/schema';
|
|
8
9
|
import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
|
|
9
10
|
export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
|
|
10
11
|
assetFiles: Record</** Destination */ string, /** Source */ string>;
|
|
12
|
+
outputMode: OutputMode | undefined;
|
|
13
|
+
hasSsrEntry: boolean;
|
|
11
14
|
}
|
|
12
15
|
export interface RenderOptions {
|
|
13
16
|
url: string;
|
|
@@ -16,5 +19,5 @@ export interface RenderOptions {
|
|
|
16
19
|
* Renders each route in routes and writes them to <outputPath>/<route>/index.html.
|
|
17
20
|
*/
|
|
18
21
|
declare function renderPage({ url }: RenderOptions): Promise<string | null>;
|
|
19
|
-
declare const _default: typeof renderPage
|
|
22
|
+
declare const _default: Promise<typeof renderPage>;
|
|
20
23
|
export default _default;
|
|
@@ -7,19 +7,29 @@
|
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const worker_threads_1 = require("worker_threads");
|
|
10
11
|
const fetch_patch_1 = require("./fetch-patch");
|
|
12
|
+
const launch_server_1 = require("./launch-server");
|
|
11
13
|
const load_esm_from_memory_1 = require("./load-esm-from-memory");
|
|
14
|
+
/**
|
|
15
|
+
* This is passed as workerData when setting up the worker via the `piscina` package.
|
|
16
|
+
*/
|
|
17
|
+
const { outputMode, hasSsrEntry } = worker_threads_1.workerData;
|
|
18
|
+
let serverURL = launch_server_1.DEFAULT_URL;
|
|
12
19
|
/**
|
|
13
20
|
* Renders each route in routes and writes them to <outputPath>/<route>/index.html.
|
|
14
21
|
*/
|
|
15
22
|
async function renderPage({ url }) {
|
|
16
23
|
const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
|
|
17
24
|
const angularServerApp = getOrCreateAngularServerApp();
|
|
18
|
-
const response = await angularServerApp.renderStatic(new URL(url,
|
|
25
|
+
const response = await angularServerApp.renderStatic(new URL(url, serverURL), AbortSignal.timeout(30_000));
|
|
19
26
|
return response ? response.text() : null;
|
|
20
27
|
}
|
|
21
|
-
function initialize() {
|
|
22
|
-
(
|
|
28
|
+
async function initialize() {
|
|
29
|
+
if (outputMode !== undefined && hasSsrEntry) {
|
|
30
|
+
serverURL = await (0, launch_server_1.launchServer)();
|
|
31
|
+
}
|
|
32
|
+
(0, fetch_patch_1.patchFetchToLoadInMemoryAssets)(serverURL);
|
|
23
33
|
return renderPage;
|
|
24
34
|
}
|
|
25
35
|
exports.default = initialize();
|