@angular/build 19.0.0-next.8 → 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 (97) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +22 -19
  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 +34 -6
  6. package/src/builders/application/execute-post-bundle.js +9 -1
  7. package/src/builders/application/index.d.ts +0 -16
  8. package/src/builders/application/index.js +15 -10
  9. package/src/builders/application/options.d.ts +11 -1
  10. package/src/builders/application/options.js +17 -10
  11. package/src/builders/application/results.d.ts +5 -3
  12. package/src/builders/application/schema.d.ts +86 -0
  13. package/src/builders/application/schema.js +19 -1
  14. package/src/builders/application/schema.json +73 -4
  15. package/src/builders/application/setup-bundling.d.ts +3 -1
  16. package/src/builders/application/setup-bundling.js +33 -6
  17. package/src/builders/dev-server/vite-server.d.ts +2 -2
  18. package/src/builders/dev-server/vite-server.js +46 -16
  19. package/src/index.d.ts +1 -0
  20. package/src/tools/angular/angular-host.d.ts +1 -1
  21. package/src/tools/angular/angular-host.js +1 -4
  22. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  23. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  24. package/src/tools/angular/compilation/aot-compilation.js +39 -0
  25. package/src/tools/angular/compilation/parallel-compilation.js +2 -2
  26. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  27. package/src/tools/angular/compilation/parallel-worker.js +5 -2
  28. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  29. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  30. package/src/tools/babel/plugins/types.d.ts +20 -0
  31. package/src/tools/esbuild/angular/compiler-plugin.d.ts +3 -4
  32. package/src/tools/esbuild/angular/compiler-plugin.js +55 -34
  33. package/src/tools/esbuild/angular/component-stylesheets.d.ts +17 -18
  34. package/src/tools/esbuild/angular/component-stylesheets.js +63 -38
  35. package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
  36. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +11 -3
  37. package/src/tools/esbuild/application-code-bundle.d.ts +5 -4
  38. package/src/tools/esbuild/application-code-bundle.js +66 -41
  39. package/src/tools/esbuild/bundler-context.d.ts +2 -1
  40. package/src/tools/esbuild/bundler-context.js +10 -12
  41. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  42. package/src/tools/esbuild/bundler-execution-result.js +5 -1
  43. package/src/tools/esbuild/commonjs-checker.js +2 -2
  44. package/src/tools/esbuild/compiler-plugin-options.d.ts +1 -4
  45. package/src/tools/esbuild/compiler-plugin-options.js +14 -36
  46. package/src/tools/esbuild/global-scripts.js +1 -1
  47. package/src/tools/esbuild/global-styles.js +4 -1
  48. package/src/tools/esbuild/index-html-generator.js +8 -0
  49. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  50. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  51. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  52. package/src/tools/esbuild/javascript-transformer.js +7 -3
  53. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
  54. package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
  55. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  56. package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
  57. package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
  58. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
  59. package/src/tools/esbuild/utils.js +13 -31
  60. package/src/tools/sass/worker.js +19 -0
  61. package/src/tools/vite/middlewares/assets-middleware.d.ts +1 -1
  62. package/src/tools/vite/middlewares/assets-middleware.js +19 -3
  63. package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
  64. package/src/tools/vite/middlewares/component-middleware.js +33 -0
  65. package/src/tools/vite/middlewares/index.d.ts +1 -0
  66. package/src/tools/vite/middlewares/index.js +3 -1
  67. package/src/tools/vite/middlewares/ssr-middleware.js +13 -12
  68. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +2 -1
  69. package/src/tools/vite/plugins/setup-middlewares-plugin.js +2 -1
  70. package/src/tools/vite/utils.d.ts +1 -0
  71. package/src/typings.d.ts +1 -1
  72. package/src/utils/environment-options.js +1 -1
  73. package/src/utils/index-file/auto-csp.d.ts +23 -0
  74. package/src/utils/index-file/auto-csp.js +283 -0
  75. package/src/utils/index-file/html-rewriting-stream.d.ts +5 -1
  76. package/src/utils/index-file/index-html-generator.d.ts +4 -0
  77. package/src/utils/index-file/index-html-generator.js +16 -0
  78. package/src/utils/index-file/inline-critical-css.js +17 -18
  79. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  80. package/src/utils/index-file/ngcm-attribute.js +37 -0
  81. package/src/utils/index-file/valid-self-closing-tags.js +27 -0
  82. package/src/utils/normalize-cache.js +1 -1
  83. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  84. package/src/utils/server-rendering/fetch-patch.js +2 -2
  85. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  86. package/src/utils/server-rendering/launch-server.js +63 -0
  87. package/src/utils/server-rendering/load-esm-from-memory.d.ts +7 -0
  88. package/src/utils/server-rendering/manifest.d.ts +1 -1
  89. package/src/utils/server-rendering/manifest.js +4 -13
  90. package/src/utils/server-rendering/prerender.js +10 -6
  91. package/src/utils/server-rendering/render-worker.d.ts +4 -1
  92. package/src/utils/server-rendering/render-worker.js +13 -3
  93. package/src/utils/server-rendering/routes-extractor-worker.d.ts +5 -4
  94. package/src/utils/server-rendering/routes-extractor-worker.js +14 -4
  95. package/src/utils/server-rendering/utils.d.ts +11 -0
  96. package/src/utils/server-rendering/utils.js +17 -0
  97. package/src/utils/supported-browsers.js +1 -0
@@ -18,7 +18,18 @@
18
18
  },
19
19
  "server": {
20
20
  "type": "string",
21
- "description": "The full path for the server entry point to the application, relative to the current workspace."
21
+ "description": "The full path for the server entry point to the application, relative to the current workspace.",
22
+ "oneOf": [
23
+ {
24
+ "type": "string",
25
+ "description": "The full path for the server entry point to the application, relative to the current workspace."
26
+ },
27
+ {
28
+ "const": false,
29
+ "type": "boolean",
30
+ "description": "Indicates that a server entry point is not provided."
31
+ }
32
+ ]
22
33
  },
23
34
  "polyfills": {
24
35
  "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
@@ -37,6 +48,33 @@
37
48
  "type": "string",
38
49
  "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations."
39
50
  },
51
+ "security": {
52
+ "description": "Security features to protect against XSS and other common attacks",
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "properties": {
56
+ "autoCsp": {
57
+ "description": "Enables automatic generation of a hash-based Strict Content Security Policy (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will default to true once we are out of experimental/preview phases.",
58
+ "default": false,
59
+ "oneOf": [
60
+ {
61
+ "type": "object",
62
+ "properties": {
63
+ "unsafeEval": {
64
+ "type": "boolean",
65
+ "description": "Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.",
66
+ "default": false
67
+ }
68
+ },
69
+ "additionalProperties": false
70
+ },
71
+ {
72
+ "type": "boolean"
73
+ }
74
+ ]
75
+ }
76
+ }
77
+ },
40
78
  "scripts": {
41
79
  "description": "Global scripts to be included in the build.",
42
80
  "type": "array",
@@ -125,6 +163,34 @@
125
163
  "type": "string"
126
164
  },
127
165
  "default": []
166
+ },
167
+ "sass": {
168
+ "description": "Options to pass to the sass preprocessor.",
169
+ "type": "object",
170
+ "properties": {
171
+ "fatalDeprecations": {
172
+ "description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.",
173
+ "type": "array",
174
+ "items": {
175
+ "type": "string"
176
+ }
177
+ },
178
+ "silenceDeprecations": {
179
+ "description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.",
180
+ "type": "array",
181
+ "items": {
182
+ "type": "string"
183
+ }
184
+ },
185
+ "futureDeprecations": {
186
+ "description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.",
187
+ "type": "array",
188
+ "items": {
189
+ "type": "string"
190
+ }
191
+ }
192
+ },
193
+ "additionalProperties": false
128
194
  }
129
195
  },
130
196
  "additionalProperties": false
@@ -481,7 +547,6 @@
481
547
  },
482
548
  "prerender": {
483
549
  "description": "Prerender (SSG) pages of your application during build time.",
484
- "default": false,
485
550
  "oneOf": [
486
551
  {
487
552
  "type": "boolean",
@@ -518,6 +583,11 @@
518
583
  "entry": {
519
584
  "type": "string",
520
585
  "description": "The server entry-point that when executed will spawn the web server."
586
+ },
587
+ "experimentalPlatform": {
588
+ "description": "Specifies the platform for which the server bundle is generated. This affects the APIs and modules available in the server-side code. \n\n- `node`: (Default) Generates a bundle optimized for Node.js environments. \n- `neutral`: Generates a platform-neutral bundle suitable for environments like edge workers, and other serverless platforms. This option avoids using Node.js-specific APIs, making the bundle more portable. \n\nPlease note that this feature does not provide polyfills for Node.js modules. Additionally, it is experimental, and the schematics may undergo changes in future versions.",
589
+ "default": "node",
590
+ "enum": ["node", "neutral"]
521
591
  }
522
592
  },
523
593
  "additionalProperties": false
@@ -526,8 +596,7 @@
526
596
  },
527
597
  "appShell": {
528
598
  "type": "boolean",
529
- "description": "Generates an application shell during build time.",
530
- "default": false
599
+ "description": "Generates an application shell during build time."
531
600
  },
532
601
  "outputMode": {
533
602
  "type": "string",
@@ -5,6 +5,7 @@
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 { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets';
8
9
  import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
9
10
  import { BundlerContext } from '../../tools/esbuild/bundler-context';
10
11
  import { NormalizedApplicationBuildOptions } from './options';
@@ -16,4 +17,5 @@ import { NormalizedApplicationBuildOptions } from './options';
16
17
  * @param codeBundleCache An instance of the TypeScript source file cache.
17
18
  * @returns An array of BundlerContext objects.
18
19
  */
19
- export declare function setupBundlerContexts(options: NormalizedApplicationBuildOptions, browsers: string[], codeBundleCache: SourceFileCache): BundlerContext[];
20
+ export declare function setupBundlerContexts(options: NormalizedApplicationBuildOptions, target: string[], codeBundleCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler): BundlerContext[];
21
+ export declare function createComponentStyleBundler(options: NormalizedApplicationBuildOptions, target: string[]): ComponentStylesheetBundler;
@@ -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,11 @@ 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
+ browserOptions.ssr ||= true;
72
73
  }
73
74
  // Set all packages as external to support Vite's prebundle caching
74
75
  browserOptions.externalPackages = serverOptions.prebundle;
@@ -91,8 +92,13 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
91
92
  // https://nodejs.org/api/process.html#processsetsourcemapsenabledval
92
93
  process.setSourceMapsEnabled(true);
93
94
  }
94
- // TODO: Enable by default once full support across CLI and FW is integrated
95
- 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
+ }
96
102
  // Setup the prebundling transformer that will be shared across Vite prebundling requests
97
103
  const prebundleTransformer = new internal_1.JavaScriptTransformer(
98
104
  // Always enable JIT linking to support applications built with and without AOT.
@@ -115,6 +121,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
115
121
  explicitServer: [],
116
122
  };
117
123
  const usedComponentStyles = new Map();
124
+ const templateUpdates = new Map();
118
125
  // Add cleanup logic via a builder teardown.
119
126
  let deferred;
120
127
  context.addTeardown(async () => {
@@ -128,7 +135,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
128
135
  case results_1.ResultKind.Failure:
129
136
  if (result.errors.length && server) {
130
137
  hadError = true;
131
- server.hot.send({
138
+ server.ws.send({
132
139
  type: 'error',
133
140
  err: {
134
141
  message: result.errors[0].text,
@@ -156,6 +163,8 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
156
163
  assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
157
164
  }
158
165
  }
166
+ // Clear stale template updates on a code rebuilds
167
+ templateUpdates.clear();
159
168
  // Analyze result files for changes
160
169
  analyzeResultFiles(normalizePath, htmlIndexPath, result.files, generatedFiles);
161
170
  break;
@@ -165,8 +174,18 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
165
174
  break;
166
175
  case results_1.ResultKind.ComponentUpdate:
167
176
  (0, node_assert_1.default)(serverOptions.hmr, 'Component updates are only supported with HMR enabled.');
168
- // TODO: Implement support -- application builder currently does not use
169
- 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;
170
189
  default:
171
190
  context.logger.warn(`Unknown result kind [${result.kind}] provided by build.`);
172
191
  continue;
@@ -175,7 +194,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
175
194
  if (hadError && server) {
176
195
  hadError = false;
177
196
  // Send an empty update to clear the error overlay
178
- server.hot.send({
197
+ server.ws.send({
179
198
  'type': 'update',
180
199
  updates: [],
181
200
  });
@@ -248,11 +267,17 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
248
267
  browserOptions.ssr.entry) {
249
268
  ssrMode = plugins_1.ServerSsrMode.ExternalSsrMiddleware;
250
269
  }
251
- else if (browserOptions.server) {
270
+ else if (browserOptions.ssr) {
252
271
  ssrMode = plugins_1.ServerSsrMode.InternalSsrMiddleware;
253
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
+ }
254
279
  // Setup server and start listening
255
- 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);
256
281
  server = await createServer(serverConfiguration);
257
282
  await server.listen();
258
283
  const urls = server.resolvedUrls;
@@ -268,7 +293,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
268
293
  key: 'r',
269
294
  description: 'force reload browser',
270
295
  action(server) {
271
- server.hot.send({
296
+ server.ws.send({
272
297
  type: 'full-reload',
273
298
  path: '*',
274
299
  });
@@ -311,7 +336,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
311
336
  if (serverOptions.liveReload || serverOptions.hmr) {
312
337
  if (updatedFiles.every((f) => f.endsWith('.css'))) {
313
338
  const timestamp = Date.now();
314
- server.hot.send({
339
+ server.ws.send({
315
340
  type: 'update',
316
341
  updates: updatedFiles.flatMap((filePath) => {
317
342
  // For component styles, an HMR update must be sent for each one with the corresponding
@@ -321,7 +346,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
321
346
  // are not typically reused across components.
322
347
  const componentIds = usedComponentStyles.get(filePath);
323
348
  if (componentIds) {
324
- return componentIds.map((id) => ({
349
+ return Array.from(componentIds).map((id) => ({
325
350
  type: 'css-update',
326
351
  timestamp,
327
352
  path: `${filePath}?ngcomp` + (id ? `=${id}` : ''),
@@ -342,7 +367,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
342
367
  }
343
368
  // Send reload command to clients
344
369
  if (serverOptions.liveReload) {
345
- server.hot.send({
370
+ server.ws.send({
346
371
  type: 'full-reload',
347
372
  path: '*',
348
373
  });
@@ -372,6 +397,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
372
397
  contents: file.contents,
373
398
  servable,
374
399
  size: file.contents.byteLength,
400
+ hash: file.hash,
375
401
  type: file.type,
376
402
  updated: false,
377
403
  });
@@ -402,13 +428,13 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
402
428
  }
403
429
  }
404
430
  }
405
- 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) {
406
432
  const proxy = await (0, utils_1.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
407
433
  // dynamically import Vite for ESM compatibility
408
434
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
409
435
  // Path will not exist on disk and only used to provide separate path for Vite requests
410
436
  const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project));
411
- const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, 'vite');
437
+ const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite');
412
438
  const configuration = {
413
439
  configFile: false,
414
440
  envFile: false,
@@ -495,6 +521,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
495
521
  indexHtmlTransformer,
496
522
  extensionMiddleware,
497
523
  usedComponentStyles,
524
+ templateUpdates,
498
525
  ssrMode,
499
526
  }),
500
527
  (0, plugins_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
@@ -567,6 +594,9 @@ function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless
567
594
  supported: (0, internal_1.getFeatureSupport)(target, zoneless),
568
595
  plugins,
569
596
  loader,
597
+ define: {
598
+ 'ngServerMode': `${ssr}`,
599
+ },
570
600
  resolveExtensions: ['.mjs', '.js', '.cjs'],
571
601
  },
572
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: {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import { PluginObj } from '@babel/core';
9
+ /**
10
+ * A babel plugin factory function for adding istanbul instrumentation.
11
+ *
12
+ * @returns A babel plugin object instance.
13
+ */
14
+ export default function (): PluginObj;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.default = default_1;
14
+ const core_1 = require("@babel/core");
15
+ const istanbul_lib_instrument_1 = require("istanbul-lib-instrument");
16
+ const node_assert_1 = __importDefault(require("node:assert"));
17
+ /**
18
+ * A babel plugin factory function for adding istanbul instrumentation.
19
+ *
20
+ * @returns A babel plugin object instance.
21
+ */
22
+ function default_1() {
23
+ const visitors = new WeakMap();
24
+ return {
25
+ visitor: {
26
+ Program: {
27
+ enter(path, state) {
28
+ const visitor = (0, istanbul_lib_instrument_1.programVisitor)(core_1.types, state.filename, {
29
+ // Babel returns a Converter object from the `convert-source-map` package
30
+ inputSourceMap: state.file.inputMap?.toObject(),
31
+ });
32
+ visitors.set(path, visitor);
33
+ visitor.enter(path);
34
+ },
35
+ exit(path) {
36
+ const visitor = visitors.get(path);
37
+ (0, node_assert_1.default)(visitor, 'Instrumentation visitor should always be present for program path.');
38
+ visitor.exit(path);
39
+ visitors.delete(path);
40
+ },
41
+ },
42
+ },
43
+ };
44
+ }