@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
@@ -12,12 +12,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.createBrowserCodeBundleOptions = createBrowserCodeBundleOptions;
14
14
  exports.createBrowserPolyfillBundleOptions = createBrowserPolyfillBundleOptions;
15
- exports.createServerCodeBundleOptions = createServerCodeBundleOptions;
16
15
  exports.createServerPolyfillBundleOptions = createServerPolyfillBundleOptions;
16
+ exports.createServerMainCodeBundleOptions = createServerMainCodeBundleOptions;
17
17
  const node_assert_1 = __importDefault(require("node:assert"));
18
18
  const node_crypto_1 = require("node:crypto");
19
19
  const node_path_1 = require("node:path");
20
20
  const environment_options_1 = require("../../utils/environment-options");
21
+ const manifest_1 = require("../../utils/server-rendering/manifest");
21
22
  const compiler_plugin_1 = require("./angular/compiler-plugin");
22
23
  const compiler_plugin_options_1 = require("./compiler-plugin-options");
23
24
  const external_packages_plugin_1 = require("./external-packages-plugin");
@@ -110,29 +111,28 @@ function createBrowserPolyfillBundleOptions(options, target, sourceFileCache) {
110
111
  // cannot be used with fully incremental bundling yet.
111
112
  return hasTypeScriptEntries ? buildOptions : () => buildOptions;
112
113
  }
113
- /**
114
- * Create an esbuild 'build' options object for the server bundle.
115
- * @param options The builder's user-provider normalized options.
116
- * @returns An esbuild BuildOptions object.
117
- */
118
- function createServerCodeBundleOptions(options, target, sourceFileCache) {
119
- const { serverEntryPoint, workspaceRoot, ssrOptions, watch, externalPackages, prerenderOptions, polyfills, } = options;
120
- (0, node_assert_1.default)(serverEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
121
- const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
122
- const mainServerNamespace = 'angular:server-render-utils';
123
- const entryPoints = {
124
- 'render-utils.server': mainServerNamespace,
125
- 'main.server': serverEntryPoint,
126
- };
127
- const ssrEntryPoint = ssrOptions?.entry;
128
- if (ssrEntryPoint) {
129
- entryPoints['server'] = ssrEntryPoint;
114
+ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
115
+ const serverPolyfills = [];
116
+ const polyfillsFromConfig = new Set(options.polyfills);
117
+ if (!(0, utils_1.isZonelessApp)(options.polyfills)) {
118
+ serverPolyfills.push('zone.js/node');
119
+ }
120
+ if (polyfillsFromConfig.has('@angular/localize') ||
121
+ polyfillsFromConfig.has('@angular/localize/init')) {
122
+ serverPolyfills.push('@angular/localize/init');
123
+ }
124
+ serverPolyfills.push('@angular/platform-server/init');
125
+ const namespace = 'angular:polyfills-server';
126
+ const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({
127
+ ...options,
128
+ polyfills: serverPolyfills,
129
+ }, namespace, false, sourceFileCache);
130
+ if (!polyfillBundleOptions) {
131
+ return;
130
132
  }
131
- const zoneless = (0, utils_1.isZonelessApp)(polyfills);
132
133
  const buildOptions = {
133
- ...getEsBuildCommonOptions(options),
134
+ ...polyfillBundleOptions,
134
135
  platform: 'node',
135
- splitting: true,
136
136
  outExtension: { '.js': '.mjs' },
137
137
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
138
138
  // match and the ES5 distribution would be bundled and ends up breaking at
@@ -140,14 +140,45 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
140
140
  // More details: https://github.com/angular/angular-cli/issues/25405.
141
141
  mainFields: ['es2020', 'es2015', 'module', 'main'],
142
142
  entryNames: '[name]',
143
- target,
144
143
  banner: {
145
- js: `import './polyfills.server.mjs';`,
144
+ js: [
145
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
146
+ // See: https://github.com/evanw/esbuild/issues/1921.
147
+ `import { createRequire } from 'node:module';`,
148
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
149
+ ].join('\n'),
150
+ },
151
+ target,
152
+ entryPoints: {
153
+ 'polyfills.server': namespace,
146
154
  },
155
+ };
156
+ return () => buildOptions;
157
+ }
158
+ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
159
+ const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, externalPackages, ssrOptions, polyfills, } = options;
160
+ (0, node_assert_1.default)(mainServerEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
161
+ const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
162
+ const mainServerNamespace = 'angular:main-server';
163
+ const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
164
+ const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
165
+ const zoneless = (0, utils_1.isZonelessApp)(polyfills);
166
+ const entryPoints = {
167
+ 'main.server': mainServerNamespace,
168
+ };
169
+ const ssrEntryPoint = ssrOptions?.entry;
170
+ if (ssrEntryPoint) {
171
+ // Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code.
172
+ // This approach combined server-side logic and rendering into a single bundle.
173
+ entryPoints['server'] = ssrEntryPoint;
174
+ }
175
+ const buildOptions = {
176
+ ...getEsBuildServerCommonOptions(options),
177
+ target,
178
+ inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace],
147
179
  entryPoints,
148
180
  supported: (0, utils_1.getFeatureSupport)(target, zoneless),
149
181
  plugins: [
150
- (0, loader_import_attribute_plugin_1.createLoaderImportAttributePlugin)(),
151
182
  (0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: sourceFileCache?.loadResultCache }),
152
183
  (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
153
184
  (0, compiler_plugin_1.createCompilerPlugin)(
@@ -164,20 +195,49 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
164
195
  else {
165
196
  buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
166
197
  }
198
+ // Mark manifest and polyfills file as external as these are generated by a different bundle step.
199
+ (buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS);
167
200
  buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
201
+ namespace: mainServerInjectPolyfillsNamespace,
202
+ cache: sourceFileCache?.loadResultCache,
203
+ loadContent: () => ({
204
+ contents: `import './polyfills.server.mjs';`,
205
+ loader: 'js',
206
+ resolveDir: workspaceRoot,
207
+ }),
208
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
209
+ namespace: mainServerInjectManifestNamespace,
210
+ cache: sourceFileCache?.loadResultCache,
211
+ loadContent: async () => {
212
+ const contents = [
213
+ // Configure `@angular/ssr` manifest.
214
+ `import manifest from './${manifest_1.SERVER_APP_MANIFEST_FILENAME}';`,
215
+ `import { ɵsetAngularAppManifest } from '@angular/ssr';`,
216
+ `ɵsetAngularAppManifest(manifest);`,
217
+ ];
218
+ return {
219
+ contents: contents.join('\n'),
220
+ loader: 'js',
221
+ resolveDir: workspaceRoot,
222
+ };
223
+ },
224
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
168
225
  namespace: mainServerNamespace,
169
226
  cache: sourceFileCache?.loadResultCache,
170
227
  loadContent: async () => {
228
+ const mainServerEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, mainServerEntryPoint);
171
229
  const contents = [
172
- `export { ɵConsole } from '@angular/core';`,
173
- `export { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';`,
230
+ // Re-export all symbols including default export from 'main.server.ts'
231
+ `export { default } from '${mainServerEntryPointJsImport}';`,
232
+ `export * from '${mainServerEntryPointJsImport}';`,
233
+ // Add @angular/ssr exports
234
+ `export {
235
+ ɵServerRenderContext,
236
+ ɵdestroyAngularServerApp,
237
+ ɵextractRoutesAndCreateRouteTree,
238
+ ɵgetOrCreateAngularServerApp,
239
+ } from '@angular/ssr';`,
174
240
  ];
175
- if (watch) {
176
- contents.push(`export { ɵresetCompiledComponents } from '@angular/core';`);
177
- }
178
- if (prerenderOptions?.discoverRoutes) {
179
- contents.push(`export { ɵgetRoutesFromAngularRouterConfig } from '@angular/ssr';`);
180
- }
181
241
  return {
182
242
  contents: contents.join('\n'),
183
243
  loader: 'js',
@@ -190,27 +250,9 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
190
250
  }
191
251
  return buildOptions;
192
252
  }
193
- function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
194
- const serverPolyfills = [];
195
- const polyfillsFromConfig = new Set(options.polyfills);
196
- if (!(0, utils_1.isZonelessApp)(options.polyfills)) {
197
- serverPolyfills.push('zone.js/node');
198
- }
199
- if (polyfillsFromConfig.has('@angular/localize') ||
200
- polyfillsFromConfig.has('@angular/localize/init')) {
201
- serverPolyfills.push('@angular/localize/init');
202
- }
203
- serverPolyfills.push('@angular/platform-server/init');
204
- const namespace = 'angular:polyfills-server';
205
- const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({
206
- ...options,
207
- polyfills: serverPolyfills,
208
- }, namespace, false, sourceFileCache);
209
- if (!polyfillBundleOptions) {
210
- return;
211
- }
212
- const buildOptions = {
213
- ...polyfillBundleOptions,
253
+ function getEsBuildServerCommonOptions(options) {
254
+ return {
255
+ ...getEsBuildCommonOptions(options),
214
256
  platform: 'node',
215
257
  outExtension: { '.js': '.mjs' },
216
258
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
@@ -219,31 +261,18 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
219
261
  // More details: https://github.com/angular/angular-cli/issues/25405.
220
262
  mainFields: ['es2020', 'es2015', 'module', 'main'],
221
263
  entryNames: '[name]',
222
- banner: {
223
- js: [
224
- // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
225
- // See: https://github.com/evanw/esbuild/issues/1921.
226
- `import { createRequire } from 'node:module';`,
227
- `globalThis['require'] ??= createRequire(import.meta.url);`,
228
- ].join('\n'),
229
- },
230
- target,
231
- entryPoints: {
232
- 'polyfills.server': namespace,
233
- },
234
264
  };
235
- return () => buildOptions;
236
265
  }
237
266
  function getEsBuildCommonOptions(options) {
238
- const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, } = options;
267
+ const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, i18nOptions, } = options;
239
268
  // Ensure unique hashes for i18n translation changes when using post-process inlining.
240
269
  // This hash value is added as a footer to each file and ensures that the output file names (with hashes)
241
270
  // change when translation files have changed. If this is not done the post processed files may have
242
271
  // different content but would retain identical production file names which would lead to browser caching problems.
243
272
  let footer;
244
- if (options.i18nOptions.shouldInline) {
273
+ if (i18nOptions.shouldInline) {
245
274
  // Update file hashes to include translation file content
246
- const i18nHash = Object.values(options.i18nOptions.locales).reduce((data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), '');
275
+ const i18nHash = Object.values(i18nOptions.locales).reduce((data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), '');
247
276
  footer = { js: `/**i18n:${(0, node_crypto_1.createHash)('sha256').update(i18nHash).digest('hex')}*/` };
248
277
  }
249
278
  return {
@@ -267,7 +296,7 @@ function getEsBuildCommonOptions(options) {
267
296
  splitting: true,
268
297
  chunkNames: options.namedChunks ? '[name]-[hash]' : 'chunk-[hash]',
269
298
  tsconfig,
270
- external: externalDependencies,
299
+ external: externalDependencies ? [...externalDependencies] : undefined,
271
300
  write: false,
272
301
  preserveSymlinks,
273
302
  define: {
@@ -360,3 +389,9 @@ function getEsBuildCommonPolyfillsOptions(options, namespace, tryToResolvePolyfi
360
389
  }));
361
390
  return buildOptions;
362
391
  }
392
+ function entryFileToWorkspaceRelative(workspaceRoot, entryFile) {
393
+ return ('./' +
394
+ (0, node_path_1.relative)(workspaceRoot, entryFile)
395
+ .replace(/.[mc]?ts$/, '')
396
+ .replace(/\\/g, '/'));
397
+ }
@@ -138,6 +138,7 @@ class BundlerContext {
138
138
  }
139
139
  return result;
140
140
  }
141
+ // eslint-disable-next-line max-lines-per-function
141
142
  async #performBundle() {
142
143
  // Create esbuild options if not present
143
144
  if (this.#esbuildOptions === undefined) {
@@ -165,12 +166,6 @@ class BundlerContext {
165
166
  // For non-incremental builds, perform a single build
166
167
  result = await (0, esbuild_1.build)(this.#esbuildOptions);
167
168
  }
168
- if (this.#platformIsServer) {
169
- for (const entry of Object.values(result.metafile.outputs)) {
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
- entry['ng-platform-server'] = true;
172
- }
173
- }
174
169
  }
175
170
  catch (failure) {
176
171
  // Build failures will throw an exception which contains errors/warnings
@@ -280,6 +275,7 @@ class BundlerContext {
280
275
  for (const { imports } of Object.values(result.metafile.outputs)) {
281
276
  for (const importData of imports) {
282
277
  if (!importData.external ||
278
+ utils_1.SERVER_GENERATED_EXTERNALS.has(importData.path) ||
283
279
  (importData.kind !== 'import-statement' &&
284
280
  importData.kind !== 'dynamic-import' &&
285
281
  importData.kind !== 'require-call')) {
@@ -295,13 +291,21 @@ class BundlerContext {
295
291
  if (!/\.([cm]?js|css|wasm)(\.map)?$/i.test(file.path)) {
296
292
  fileType = BuildOutputFileType.Media;
297
293
  }
294
+ else if (this.#platformIsServer) {
295
+ fileType = BuildOutputFileType.Server;
296
+ }
298
297
  else {
299
- fileType = this.#platformIsServer
300
- ? BuildOutputFileType.Server
301
- : BuildOutputFileType.Browser;
298
+ fileType = BuildOutputFileType.Browser;
302
299
  }
303
300
  return (0, utils_1.convertOutputFile)(file, fileType);
304
301
  });
302
+ let externalConfiguration = this.#esbuildOptions.external;
303
+ if (this.#platformIsServer && externalConfiguration) {
304
+ externalConfiguration = externalConfiguration.filter((dep) => !utils_1.SERVER_GENERATED_EXTERNALS.has(dep));
305
+ if (!externalConfiguration.length) {
306
+ externalConfiguration = undefined;
307
+ }
308
+ }
305
309
  // Return the successful build results
306
310
  return {
307
311
  ...result,
@@ -310,7 +314,7 @@ class BundlerContext {
310
314
  externalImports: {
311
315
  [this.#platformIsServer ? 'server' : 'browser']: externalImports,
312
316
  },
313
- externalConfiguration: this.#esbuildOptions.external,
317
+ externalConfiguration,
314
318
  errors: undefined,
315
319
  };
316
320
  }
@@ -84,5 +84,5 @@ export declare class MemoryCache<V> extends Cache<V, Map<string, V>> {
84
84
  * Provides all the values currently present in the cache instance.
85
85
  * @returns An iterable of all values in the cache.
86
86
  */
87
- values(): IterableIterator<V>;
87
+ values(): MapIterator<V>;
88
88
  }
@@ -47,3 +47,12 @@ export declare function logMessages(logger: BuilderContext['logger'], executionR
47
47
  */
48
48
  export declare function isZonelessApp(polyfills: string[] | undefined): boolean;
49
49
  export declare function getEntryPointName(entryPoint: string): string;
50
+ /**
51
+ * A set of server-generated dependencies that are treated as external.
52
+ *
53
+ * These dependencies are marked as external because they are produced by a
54
+ * separate bundling process and are not included in the primary bundle. This
55
+ * ensures that these generated files are resolved from an external source rather
56
+ * than being part of the main bundle.
57
+ */
58
+ export declare const SERVER_GENERATED_EXTERNALS: Set<string>;
@@ -7,6 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SERVER_GENERATED_EXTERNALS = void 0;
10
11
  exports.logBuildStats = logBuildStats;
11
12
  exports.getChunkNameFromMetafile = getChunkNameFromMetafile;
12
13
  exports.calculateEstimatedTransferSizes = calculateEstimatedTransferSizes;
@@ -29,6 +30,7 @@ const node_path_1 = require("node:path");
29
30
  const node_url_1 = require("node:url");
30
31
  const node_zlib_1 = require("node:zlib");
31
32
  const semver_1 = require("semver");
33
+ const manifest_1 = require("../../utils/server-rendering/manifest");
32
34
  const stats_table_1 = require("../../utils/stats-table");
33
35
  const bundler_context_1 = require("./bundler-context");
34
36
  function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
@@ -385,3 +387,15 @@ function getEntryPointName(entryPoint) {
385
387
  .replace(/\.[cm]?[jt]s$/, '')
386
388
  .replace(/[\\/.]/g, '-');
387
389
  }
390
+ /**
391
+ * A set of server-generated dependencies that are treated as external.
392
+ *
393
+ * These dependencies are marked as external because they are produced by a
394
+ * separate bundling process and are not included in the primary bundle. This
395
+ * ensures that these generated files are resolved from an external source rather
396
+ * than being part of the main bundle.
397
+ */
398
+ exports.SERVER_GENERATED_EXTERNALS = new Set([
399
+ './polyfills.server.mjs',
400
+ './' + manifest_1.SERVER_APP_MANIFEST_FILENAME,
401
+ ]);
@@ -34,17 +34,22 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
34
34
  env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
35
35
  env.hasError = true;
36
36
  }
37
+ var r, s = 0;
37
38
  function next() {
38
- while (env.stack.length) {
39
- var rec = env.stack.pop();
39
+ while (r = env.stack.pop()) {
40
40
  try {
41
- var result = rec.dispose && rec.dispose.call(rec.value);
42
- if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
41
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
42
+ if (r.dispose) {
43
+ var result = r.dispose.call(r.value);
44
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
45
+ }
46
+ else s |= 1;
43
47
  }
44
48
  catch (e) {
45
49
  fail(e);
46
50
  }
47
51
  }
52
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
48
53
  if (env.hasError) throw env.error;
49
54
  }
50
55
  return next();
@@ -82,10 +82,10 @@ function createAngularMemoryPlugin(options) {
82
82
  // Returning a function, installs middleware after the main transform middleware but
83
83
  // before the built-in HTML middleware
84
84
  return () => {
85
- server.middlewares.use(middlewares_1.angularHtmlFallbackMiddleware);
86
85
  if (ssr) {
87
- server.middlewares.use((0, middlewares_1.createAngularSSRMiddleware)(server, outputFiles, indexHtmlTransformer));
86
+ server.middlewares.use((0, middlewares_1.createAngularSSRMiddleware)(server, indexHtmlTransformer));
88
87
  }
88
+ server.middlewares.use(middlewares_1.angularHtmlFallbackMiddleware);
89
89
  server.middlewares.use((0, middlewares_1.createAngularIndexHtmlMiddleware)(server, outputFiles, indexHtmlTransformer));
90
90
  };
91
91
  },
@@ -6,7 +6,4 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { Connect, ViteDevServer } from 'vite';
9
- export declare function createAngularSSRMiddleware(server: ViteDevServer, outputFiles: Map<string, {
10
- contents: Uint8Array;
11
- servable: boolean;
12
- }>, indexHtmlTransformer?: (content: string) => Promise<string>): Connect.NextHandleFunction;
9
+ export declare function createAngularSSRMiddleware(server: ViteDevServer, indexHtmlTransformer?: (content: string) => Promise<string>): Connect.NextHandleFunction;
@@ -8,49 +8,36 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createAngularSSRMiddleware = createAngularSSRMiddleware;
11
- const render_page_1 = require("../../../utils/server-rendering/render-page");
12
11
  const utils_1 = require("../utils");
13
- function createAngularSSRMiddleware(server, outputFiles, indexHtmlTransformer) {
12
+ function createAngularSSRMiddleware(server, indexHtmlTransformer) {
13
+ let cachedAngularServerApp;
14
14
  return function (req, res, next) {
15
- const url = req.originalUrl;
16
- if (!req.url ||
17
- // Skip if path is not defined.
18
- !url ||
19
- // Skip if path is like a file.
20
- // NOTE: We use a mime type lookup to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
21
- (0, utils_1.lookupMimeTypeFromRequest)(url)) {
22
- next();
23
- return;
15
+ if (req.url === undefined) {
16
+ return next();
24
17
  }
25
- const rawHtml = outputFiles.get('/index.server.html')?.contents;
26
- if (!rawHtml) {
27
- next();
28
- return;
29
- }
30
- server
31
- .transformIndexHtml(req.url, Buffer.from(rawHtml).toString('utf-8'))
32
- .then(async (processedHtml) => {
33
- const resolvedUrls = server.resolvedUrls;
34
- const baseUrl = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
35
- if (indexHtmlTransformer) {
36
- processedHtml = await indexHtmlTransformer(processedHtml);
18
+ const resolvedUrls = server.resolvedUrls;
19
+ const baseUrl = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
20
+ const url = new URL(req.url, baseUrl);
21
+ (async () => {
22
+ const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
23
+ const angularServerApp = ɵgetOrCreateAngularServerApp();
24
+ // Only Add the transform hook only if it's a different instance.
25
+ if (cachedAngularServerApp !== angularServerApp) {
26
+ angularServerApp.hooks.on('html:transform:pre', async ({ html }) => {
27
+ const processedHtml = await server.transformIndexHtml(url.pathname, html);
28
+ return indexHtmlTransformer?.(processedHtml) ?? processedHtml;
29
+ });
30
+ cachedAngularServerApp = angularServerApp;
31
+ }
32
+ const response = await angularServerApp.render(new Request(url, { signal: AbortSignal.timeout(30_000) }), undefined);
33
+ return response?.text();
34
+ })()
35
+ .then((content) => {
36
+ if (typeof content !== 'string') {
37
+ return next();
37
38
  }
38
- const { content: ssrContent } = await (0, render_page_1.renderPage)({
39
- document: processedHtml,
40
- route: new URL(req.originalUrl ?? '/', baseUrl).toString(),
41
- serverContext: 'ssr',
42
- loadBundle: (uri) =>
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- server.ssrLoadModule(uri.slice(1)),
45
- // Files here are only needed for critical CSS inlining.
46
- outputFiles: {},
47
- // TODO: add support for critical css inlining.
48
- inlineCriticalCss: false,
49
- });
50
- res.setHeader('Content-Type', 'text/html');
51
- res.setHeader('Cache-Control', 'no-cache');
52
39
  (0, utils_1.appendServerConfiguredHeaders)(server, res);
53
- res.end(ssrContent);
40
+ res.end(content);
54
41
  })
55
42
  .catch((error) => next(error));
56
43
  };
@@ -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.2';
13
+ const VERSION = '19.0.0-next.3';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -17,7 +17,6 @@ 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
- const RESOLVE_PROTOCOL = 'resolve:';
21
20
  function patchFetchToLoadInMemoryAssets() {
22
21
  const originalFetch = globalThis.fetch;
23
22
  const patchedFetch = async (input, init) => {
@@ -26,17 +25,17 @@ function patchFetchToLoadInMemoryAssets() {
26
25
  url = input;
27
26
  }
28
27
  else if (typeof input === 'string') {
29
- url = new URL(input, RESOLVE_PROTOCOL + '//');
28
+ url = new URL(input);
30
29
  }
31
30
  else if (typeof input === 'object' && 'url' in input) {
32
- url = new URL(input.url, RESOLVE_PROTOCOL + '//');
31
+ url = new URL(input.url);
33
32
  }
34
33
  else {
35
34
  return originalFetch(input, init);
36
35
  }
37
- const { protocol } = url;
36
+ const { hostname } = url;
38
37
  const pathname = decodeURIComponent(url.pathname);
39
- if (protocol !== RESOLVE_PROTOCOL || !assetFiles[pathname]) {
38
+ if (hostname !== 'local-angular-prerender' || !assetFiles[pathname]) {
40
39
  // Only handle relative requests or files that are in assets.
41
40
  return originalFetch(input, init);
42
41
  }
@@ -5,6 +5,16 @@
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 { MainServerBundleExports, RenderUtilsServerBundleExports } from './main-bundle-exports';
8
+ import type { ApplicationRef, Type } from '@angular/core';
9
+ import type { ɵServerRenderContext, ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp } from '@angular/ssr';
10
+ /**
11
+ * Represents the exports available from the main server bundle.
12
+ */
13
+ interface MainServerBundleExports {
14
+ default: (() => Promise<ApplicationRef>) | Type<unknown>;
15
+ ɵServerRenderContext: typeof ɵServerRenderContext;
16
+ ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree;
17
+ ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp;
18
+ }
9
19
  export declare function loadEsmModuleFromMemory(path: './main.server.mjs'): Promise<MainServerBundleExports>;
10
- export declare function loadEsmModuleFromMemory(path: './render-utils.server.mjs'): Promise<RenderUtilsServerBundleExports>;
20
+ export {};
@@ -0,0 +1,44 @@
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 { NormalizedApplicationBuildOptions } from '../../builders/application/options';
9
+ import type { BuildOutputFile } from '../../tools/esbuild/bundler-context';
10
+ export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
11
+ /**
12
+ * Generates the server manifest for the App Engine environment.
13
+ *
14
+ * This manifest is used to configure the server-side rendering (SSR) setup for the
15
+ * Angular application when deployed to Google App Engine. It includes the entry points
16
+ * for different locales and the base HREF for the application.
17
+ *
18
+ * @param i18nOptions - The internationalization options for the application build. This
19
+ * includes settings for inlining locales and determining the output structure.
20
+ * @param baseHref - The base HREF for the application. This is used to set the base URL
21
+ * for all relative URLs in the application.
22
+ * @returns A string representing the content of the SSR server manifest for App Engine.
23
+ */
24
+ export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined): string;
25
+ /**
26
+ * Generates the server manifest for the standard Node.js environment.
27
+ *
28
+ * This manifest is used to configure the server-side rendering (SSR) setup for the
29
+ * Angular application when running in a standard Node.js environment. It includes
30
+ * information about the bootstrap module, whether to inline critical CSS, and any
31
+ * additional HTML and CSS output files.
32
+ *
33
+ * @param additionalHtmlOutputFiles - A map of additional HTML output files generated
34
+ * during the build process, keyed by their file paths.
35
+ * @param outputFiles - An array of all output files from the build process, including
36
+ * JavaScript and CSS files.
37
+ * @param inlineCriticalCss - A boolean indicating whether critical CSS should be inlined
38
+ * in the server-side rendered pages.
39
+ * @param routes - An optional array of route definitions for the application, used for
40
+ * server-side rendering and routing.
41
+ * @returns A string representing the content of the SSR server manifest for the Node.js
42
+ * environment.
43
+ */
44
+ export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined): string;