@angular/build 19.0.0-next.9 → 19.0.0-rc.0

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 (77) hide show
  1. package/package.json +21 -19
  2. package/src/builders/application/build-action.js +9 -9
  3. package/src/builders/application/chunk-optimizer.js +1 -4
  4. package/src/builders/application/execute-build.js +34 -6
  5. package/src/builders/application/execute-post-bundle.js +9 -1
  6. package/src/builders/application/index.d.ts +0 -16
  7. package/src/builders/application/index.js +15 -10
  8. package/src/builders/application/options.d.ts +4 -1
  9. package/src/builders/application/options.js +16 -10
  10. package/src/builders/application/results.d.ts +5 -3
  11. package/src/builders/application/schema.d.ts +86 -0
  12. package/src/builders/application/schema.js +19 -1
  13. package/src/builders/application/schema.json +73 -4
  14. package/src/builders/application/setup-bundling.d.ts +3 -1
  15. package/src/builders/application/setup-bundling.js +33 -6
  16. package/src/builders/dev-server/vite-server.d.ts +2 -2
  17. package/src/builders/dev-server/vite-server.js +39 -10
  18. package/src/index.d.ts +1 -0
  19. package/src/tools/angular/angular-host.d.ts +1 -1
  20. package/src/tools/angular/angular-host.js +1 -4
  21. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  22. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  23. package/src/tools/angular/compilation/aot-compilation.js +39 -0
  24. package/src/tools/angular/compilation/parallel-compilation.js +2 -2
  25. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  26. package/src/tools/angular/compilation/parallel-worker.js +5 -2
  27. package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -4
  28. package/src/tools/esbuild/angular/compiler-plugin.js +53 -33
  29. package/src/tools/esbuild/angular/component-stylesheets.d.ts +17 -18
  30. package/src/tools/esbuild/angular/component-stylesheets.js +63 -38
  31. package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
  32. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +11 -3
  33. package/src/tools/esbuild/application-code-bundle.d.ts +5 -4
  34. package/src/tools/esbuild/application-code-bundle.js +66 -41
  35. package/src/tools/esbuild/bundler-context.d.ts +2 -1
  36. package/src/tools/esbuild/bundler-context.js +10 -12
  37. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  38. package/src/tools/esbuild/bundler-execution-result.js +5 -1
  39. package/src/tools/esbuild/commonjs-checker.js +2 -2
  40. package/src/tools/esbuild/compiler-plugin-options.d.ts +1 -4
  41. package/src/tools/esbuild/compiler-plugin-options.js +14 -37
  42. package/src/tools/esbuild/global-scripts.js +1 -1
  43. package/src/tools/esbuild/global-styles.js +4 -1
  44. package/src/tools/esbuild/index-html-generator.js +8 -0
  45. package/src/tools/esbuild/javascript-transformer.js +2 -0
  46. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
  47. package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
  48. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  49. package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
  50. package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
  51. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
  52. package/src/tools/esbuild/utils.js +13 -31
  53. package/src/tools/sass/worker.js +19 -0
  54. package/src/tools/vite/middlewares/assets-middleware.d.ts +1 -1
  55. package/src/tools/vite/middlewares/assets-middleware.js +19 -3
  56. package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
  57. package/src/tools/vite/middlewares/component-middleware.js +33 -0
  58. package/src/tools/vite/middlewares/index.d.ts +1 -0
  59. package/src/tools/vite/middlewares/index.js +3 -1
  60. package/src/tools/vite/middlewares/ssr-middleware.js +6 -6
  61. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +2 -1
  62. package/src/tools/vite/plugins/setup-middlewares-plugin.js +2 -1
  63. package/src/tools/vite/utils.d.ts +1 -0
  64. package/src/typings.d.ts +1 -1
  65. package/src/utils/environment-options.js +1 -1
  66. package/src/utils/index-file/auto-csp.d.ts +23 -0
  67. package/src/utils/index-file/auto-csp.js +283 -0
  68. package/src/utils/index-file/html-rewriting-stream.d.ts +5 -1
  69. package/src/utils/index-file/index-html-generator.d.ts +4 -0
  70. package/src/utils/index-file/index-html-generator.js +11 -0
  71. package/src/utils/index-file/inline-critical-css.js +17 -18
  72. package/src/utils/normalize-cache.js +1 -1
  73. package/src/utils/server-rendering/launch-server.js +5 -5
  74. package/src/utils/server-rendering/load-esm-from-memory.d.ts +1 -1
  75. package/src/utils/server-rendering/manifest.d.ts +1 -1
  76. package/src/utils/server-rendering/prerender.js +5 -3
  77. package/src/utils/supported-browsers.js +1 -0
@@ -8,6 +8,8 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.setupBundlerContexts = setupBundlerContexts;
11
+ exports.createComponentStyleBundler = createComponentStyleBundler;
12
+ const component_stylesheets_1 = require("../../tools/esbuild/angular/component-stylesheets");
11
13
  const application_code_bundle_1 = require("../../tools/esbuild/application-code-bundle");
12
14
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
13
15
  const global_scripts_1 = require("../../tools/esbuild/global-scripts");
@@ -21,14 +23,13 @@ const utils_1 = require("../../tools/esbuild/utils");
21
23
  * @param codeBundleCache An instance of the TypeScript source file cache.
22
24
  * @returns An array of BundlerContext objects.
23
25
  */
24
- function setupBundlerContexts(options, browsers, codeBundleCache) {
26
+ function setupBundlerContexts(options, target, codeBundleCache, stylesheetBundler) {
25
27
  const { outputMode, serverEntryPoint, appShellOptions, prerenderOptions, ssrOptions, workspaceRoot, watch = false, } = options;
26
- const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
27
28
  const bundlerContexts = [];
28
29
  // Browser application code
29
- bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createBrowserCodeBundleOptions)(options, target, codeBundleCache)));
30
+ bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createBrowserCodeBundleOptions)(options, target, codeBundleCache, stylesheetBundler)));
30
31
  // Browser polyfills code
31
- const browserPolyfillBundleOptions = (0, application_code_bundle_1.createBrowserPolyfillBundleOptions)(options, target, codeBundleCache);
32
+ const browserPolyfillBundleOptions = (0, application_code_bundle_1.createBrowserPolyfillBundleOptions)(options, target, codeBundleCache, stylesheetBundler);
32
33
  if (browserPolyfillBundleOptions) {
33
34
  bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, browserPolyfillBundleOptions));
34
35
  }
@@ -53,10 +54,10 @@ function setupBundlerContexts(options, browsers, codeBundleCache) {
53
54
  // Skip server build when none of the features are enabled.
54
55
  if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) {
55
56
  const nodeTargets = [...target, ...(0, utils_1.getSupportedNodeTargets)()];
56
- bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createServerMainCodeBundleOptions)(options, nodeTargets, codeBundleCache)));
57
+ bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createServerMainCodeBundleOptions)(options, nodeTargets, codeBundleCache, stylesheetBundler)));
57
58
  if (outputMode && ssrOptions?.entry) {
58
59
  // New behavior introduced: 'server.ts' is now bundled separately from 'main.server.ts'.
59
- bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createSsrEntryCodeBundleOptions)(options, nodeTargets, codeBundleCache)));
60
+ bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createSsrEntryCodeBundleOptions)(options, nodeTargets, codeBundleCache, stylesheetBundler)));
60
61
  }
61
62
  // Server polyfills code
62
63
  const serverPolyfillBundleOptions = (0, application_code_bundle_1.createServerPolyfillBundleOptions)(options, nodeTargets, codeBundleCache);
@@ -66,3 +67,29 @@ function setupBundlerContexts(options, browsers, codeBundleCache) {
66
67
  }
67
68
  return bundlerContexts;
68
69
  }
70
+ function createComponentStyleBundler(options, target) {
71
+ const { workspaceRoot, optimizationOptions, sourcemapOptions, outputNames, externalDependencies, preserveSymlinks, stylePreprocessorOptions, inlineStyleLanguage, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, } = options;
72
+ const incremental = !!options.watch;
73
+ return new component_stylesheets_1.ComponentStylesheetBundler({
74
+ workspaceRoot,
75
+ inlineFonts: !!optimizationOptions.fonts.inline,
76
+ optimization: !!optimizationOptions.styles.minify,
77
+ sourcemap:
78
+ // Hidden component stylesheet sourcemaps are inaccessible which is effectively
79
+ // the same as being disabled. Disabling has the advantage of avoiding the overhead
80
+ // of sourcemap processing.
81
+ sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
82
+ outputNames,
83
+ includePaths: stylePreprocessorOptions?.includePaths,
84
+ // string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ sass: stylePreprocessorOptions?.sass,
87
+ externalDependencies,
88
+ target,
89
+ preserveSymlinks,
90
+ tailwindConfiguration,
91
+ postcssConfiguration,
92
+ cacheOptions,
93
+ publicPath,
94
+ }, inlineStyleLanguage, incremental);
95
+ }
@@ -16,7 +16,7 @@ import type { DevServerBuilderOutput } from './output';
16
16
  interface OutputFileRecord {
17
17
  contents: Uint8Array;
18
18
  size: number;
19
- hash?: string;
19
+ hash: string;
20
20
  updated: boolean;
21
21
  servable: boolean;
22
22
  type: BuildOutputFileType;
@@ -32,6 +32,6 @@ export declare function serveWithVite(serverOptions: NormalizedDevServerOptions,
32
32
  middleware?: Connect.NextHandleFunction[];
33
33
  buildPlugins?: Plugin[];
34
34
  }): AsyncIterableIterator<DevServerBuilderOutput>;
35
- export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: DevServerExternalResultMetadata, ssrMode: ServerSsrMode, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, usedComponentStyles: Map<string, string[]>, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>, thirdPartySourcemaps?: boolean): Promise<InlineConfig>;
35
+ export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: DevServerExternalResultMetadata, ssrMode: ServerSsrMode, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, usedComponentStyles: Map<string, Set<string>>, templateUpdates: Map<string, string>, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>, thirdPartySourcemaps?: boolean): Promise<InlineConfig>;
36
36
  type EsbuildLoaderOption = Exclude<DepOptimizationConfig['esbuildOptions'], undefined>['loader'];
37
37
  export {};
@@ -65,10 +65,10 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
65
65
  }
66
66
  // TODO: Adjust architect to not force a JsonObject derived return type
67
67
  const browserOptions = (await context.validateOptions(rawBrowserOptions, builderName));
68
- if (browserOptions.prerender) {
68
+ if (browserOptions.prerender || (browserOptions.outputMode && browserOptions.server)) {
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
- browserOptions.prerender = false;
71
+ browserOptions.prerender = undefined;
72
72
  browserOptions.ssr ||= true;
73
73
  }
74
74
  // Set all packages as external to support Vite's prebundle caching
@@ -92,8 +92,13 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
92
92
  // https://nodejs.org/api/process.html#processsetsourcemapsenabledval
93
93
  process.setSourceMapsEnabled(true);
94
94
  }
95
- // TODO: Enable by default once full support across CLI and FW is integrated
96
- browserOptions.externalRuntimeStyles = environment_options_1.useComponentStyleHmr;
95
+ // Enable to support component style hot reloading (`NG_HMR_CSTYLES=0` can be used to disable)
96
+ browserOptions.externalRuntimeStyles = !!serverOptions.liveReload && environment_options_1.useComponentStyleHmr;
97
+ if (browserOptions.externalRuntimeStyles) {
98
+ // Preload the @angular/compiler package to avoid first stylesheet request delays.
99
+ // Once @angular/build is native ESM, this should be re-evaluated.
100
+ void (0, load_esm_1.loadEsmModule)('@angular/compiler');
101
+ }
97
102
  // Setup the prebundling transformer that will be shared across Vite prebundling requests
98
103
  const prebundleTransformer = new internal_1.JavaScriptTransformer(
99
104
  // Always enable JIT linking to support applications built with and without AOT.
@@ -116,6 +121,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
116
121
  explicitServer: [],
117
122
  };
118
123
  const usedComponentStyles = new Map();
124
+ const templateUpdates = new Map();
119
125
  // Add cleanup logic via a builder teardown.
120
126
  let deferred;
121
127
  context.addTeardown(async () => {
@@ -157,6 +163,8 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
157
163
  assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
158
164
  }
159
165
  }
166
+ // Clear stale template updates on a code rebuilds
167
+ templateUpdates.clear();
160
168
  // Analyze result files for changes
161
169
  analyzeResultFiles(normalizePath, htmlIndexPath, result.files, generatedFiles);
162
170
  break;
@@ -166,8 +174,18 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
166
174
  break;
167
175
  case results_1.ResultKind.ComponentUpdate:
168
176
  (0, node_assert_1.default)(serverOptions.hmr, 'Component updates are only supported with HMR enabled.');
169
- // TODO: Implement support -- application builder currently does not use
170
- break;
177
+ (0, node_assert_1.default)(server, 'Builder must provide an initial full build before component update results.');
178
+ for (const componentUpdate of result.updates) {
179
+ if (componentUpdate.type === 'template') {
180
+ templateUpdates.set(componentUpdate.id, componentUpdate.content);
181
+ server.ws.send('angular:component-update', {
182
+ id: componentUpdate.id,
183
+ timestamp: Date.now(),
184
+ });
185
+ }
186
+ }
187
+ context.logger.info('Component update sent to client(s).');
188
+ continue;
171
189
  default:
172
190
  context.logger.warn(`Unknown result kind [${result.kind}] provided by build.`);
173
191
  continue;
@@ -249,11 +267,17 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
249
267
  browserOptions.ssr.entry) {
250
268
  ssrMode = plugins_1.ServerSsrMode.ExternalSsrMiddleware;
251
269
  }
252
- else if (browserOptions.server) {
270
+ else if (browserOptions.ssr) {
253
271
  ssrMode = plugins_1.ServerSsrMode.InternalSsrMiddleware;
254
272
  }
273
+ if (browserOptions.progress !== false && ssrMode !== plugins_1.ServerSsrMode.NoSsr) {
274
+ // This is a workaround for https://github.com/angular/angular-cli/issues/28336, which is caused by the interaction between `zone.js` and `listr2`.
275
+ process.once('SIGINT', () => {
276
+ process.kill(process.pid);
277
+ });
278
+ }
255
279
  // Setup server and start listening
256
- const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), usedComponentStyles, browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
280
+ const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), usedComponentStyles, templateUpdates, browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
257
281
  server = await createServer(serverConfiguration);
258
282
  await server.listen();
259
283
  const urls = server.resolvedUrls;
@@ -322,7 +346,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
322
346
  // are not typically reused across components.
323
347
  const componentIds = usedComponentStyles.get(filePath);
324
348
  if (componentIds) {
325
- return componentIds.map((id) => ({
349
+ return Array.from(componentIds).map((id) => ({
326
350
  type: 'css-update',
327
351
  timestamp,
328
352
  path: `${filePath}?ngcomp` + (id ? `=${id}` : ''),
@@ -373,6 +397,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
373
397
  contents: file.contents,
374
398
  servable,
375
399
  size: file.contents.byteLength,
400
+ hash: file.hash,
376
401
  type: file.type,
377
402
  updated: false,
378
403
  });
@@ -403,7 +428,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
403
428
  }
404
429
  }
405
430
  }
406
- async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, usedComponentStyles, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
431
+ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, usedComponentStyles, templateUpdates, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
407
432
  const proxy = await (0, utils_1.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
408
433
  // dynamically import Vite for ESM compatibility
409
434
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
@@ -496,6 +521,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
496
521
  indexHtmlTransformer,
497
522
  extensionMiddleware,
498
523
  usedComponentStyles,
524
+ templateUpdates,
499
525
  ssrMode,
500
526
  }),
501
527
  (0, plugins_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
@@ -568,6 +594,9 @@ function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless
568
594
  supported: (0, internal_1.getFeatureSupport)(target, zoneless),
569
595
  plugins,
570
596
  loader,
597
+ define: {
598
+ 'ngServerMode': `${ssr}`,
599
+ },
571
600
  resolveExtensions: ['.mjs', '.js', '.cjs'],
572
601
  },
573
602
  };
package/src/index.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  export { buildApplication, type ApplicationBuilderOptions, type ApplicationBuilderOutput, } from './builders/application';
9
+ export type { ApplicationBuilderExtensions } from './builders/application/options';
9
10
  export { type BuildOutputFile, BuildOutputFileType } from './tools/esbuild/bundler-context';
10
11
  export type { BuildOutputAsset } from './tools/esbuild/bundler-execution-result';
11
12
  export { executeDevServerBuilder, type DevServerBuilderOptions, type DevServerBuilderOutput, } from './builders/dev-server';
@@ -14,7 +14,7 @@ export interface AngularHostOptions {
14
14
  sourceFileCache?: Map<string, ts.SourceFile>;
15
15
  modifiedFiles?: Set<string>;
16
16
  externalStylesheets?: Map<string, string>;
17
- transformStylesheet(data: string, containingFile: string, stylesheetFile?: string, order?: number): Promise<string | null>;
17
+ transformStylesheet(data: string, containingFile: string, stylesheetFile?: string, order?: number, className?: string): Promise<string | null>;
18
18
  processWebWorker(workerFile: string, containingFile: string): string;
19
19
  }
20
20
  /**
@@ -113,10 +113,7 @@ function createAngularCompilerHost(typescript, compilerOptions, hostOptions) {
113
113
  if (data.trim().length === 0) {
114
114
  return { content: '' };
115
115
  }
116
- const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined,
117
- // TODO: Remove once available in compiler-cli types
118
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
- context.order);
116
+ const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined, context.order, context.className);
120
117
  return typeof result === 'string' ? { content: result } : null;
121
118
  };
122
119
  host.resourceNameToFileName = function (resourceName, containingFile) {
@@ -31,6 +31,7 @@ export declare abstract class AngularCompilation {
31
31
  compilerOptions: ng.CompilerOptions;
32
32
  referencedFiles: readonly string[];
33
33
  externalStylesheets?: ReadonlyMap<string, string>;
34
+ templateUpdates?: ReadonlyMap<string, string>;
34
35
  }>;
35
36
  abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;
36
37
  protected abstract collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
@@ -16,6 +16,7 @@ export declare class AotCompilation extends AngularCompilation {
16
16
  compilerOptions: ng.CompilerOptions;
17
17
  referencedFiles: readonly string[];
18
18
  externalStylesheets?: ReadonlyMap<string, string>;
19
+ templateUpdates?: ReadonlyMap<string, string>;
19
20
  }>;
20
21
  collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
21
22
  emitAffectedFiles(): Iterable<EmitFileResult>;
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.AotCompilation = void 0;
14
14
  const node_assert_1 = __importDefault(require("node:assert"));
15
+ const node_path_1 = require("node:path");
15
16
  const typescript_1 = __importDefault(require("typescript"));
16
17
  const profiling_1 = require("../../esbuild/profiling");
17
18
  const angular_host_1 = require("../angular-host");
@@ -65,6 +66,33 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
65
66
  }
66
67
  const typeScriptProgram = typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(angularTypeScriptProgram, host, oldProgram, configurationDiagnostics);
67
68
  await (0, profiling_1.profileAsync)('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync());
69
+ let templateUpdates;
70
+ if (compilerOptions['_enableHmr'] &&
71
+ hostOptions.modifiedFiles &&
72
+ hasOnlyTemplates(hostOptions.modifiedFiles)) {
73
+ const componentNodes = [...hostOptions.modifiedFiles].flatMap((file) => [
74
+ ...angularCompiler.getComponentsWithTemplateFile(file),
75
+ ]);
76
+ for (const node of componentNodes) {
77
+ if (!typescript_1.default.isClassDeclaration(node)) {
78
+ continue;
79
+ }
80
+ const componentFilename = node.getSourceFile().fileName;
81
+ let relativePath = (0, node_path_1.relative)(host.getCurrentDirectory(), componentFilename);
82
+ if (relativePath.startsWith('..')) {
83
+ relativePath = componentFilename;
84
+ }
85
+ const updateId = encodeURIComponent(`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`);
86
+ const updateText = angularCompiler.emitHmrUpdateModule(node);
87
+ if (updateText === null) {
88
+ // Build is needed if a template cannot be updated
89
+ templateUpdates = undefined;
90
+ break;
91
+ }
92
+ templateUpdates ??= new Map();
93
+ templateUpdates.set(updateId, updateText);
94
+ }
95
+ }
68
96
  const affectedFiles = (0, profiling_1.profileSync)('NG_FIND_AFFECTED', () => findAffectedFiles(typeScriptProgram, angularCompiler, usingBuildInfo));
69
97
  // Get all files referenced in the TypeScript/Angular program including component resources
70
98
  const referencedFiles = typeScriptProgram
@@ -90,6 +118,7 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
90
118
  compilerOptions,
91
119
  referencedFiles,
92
120
  externalStylesheets: hostOptions.externalStylesheets,
121
+ templateUpdates,
93
122
  };
94
123
  }
95
124
  *collectDiagnostics(modes) {
@@ -267,3 +296,13 @@ function findAffectedFiles(builder, { ignoreForDiagnostics }, includeTTC) {
267
296
  }
268
297
  return affectedFiles;
269
298
  }
299
+ function hasOnlyTemplates(modifiedFiles) {
300
+ for (const file of modifiedFiles) {
301
+ const lowerFile = file.toLowerCase();
302
+ if (lowerFile.endsWith('.html') || lowerFile.endsWith('.svg')) {
303
+ continue;
304
+ }
305
+ return false;
306
+ }
307
+ return true;
308
+ }
@@ -37,9 +37,9 @@ class ParallelCompilation extends angular_compilation_1.AngularCompilation {
37
37
  initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
38
38
  const stylesheetChannel = new node_worker_threads_1.MessageChannel();
39
39
  // The request identifier is required because Angular can issue multiple concurrent requests
40
- stylesheetChannel.port1.on('message', ({ requestId, data, containingFile, stylesheetFile }) => {
40
+ stylesheetChannel.port1.on('message', ({ requestId, data, containingFile, stylesheetFile, order, className }) => {
41
41
  hostOptions
42
- .transformStylesheet(data, containingFile, stylesheetFile)
42
+ .transformStylesheet(data, containingFile, stylesheetFile, order, className)
43
43
  .then((value) => stylesheetChannel.port1.postMessage({ requestId, value }))
44
44
  .catch((error) => stylesheetChannel.port1.postMessage({ requestId, error }));
45
45
  });
@@ -20,6 +20,7 @@ export interface InitRequest {
20
20
  }
21
21
  export declare function initialize(request: InitRequest): Promise<{
22
22
  externalStylesheets: ReadonlyMap<string, string> | undefined;
23
+ templateUpdates: ReadonlyMap<string, string> | undefined;
23
24
  referencedFiles: readonly string[];
24
25
  compilerOptions: {
25
26
  allowJs: boolean | undefined;
@@ -33,11 +33,11 @@ async function initialize(request) {
33
33
  stylesheetRequests.get(requestId)?.[0](value);
34
34
  }
35
35
  });
36
- const { compilerOptions, referencedFiles, externalStylesheets } = await compilation.initialize(request.tsconfig, {
36
+ const { compilerOptions, referencedFiles, externalStylesheets, templateUpdates } = await compilation.initialize(request.tsconfig, {
37
37
  fileReplacements: request.fileReplacements,
38
38
  sourceFileCache,
39
39
  modifiedFiles: sourceFileCache.modifiedFiles,
40
- transformStylesheet(data, containingFile, stylesheetFile) {
40
+ transformStylesheet(data, containingFile, stylesheetFile, order, className) {
41
41
  const requestId = (0, node_crypto_1.randomUUID)();
42
42
  const resultPromise = new Promise((resolve, reject) => stylesheetRequests.set(requestId, [resolve, reject]));
43
43
  request.stylesheetPort.postMessage({
@@ -45,6 +45,8 @@ async function initialize(request) {
45
45
  data,
46
46
  containingFile,
47
47
  stylesheetFile,
48
+ order,
49
+ className,
48
50
  });
49
51
  return resultPromise;
50
52
  },
@@ -70,6 +72,7 @@ async function initialize(request) {
70
72
  });
71
73
  return {
72
74
  externalStylesheets,
75
+ templateUpdates,
73
76
  referencedFiles,
74
77
  // TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently.
75
78
  compilerOptions: {
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { Plugin } from 'esbuild';
9
9
  import { LoadResultCache } from '../load-result-cache';
10
- import { BundleStylesheetOptions } from '../stylesheets/bundle-options';
10
+ import { ComponentStylesheetBundler } from './component-stylesheets';
11
11
  import { SourceFileCache } from './source-file-cache';
12
12
  export interface CompilerPluginOptions {
13
13
  sourcemap: boolean | 'external';
@@ -24,6 +24,4 @@ export interface CompilerPluginOptions {
24
24
  externalRuntimeStyles?: boolean;
25
25
  instrumentForCoverage?: (request: string) => boolean;
26
26
  }
27
- export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, styleOptions: BundleStylesheetOptions & {
28
- inlineStyleLanguage: string;
29
- }): Plugin;
27
+ export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, stylesheetBundler: ComponentStylesheetBundler): Plugin;
@@ -43,11 +43,10 @@ const javascript_transformer_1 = require("../javascript-transformer");
43
43
  const load_result_cache_1 = require("../load-result-cache");
44
44
  const profiling_1 = require("../profiling");
45
45
  const compilation_state_1 = require("./compilation-state");
46
- const component_stylesheets_1 = require("./component-stylesheets");
47
46
  const file_reference_tracker_1 = require("./file-reference-tracker");
48
47
  const jit_plugin_callbacks_1 = require("./jit-plugin-callbacks");
49
48
  // eslint-disable-next-line max-lines-per-function
50
- function createCompilerPlugin(pluginOptions, styleOptions) {
49
+ function createCompilerPlugin(pluginOptions, stylesheetBundler) {
51
50
  return {
52
51
  name: 'angular-compiler',
53
52
  // eslint-disable-next-line max-lines-per-function
@@ -101,8 +100,6 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
101
100
  let shouldTsIgnoreJs = true;
102
101
  // Determines if transpilation should be handle by TypeScript or esbuild
103
102
  let useTypeScriptTranspilation = true;
104
- // Track incremental component stylesheet builds
105
- const stylesheetBundler = new component_stylesheets_1.ComponentStylesheetBundler(styleOptions, pluginOptions.incremental);
106
103
  let sharedTSCompilationState;
107
104
  // To fully invalidate files, track resource referenced files and their referencing source
108
105
  const referencedFileTracker = new file_reference_tracker_1.FileReferenceTracker();
@@ -121,14 +118,17 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
121
118
  // Angular compiler which does not have direct knowledge of transitive resource
122
119
  // dependencies or web worker processing.
123
120
  let modifiedFiles;
124
- let invalidatedStylesheetEntries;
125
121
  if (pluginOptions.sourceFileCache?.modifiedFiles.size &&
126
122
  referencedFileTracker &&
127
123
  !pluginOptions.noopTypeScriptCompilation) {
128
124
  // TODO: Differentiate between changed input files and stale output files
129
125
  modifiedFiles = referencedFileTracker.update(pluginOptions.sourceFileCache.modifiedFiles);
130
126
  pluginOptions.sourceFileCache.invalidate(modifiedFiles);
131
- invalidatedStylesheetEntries = stylesheetBundler.invalidate(modifiedFiles);
127
+ // External runtime styles are invalidated and rebuilt at the beginning of a rebuild to avoid
128
+ // the need to execute the application bundler for component style only changes.
129
+ if (!pluginOptions.externalRuntimeStyles) {
130
+ stylesheetBundler.invalidate(modifiedFiles);
131
+ }
132
132
  }
133
133
  if (!pluginOptions.noopTypeScriptCompilation &&
134
134
  compilation.update &&
@@ -140,7 +140,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
140
140
  fileReplacements: pluginOptions.fileReplacements,
141
141
  modifiedFiles,
142
142
  sourceFileCache: pluginOptions.sourceFileCache,
143
- async transformStylesheet(data, containingFile, stylesheetFile, order) {
143
+ async transformStylesheet(data, containingFile, stylesheetFile, order, className) {
144
144
  let stylesheetResult;
145
145
  // Stylesheet file only exists for external stylesheets
146
146
  if (stylesheetFile) {
@@ -148,8 +148,8 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
148
148
  }
149
149
  else {
150
150
  stylesheetResult = await stylesheetBundler.bundleInline(data, containingFile,
151
- // Inline stylesheets from a template style element are always CSS
152
- containingFile.endsWith('.html') ? 'css' : styleOptions.inlineStyleLanguage,
151
+ // Inline stylesheets from a template style element are always CSS; Otherwise, use default.
152
+ containingFile.endsWith('.html') ? 'css' : undefined,
153
153
  // When external runtime styles are enabled, an identifier for the style that does not change
154
154
  // based on the content is required to avoid emitted JS code changes. Any JS code changes will
155
155
  // invalid the output and force a full page reload for HMR cases. The containing file and order
@@ -158,14 +158,16 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
158
158
  ? (0, node_crypto_1.createHash)('sha-256')
159
159
  .update(containingFile)
160
160
  .update((order ?? 0).toString())
161
+ .update(className ?? '')
161
162
  .digest('hex')
162
163
  : undefined);
163
164
  }
164
- const { contents, outputFiles, metafile, referencedFiles, errors, warnings } = stylesheetResult;
165
- if (errors) {
166
- (result.errors ??= []).push(...errors);
165
+ (result.warnings ??= []).push(...stylesheetResult.warnings);
166
+ if (stylesheetResult.errors) {
167
+ (result.errors ??= []).push(...stylesheetResult.errors);
168
+ return '';
167
169
  }
168
- (result.warnings ??= []).push(...warnings);
170
+ const { contents, outputFiles, metafile, referencedFiles } = stylesheetResult;
169
171
  additionalResults.set(stylesheetFile ?? containingFile, {
170
172
  outputFiles,
171
173
  metafile,
@@ -215,7 +217,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
215
217
  let referencedFiles;
216
218
  let externalStylesheets;
217
219
  try {
218
- const initializationResult = await compilation.initialize(pluginOptions.tsconfig, hostOptions, createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks));
220
+ const initializationResult = await compilation.initialize(pluginOptions.tsconfig, hostOptions, createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks, build.initialOptions.conditions));
219
221
  shouldTsIgnoreJs = !initializationResult.compilerOptions.allowJs;
220
222
  // Isolated modules option ensures safe non-TypeScript transpilation.
221
223
  // Typescript printing support for sourcemaps is not yet integrated.
@@ -250,13 +252,6 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
250
252
  for (const [stylesheetFile, externalId] of externalStylesheets) {
251
253
  await bundleExternalStylesheet(stylesheetBundler, stylesheetFile, externalId, result, additionalResults);
252
254
  }
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
255
  }
261
256
  // Update TypeScript file output cache for all affected files
262
257
  try {
@@ -344,9 +339,22 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
344
339
  // Store as the returned Uint8Array to allow caching the fully transformed code
345
340
  typeScriptFileCache.set(request, contents);
346
341
  }
342
+ let loader;
343
+ if (useTypeScriptTranspilation || isJS) {
344
+ // TypeScript has transpiled to JS or is already JS
345
+ loader = 'js';
346
+ }
347
+ else if (request.at(-1) === 'x') {
348
+ // TSX and TS have different syntax rules. Only set if input is a TSX file.
349
+ loader = 'tsx';
350
+ }
351
+ else {
352
+ // Otherwise, directly bundle TS
353
+ loader = 'ts';
354
+ }
347
355
  return {
348
356
  contents,
349
- loader: useTypeScriptTranspilation || isJS ? 'js' : 'ts',
357
+ loader,
350
358
  };
351
359
  });
352
360
  build.onLoad({ filter: /\.[cm]?js$/ }, (0, load_result_cache_1.createCachedLoad)(pluginOptions.loadResultCache, async (args) => {
@@ -361,7 +369,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
361
369
  }));
362
370
  // Setup bundling of component templates and stylesheets when in JIT mode
363
371
  if (pluginOptions.jit) {
364
- (0, jit_plugin_callbacks_1.setupJitPluginCallbacks)(build, stylesheetBundler, additionalResults, styleOptions.inlineStyleLanguage, pluginOptions.loadResultCache);
372
+ (0, jit_plugin_callbacks_1.setupJitPluginCallbacks)(build, stylesheetBundler, additionalResults, pluginOptions.loadResultCache);
365
373
  }
366
374
  build.onEnd((result) => {
367
375
  // Ensure other compilations are unblocked if the main compilation throws during start
@@ -384,7 +392,6 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
384
392
  });
385
393
  build.onDispose(() => {
386
394
  sharedTSCompilationState?.dispose();
387
- void stylesheetBundler.dispose();
388
395
  void compilation.close?.();
389
396
  void cacheStore?.close();
390
397
  });
@@ -405,17 +412,25 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
405
412
  };
406
413
  }
407
414
  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);
415
+ const styleResult = await stylesheetBundler.bundleFile(stylesheetFile, externalId);
416
+ (result.warnings ??= []).push(...styleResult.warnings);
417
+ if (styleResult.errors) {
418
+ (result.errors ??= []).push(...styleResult.errors);
419
+ }
420
+ else {
421
+ const { outputFiles, metafile } = styleResult;
422
+ // Clear inputs to prevent triggering a rebuild of the application code for component
423
+ // stylesheet file only changes when the dev server enables the internal-only external
424
+ // stylesheet option. This does not affect builds since only the dev server can enable
425
+ // the internal option.
426
+ metafile.inputs = {};
427
+ additionalResults.set(stylesheetFile, {
428
+ outputFiles,
429
+ metafile,
430
+ });
411
431
  }
412
- (result.warnings ??= []).push(...warnings);
413
- additionalResults.set(stylesheetFile, {
414
- outputFiles,
415
- metafile,
416
- });
417
432
  }
418
- function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks) {
433
+ function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks, customConditions) {
419
434
  return (compilerOptions) => {
420
435
  // target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
421
436
  if (compilerOptions.target === undefined || compilerOptions.target < 9 /** ES2022 */) {
@@ -463,6 +478,11 @@ function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserve
463
478
  notes: [{ text: `The 'module' option will be set to 'ES2022' instead.` }],
464
479
  });
465
480
  }
481
+ // Synchronize custom resolve conditions.
482
+ // Set if using the supported bundler resolution mode (bundler is the default in new projects)
483
+ if (compilerOptions.moduleResolution === 100 /* ModuleResolutionKind.Bundler */) {
484
+ compilerOptions.customConditions = customConditions;
485
+ }
466
486
  return {
467
487
  ...compilerOptions,
468
488
  noEmitOnError: false,