@angular/build 19.0.0-next.1 → 19.0.0-next.10

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 (115) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +20 -16
  3. package/src/builders/application/build-action.js +9 -9
  4. package/src/builders/application/chunk-optimizer.js +1 -4
  5. package/src/builders/application/execute-build.js +19 -2
  6. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  7. package/src/builders/application/execute-post-bundle.js +58 -20
  8. package/src/builders/application/i18n.d.ts +2 -2
  9. package/src/builders/application/i18n.js +6 -16
  10. package/src/builders/application/index.js +8 -5
  11. package/src/builders/application/options.d.ts +36 -1
  12. package/src/builders/application/options.js +60 -3
  13. package/src/builders/application/schema.d.ts +15 -0
  14. package/src/builders/application/schema.js +11 -1
  15. package/src/builders/application/schema.json +5 -0
  16. package/src/builders/application/setup-bundling.js +12 -9
  17. package/src/builders/dev-server/internal.d.ts +0 -1
  18. package/src/builders/dev-server/internal.js +1 -3
  19. package/src/builders/dev-server/vite-server.d.ts +8 -2
  20. package/src/builders/dev-server/vite-server.js +111 -56
  21. package/src/builders/extract-i18n/application-extraction.js +3 -3
  22. package/src/tools/angular/angular-host.d.ts +2 -1
  23. package/src/tools/angular/angular-host.js +20 -1
  24. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  25. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  26. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  27. package/src/tools/angular/compilation/parallel-compilation.d.ts +2 -1
  28. package/src/tools/angular/compilation/parallel-compilation.js +2 -10
  29. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  30. package/src/tools/angular/compilation/parallel-worker.js +2 -1
  31. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  32. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  33. package/src/tools/babel/plugins/types.d.ts +20 -0
  34. package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -0
  35. package/src/tools/esbuild/angular/compiler-plugin.js +44 -4
  36. package/src/tools/esbuild/angular/component-stylesheets.d.ts +8 -3
  37. package/src/tools/esbuild/angular/component-stylesheets.js +46 -11
  38. package/src/tools/esbuild/angular/file-reference-tracker.d.ts +1 -1
  39. package/src/tools/esbuild/application-code-bundle.d.ts +2 -6
  40. package/src/tools/esbuild/application-code-bundle.js +208 -67
  41. package/src/tools/esbuild/budget-stats.js +1 -1
  42. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  43. package/src/tools/esbuild/bundler-context.js +21 -13
  44. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  45. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  46. package/src/tools/esbuild/cache.d.ts +6 -1
  47. package/src/tools/esbuild/cache.js +7 -0
  48. package/src/tools/esbuild/compiler-plugin-options.js +3 -1
  49. package/src/tools/esbuild/i18n-inliner.js +4 -4
  50. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  51. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  52. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  53. package/src/tools/esbuild/javascript-transformer.js +7 -12
  54. package/src/tools/esbuild/utils.d.ts +9 -0
  55. package/src/tools/esbuild/utils.js +21 -3
  56. package/src/tools/sass/sass-service.js +11 -13
  57. package/src/tools/sass/worker.d.ts +13 -32
  58. package/src/tools/sass/worker.js +1 -0
  59. package/src/tools/vite/middlewares/assets-middleware.d.ts +1 -1
  60. package/src/tools/vite/middlewares/assets-middleware.js +43 -4
  61. package/src/tools/vite/middlewares/headers-middleware.d.ts +19 -0
  62. package/src/tools/vite/middlewares/headers-middleware.js +34 -0
  63. package/src/tools/vite/middlewares/html-fallback-middleware.d.ts +1 -1
  64. package/src/tools/vite/middlewares/html-fallback-middleware.js +23 -7
  65. package/src/tools/vite/middlewares/index-html-middleware.js +1 -2
  66. package/src/tools/vite/middlewares/index.d.ts +2 -1
  67. package/src/tools/vite/middlewares/index.js +5 -2
  68. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -4
  69. package/src/tools/vite/middlewares/ssr-middleware.js +75 -43
  70. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  71. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -40
  72. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  73. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  74. package/src/tools/vite/plugins/index.d.ts +12 -0
  75. package/src/tools/vite/plugins/index.js +21 -0
  76. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +41 -0
  77. package/src/tools/vite/plugins/setup-middlewares-plugin.js +62 -0
  78. package/src/{utils/server-rendering/main-bundle-exports.js → tools/vite/plugins/ssr-transform-plugin.d.ts} +2 -2
  79. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  80. package/src/tools/vite/utils.d.ts +0 -3
  81. package/src/tools/vite/utils.js +0 -12
  82. package/src/typings.d.ts +26 -0
  83. package/src/utils/environment-options.d.ts +2 -0
  84. package/src/utils/environment-options.js +5 -1
  85. package/src/utils/index-file/index-html-generator.js +5 -0
  86. package/src/utils/index-file/inline-critical-css.js +43 -33
  87. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  88. package/src/utils/index-file/ngcm-attribute.js +37 -0
  89. package/src/utils/index-file/valid-self-closing-tags.js +28 -0
  90. package/src/utils/normalize-cache.js +1 -1
  91. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  92. package/src/utils/server-rendering/fetch-patch.js +5 -6
  93. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  94. package/src/utils/server-rendering/launch-server.js +63 -0
  95. package/src/utils/server-rendering/load-esm-from-memory.d.ts +18 -2
  96. package/src/utils/server-rendering/manifest.d.ts +50 -0
  97. package/src/utils/server-rendering/manifest.js +126 -0
  98. package/src/utils/server-rendering/models.d.ts +27 -0
  99. package/src/utils/server-rendering/models.js +22 -0
  100. package/src/utils/server-rendering/prerender.d.ts +26 -10
  101. package/src/utils/server-rendering/prerender.js +122 -75
  102. package/src/utils/server-rendering/render-worker.d.ts +9 -8
  103. package/src/utils/server-rendering/render-worker.js +19 -14
  104. package/src/utils/server-rendering/routes-extractor-worker.d.ts +6 -10
  105. package/src/utils/server-rendering/routes-extractor-worker.js +16 -33
  106. package/src/utils/server-rendering/utils.d.ts +11 -0
  107. package/src/utils/server-rendering/utils.js +17 -0
  108. package/src/utils/worker-pool.d.ts +12 -0
  109. package/src/utils/worker-pool.js +43 -0
  110. package/src/tools/vite/angular-memory-plugin.d.ts +0 -21
  111. package/src/utils/server-rendering/main-bundle-exports.d.ts +0 -27
  112. package/src/utils/server-rendering/render-page.d.ts +0 -26
  113. package/src/utils/server-rendering/render-page.js +0 -114
  114. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  115. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -35,6 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.createCompilerPlugin = createCompilerPlugin;
37
37
  const node_assert_1 = __importDefault(require("node:assert"));
38
+ const node_crypto_1 = require("node:crypto");
38
39
  const path = __importStar(require("node:path"));
39
40
  const environment_options_1 = require("../../../utils/environment-options");
40
41
  const compilation_1 = require("../../angular/compilation");
@@ -120,13 +121,14 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
120
121
  // Angular compiler which does not have direct knowledge of transitive resource
121
122
  // dependencies or web worker processing.
122
123
  let modifiedFiles;
124
+ let invalidatedStylesheetEntries;
123
125
  if (pluginOptions.sourceFileCache?.modifiedFiles.size &&
124
126
  referencedFileTracker &&
125
127
  !pluginOptions.noopTypeScriptCompilation) {
126
128
  // TODO: Differentiate between changed input files and stale output files
127
129
  modifiedFiles = referencedFileTracker.update(pluginOptions.sourceFileCache.modifiedFiles);
128
130
  pluginOptions.sourceFileCache.invalidate(modifiedFiles);
129
- stylesheetBundler.invalidate(modifiedFiles);
131
+ invalidatedStylesheetEntries = stylesheetBundler.invalidate(modifiedFiles);
130
132
  }
131
133
  if (!pluginOptions.noopTypeScriptCompilation &&
132
134
  compilation.update &&
@@ -138,7 +140,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
138
140
  fileReplacements: pluginOptions.fileReplacements,
139
141
  modifiedFiles,
140
142
  sourceFileCache: pluginOptions.sourceFileCache,
141
- async transformStylesheet(data, containingFile, stylesheetFile) {
143
+ async transformStylesheet(data, containingFile, stylesheetFile, order) {
142
144
  let stylesheetResult;
143
145
  // Stylesheet file only exists for external stylesheets
144
146
  if (stylesheetFile) {
@@ -147,7 +149,17 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
147
149
  else {
148
150
  stylesheetResult = await stylesheetBundler.bundleInline(data, containingFile,
149
151
  // Inline stylesheets from a template style element are always CSS
150
- containingFile.endsWith('.html') ? 'css' : styleOptions.inlineStyleLanguage);
152
+ containingFile.endsWith('.html') ? 'css' : styleOptions.inlineStyleLanguage,
153
+ // When external runtime styles are enabled, an identifier for the style that does not change
154
+ // based on the content is required to avoid emitted JS code changes. Any JS code changes will
155
+ // invalid the output and force a full page reload for HMR cases. The containing file and order
156
+ // of the style within the containing file is used.
157
+ pluginOptions.externalRuntimeStyles
158
+ ? (0, node_crypto_1.createHash)('sha-256')
159
+ .update(containingFile)
160
+ .update((order ?? 0).toString())
161
+ .digest('hex')
162
+ : undefined);
151
163
  }
152
164
  const { contents, outputFiles, metafile, referencedFiles, errors, warnings } = stylesheetResult;
153
165
  if (errors) {
@@ -201,6 +213,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
201
213
  // Initialize the Angular compilation for the current build.
202
214
  // In watch mode, previous build state will be reused.
203
215
  let referencedFiles;
216
+ let externalStylesheets;
204
217
  try {
205
218
  const initializationResult = await compilation.initialize(pluginOptions.tsconfig, hostOptions, createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks));
206
219
  shouldTsIgnoreJs = !initializationResult.compilerOptions.allowJs;
@@ -211,6 +224,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
211
224
  !!initializationResult.compilerOptions.sourceMap ||
212
225
  !!initializationResult.compilerOptions.inlineSourceMap;
213
226
  referencedFiles = initializationResult.referencedFiles;
227
+ externalStylesheets = initializationResult.externalStylesheets;
214
228
  }
215
229
  catch (error) {
216
230
  (result.errors ??= []).push({
@@ -231,6 +245,19 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
231
245
  hasCompilationErrors = await sharedTSCompilationState.waitUntilReady;
232
246
  return result;
233
247
  }
248
+ if (externalStylesheets) {
249
+ // Process any new external stylesheets
250
+ for (const [stylesheetFile, externalId] of externalStylesheets) {
251
+ await bundleExternalStylesheet(stylesheetBundler, stylesheetFile, externalId, result, additionalResults);
252
+ }
253
+ // Process any updated stylesheets
254
+ if (invalidatedStylesheetEntries) {
255
+ for (const stylesheetFile of invalidatedStylesheetEntries) {
256
+ // externalId is already linked in the bundler context so only enabling is required here
257
+ await bundleExternalStylesheet(stylesheetBundler, stylesheetFile, true, result, additionalResults);
258
+ }
259
+ }
260
+ }
234
261
  // Update TypeScript file output cache for all affected files
235
262
  try {
236
263
  await (0, profiling_1.profileAsync)('NG_EMIT_TS', async () => {
@@ -312,7 +339,8 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
312
339
  // A string indicates untransformed output from the TS/NG compiler.
313
340
  // This step is unneeded when using esbuild transpilation.
314
341
  const sideEffects = await hasSideEffects(request);
315
- 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);
316
344
  // Store as the returned Uint8Array to allow caching the fully transformed code
317
345
  typeScriptFileCache.set(request, contents);
318
346
  }
@@ -376,6 +404,17 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
376
404
  },
377
405
  };
378
406
  }
407
+ async function bundleExternalStylesheet(stylesheetBundler, stylesheetFile, externalId, result, additionalResults) {
408
+ const { outputFiles, metafile, errors, warnings } = await stylesheetBundler.bundleFile(stylesheetFile, externalId);
409
+ if (errors) {
410
+ (result.errors ??= []).push(...errors);
411
+ }
412
+ (result.warnings ??= []).push(...warnings);
413
+ additionalResults.set(stylesheetFile, {
414
+ outputFiles,
415
+ metafile,
416
+ });
417
+ }
379
418
  function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks) {
380
419
  return (compilerOptions) => {
381
420
  // target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
@@ -433,6 +472,7 @@ function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserve
433
472
  mapRoot: undefined,
434
473
  sourceRoot: undefined,
435
474
  preserveSymlinks,
475
+ externalRuntimeStyles: pluginOptions.externalRuntimeStyles,
436
476
  };
437
477
  };
438
478
  }
@@ -22,7 +22,7 @@ export declare class ComponentStylesheetBundler {
22
22
  * @param cache A load result cache to use when bundling.
23
23
  */
24
24
  constructor(options: BundleStylesheetOptions, incremental: boolean);
25
- bundleFile(entry: string): Promise<{
25
+ bundleFile(entry: string, externalId?: string | boolean): Promise<{
26
26
  errors: import("esbuild").Message[] | undefined;
27
27
  warnings: import("esbuild").Message[];
28
28
  contents: string;
@@ -30,7 +30,7 @@ export declare class ComponentStylesheetBundler {
30
30
  metafile: import("esbuild").Metafile | undefined;
31
31
  referencedFiles: Set<string> | undefined;
32
32
  }>;
33
- bundleInline(data: string, filename: string, language: string): Promise<{
33
+ bundleInline(data: string, filename: string, language: string, externalId?: string): Promise<{
34
34
  errors: import("esbuild").Message[] | undefined;
35
35
  warnings: import("esbuild").Message[];
36
36
  contents: string;
@@ -38,7 +38,12 @@ export declare class ComponentStylesheetBundler {
38
38
  metafile: import("esbuild").Metafile | undefined;
39
39
  referencedFiles: Set<string> | undefined;
40
40
  }>;
41
- invalidate(files: Iterable<string>): void;
41
+ /**
42
+ * Invalidates both file and inline based component style bundling state for a set of modified files.
43
+ * @param files The group of files that have been modified
44
+ * @returns An array of file based stylesheet entries if any were invalidated; otherwise, undefined.
45
+ */
46
+ invalidate(files: Iterable<string>): string[] | undefined;
42
47
  dispose(): Promise<void>;
43
48
  private extractResult;
44
49
  }
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.ComponentStylesheetBundler = void 0;
14
+ const node_assert_1 = __importDefault(require("node:assert"));
14
15
  const node_crypto_1 = require("node:crypto");
15
16
  const node_path_1 = __importDefault(require("node:path"));
16
17
  const bundler_context_1 = require("../bundler-context");
@@ -35,21 +36,31 @@ class ComponentStylesheetBundler {
35
36
  this.options = options;
36
37
  this.incremental = incremental;
37
38
  }
38
- async bundleFile(entry) {
39
+ async bundleFile(entry, externalId) {
39
40
  const bundlerContext = await this.#fileContexts.getOrCreate(entry, () => {
40
41
  return new bundler_context_1.BundlerContext(this.options.workspaceRoot, this.incremental, (loadCache) => {
41
42
  const buildOptions = (0, bundle_options_1.createStylesheetBundleOptions)(this.options, loadCache);
42
- buildOptions.entryPoints = [entry];
43
+ if (externalId) {
44
+ (0, node_assert_1.default)(typeof externalId === 'string', 'Initial external component stylesheets must have a string identifier');
45
+ buildOptions.entryPoints = { [externalId]: entry };
46
+ delete buildOptions.publicPath;
47
+ }
48
+ else {
49
+ buildOptions.entryPoints = [entry];
50
+ }
43
51
  return buildOptions;
44
52
  });
45
53
  });
46
- return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles);
54
+ return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles, !!externalId);
47
55
  }
48
- async bundleInline(data, filename, language) {
56
+ async bundleInline(data, filename, language, externalId) {
49
57
  // Use a hash of the inline stylesheet content to ensure a consistent identifier. External stylesheets will resolve
50
58
  // to the actual stylesheet file path.
51
59
  // TODO: Consider xxhash instead for hashing
52
- const id = (0, node_crypto_1.createHash)('sha256').update(data).digest('hex');
60
+ const id = (0, node_crypto_1.createHash)('sha256')
61
+ .update(data)
62
+ .update(externalId ?? '')
63
+ .digest('hex');
53
64
  const entry = [language, id, filename].join(';');
54
65
  const bundlerContext = await this.#inlineContexts.getOrCreate(entry, () => {
55
66
  const namespace = 'angular:styles/component';
@@ -57,7 +68,13 @@ class ComponentStylesheetBundler {
57
68
  const buildOptions = (0, bundle_options_1.createStylesheetBundleOptions)(this.options, loadCache, {
58
69
  [entry]: data,
59
70
  });
60
- buildOptions.entryPoints = [`${namespace};${entry}`];
71
+ if (externalId) {
72
+ buildOptions.entryPoints = { [externalId]: `${namespace};${entry}` };
73
+ delete buildOptions.publicPath;
74
+ }
75
+ else {
76
+ buildOptions.entryPoints = [`${namespace};${entry}`];
77
+ }
61
78
  buildOptions.plugins.push({
62
79
  name: 'angular-component-styles',
63
80
  setup(build) {
@@ -83,19 +100,29 @@ class ComponentStylesheetBundler {
83
100
  });
84
101
  });
85
102
  // Extract the result of the bundling from the output files
86
- return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles);
103
+ return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles, !!externalId);
87
104
  }
105
+ /**
106
+ * Invalidates both file and inline based component style bundling state for a set of modified files.
107
+ * @param files The group of files that have been modified
108
+ * @returns An array of file based stylesheet entries if any were invalidated; otherwise, undefined.
109
+ */
88
110
  invalidate(files) {
89
111
  if (!this.incremental) {
90
112
  return;
91
113
  }
92
114
  const normalizedFiles = [...files].map(node_path_1.default.normalize);
93
- for (const bundler of this.#fileContexts.values()) {
94
- bundler.invalidate(normalizedFiles);
115
+ let entries;
116
+ for (const [entry, bundler] of this.#fileContexts.entries()) {
117
+ if (bundler.invalidate(normalizedFiles)) {
118
+ entries ??= [];
119
+ entries.push(entry);
120
+ }
95
121
  }
96
122
  for (const bundler of this.#inlineContexts.values()) {
97
123
  bundler.invalidate(normalizedFiles);
98
124
  }
125
+ return entries;
99
126
  }
100
127
  async dispose() {
101
128
  const contexts = [...this.#fileContexts.values(), ...this.#inlineContexts.values()];
@@ -103,7 +130,7 @@ class ComponentStylesheetBundler {
103
130
  this.#inlineContexts.clear();
104
131
  await Promise.allSettled(contexts.map((context) => context.dispose()));
105
132
  }
106
- extractResult(result, referencedFiles) {
133
+ extractResult(result, referencedFiles, external) {
107
134
  let contents = '';
108
135
  let metafile;
109
136
  const outputFiles = [];
@@ -122,7 +149,15 @@ class ComponentStylesheetBundler {
122
149
  outputFiles.push(clonedOutputFile);
123
150
  }
124
151
  else if (filename.endsWith('.css')) {
125
- contents = outputFile.text;
152
+ if (external) {
153
+ const clonedOutputFile = outputFile.clone();
154
+ clonedOutputFile.path = node_path_1.default.join(this.options.workspaceRoot, outputFile.path);
155
+ outputFiles.push(clonedOutputFile);
156
+ contents = node_path_1.default.posix.join(this.options.publicPath ?? '', filename);
157
+ }
158
+ else {
159
+ contents = outputFile.text;
160
+ }
126
161
  }
127
162
  else {
128
163
  throw new Error(`Unexpected non CSS/Media file "${filename}" outputted during component stylesheet processing.`);
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export declare class FileReferenceTracker {
9
9
  #private;
10
- get referencedFiles(): IterableIterator<string>;
10
+ get referencedFiles(): MapIterator<string>;
11
11
  add(containingFile: string, referencedFiles: Iterable<string>): void;
12
12
  /**
13
13
  *
@@ -11,10 +11,6 @@ import { SourceFileCache } from './angular/source-file-cache';
11
11
  import { BundlerOptionsFactory } from './bundler-context';
12
12
  export declare function createBrowserCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache?: SourceFileCache): BuildOptions;
13
13
  export declare function createBrowserPolyfillBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache?: SourceFileCache): BuildOptions | BundlerOptionsFactory | undefined;
14
- /**
15
- * Create an esbuild 'build' options object for the server bundle.
16
- * @param options The builder's user-provider normalized options.
17
- * @returns An esbuild BuildOptions object.
18
- */
19
- export declare function createServerCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache): BuildOptions;
20
14
  export declare function createServerPolyfillBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache?: SourceFileCache): BundlerOptionsFactory | undefined;
15
+ export declare function createServerMainCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache): BuildOptions;
16
+ export declare function createSsrEntryCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache): BuildOptions;
@@ -12,12 +12,14 @@ 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
+ exports.createSsrEntryCodeBundleOptions = createSsrEntryCodeBundleOptions;
17
18
  const node_assert_1 = __importDefault(require("node:assert"));
18
19
  const node_crypto_1 = require("node:crypto");
19
20
  const node_path_1 = require("node:path");
20
21
  const environment_options_1 = require("../../utils/environment-options");
22
+ const manifest_1 = require("../../utils/server-rendering/manifest");
21
23
  const compiler_plugin_1 = require("./angular/compiler-plugin");
22
24
  const compiler_plugin_options_1 = require("./compiler-plugin-options");
23
25
  const external_packages_plugin_1 = require("./external-packages-plugin");
@@ -110,29 +112,28 @@ function createBrowserPolyfillBundleOptions(options, target, sourceFileCache) {
110
112
  // cannot be used with fully incremental bundling yet.
111
113
  return hasTypeScriptEntries ? buildOptions : () => buildOptions;
112
114
  }
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;
115
+ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
116
+ const serverPolyfills = [];
117
+ const polyfillsFromConfig = new Set(options.polyfills);
118
+ if (!(0, utils_1.isZonelessApp)(options.polyfills)) {
119
+ serverPolyfills.push('zone.js/node');
120
+ }
121
+ if (polyfillsFromConfig.has('@angular/localize') ||
122
+ polyfillsFromConfig.has('@angular/localize/init')) {
123
+ serverPolyfills.push('@angular/localize/init');
124
+ }
125
+ serverPolyfills.push('@angular/platform-server/init');
126
+ const namespace = 'angular:polyfills-server';
127
+ const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({
128
+ ...options,
129
+ polyfills: serverPolyfills,
130
+ }, namespace, false, sourceFileCache);
131
+ if (!polyfillBundleOptions) {
132
+ return;
130
133
  }
131
- const zoneless = (0, utils_1.isZonelessApp)(polyfills);
132
134
  const buildOptions = {
133
- ...getEsBuildCommonOptions(options),
135
+ ...polyfillBundleOptions,
134
136
  platform: 'node',
135
- splitting: true,
136
137
  outExtension: { '.js': '.mjs' },
137
138
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
138
139
  // match and the ES5 distribution would be bundled and ends up breaking at
@@ -140,14 +141,46 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
140
141
  // More details: https://github.com/angular/angular-cli/issues/25405.
141
142
  mainFields: ['es2020', 'es2015', 'module', 'main'],
142
143
  entryNames: '[name]',
143
- target,
144
144
  banner: {
145
- js: `import './polyfills.server.mjs';`,
145
+ js: [
146
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
147
+ // See: https://github.com/evanw/esbuild/issues/1921.
148
+ `import { createRequire } from 'node:module';`,
149
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
150
+ ].join('\n'),
151
+ },
152
+ target,
153
+ entryPoints: {
154
+ 'polyfills.server': namespace,
146
155
  },
156
+ };
157
+ return () => buildOptions;
158
+ }
159
+ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
160
+ const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, outputMode, externalPackages, ssrOptions, polyfills, } = options;
161
+ (0, node_assert_1.default)(mainServerEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
162
+ const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
163
+ const mainServerNamespace = 'angular:main-server';
164
+ const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
165
+ const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
166
+ const zoneless = (0, utils_1.isZonelessApp)(polyfills);
167
+ const entryPoints = {
168
+ 'main.server': mainServerNamespace,
169
+ };
170
+ const ssrEntryPoint = ssrOptions?.entry;
171
+ const isOldBehaviour = !outputMode;
172
+ if (ssrEntryPoint && isOldBehaviour) {
173
+ // Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code.
174
+ // This approach combined server-side logic and rendering into a single bundle.
175
+ entryPoints['server'] = ssrEntryPoint;
176
+ }
177
+ const buildOptions = {
178
+ ...getEsBuildServerCommonOptions(options),
179
+ target,
180
+ inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace],
147
181
  entryPoints,
148
182
  supported: (0, utils_1.getFeatureSupport)(target, zoneless),
149
183
  plugins: [
150
- (0, loader_import_attribute_plugin_1.createLoaderImportAttributePlugin)(),
151
184
  (0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: sourceFileCache?.loadResultCache }),
152
185
  (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
153
186
  (0, compiler_plugin_1.createCompilerPlugin)(
@@ -164,20 +197,48 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
164
197
  else {
165
198
  buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
166
199
  }
200
+ // Mark manifest and polyfills file as external as these are generated by a different bundle step.
201
+ (buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS);
167
202
  buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
203
+ namespace: mainServerInjectPolyfillsNamespace,
204
+ cache: sourceFileCache?.loadResultCache,
205
+ loadContent: () => ({
206
+ contents: `import './polyfills.server.mjs';`,
207
+ loader: 'js',
208
+ resolveDir: workspaceRoot,
209
+ }),
210
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
211
+ namespace: mainServerInjectManifestNamespace,
212
+ cache: sourceFileCache?.loadResultCache,
213
+ loadContent: async () => {
214
+ const contents = [
215
+ // Configure `@angular/ssr` manifest.
216
+ `import manifest from './${manifest_1.SERVER_APP_MANIFEST_FILENAME}';`,
217
+ `import { ɵsetAngularAppManifest } from '@angular/ssr';`,
218
+ `ɵsetAngularAppManifest(manifest);`,
219
+ ];
220
+ return {
221
+ contents: contents.join('\n'),
222
+ loader: 'js',
223
+ resolveDir: workspaceRoot,
224
+ };
225
+ },
226
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
168
227
  namespace: mainServerNamespace,
169
228
  cache: sourceFileCache?.loadResultCache,
170
229
  loadContent: async () => {
230
+ const mainServerEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, mainServerEntryPoint);
171
231
  const contents = [
172
- `export { ɵConsole } from '@angular/core';`,
173
- `export { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';`,
232
+ // Re-export all symbols including default export from 'main.server.ts'
233
+ `export { default } from '${mainServerEntryPointJsImport}';`,
234
+ `export * from '${mainServerEntryPointJsImport}';`,
235
+ // Add @angular/ssr exports
236
+ `export {
237
+ ɵdestroyAngularServerApp,
238
+ ɵextractRoutesAndCreateRouteTree,
239
+ ɵgetOrCreateAngularServerApp,
240
+ } from '@angular/ssr';`,
174
241
  ];
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
242
  return {
182
243
  contents: contents.join('\n'),
183
244
  loader: 'js',
@@ -190,27 +251,114 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
190
251
  }
191
252
  return buildOptions;
192
253
  }
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');
254
+ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
255
+ const { workspaceRoot, ssrOptions, externalPackages } = options;
256
+ const serverEntryPoint = ssrOptions?.entry;
257
+ (0, node_assert_1.default)(serverEntryPoint, 'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.');
258
+ const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
259
+ const ssrEntryNamespace = 'angular:ssr-entry';
260
+ const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
261
+ const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
262
+ const buildOptions = {
263
+ ...getEsBuildServerCommonOptions(options),
264
+ target,
265
+ entryPoints: {
266
+ // TODO: consider renaming to index
267
+ 'server': ssrEntryNamespace,
268
+ },
269
+ supported: (0, utils_1.getFeatureSupport)(target, true),
270
+ plugins: [
271
+ (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
272
+ (0, compiler_plugin_1.createCompilerPlugin)(
273
+ // JS/TS options
274
+ { ...pluginOptions, noopTypeScriptCompilation: true },
275
+ // Component stylesheet options
276
+ styleOptions),
277
+ ],
278
+ inject: [ssrInjectRequireNamespace, ssrInjectManifestNamespace],
279
+ };
280
+ buildOptions.plugins ??= [];
281
+ if (externalPackages) {
282
+ buildOptions.packages = 'external';
198
283
  }
199
- if (polyfillsFromConfig.has('@angular/localize') ||
200
- polyfillsFromConfig.has('@angular/localize/init')) {
201
- serverPolyfills.push('@angular/localize/init');
284
+ else {
285
+ buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
202
286
  }
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;
287
+ // Mark manifest file as external. As this will be generated later on.
288
+ (buildOptions.external ??= []).push('*/main.server.mjs', ...utils_1.SERVER_GENERATED_EXTERNALS);
289
+ buildOptions.plugins.push({
290
+ name: 'angular-ssr-metadata',
291
+ setup(build) {
292
+ build.onEnd((result) => {
293
+ if (result.metafile) {
294
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
295
+ result.metafile['ng-ssr-entry-bundle'] = true;
296
+ }
297
+ });
298
+ },
299
+ }, (0, virtual_module_plugin_1.createVirtualModulePlugin)({
300
+ namespace: ssrInjectRequireNamespace,
301
+ cache: sourceFileCache?.loadResultCache,
302
+ loadContent: () => {
303
+ const contents = [
304
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
305
+ // See: https://github.com/evanw/esbuild/issues/1921.
306
+ `import { createRequire } from 'node:module';`,
307
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
308
+ ];
309
+ return {
310
+ contents: contents.join('\n'),
311
+ loader: 'js',
312
+ resolveDir: workspaceRoot,
313
+ };
314
+ },
315
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
316
+ namespace: ssrInjectManifestNamespace,
317
+ cache: sourceFileCache?.loadResultCache,
318
+ loadContent: () => {
319
+ const contents = [
320
+ // Configure `@angular/ssr` app engine manifest.
321
+ `import manifest from './${manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME}';`,
322
+ `import { ɵsetAngularAppEngineManifest } from '@angular/ssr';`,
323
+ `ɵsetAngularAppEngineManifest(manifest);`,
324
+ ];
325
+ return {
326
+ contents: contents.join('\n'),
327
+ loader: 'js',
328
+ resolveDir: workspaceRoot,
329
+ };
330
+ },
331
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
332
+ namespace: ssrEntryNamespace,
333
+ cache: sourceFileCache?.loadResultCache,
334
+ loadContent: () => {
335
+ const serverEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, serverEntryPoint);
336
+ const contents = [
337
+ // Re-export all symbols including default export
338
+ `import * as server from '${serverEntryPointJsImport}';`,
339
+ `export * from '${serverEntryPointJsImport}';`,
340
+ // The below is needed to avoid
341
+ // `Import "default" will always be undefined because there is no matching export` warning when no default is present.
342
+ `const defaultExportName = 'default';`,
343
+ `export default server[defaultExportName]`,
344
+ // Add @angular/ssr exports
345
+ `export { AngularAppEngine } from '@angular/ssr';`,
346
+ ];
347
+ return {
348
+ contents: contents.join('\n'),
349
+ loader: 'js',
350
+ resolveDir: workspaceRoot,
351
+ };
352
+ },
353
+ }));
354
+ if (options.plugins) {
355
+ buildOptions.plugins.push(...options.plugins);
211
356
  }
212
- const buildOptions = {
213
- ...polyfillBundleOptions,
357
+ return buildOptions;
358
+ }
359
+ function getEsBuildServerCommonOptions(options) {
360
+ return {
361
+ ...getEsBuildCommonOptions(options),
214
362
  platform: 'node',
215
363
  outExtension: { '.js': '.mjs' },
216
364
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
@@ -219,31 +367,18 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
219
367
  // More details: https://github.com/angular/angular-cli/issues/25405.
220
368
  mainFields: ['es2020', 'es2015', 'module', 'main'],
221
369
  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
370
  };
235
- return () => buildOptions;
236
371
  }
237
372
  function getEsBuildCommonOptions(options) {
238
- const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, } = options;
373
+ const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, i18nOptions, } = options;
239
374
  // Ensure unique hashes for i18n translation changes when using post-process inlining.
240
375
  // This hash value is added as a footer to each file and ensures that the output file names (with hashes)
241
376
  // change when translation files have changed. If this is not done the post processed files may have
242
377
  // different content but would retain identical production file names which would lead to browser caching problems.
243
378
  let footer;
244
- if (options.i18nOptions.shouldInline) {
379
+ if (i18nOptions.shouldInline) {
245
380
  // 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('|'), '');
381
+ const i18nHash = Object.values(i18nOptions.locales).reduce((data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), '');
247
382
  footer = { js: `/**i18n:${(0, node_crypto_1.createHash)('sha256').update(i18nHash).digest('hex')}*/` };
248
383
  }
249
384
  return {
@@ -267,7 +402,7 @@ function getEsBuildCommonOptions(options) {
267
402
  splitting: true,
268
403
  chunkNames: options.namedChunks ? '[name]-[hash]' : 'chunk-[hash]',
269
404
  tsconfig,
270
- external: externalDependencies,
405
+ external: externalDependencies ? [...externalDependencies] : undefined,
271
406
  write: false,
272
407
  preserveSymlinks,
273
408
  define: {
@@ -360,3 +495,9 @@ function getEsBuildCommonPolyfillsOptions(options, namespace, tryToResolvePolyfi
360
495
  }));
361
496
  return buildOptions;
362
497
  }
498
+ function entryFileToWorkspaceRelative(workspaceRoot, entryFile) {
499
+ return ('./' +
500
+ (0, node_path_1.relative)(workspaceRoot, entryFile)
501
+ .replace(/.[mc]?ts$/, '')
502
+ .replace(/\\/g, '/'));
503
+ }