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

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 (45) hide show
  1. package/package.json +13 -13
  2. package/src/builders/application/options.d.ts +3 -1
  3. package/src/builders/application/options.js +2 -1
  4. package/src/builders/application/results.d.ts +5 -3
  5. package/src/builders/application/schema.d.ts +57 -0
  6. package/src/builders/application/schema.js +19 -1
  7. package/src/builders/application/schema.json +33 -0
  8. package/src/builders/dev-server/vite-server.d.ts +1 -1
  9. package/src/builders/dev-server/vite-server.js +26 -7
  10. package/src/tools/angular/angular-host.d.ts +1 -1
  11. package/src/tools/angular/angular-host.js +1 -4
  12. package/src/tools/angular/compilation/parallel-compilation.js +2 -2
  13. package/src/tools/angular/compilation/parallel-worker.js +3 -1
  14. package/src/tools/esbuild/angular/compiler-plugin.js +27 -8
  15. package/src/tools/esbuild/angular/component-stylesheets.d.ts +3 -2
  16. package/src/tools/esbuild/angular/component-stylesheets.js +4 -2
  17. package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
  18. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +2 -2
  19. package/src/tools/esbuild/application-code-bundle.js +46 -25
  20. package/src/tools/esbuild/bundler-context.js +7 -10
  21. package/src/tools/esbuild/compiler-plugin-options.js +3 -0
  22. package/src/tools/esbuild/global-scripts.js +1 -1
  23. package/src/tools/esbuild/global-styles.js +3 -0
  24. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
  25. package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
  26. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  27. package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
  28. package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
  29. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
  30. package/src/tools/esbuild/utils.js +12 -1
  31. package/src/tools/sass/worker.js +19 -0
  32. package/src/tools/vite/middlewares/assets-middleware.d.ts +1 -1
  33. package/src/tools/vite/middlewares/assets-middleware.js +2 -2
  34. package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
  35. package/src/tools/vite/middlewares/component-middleware.js +33 -0
  36. package/src/tools/vite/middlewares/index.d.ts +1 -0
  37. package/src/tools/vite/middlewares/index.js +3 -1
  38. package/src/tools/vite/middlewares/ssr-middleware.js +6 -6
  39. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +2 -1
  40. package/src/tools/vite/plugins/setup-middlewares-plugin.js +2 -1
  41. package/src/utils/environment-options.js +1 -1
  42. package/src/utils/normalize-cache.js +1 -1
  43. package/src/utils/server-rendering/launch-server.js +5 -5
  44. package/src/utils/server-rendering/load-esm-from-memory.d.ts +1 -1
  45. package/src/utils/supported-browsers.js +1 -0
@@ -18,6 +18,7 @@ exports.createSsrEntryCodeBundleOptions = createSsrEntryCodeBundleOptions;
18
18
  const node_assert_1 = __importDefault(require("node:assert"));
19
19
  const node_crypto_1 = require("node:crypto");
20
20
  const node_path_1 = require("node:path");
21
+ const schema_1 = require("../../builders/application/schema");
21
22
  const environment_options_1 = require("../../utils/environment-options");
22
23
  const manifest_1 = require("../../utils/server-rendering/manifest");
23
24
  const compiler_plugin_1 = require("./angular/compiler-plugin");
@@ -26,6 +27,7 @@ const external_packages_plugin_1 = require("./external-packages-plugin");
26
27
  const i18n_locale_plugin_1 = require("./i18n-locale-plugin");
27
28
  const loader_import_attribute_plugin_1 = require("./loader-import-attribute-plugin");
28
29
  const rxjs_esm_resolution_plugin_1 = require("./rxjs-esm-resolution-plugin");
30
+ const server_bundle_metadata_plugin_1 = require("./server-bundle-metadata-plugin");
29
31
  const sourcemap_ignorelist_plugin_1 = require("./sourcemap-ignorelist-plugin");
30
32
  const utils_1 = require("./utils");
31
33
  const virtual_module_plugin_1 = require("./virtual-module-plugin");
@@ -115,8 +117,9 @@ function createBrowserPolyfillBundleOptions(options, target, sourceFileCache) {
115
117
  function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
116
118
  const serverPolyfills = [];
117
119
  const polyfillsFromConfig = new Set(options.polyfills);
120
+ const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral;
118
121
  if (!(0, utils_1.isZonelessApp)(options.polyfills)) {
119
- serverPolyfills.push('zone.js/node');
122
+ serverPolyfills.push(isNodePlatform ? 'zone.js/node' : 'zone.js');
120
123
  }
121
124
  if (polyfillsFromConfig.has('@angular/localize') ||
122
125
  polyfillsFromConfig.has('@angular/localize/init')) {
@@ -133,7 +136,7 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
133
136
  }
134
137
  const buildOptions = {
135
138
  ...polyfillBundleOptions,
136
- platform: 'node',
139
+ platform: isNodePlatform ? 'node' : 'neutral',
137
140
  outExtension: { '.js': '.mjs' },
138
141
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
139
142
  // match and the ES5 distribution would be bundled and ends up breaking at
@@ -141,19 +144,23 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
141
144
  // More details: https://github.com/angular/angular-cli/issues/25405.
142
145
  mainFields: ['es2020', 'es2015', 'module', 'main'],
143
146
  entryNames: '[name]',
144
- banner: {
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
- },
147
+ banner: isNodePlatform
148
+ ? {
149
+ js: [
150
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
151
+ // See: https://github.com/evanw/esbuild/issues/1921.
152
+ `import { createRequire } from 'node:module';`,
153
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
154
+ ].join('\n'),
155
+ }
156
+ : undefined,
152
157
  target,
153
158
  entryPoints: {
154
159
  'polyfills.server': namespace,
155
160
  },
156
161
  };
162
+ buildOptions.plugins ??= [];
163
+ buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)());
157
164
  return () => buildOptions;
158
165
  }
159
166
  function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
@@ -199,7 +206,14 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
199
206
  }
200
207
  // Mark manifest and polyfills file as external as these are generated by a different bundle step.
201
208
  (buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS);
202
- buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
209
+ const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral;
210
+ if (!isNodePlatform) {
211
+ // `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client.
212
+ // Since `xhr2` has Node.js dependencies, it cannot be used when targeting non-Node.js platforms.
213
+ // Note: The framework already issues a warning when using XHR with SSR.
214
+ buildOptions.external.push('xhr2');
215
+ }
216
+ buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)(), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
203
217
  namespace: mainServerInjectPolyfillsNamespace,
204
218
  cache: sourceFileCache?.loadResultCache,
205
219
  loadContent: () => ({
@@ -259,6 +273,11 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
259
273
  const ssrEntryNamespace = 'angular:ssr-entry';
260
274
  const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
261
275
  const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
276
+ const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral;
277
+ const inject = [ssrInjectManifestNamespace];
278
+ if (isNodePlatform) {
279
+ inject.unshift(ssrInjectRequireNamespace);
280
+ }
262
281
  const buildOptions = {
263
282
  ...getEsBuildServerCommonOptions(options),
264
283
  target,
@@ -275,7 +294,7 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
275
294
  // Component stylesheet options
276
295
  styleOptions),
277
296
  ],
278
- inject: [ssrInjectRequireNamespace, ssrInjectManifestNamespace],
297
+ inject,
279
298
  };
280
299
  buildOptions.plugins ??= [];
281
300
  if (externalPackages) {
@@ -286,17 +305,13 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
286
305
  }
287
306
  // Mark manifest file as external. As this will be generated later on.
288
307
  (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)({
308
+ if (!isNodePlatform) {
309
+ // `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client.
310
+ // Since `xhr2` has Node.js dependencies, it cannot be used when targeting non-Node.js platforms.
311
+ // Note: The framework already issues a warning when using XHR with SSR.
312
+ buildOptions.external.push('xhr2');
313
+ }
314
+ buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)({ ssrEntryBundle: true }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
300
315
  namespace: ssrInjectRequireNamespace,
301
316
  cache: sourceFileCache?.loadResultCache,
302
317
  loadContent: () => {
@@ -357,9 +372,10 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
357
372
  return buildOptions;
358
373
  }
359
374
  function getEsBuildServerCommonOptions(options) {
375
+ const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral;
360
376
  return {
361
377
  ...getEsBuildCommonOptions(options),
362
- platform: 'node',
378
+ platform: isNodePlatform ? 'node' : 'neutral',
363
379
  outExtension: { '.js': '.mjs' },
364
380
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
365
381
  // match and the ES5 distribution would be bundled and ends up breaking at
@@ -387,7 +403,12 @@ function getEsBuildCommonOptions(options) {
387
403
  bundle: true,
388
404
  packages: 'bundle',
389
405
  assetNames: outputNames.media,
390
- conditions: ['es2020', 'es2015', 'module'],
406
+ conditions: [
407
+ 'es2020',
408
+ 'es2015',
409
+ 'module',
410
+ optimizationOptions.scripts ? 'production' : 'development',
411
+ ],
391
412
  resolveExtensions: ['.ts', '.tsx', '.mjs', '.js', '.cjs'],
392
413
  metafile: true,
393
414
  legalComments: options.extractLicenses ? 'none' : 'eof',
@@ -211,6 +211,7 @@ class BundlerContext {
211
211
  warnings: result.warnings,
212
212
  };
213
213
  }
214
+ const { 'ng-platform-server': isPlatformServer = false, 'ng-ssr-entry-bundle': isSsrEntryBundle = false, } = result.metafile;
214
215
  // Find all initial files
215
216
  const initialFiles = new Map();
216
217
  for (const outputFile of result.outputFiles) {
@@ -231,7 +232,7 @@ class BundlerContext {
231
232
  name,
232
233
  type,
233
234
  entrypoint: true,
234
- serverFile: this.#platformIsServer,
235
+ serverFile: isPlatformServer,
235
236
  depth: 0,
236
237
  };
237
238
  if (!this.initialFilter || this.initialFilter(record)) {
@@ -259,7 +260,7 @@ class BundlerContext {
259
260
  type: initialImport.kind === 'import-rule' ? 'style' : 'script',
260
261
  entrypoint: false,
261
262
  external: initialImport.external,
262
- serverFile: this.#platformIsServer,
263
+ serverFile: isPlatformServer,
263
264
  depth: entryRecord.depth + 1,
264
265
  };
265
266
  if (!this.initialFilter || this.initialFilter(record)) {
@@ -292,9 +293,8 @@ class BundlerContext {
292
293
  if (!/\.([cm]?js|css|wasm)(\.map)?$/i.test(file.path)) {
293
294
  fileType = BuildOutputFileType.Media;
294
295
  }
295
- else if (this.#platformIsServer) {
296
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
- fileType = result.metafile['ng-ssr-entry-bundle']
296
+ else if (isPlatformServer) {
297
+ fileType = isSsrEntryBundle
298
298
  ? BuildOutputFileType.ServerRoot
299
299
  : BuildOutputFileType.ServerApplication;
300
300
  }
@@ -304,7 +304,7 @@ class BundlerContext {
304
304
  return (0, utils_1.convertOutputFile)(file, fileType);
305
305
  });
306
306
  let externalConfiguration = this.#esbuildOptions.external;
307
- if (this.#platformIsServer && externalConfiguration) {
307
+ if (isPlatformServer && externalConfiguration) {
308
308
  externalConfiguration = externalConfiguration.filter((dep) => !utils_1.SERVER_GENERATED_EXTERNALS.has(dep));
309
309
  if (!externalConfiguration.length) {
310
310
  externalConfiguration = undefined;
@@ -316,7 +316,7 @@ class BundlerContext {
316
316
  outputFiles,
317
317
  initialFiles,
318
318
  externalImports: {
319
- [this.#platformIsServer ? 'server' : 'browser']: externalImports,
319
+ [isPlatformServer ? 'server' : 'browser']: externalImports,
320
320
  },
321
321
  externalConfiguration,
322
322
  errors: undefined,
@@ -336,9 +336,6 @@ class BundlerContext {
336
336
  }
337
337
  }
338
338
  }
339
- get #platformIsServer() {
340
- return this.#esbuildOptions?.platform === 'node';
341
- }
342
339
  /**
343
340
  * Invalidate a stored bundler result based on the previous watch files
344
341
  * and a list of changed files.
@@ -37,6 +37,9 @@ function createCompilerPluginOptions(options, target, sourceFileCache) {
37
37
  sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
38
38
  outputNames,
39
39
  includePaths: stylePreprocessorOptions?.includePaths,
40
+ // string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ sass: stylePreprocessorOptions?.sass,
40
43
  externalDependencies,
41
44
  target,
42
45
  inlineStyleLanguage,
@@ -72,7 +72,7 @@ function createGlobalScriptsBundleOptions(options, target, initial) {
72
72
  entryNames: initial ? outputNames.bundles : '[name]',
73
73
  assetNames: outputNames.media,
74
74
  mainFields: ['script', 'browser', 'main'],
75
- conditions: ['script'],
75
+ conditions: ['script', optimizationOptions.scripts ? 'production' : 'development'],
76
76
  resolveExtensions: ['.mjs', '.js', '.cjs'],
77
77
  logLevel: options.verbose && !jsonLogs ? 'debug' : 'silent',
78
78
  metafile: true,
@@ -45,6 +45,9 @@ function createGlobalStylesBundleOptions(options, target, initial) {
45
45
  bundles: '[name]',
46
46
  },
47
47
  includePaths: stylePreprocessorOptions?.includePaths,
48
+ // string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ sass: stylePreprocessorOptions?.sass,
48
51
  tailwindConfiguration,
49
52
  postcssConfiguration,
50
53
  cacheOptions,
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import type { Plugin } from 'esbuild';
9
+ /**
10
+ * Generates an esbuild plugin that appends metadata to the output bundle,
11
+ * marking it with server-side rendering (SSR) details for Angular SSR scenarios.
12
+ *
13
+ * @param options Optional configuration object.
14
+ * - `ssrEntryBundle`: If `true`, marks the bundle as an SSR entry point.
15
+ *
16
+ * @note We can't rely on `platform: node` or `platform: neutral`, as the latter
17
+ * is used for non-SSR-related code too (e.g., global scripts).
18
+ * @returns An esbuild plugin that injects SSR metadata into the build result's metafile.
19
+ */
20
+ export declare function createServerBundleMetadata(options?: {
21
+ ssrEntryBundle?: boolean;
22
+ }): Plugin;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.createServerBundleMetadata = createServerBundleMetadata;
11
+ /**
12
+ * Generates an esbuild plugin that appends metadata to the output bundle,
13
+ * marking it with server-side rendering (SSR) details for Angular SSR scenarios.
14
+ *
15
+ * @param options Optional configuration object.
16
+ * - `ssrEntryBundle`: If `true`, marks the bundle as an SSR entry point.
17
+ *
18
+ * @note We can't rely on `platform: node` or `platform: neutral`, as the latter
19
+ * is used for non-SSR-related code too (e.g., global scripts).
20
+ * @returns An esbuild plugin that injects SSR metadata into the build result's metafile.
21
+ */
22
+ function createServerBundleMetadata(options) {
23
+ return {
24
+ name: 'angular-server-bundle-metadata',
25
+ setup(build) {
26
+ build.onEnd((result) => {
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const metafile = result.metafile;
29
+ if (metafile) {
30
+ metafile['ng-ssr-entry-bundle'] = !!options?.ssrEntryBundle;
31
+ metafile['ng-platform-server'] = true;
32
+ }
33
+ });
34
+ },
35
+ };
36
+ }
@@ -9,6 +9,7 @@ import type { BuildOptions } from 'esbuild';
9
9
  import { NormalizedCachedOptions } from '../../../utils/normalize-cache';
10
10
  import { PostcssConfiguration } from '../../../utils/postcss-configuration';
11
11
  import { LoadResultCache } from '../load-result-cache';
12
+ import { StylesheetPluginsass } from './stylesheet-plugin-factory';
12
13
  export interface BundleStylesheetOptions {
13
14
  workspaceRoot: string;
14
15
  optimization: boolean;
@@ -20,6 +21,7 @@ export interface BundleStylesheetOptions {
20
21
  media: string;
21
22
  };
22
23
  includePaths?: string[];
24
+ sass?: StylesheetPluginsass;
23
25
  externalDependencies?: string[];
24
26
  target: string[];
25
27
  tailwindConfiguration?: {
@@ -27,6 +27,7 @@ function createStylesheetBundleOptions(options, cache, inlineComponentData) {
27
27
  inlineComponentData,
28
28
  tailwindConfiguration: options.tailwindConfiguration,
29
29
  postcssConfiguration: options.postcssConfiguration,
30
+ sass: options.sass,
30
31
  }, cache);
31
32
  const plugins = [
32
33
  pluginFactory.create(sass_language_1.SassStylesheetLanguage),
@@ -53,7 +54,7 @@ function createStylesheetBundleOptions(options, cache, inlineComponentData) {
53
54
  preserveSymlinks: options.preserveSymlinks,
54
55
  external: options.externalDependencies,
55
56
  publicPath: options.publicPath,
56
- conditions: ['style', 'sass', 'less'],
57
+ conditions: ['style', 'sass', 'less', options.optimization ? 'production' : 'development'],
57
58
  mainFields: ['style', 'sass'],
58
59
  // Unlike JS, CSS does not have implicit file extensions in the general case.
59
60
  // Preprocessor specific behavior is handled in each stylesheet language plugin.
@@ -99,6 +99,7 @@ async function compileString(data, filePath, syntax, options, resolveUrl) {
99
99
  const resolutionCache = new cache_1.MemoryCache();
100
100
  const packageRootCache = new cache_1.MemoryCache();
101
101
  const warnings = [];
102
+ const { silenceDeprecations, futureDeprecations, fatalDeprecations } = options.sass ?? {};
102
103
  try {
103
104
  const { css, sourceMap, loadedUrls } = await sassWorkerPool.compileStringAsync(data, {
104
105
  url: (0, node_url_1.pathToFileURL)(filePath),
@@ -107,6 +108,9 @@ async function compileString(data, filePath, syntax, options, resolveUrl) {
107
108
  loadPaths: options.includePaths,
108
109
  sourceMap: options.sourcemap,
109
110
  sourceMapIncludeSources: options.sourcemap,
111
+ silenceDeprecations,
112
+ fatalDeprecations,
113
+ futureDeprecations,
110
114
  quietDeps: true,
111
115
  importers: [
112
116
  {
@@ -6,8 +6,13 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { OnLoadResult, Plugin, PluginBuild } from 'esbuild';
9
+ import type { Options } from 'sass';
9
10
  import type { PostcssConfiguration } from '../../../utils/postcss-configuration';
10
11
  import { LoadResultCache } from '../load-result-cache';
12
+ /**
13
+ * Configuration options for handling Sass-specific deprecations in a stylesheet plugin.
14
+ */
15
+ export type StylesheetPluginsass = Pick<Options<'async'>, 'futureDeprecations' | 'fatalDeprecations' | 'silenceDeprecations'>;
11
16
  /**
12
17
  * An object containing the plugin options to use when processing stylesheets.
13
18
  */
@@ -42,6 +47,10 @@ export interface StylesheetPluginOptions {
42
47
  * and any tailwind usage must be manually configured in the custom postcss usage.
43
48
  */
44
49
  postcssConfiguration?: PostcssConfiguration;
50
+ /**
51
+ * Optional Options for configuring Sass behavior.
52
+ */
53
+ sass?: StylesheetPluginsass;
45
54
  }
46
55
  export interface StylesheetLanguage {
47
56
  name: string;
@@ -38,6 +38,7 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
38
38
  const browserStats = [];
39
39
  const serverStats = [];
40
40
  let unchangedCount = 0;
41
+ let componentStyleChange = false;
41
42
  for (const { path: file, size, type } of outputFiles) {
42
43
  // Only display JavaScript and CSS files
43
44
  if (!/\.(?:css|m?js)$/.test(file)) {
@@ -53,6 +54,11 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
53
54
  // Only log server build stats when SSR is enabled.
54
55
  continue;
55
56
  }
57
+ // Skip logging external component stylesheets used for HMR
58
+ if (metafile.outputs[file] && 'ng-component' in metafile.outputs[file]) {
59
+ componentStyleChange = true;
60
+ continue;
61
+ }
56
62
  const name = initial.get(file)?.name ?? getChunkNameFromMetafile(metafile, file);
57
63
  const stat = {
58
64
  initial: initial.has(file),
@@ -70,7 +76,12 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
70
76
  return tableText + '\n';
71
77
  }
72
78
  else if (changedFiles !== undefined) {
73
- return '\nNo output file changes.\n';
79
+ if (componentStyleChange) {
80
+ return '\nComponent stylesheet(s) changed.\n';
81
+ }
82
+ else {
83
+ return '\nNo output file changes.\n';
84
+ }
74
85
  }
75
86
  if (unchangedCount > 0) {
76
87
  return `Unchanged output files: ${unchangedCount}`;
@@ -40,6 +40,25 @@ async function renderSassStylesheet(request) {
40
40
  containingUrl: containingUrl ? (0, node_url_1.fileURLToPath)(containingUrl) : null,
41
41
  },
42
42
  });
43
+ // Wait for the main thread to set the signal to 1 and notify, which tells
44
+ // us that a message can be received on the port.
45
+ // If the main thread is fast, the signal will already be set to 1, and no
46
+ // sleep/notify is necessary.
47
+ // However, there can be a race condition here:
48
+ // - the main thread sets the signal to 1, but does not get to the notify instruction yet
49
+ // - the worker does not pause because the signal is set to 1
50
+ // - the worker very soon enters this method again
51
+ // - this method sets the signal to 0 and sends the message
52
+ // - the signal is 0 and so the `Atomics.wait` call blocks
53
+ // - only now the main thread runs the `notify` from the first invocation, so the
54
+ // worker continues.
55
+ // - but there is no message yet in the port, because the thread should not have been
56
+ // waken up yet.
57
+ // To combat this, wait for a non-0 value _twice_.
58
+ // Almost every time, this immediately continues with "not-equal", because
59
+ // the signal is still set to 1, except during the race condition, when the second
60
+ // wait will wait for the correct notify.
61
+ Atomics.wait(importerChannel.signal, 0, 0);
43
62
  Atomics.wait(importerChannel.signal, 0, 0);
44
63
  const result = (0, node_worker_threads_1.receiveMessageOnPort)(importerChannel.port)?.message;
45
64
  return result ? (0, node_url_1.pathToFileURL)(result) : null;
@@ -7,4 +7,4 @@
7
7
  */
8
8
  import type { Connect, ViteDevServer } from 'vite';
9
9
  import { AngularMemoryOutputFiles } from '../utils';
10
- export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles, usedComponentStyles: Map<string, string[]>): Connect.NextHandleFunction;
10
+ export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles, usedComponentStyles: Map<string, Set<string>>): Connect.NextHandleFunction;
@@ -67,10 +67,10 @@ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponen
67
67
  // Record the component style usage for HMR updates
68
68
  const usedIds = usedComponentStyles.get(pathname);
69
69
  if (usedIds === undefined) {
70
- usedComponentStyles.set(pathname, [componentId]);
70
+ usedComponentStyles.set(pathname, new Set([componentId]));
71
71
  }
72
72
  else {
73
- usedIds.push(componentId);
73
+ usedIds.add(componentId);
74
74
  }
75
75
  // Shim the stylesheet if a component ID is provided
76
76
  if (componentId.length > 0) {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import type { Connect } from 'vite';
9
+ export declare function createAngularComponentMiddleware(templateUpdates: ReadonlyMap<string, string>): Connect.NextHandleFunction;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.createAngularComponentMiddleware = createAngularComponentMiddleware;
11
+ const ANGULAR_COMPONENT_PREFIX = '/@ng/component';
12
+ function createAngularComponentMiddleware(templateUpdates) {
13
+ return function angularComponentMiddleware(req, res, next) {
14
+ if (req.url === undefined || res.writableEnded) {
15
+ return;
16
+ }
17
+ if (!req.url.startsWith(ANGULAR_COMPONENT_PREFIX)) {
18
+ next();
19
+ return;
20
+ }
21
+ const requestUrl = new URL(req.url, 'http://localhost');
22
+ const componentId = requestUrl.searchParams.get('c');
23
+ if (!componentId) {
24
+ res.statusCode = 400;
25
+ res.end();
26
+ return;
27
+ }
28
+ const updateCode = templateUpdates.get(componentId) ?? '';
29
+ res.setHeader('Content-Type', 'text/javascript');
30
+ res.setHeader('Cache-Control', 'no-cache');
31
+ res.end(updateCode);
32
+ };
33
+ }
@@ -10,3 +10,4 @@ export { angularHtmlFallbackMiddleware } from './html-fallback-middleware';
10
10
  export { createAngularIndexHtmlMiddleware } from './index-html-middleware';
11
11
  export { createAngularSsrExternalMiddleware, createAngularSsrInternalMiddleware, } from './ssr-middleware';
12
12
  export { createAngularHeadersMiddleware } from './headers-middleware';
13
+ export { createAngularComponentMiddleware } from './component-middleware';
@@ -7,7 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.createAngularHeadersMiddleware = exports.createAngularSsrInternalMiddleware = exports.createAngularSsrExternalMiddleware = exports.createAngularIndexHtmlMiddleware = exports.angularHtmlFallbackMiddleware = exports.createAngularAssetsMiddleware = void 0;
10
+ exports.createAngularComponentMiddleware = exports.createAngularHeadersMiddleware = exports.createAngularSsrInternalMiddleware = exports.createAngularSsrExternalMiddleware = exports.createAngularIndexHtmlMiddleware = exports.angularHtmlFallbackMiddleware = exports.createAngularAssetsMiddleware = void 0;
11
11
  var assets_middleware_1 = require("./assets-middleware");
12
12
  Object.defineProperty(exports, "createAngularAssetsMiddleware", { enumerable: true, get: function () { return assets_middleware_1.createAngularAssetsMiddleware; } });
13
13
  var html_fallback_middleware_1 = require("./html-fallback-middleware");
@@ -19,3 +19,5 @@ Object.defineProperty(exports, "createAngularSsrExternalMiddleware", { enumerabl
19
19
  Object.defineProperty(exports, "createAngularSsrInternalMiddleware", { enumerable: true, get: function () { return ssr_middleware_1.createAngularSsrInternalMiddleware; } });
20
20
  var headers_middleware_1 = require("./headers-middleware");
21
21
  Object.defineProperty(exports, "createAngularHeadersMiddleware", { enumerable: true, get: function () { return headers_middleware_1.createAngularHeadersMiddleware; } });
22
+ var component_middleware_1 = require("./component-middleware");
23
+ Object.defineProperty(exports, "createAngularComponentMiddleware", { enumerable: true, get: function () { return component_middleware_1.createAngularComponentMiddleware; } });
@@ -53,11 +53,11 @@ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer)
53
53
  const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
54
54
  return function angularSsrExternalMiddleware(req, res, next) {
55
55
  (async () => {
56
- const { default: handler, AngularAppEngine } = (await server.ssrLoadModule('./server.mjs'));
57
- if (!(0, utils_1.isSsrNodeRequestHandler)(handler) && !(0, utils_1.isSsrRequestHandler)(handler)) {
56
+ const { reqHandler, AngularAppEngine } = (await server.ssrLoadModule('./server.mjs'));
57
+ if (!(0, utils_1.isSsrNodeRequestHandler)(reqHandler) && !(0, utils_1.isSsrRequestHandler)(reqHandler)) {
58
58
  if (!fallbackWarningShown) {
59
59
  // eslint-disable-next-line no-console
60
- console.warn(`The default export in 'server.ts' does not provide a Node.js request handler. ` +
60
+ console.warn(`The 'reqHandler' export in 'server.ts' is either undefined or does not provide a recognized request handler. ` +
61
61
  'Using the internal SSR middleware instead.');
62
62
  fallbackWarningShown = true;
63
63
  }
@@ -73,11 +73,11 @@ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer)
73
73
  cachedAngularAppEngine = AngularAppEngine;
74
74
  }
75
75
  // Forward the request to the middleware in server.ts
76
- if ((0, utils_1.isSsrNodeRequestHandler)(handler)) {
77
- await handler(req, res, next);
76
+ if ((0, utils_1.isSsrNodeRequestHandler)(reqHandler)) {
77
+ await reqHandler(req, res, next);
78
78
  }
79
79
  else {
80
- const webRes = await handler(createWebRequestFromNodeRequest(req));
80
+ const webRes = await reqHandler(createWebRequestFromNodeRequest(req));
81
81
  if (!webRes) {
82
82
  next();
83
83
  return;
@@ -34,7 +34,8 @@ interface AngularSetupMiddlewaresPluginOptions {
34
34
  assets: Map<string, string>;
35
35
  extensionMiddleware?: Connect.NextHandleFunction[];
36
36
  indexHtmlTransformer?: (content: string) => Promise<string>;
37
- usedComponentStyles: Map<string, string[]>;
37
+ usedComponentStyles: Map<string, Set<string>>;
38
+ templateUpdates: Map<string, string>;
38
39
  ssrMode: ServerSsrMode;
39
40
  }
40
41
  export declare function createAngularSetupMiddlewaresPlugin(options: AngularSetupMiddlewaresPluginOptions): Plugin;
@@ -38,9 +38,10 @@ function createAngularSetupMiddlewaresPlugin(options) {
38
38
  name: 'vite:angular-setup-middlewares',
39
39
  enforce: 'pre',
40
40
  configureServer(server) {
41
- const { indexHtmlTransformer, outputFiles, extensionMiddleware, assets, usedComponentStyles, ssrMode, } = options;
41
+ const { indexHtmlTransformer, outputFiles, extensionMiddleware, assets, usedComponentStyles, templateUpdates, ssrMode, } = options;
42
42
  // Headers, assets and resources get handled first
43
43
  server.middlewares.use((0, middlewares_1.createAngularHeadersMiddleware)(server));
44
+ server.middlewares.use((0, middlewares_1.createAngularComponentMiddleware)(templateUpdates));
44
45
  server.middlewares.use((0, middlewares_1.createAngularAssetsMiddleware)(server, assets, outputFiles, usedComponentStyles));
45
46
  extensionMiddleware?.forEach((middleware) => server.middlewares.use(middleware));
46
47
  // Returning a function, installs middleware after the main transform middleware but
@@ -83,6 +83,6 @@ exports.useJSONBuildLogs = isPresent(buildLogsJsonVariable) && isEnabled(buildLo
83
83
  const optimizeChunksVariable = process.env['NG_BUILD_OPTIMIZE_CHUNKS'];
84
84
  exports.shouldOptimizeChunks = isPresent(optimizeChunksVariable) && isEnabled(optimizeChunksVariable);
85
85
  const hmrComponentStylesVariable = process.env['NG_HMR_CSTYLES'];
86
- exports.useComponentStyleHmr = isPresent(hmrComponentStylesVariable) && isEnabled(hmrComponentStylesVariable);
86
+ exports.useComponentStyleHmr = !isPresent(hmrComponentStylesVariable) || !isDisabled(hmrComponentStylesVariable);
87
87
  const partialSsrBuildVariable = process.env['NG_BUILD_PARTIAL_SSR'];
88
88
  exports.usePartialSsrBuild = isPresent(partialSsrBuildVariable) && isEnabled(partialSsrBuildVariable);
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.normalizeCacheOptions = normalizeCacheOptions;
11
11
  const node_path_1 = require("node:path");
12
12
  /** Version placeholder is replaced during the build process with actual package version */
13
- const VERSION = '19.0.0-next.10';
13
+ const VERSION = '19.0.0-next.11';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&