@angular/build 19.0.0-next.8 → 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.
Files changed (34) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +6 -5
  3. package/src/builders/application/options.d.ts +7 -0
  4. package/src/builders/application/options.js +2 -1
  5. package/src/builders/dev-server/vite-server.js +7 -6
  6. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  7. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  8. package/src/tools/babel/plugins/types.d.ts +20 -0
  9. package/src/tools/esbuild/angular/compiler-plugin.d.ts +1 -0
  10. package/src/tools/esbuild/angular/compiler-plugin.js +2 -1
  11. package/src/tools/esbuild/compiler-plugin-options.js +2 -1
  12. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  13. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  14. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  15. package/src/tools/esbuild/javascript-transformer.js +5 -3
  16. package/src/tools/vite/middlewares/ssr-middleware.js +9 -8
  17. package/src/utils/index-file/index-html-generator.js +5 -0
  18. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  19. package/src/utils/index-file/ngcm-attribute.js +37 -0
  20. package/src/utils/index-file/valid-self-closing-tags.js +27 -0
  21. package/src/utils/normalize-cache.js +1 -1
  22. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  23. package/src/utils/server-rendering/fetch-patch.js +2 -2
  24. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  25. package/src/utils/server-rendering/launch-server.js +63 -0
  26. package/src/utils/server-rendering/load-esm-from-memory.d.ts +7 -0
  27. package/src/utils/server-rendering/manifest.js +4 -13
  28. package/src/utils/server-rendering/prerender.js +7 -5
  29. package/src/utils/server-rendering/render-worker.d.ts +4 -1
  30. package/src/utils/server-rendering/render-worker.js +13 -3
  31. package/src/utils/server-rendering/routes-extractor-worker.d.ts +5 -4
  32. package/src/utils/server-rendering/routes-extractor-worker.js +14 -4
  33. package/src/utils/server-rendering/utils.d.ts +11 -0
  34. package/src/utils/server-rendering/utils.js +17 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2017 Google, Inc.
3
+ Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  copies of the Software, and to permit persons to whom the Software is
10
10
  furnished to do so, subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/build",
3
- "version": "19.0.0-next.8",
3
+ "version": "19.0.0-next.9",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,7 +23,7 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/architect": "0.1900.0-next.8",
26
+ "@angular-devkit/architect": "0.1900.0-next.9",
27
27
  "@babel/core": "7.25.2",
28
28
  "@babel/helper-annotate-as-pure": "7.24.7",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
@@ -35,6 +35,7 @@
35
35
  "esbuild": "0.24.0",
36
36
  "fast-glob": "3.3.2",
37
37
  "https-proxy-agent": "7.0.5",
38
+ "istanbul-lib-instrument": "6.0.3",
38
39
  "listr2": "8.2.4",
39
40
  "lmdb": "3.1.3",
40
41
  "magic-string": "0.30.11",
@@ -42,8 +43,8 @@
42
43
  "parse5-html-rewriting-stream": "7.0.0",
43
44
  "picomatch": "4.0.2",
44
45
  "piscina": "4.7.0",
45
- "rollup": "4.22.4",
46
- "sass": "1.79.3",
46
+ "rollup": "4.22.5",
47
+ "sass": "1.79.4",
47
48
  "semver": "7.6.3",
48
49
  "vite": "5.4.8",
49
50
  "watchpack": "2.4.2"
@@ -54,7 +55,7 @@
54
55
  "@angular/localize": "^19.0.0-next.0",
55
56
  "@angular/platform-server": "^19.0.0-next.0",
56
57
  "@angular/service-worker": "^19.0.0-next.0",
57
- "@angular/ssr": "^19.0.0-next.8",
58
+ "@angular/ssr": "^19.0.0-next.9",
58
59
  "less": "^4.2.0",
59
60
  "postcss": "^8.4.0",
60
61
  "tailwindcss": "^2.0.0 || ^3.0.0",
@@ -66,6 +66,12 @@ interface InternalOptions {
66
66
  * styles.
67
67
  */
68
68
  externalRuntimeStyles?: boolean;
69
+ /**
70
+ * Enables instrumentation to collect code coverage data for specific files.
71
+ *
72
+ * Used exclusively for tests and shouldn't be used for other kinds of builds.
73
+ */
74
+ instrumentForCoverage?: (filename: string) => boolean;
69
75
  }
70
76
  /** Full set of options for `application` builder. */
71
77
  export type ApplicationBuilderInternalOptions = Omit<ApplicationBuilderOptions & InternalOptions, 'browser'> & {
@@ -174,6 +180,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
174
180
  } | undefined;
175
181
  partialSSRBuild: boolean;
176
182
  externalRuntimeStyles: boolean | undefined;
183
+ instrumentForCoverage: ((filename: string) => boolean) | undefined;
177
184
  }>;
178
185
  export declare function getLocaleBaseHref(baseHref: string | undefined, i18n: NormalizedApplicationBuildOptions['i18nOptions'], locale: string): string | undefined;
179
186
  export {};
@@ -231,7 +231,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
231
231
  }
232
232
  }
233
233
  // Initial options to keep
234
- const { allowedCommonJsDependencies, aot, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, } = options;
234
+ const { allowedCommonJsDependencies, aot, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, } = options;
235
235
  // Return all the normalized options
236
236
  return {
237
237
  advancedOptimizations: !!aot && optimizationOptions.scripts,
@@ -289,6 +289,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
289
289
  define,
290
290
  partialSSRBuild: environment_options_1.usePartialSsrBuild || partialSSRBuild,
291
291
  externalRuntimeStyles,
292
+ instrumentForCoverage,
292
293
  };
293
294
  }
294
295
  async function getTailwindConfig(searchDirectories, workspaceRoot, context) {
@@ -69,6 +69,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
69
69
  // Disable prerendering if enabled and force SSR.
70
70
  // This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested.
71
71
  browserOptions.prerender = false;
72
+ browserOptions.ssr ||= true;
72
73
  }
73
74
  // Set all packages as external to support Vite's prebundle caching
74
75
  browserOptions.externalPackages = serverOptions.prebundle;
@@ -128,7 +129,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
128
129
  case results_1.ResultKind.Failure:
129
130
  if (result.errors.length && server) {
130
131
  hadError = true;
131
- server.hot.send({
132
+ server.ws.send({
132
133
  type: 'error',
133
134
  err: {
134
135
  message: result.errors[0].text,
@@ -175,7 +176,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
175
176
  if (hadError && server) {
176
177
  hadError = false;
177
178
  // Send an empty update to clear the error overlay
178
- server.hot.send({
179
+ server.ws.send({
179
180
  'type': 'update',
180
181
  updates: [],
181
182
  });
@@ -268,7 +269,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
268
269
  key: 'r',
269
270
  description: 'force reload browser',
270
271
  action(server) {
271
- server.hot.send({
272
+ server.ws.send({
272
273
  type: 'full-reload',
273
274
  path: '*',
274
275
  });
@@ -311,7 +312,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
311
312
  if (serverOptions.liveReload || serverOptions.hmr) {
312
313
  if (updatedFiles.every((f) => f.endsWith('.css'))) {
313
314
  const timestamp = Date.now();
314
- server.hot.send({
315
+ server.ws.send({
315
316
  type: 'update',
316
317
  updates: updatedFiles.flatMap((filePath) => {
317
318
  // For component styles, an HMR update must be sent for each one with the corresponding
@@ -342,7 +343,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
342
343
  }
343
344
  // Send reload command to clients
344
345
  if (serverOptions.liveReload) {
345
- server.hot.send({
346
+ server.ws.send({
346
347
  type: 'full-reload',
347
348
  path: '*',
348
349
  });
@@ -408,7 +409,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
408
409
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
409
410
  // Path will not exist on disk and only used to provide separate path for Vite requests
410
411
  const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project));
411
- const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, 'vite');
412
+ const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite');
412
413
  const configuration = {
413
414
  configFile: false,
414
415
  envFile: false,
@@ -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
+ import { PluginObj } from '@babel/core';
9
+ /**
10
+ * A babel plugin factory function for adding istanbul instrumentation.
11
+ *
12
+ * @returns A babel plugin object instance.
13
+ */
14
+ export default function (): PluginObj;
@@ -0,0 +1,44 @@
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 = default_1;
14
+ const core_1 = require("@babel/core");
15
+ const istanbul_lib_instrument_1 = require("istanbul-lib-instrument");
16
+ const node_assert_1 = __importDefault(require("node:assert"));
17
+ /**
18
+ * A babel plugin factory function for adding istanbul instrumentation.
19
+ *
20
+ * @returns A babel plugin object instance.
21
+ */
22
+ function default_1() {
23
+ const visitors = new WeakMap();
24
+ return {
25
+ visitor: {
26
+ Program: {
27
+ enter(path, state) {
28
+ const visitor = (0, istanbul_lib_instrument_1.programVisitor)(core_1.types, state.filename, {
29
+ // Babel returns a Converter object from the `convert-source-map` package
30
+ inputSourceMap: state.file.inputMap?.toObject(),
31
+ });
32
+ visitors.set(path, visitor);
33
+ visitor.enter(path);
34
+ },
35
+ exit(path) {
36
+ const visitor = visitors.get(path);
37
+ (0, node_assert_1.default)(visitor, 'Instrumentation visitor should always be present for program path.');
38
+ visitor.exit(path);
39
+ visitors.delete(path);
40
+ },
41
+ },
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+
9
+ declare module 'istanbul-lib-instrument' {
10
+ export interface Visitor {
11
+ enter(path: import('@babel/core').NodePath<types.Program>): void;
12
+ exit(path: import('@babel/core').NodePath<types.Program>): void;
13
+ }
14
+
15
+ export function programVisitor(
16
+ types: typeof import('@babel/core').types,
17
+ filePath?: string,
18
+ options?: { inputSourceMap?: object | null },
19
+ ): Visitor;
20
+ }
@@ -22,6 +22,7 @@ export interface CompilerPluginOptions {
22
22
  loadResultCache?: LoadResultCache;
23
23
  incremental: boolean;
24
24
  externalRuntimeStyles?: boolean;
25
+ instrumentForCoverage?: (request: string) => boolean;
25
26
  }
26
27
  export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, styleOptions: BundleStylesheetOptions & {
27
28
  inlineStyleLanguage: string;
@@ -339,7 +339,8 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
339
339
  // A string indicates untransformed output from the TS/NG compiler.
340
340
  // This step is unneeded when using esbuild transpilation.
341
341
  const sideEffects = await hasSideEffects(request);
342
- contents = await javascriptTransformer.transformData(request, contents, true /* skipLinker */, sideEffects);
342
+ const instrumentForCoverage = pluginOptions.instrumentForCoverage?.(request);
343
+ contents = await javascriptTransformer.transformData(request, contents, true /* skipLinker */, sideEffects, instrumentForCoverage);
343
344
  // Store as the returned Uint8Array to allow caching the fully transformed code
344
345
  typeScriptFileCache.set(request, contents);
345
346
  }
@@ -9,7 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createCompilerPluginOptions = createCompilerPluginOptions;
11
11
  function createCompilerPluginOptions(options, target, sourceFileCache) {
12
- const { workspaceRoot, optimizationOptions, sourcemapOptions, tsconfig, outputNames, fileReplacements, externalDependencies, preserveSymlinks, stylePreprocessorOptions, advancedOptimizations, inlineStyleLanguage, jit, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, externalRuntimeStyles, } = options;
12
+ const { workspaceRoot, optimizationOptions, sourcemapOptions, tsconfig, outputNames, fileReplacements, externalDependencies, preserveSymlinks, stylePreprocessorOptions, advancedOptimizations, inlineStyleLanguage, jit, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, externalRuntimeStyles, instrumentForCoverage, } = options;
13
13
  return {
14
14
  // JS/TS options
15
15
  pluginOptions: {
@@ -23,6 +23,7 @@ function createCompilerPluginOptions(options, target, sourceFileCache) {
23
23
  loadResultCache: sourceFileCache?.loadResultCache,
24
24
  incremental: !!options.watch,
25
25
  externalRuntimeStyles,
26
+ instrumentForCoverage,
26
27
  },
27
28
  // Component stylesheet options
28
29
  styleOptions: {
@@ -14,6 +14,7 @@ interface JavaScriptTransformRequest {
14
14
  skipLinker?: boolean;
15
15
  sideEffects?: boolean;
16
16
  jit: boolean;
17
+ instrumentForCoverage?: boolean;
17
18
  }
18
19
  export default function transformJavaScript(request: JavaScriptTransformRequest): Promise<unknown>;
19
20
  export {};
@@ -63,8 +63,12 @@ async function transformWithBabel(filename, data, options) {
63
63
  // @ts-expect-error Import attribute syntax plugin does not currently have type definitions
64
64
  const { default: importAttributePlugin } = await Promise.resolve().then(() => __importStar(require('@babel/plugin-syntax-import-attributes')));
65
65
  const plugins = [importAttributePlugin];
66
- // Lazy load the linker plugin only when linking is required
66
+ if (options.instrumentForCoverage) {
67
+ const { default: coveragePlugin } = await Promise.resolve().then(() => __importStar(require('../babel/plugins/add-code-coverage.js')));
68
+ plugins.push(coveragePlugin);
69
+ }
67
70
  if (shouldLink) {
71
+ // Lazy load the linker plugin only when linking is required
68
72
  const linkerPlugin = await createLinkerPlugin(options);
69
73
  plugins.push(linkerPlugin);
70
74
  }
@@ -35,7 +35,7 @@ export declare class JavaScriptTransformer {
35
35
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
36
36
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
37
37
  */
38
- transformFile(filename: string, skipLinker?: boolean, sideEffects?: boolean): Promise<Uint8Array>;
38
+ transformFile(filename: string, skipLinker?: boolean, sideEffects?: boolean, instrumentForCoverage?: boolean): Promise<Uint8Array>;
39
39
  /**
40
40
  * Performs JavaScript transformations on the provided data of a file. The file does not need
41
41
  * to exist on the filesystem.
@@ -45,7 +45,7 @@ export declare class JavaScriptTransformer {
45
45
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
46
46
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
47
47
  */
48
- transformData(filename: string, data: string, skipLinker: boolean, sideEffects?: boolean): Promise<Uint8Array>;
48
+ transformData(filename: string, data: string, skipLinker: boolean, sideEffects?: boolean, instrumentForCoverage?: boolean): Promise<Uint8Array>;
49
49
  /**
50
50
  * Stops all active transformation tasks and shuts down all workers.
51
51
  * @returns A void promise that resolves when closing is complete.
@@ -52,7 +52,7 @@ class JavaScriptTransformer {
52
52
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
53
53
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
54
54
  */
55
- async transformFile(filename, skipLinker, sideEffects) {
55
+ async transformFile(filename, skipLinker, sideEffects, instrumentForCoverage) {
56
56
  const data = await (0, promises_1.readFile)(filename);
57
57
  let result;
58
58
  let cacheKey;
@@ -79,6 +79,7 @@ class JavaScriptTransformer {
79
79
  data,
80
80
  skipLinker,
81
81
  sideEffects,
82
+ instrumentForCoverage,
82
83
  ...this.#commonOptions,
83
84
  }, {
84
85
  // The below is disable as with Yarn PNP this causes build failures with the below message
@@ -106,10 +107,10 @@ class JavaScriptTransformer {
106
107
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
107
108
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
108
109
  */
109
- async transformData(filename, data, skipLinker, sideEffects) {
110
+ async transformData(filename, data, skipLinker, sideEffects, instrumentForCoverage) {
110
111
  // Perform a quick test to determine if the data needs any transformations.
111
112
  // This allows directly returning the data without the worker communication overhead.
112
- if (skipLinker && !this.#commonOptions.advancedOptimizations) {
113
+ if (skipLinker && !this.#commonOptions.advancedOptimizations && !instrumentForCoverage) {
113
114
  const keepSourcemap = this.#commonOptions.sourcemap &&
114
115
  (!!this.#commonOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
115
116
  return Buffer.from(keepSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''), 'utf-8');
@@ -119,6 +120,7 @@ class JavaScriptTransformer {
119
120
  data,
120
121
  skipLinker,
121
122
  sideEffects,
123
+ instrumentForCoverage,
122
124
  ...this.#commonOptions,
123
125
  });
124
126
  }
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createAngularSsrInternalMiddleware = createAngularSsrInternalMiddleware;
11
11
  exports.createAngularSsrExternalMiddleware = createAngularSsrExternalMiddleware;
12
12
  const load_esm_1 = require("../../../utils/load-esm");
13
+ const utils_1 = require("../../../utils/server-rendering/utils");
13
14
  function createAngularSsrInternalMiddleware(server, indexHtmlTransformer) {
14
15
  let cachedAngularServerApp;
15
16
  return function angularSsrMiddleware(req, res, next) {
@@ -17,6 +18,9 @@ function createAngularSsrInternalMiddleware(server, indexHtmlTransformer) {
17
18
  return next();
18
19
  }
19
20
  (async () => {
21
+ // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages,
22
+ // which must be processed by the runtime linker, even if they are not used.
23
+ await (0, load_esm_1.loadEsmModule)('@angular/compiler');
20
24
  const { writeResponseToNodeResponse, createWebRequestFromNodeRequest } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
21
25
  const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
22
26
  const angularServerApp = ɵgetOrCreateAngularServerApp();
@@ -43,11 +47,14 @@ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer)
43
47
  let fallbackWarningShown = false;
44
48
  let cachedAngularAppEngine;
45
49
  let angularSsrInternalMiddleware;
50
+ // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages,
51
+ // which must be processed by the runtime linker, even if they are not used.
52
+ await (0, load_esm_1.loadEsmModule)('@angular/compiler');
46
53
  const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
47
54
  return function angularSsrExternalMiddleware(req, res, next) {
48
55
  (async () => {
49
56
  const { default: handler, AngularAppEngine } = (await server.ssrLoadModule('./server.mjs'));
50
- if (!isSsrNodeRequestHandler(handler) && !isSsrRequestHandler(handler)) {
57
+ if (!(0, utils_1.isSsrNodeRequestHandler)(handler) && !(0, utils_1.isSsrRequestHandler)(handler)) {
51
58
  if (!fallbackWarningShown) {
52
59
  // eslint-disable-next-line no-console
53
60
  console.warn(`The default export in 'server.ts' does not provide a Node.js request handler. ` +
@@ -66,7 +73,7 @@ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer)
66
73
  cachedAngularAppEngine = AngularAppEngine;
67
74
  }
68
75
  // Forward the request to the middleware in server.ts
69
- if (isSsrNodeRequestHandler(handler)) {
76
+ if ((0, utils_1.isSsrNodeRequestHandler)(handler)) {
70
77
  await handler(req, res, next);
71
78
  }
72
79
  else {
@@ -80,9 +87,3 @@ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer)
80
87
  })().catch(next);
81
88
  };
82
89
  }
83
- function isSsrNodeRequestHandler(value) {
84
- return typeof value === 'function' && '__ng_node_request_handler__' in value;
85
- }
86
- function isSsrRequestHandler(value) {
87
- return typeof value === 'function' && '__ng_request_handler__' in value;
88
- }
@@ -14,6 +14,7 @@ const add_event_dispatch_contract_1 = require("./add-event-dispatch-contract");
14
14
  const augment_index_html_1 = require("./augment-index-html");
15
15
  const inline_critical_css_1 = require("./inline-critical-css");
16
16
  const inline_fonts_1 = require("./inline-fonts");
17
+ const ngcm_attribute_1 = require("./ngcm-attribute");
17
18
  const nonce_1 = require("./nonce");
18
19
  class IndexHtmlGenerator {
19
20
  options;
@@ -35,6 +36,7 @@ class IndexHtmlGenerator {
35
36
  this.csrPlugins.push(addNoncePlugin());
36
37
  // SSR plugins
37
38
  if (options.generateDedicatedSSRContent) {
39
+ this.csrPlugins.push(addNgcmAttributePlugin());
38
40
  this.ssrPlugins.push(addEventDispatchContractPlugin(), addNoncePlugin());
39
41
  }
40
42
  }
@@ -134,3 +136,6 @@ function postTransformPlugin({ options }) {
134
136
  function addEventDispatchContractPlugin() {
135
137
  return (html) => (0, add_event_dispatch_contract_1.addEventDispatchContract)(html);
136
138
  }
139
+ function addNgcmAttributePlugin() {
140
+ return (html) => (0, ngcm_attribute_1.addNgcmAttribute)(html);
141
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ /**
9
+ * Transforms the provided HTML by adding the `ngcm` attribute to the `<body>` tag.
10
+ * This is used in the client-side rendered (CSR) version of `index.html` to prevent hydration warnings.
11
+ *
12
+ * @param html The HTML markup to be transformed.
13
+ * @returns A promise that resolves to the transformed HTML string with the necessary modifications.
14
+ */
15
+ export declare function addNgcmAttribute(html: string): Promise<string>;
@@ -0,0 +1,37 @@
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.addNgcmAttribute = addNgcmAttribute;
11
+ const html_rewriting_stream_1 = require("./html-rewriting-stream");
12
+ /**
13
+ * Defines a name of an attribute that is added to the `<body>` tag
14
+ * in the `index.html` file in case a given route was configured
15
+ * with `RenderMode.Client`. 'cm' is an abbreviation for "Client Mode".
16
+ *
17
+ * @see https://github.com/angular/angular/pull/58004
18
+ */
19
+ const CLIENT_RENDER_MODE_FLAG = 'ngcm';
20
+ /**
21
+ * Transforms the provided HTML by adding the `ngcm` attribute to the `<body>` tag.
22
+ * This is used in the client-side rendered (CSR) version of `index.html` to prevent hydration warnings.
23
+ *
24
+ * @param html The HTML markup to be transformed.
25
+ * @returns A promise that resolves to the transformed HTML string with the necessary modifications.
26
+ */
27
+ async function addNgcmAttribute(html) {
28
+ const { rewriter, transformedContent } = await (0, html_rewriting_stream_1.htmlRewritingStream)(html);
29
+ rewriter.on('startTag', (tag) => {
30
+ if (tag.tagName === 'body' &&
31
+ !tag.attrs.some((attr) => attr.name === CLIENT_RENDER_MODE_FLAG)) {
32
+ tag.attrs.push({ name: CLIENT_RENDER_MODE_FLAG, value: '' });
33
+ }
34
+ rewriter.emitStartTag(tag);
35
+ });
36
+ return transformedContent();
37
+ }
@@ -26,8 +26,35 @@ exports.VALID_SELF_CLOSING_TAGS = new Set([
26
26
  'wbr',
27
27
  /** SVG tags */
28
28
  'animate',
29
+ 'animateMotion',
30
+ 'animateTransform',
29
31
  'circle',
30
32
  'ellipse',
33
+ 'feBlend',
34
+ 'feColorMatrix',
35
+ 'feComponentTransfer',
36
+ 'feComposite',
37
+ 'feConvolveMatrix',
38
+ 'feDiffuseLighting',
39
+ 'feDisplacementMap',
40
+ 'feDistantLight',
41
+ 'feDropShadow',
42
+ 'feFlood',
43
+ 'feFuncA',
44
+ 'feFuncB',
45
+ 'feFuncG',
46
+ 'feFuncR',
47
+ 'feGaussianBlur',
48
+ 'feImage',
49
+ 'feMerge',
50
+ 'feMergeNode',
51
+ 'feMorphology',
52
+ 'feOffset',
53
+ 'fePointLight',
54
+ 'feSpecularLighting',
55
+ 'feSpotLight',
56
+ 'feTile',
57
+ 'feTurbulence',
31
58
  'line',
32
59
  'path',
33
60
  'polygon',
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.normalizeCacheOptions = normalizeCacheOptions;
11
11
  const node_path_1 = require("node:path");
12
12
  /** Version placeholder is replaced during the build process with actual package version */
13
- const VERSION = '19.0.0-next.8';
13
+ const VERSION = '19.0.0-next.9';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -5,4 +5,4 @@
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 declare function patchFetchToLoadInMemoryAssets(): void;
8
+ export declare function patchFetchToLoadInMemoryAssets(baseURL: URL): void;
@@ -17,7 +17,7 @@ const node_worker_threads_1 = require("node:worker_threads");
17
17
  */
18
18
  const { assetFiles } = node_worker_threads_1.workerData;
19
19
  const assetsCache = new Map();
20
- function patchFetchToLoadInMemoryAssets() {
20
+ function patchFetchToLoadInMemoryAssets(baseURL) {
21
21
  const originalFetch = globalThis.fetch;
22
22
  const patchedFetch = async (input, init) => {
23
23
  let url;
@@ -35,7 +35,7 @@ function patchFetchToLoadInMemoryAssets() {
35
35
  }
36
36
  const { hostname } = url;
37
37
  const pathname = decodeURIComponent(url.pathname);
38
- if (hostname !== 'local-angular-prerender' || !assetFiles[pathname]) {
38
+ if (hostname !== baseURL.hostname || !assetFiles[pathname]) {
39
39
  // Only handle relative requests or files that are in assets.
40
40
  return originalFetch(input, init);
41
41
  }
@@ -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 {};
@@ -18,18 +18,9 @@ const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
18
18
  * A mapping of unsafe characters to their escaped Unicode equivalents.
19
19
  */
20
20
  const UNSAFE_CHAR_MAP = {
21
- '<': '\\u003C',
22
- '>': '\\u003E',
23
- '/': '\\u002F',
21
+ '`': '\\`',
22
+ '$': '\\$',
24
23
  '\\': '\\\\',
25
- '\b': '\\b',
26
- '\f': '\\f',
27
- '\n': '\\n',
28
- '\r': '\\r',
29
- '\t': '\\t',
30
- '\0': '\\0',
31
- '\u2028': '\\u2028',
32
- '\u2029': '\\u2029',
33
24
  };
34
25
  /**
35
26
  * Escapes unsafe characters in a given string by replacing them with
@@ -39,7 +30,7 @@ const UNSAFE_CHAR_MAP = {
39
30
  * @returns The escaped string where unsafe characters are replaced.
40
31
  */
41
32
  function escapeUnsafeChars(str) {
42
- return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, (c) => UNSAFE_CHAR_MAP[c]);
33
+ return str.replace(/[$`\\]/g, (c) => UNSAFE_CHAR_MAP[c]);
43
34
  }
44
35
  /**
45
36
  * Generates the server manifest for the App Engine environment.
@@ -119,7 +110,7 @@ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles
119
110
  if (file.path === options_1.INDEX_HTML_SERVER ||
120
111
  file.path === options_1.INDEX_HTML_CSR ||
121
112
  (inlineCriticalCss && file.path.endsWith('.css'))) {
122
- serverAssetsContent.push(`['${file.path}', async () => ${escapeUnsafeChars(JSON.stringify(file.text))}]`);
113
+ serverAssetsContent.push(`['${file.path}', async () => \`${escapeUnsafeChars(file.text)}\`]`);
123
114
  }
124
115
  }
125
116
  const manifestContent = `
@@ -92,7 +92,7 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerende
92
92
  };
93
93
  }
94
94
  // Render routes
95
- const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions);
95
+ const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions, outputMode);
96
96
  errors.push(...renderingErrors);
97
97
  return {
98
98
  errors,
@@ -101,7 +101,7 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerende
101
101
  serializableRouteTreeNode,
102
102
  };
103
103
  }
104
- async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions) {
104
+ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions, outputMode) {
105
105
  const output = {};
106
106
  const errors = [];
107
107
  const workerExecArgv = [
@@ -119,6 +119,8 @@ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxTh
119
119
  workspaceRoot,
120
120
  outputFiles: outputFilesForWorker,
121
121
  assetFiles: assetFilesForWorker,
122
+ outputMode,
123
+ hasSsrEntry: !!outputFilesForWorker['/server.mjs'],
122
124
  },
123
125
  execArgv: workerExecArgv,
124
126
  });
@@ -194,13 +196,13 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
194
196
  workspaceRoot,
195
197
  outputFiles: outputFilesForWorker,
196
198
  assetFiles: assetFilesForWorker,
199
+ outputMode,
200
+ hasSsrEntry: !!outputFilesForWorker['/server.mjs'],
197
201
  },
198
202
  execArgv: workerExecArgv,
199
203
  });
200
204
  try {
201
- const { serializedRouteTree, errors } = await renderWorker.run({
202
- outputMode,
203
- });
205
+ const { serializedRouteTree, errors } = await renderWorker.run({});
204
206
  return { errors, serializedRouteTree: [...routes, ...serializedRouteTree] };
205
207
  }
206
208
  catch (err) {
@@ -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, 'http://local-angular-prerender'), AbortSignal.timeout(30_000));
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
- (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
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();
@@ -6,11 +6,12 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import { OutputMode } from '../../builders/application/schema';
9
+ import { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
9
10
  import { RoutersExtractorWorkerResult } from './models';
10
- export interface ExtractRoutesOptions {
11
- outputMode?: OutputMode;
11
+ export interface ExtractRoutesWorkerData extends ESMInMemoryFileLoaderWorkerData {
12
+ outputMode: OutputMode | undefined;
12
13
  }
13
14
  /** Renders an application based on a provided options. */
14
- declare function extractRoutes({ outputMode, }: ExtractRoutesOptions): Promise<RoutersExtractorWorkerResult>;
15
- declare const _default: typeof extractRoutes;
15
+ declare function extractRoutes(): Promise<RoutersExtractorWorkerResult>;
16
+ declare const _default: Promise<typeof extractRoutes>;
16
17
  export default _default;
@@ -7,20 +7,30 @@
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 schema_1 = require("../../builders/application/schema");
11
12
  const fetch_patch_1 = require("./fetch-patch");
13
+ const launch_server_1 = require("./launch-server");
12
14
  const load_esm_from_memory_1 = require("./load-esm-from-memory");
15
+ /**
16
+ * This is passed as workerData when setting up the worker via the `piscina` package.
17
+ */
18
+ const { outputMode, hasSsrEntry } = worker_threads_1.workerData;
19
+ let serverURL = launch_server_1.DEFAULT_URL;
13
20
  /** Renders an application based on a provided options. */
14
- async function extractRoutes({ outputMode, }) {
21
+ async function extractRoutes() {
15
22
  const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
16
- const { routeTree, errors } = await extractRoutesAndCreateRouteTree(new URL('http://local-angular-prerender/'), undefined /** manifest */, true /** invokeGetPrerenderParams */, outputMode === schema_1.OutputMode.Server /** includePrerenderFallbackRoutes */);
23
+ const { routeTree, errors } = await extractRoutesAndCreateRouteTree(serverURL, undefined /** manifest */, true /** invokeGetPrerenderParams */, outputMode === schema_1.OutputMode.Server /** includePrerenderFallbackRoutes */);
17
24
  return {
18
25
  errors,
19
26
  serializedRouteTree: routeTree.toObject(),
20
27
  };
21
28
  }
22
- function initialize() {
23
- (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
29
+ async function initialize() {
30
+ if (outputMode !== undefined && hasSsrEntry) {
31
+ serverURL = await (0, launch_server_1.launchServer)();
32
+ }
33
+ (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)(serverURL);
24
34
  return extractRoutes;
25
35
  }
26
36
  exports.default = initialize();
@@ -0,0 +1,11 @@
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 { createRequestHandler } from '@angular/ssr';
9
+ import type { createNodeRequestHandler } from '@angular/ssr/node';
10
+ export declare function isSsrNodeRequestHandler(value: unknown): value is ReturnType<typeof createNodeRequestHandler>;
11
+ export declare function isSsrRequestHandler(value: unknown): value is ReturnType<typeof createRequestHandler>;
@@ -0,0 +1,17 @@
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.isSsrNodeRequestHandler = isSsrNodeRequestHandler;
11
+ exports.isSsrRequestHandler = isSsrRequestHandler;
12
+ function isSsrNodeRequestHandler(value) {
13
+ return typeof value === 'function' && '__ng_node_request_handler__' in value;
14
+ }
15
+ function isSsrRequestHandler(value) {
16
+ return typeof value === 'function' && '__ng_request_handler__' in value;
17
+ }