@angular/build 19.0.0-next.1 → 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 (129) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +26 -22
  3. package/src/builders/application/build-action.js +9 -9
  4. package/src/builders/application/chunk-optimizer.js +1 -4
  5. package/src/builders/application/execute-build.js +19 -2
  6. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  7. package/src/builders/application/execute-post-bundle.js +58 -20
  8. package/src/builders/application/i18n.d.ts +2 -2
  9. package/src/builders/application/i18n.js +6 -16
  10. package/src/builders/application/index.js +8 -5
  11. package/src/builders/application/options.d.ts +38 -1
  12. package/src/builders/application/options.js +62 -4
  13. package/src/builders/application/results.d.ts +5 -3
  14. package/src/builders/application/schema.d.ts +72 -0
  15. package/src/builders/application/schema.js +29 -1
  16. package/src/builders/application/schema.json +38 -0
  17. package/src/builders/application/setup-bundling.js +12 -9
  18. package/src/builders/dev-server/internal.d.ts +0 -1
  19. package/src/builders/dev-server/internal.js +1 -3
  20. package/src/builders/dev-server/vite-server.d.ts +8 -2
  21. package/src/builders/dev-server/vite-server.js +132 -58
  22. package/src/builders/extract-i18n/application-extraction.js +3 -3
  23. package/src/tools/angular/angular-host.d.ts +2 -1
  24. package/src/tools/angular/angular-host.js +17 -1
  25. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  26. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  27. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  28. package/src/tools/angular/compilation/parallel-compilation.d.ts +2 -1
  29. package/src/tools/angular/compilation/parallel-compilation.js +4 -12
  30. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  31. package/src/tools/angular/compilation/parallel-worker.js +5 -2
  32. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  33. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  34. package/src/tools/babel/plugins/types.d.ts +20 -0
  35. package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -0
  36. package/src/tools/esbuild/angular/compiler-plugin.js +69 -10
  37. package/src/tools/esbuild/angular/component-stylesheets.d.ts +10 -4
  38. package/src/tools/esbuild/angular/component-stylesheets.js +49 -12
  39. package/src/tools/esbuild/angular/file-reference-tracker.d.ts +1 -1
  40. package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
  41. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +2 -2
  42. package/src/tools/esbuild/application-code-bundle.d.ts +2 -6
  43. package/src/tools/esbuild/application-code-bundle.js +233 -71
  44. package/src/tools/esbuild/budget-stats.js +1 -1
  45. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  46. package/src/tools/esbuild/bundler-context.js +24 -19
  47. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  48. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  49. package/src/tools/esbuild/cache.d.ts +6 -1
  50. package/src/tools/esbuild/cache.js +7 -0
  51. package/src/tools/esbuild/compiler-plugin-options.js +6 -1
  52. package/src/tools/esbuild/global-scripts.js +1 -1
  53. package/src/tools/esbuild/global-styles.js +3 -0
  54. package/src/tools/esbuild/i18n-inliner.js +4 -4
  55. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  56. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  57. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  58. package/src/tools/esbuild/javascript-transformer.js +7 -12
  59. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
  60. package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
  61. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  62. package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
  63. package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
  64. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
  65. package/src/tools/esbuild/utils.d.ts +9 -0
  66. package/src/tools/esbuild/utils.js +33 -4
  67. package/src/tools/sass/sass-service.js +11 -13
  68. package/src/tools/sass/worker.d.ts +13 -32
  69. package/src/tools/sass/worker.js +20 -0
  70. package/src/tools/vite/middlewares/assets-middleware.d.ts +1 -1
  71. package/src/tools/vite/middlewares/assets-middleware.js +43 -4
  72. package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
  73. package/src/tools/vite/middlewares/component-middleware.js +33 -0
  74. package/src/tools/vite/middlewares/headers-middleware.d.ts +19 -0
  75. package/src/tools/vite/middlewares/headers-middleware.js +34 -0
  76. package/src/tools/vite/middlewares/html-fallback-middleware.d.ts +1 -1
  77. package/src/tools/vite/middlewares/html-fallback-middleware.js +23 -7
  78. package/src/tools/vite/middlewares/index-html-middleware.js +1 -2
  79. package/src/tools/vite/middlewares/index.d.ts +3 -1
  80. package/src/tools/vite/middlewares/index.js +7 -2
  81. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -4
  82. package/src/tools/vite/middlewares/ssr-middleware.js +75 -43
  83. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  84. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -40
  85. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  86. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  87. package/src/tools/vite/plugins/index.d.ts +12 -0
  88. package/src/tools/vite/plugins/index.js +21 -0
  89. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +42 -0
  90. package/src/tools/vite/plugins/setup-middlewares-plugin.js +63 -0
  91. package/src/{utils/server-rendering/main-bundle-exports.js → tools/vite/plugins/ssr-transform-plugin.d.ts} +2 -2
  92. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  93. package/src/tools/vite/utils.d.ts +0 -3
  94. package/src/tools/vite/utils.js +0 -12
  95. package/src/typings.d.ts +26 -0
  96. package/src/utils/environment-options.d.ts +2 -0
  97. package/src/utils/environment-options.js +5 -1
  98. package/src/utils/index-file/index-html-generator.js +5 -0
  99. package/src/utils/index-file/inline-critical-css.js +43 -33
  100. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  101. package/src/utils/index-file/ngcm-attribute.js +37 -0
  102. package/src/utils/index-file/valid-self-closing-tags.js +28 -0
  103. package/src/utils/normalize-cache.js +1 -1
  104. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  105. package/src/utils/server-rendering/fetch-patch.js +5 -6
  106. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  107. package/src/utils/server-rendering/launch-server.js +63 -0
  108. package/src/utils/server-rendering/load-esm-from-memory.d.ts +18 -2
  109. package/src/utils/server-rendering/manifest.d.ts +50 -0
  110. package/src/utils/server-rendering/manifest.js +126 -0
  111. package/src/utils/server-rendering/models.d.ts +27 -0
  112. package/src/utils/server-rendering/models.js +22 -0
  113. package/src/utils/server-rendering/prerender.d.ts +26 -10
  114. package/src/utils/server-rendering/prerender.js +122 -75
  115. package/src/utils/server-rendering/render-worker.d.ts +9 -8
  116. package/src/utils/server-rendering/render-worker.js +19 -14
  117. package/src/utils/server-rendering/routes-extractor-worker.d.ts +6 -10
  118. package/src/utils/server-rendering/routes-extractor-worker.js +16 -33
  119. package/src/utils/server-rendering/utils.d.ts +11 -0
  120. package/src/utils/server-rendering/utils.js +17 -0
  121. package/src/utils/supported-browsers.js +1 -0
  122. package/src/utils/worker-pool.d.ts +12 -0
  123. package/src/utils/worker-pool.js +43 -0
  124. package/src/tools/vite/angular-memory-plugin.d.ts +0 -21
  125. package/src/utils/server-rendering/main-bundle-exports.d.ts +0 -27
  126. package/src/utils/server-rendering/render-page.d.ts +0 -26
  127. package/src/utils/server-rendering/render-page.js +0 -114
  128. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  129. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -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,
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.I18nInliner = void 0;
14
14
  const node_assert_1 = __importDefault(require("node:assert"));
15
- const piscina_1 = __importDefault(require("piscina"));
15
+ const worker_pool_1 = require("../../utils/worker-pool");
16
16
  const bundler_context_1 = require("./bundler-context");
17
17
  const utils_1 = require("./utils");
18
18
  /**
@@ -36,7 +36,8 @@ class I18nInliner {
36
36
  const files = new Map();
37
37
  const pendingMaps = [];
38
38
  for (const file of options.outputFiles) {
39
- if (file.type === bundler_context_1.BuildOutputFileType.Root) {
39
+ if (file.type === bundler_context_1.BuildOutputFileType.Root || file.type === bundler_context_1.BuildOutputFileType.ServerRoot) {
40
+ // Skip also the server entry-point.
40
41
  // Skip stats and similar files.
41
42
  continue;
42
43
  }
@@ -74,7 +75,7 @@ class I18nInliner {
74
75
  }
75
76
  }
76
77
  this.#localizeFiles = files;
77
- this.#workerPool = new piscina_1.default({
78
+ this.#workerPool = new worker_pool_1.WorkerPool({
78
79
  filename: require.resolve('./i18n-inliner-worker'),
79
80
  maxThreads,
80
81
  // Extract options to ensure only the named options are serialized and sent to the worker
@@ -83,7 +84,6 @@ class I18nInliner {
83
84
  shouldOptimize: options.shouldOptimize,
84
85
  files,
85
86
  },
86
- recordTiming: false,
87
87
  });
88
88
  }
89
89
  /**
@@ -14,6 +14,7 @@ interface JavaScriptTransformRequest {
14
14
  skipLinker?: boolean;
15
15
  sideEffects?: boolean;
16
16
  jit: boolean;
17
+ instrumentForCoverage?: boolean;
17
18
  }
18
19
  export default function transformJavaScript(request: JavaScriptTransformRequest): Promise<unknown>;
19
20
  export {};
@@ -63,8 +63,12 @@ async function transformWithBabel(filename, data, options) {
63
63
  // @ts-expect-error Import attribute syntax plugin does not currently have type definitions
64
64
  const { default: importAttributePlugin } = await Promise.resolve().then(() => __importStar(require('@babel/plugin-syntax-import-attributes')));
65
65
  const plugins = [importAttributePlugin];
66
- // Lazy load the linker plugin only when linking is required
66
+ if (options.instrumentForCoverage) {
67
+ const { default: coveragePlugin } = await Promise.resolve().then(() => __importStar(require('../babel/plugins/add-code-coverage.js')));
68
+ plugins.push(coveragePlugin);
69
+ }
67
70
  if (shouldLink) {
71
+ // Lazy load the linker plugin only when linking is required
68
72
  const linkerPlugin = await createLinkerPlugin(options);
69
73
  plugins.push(linkerPlugin);
70
74
  }
@@ -35,7 +35,7 @@ export declare class JavaScriptTransformer {
35
35
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
36
36
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
37
37
  */
38
- transformFile(filename: string, skipLinker?: boolean, sideEffects?: boolean): Promise<Uint8Array>;
38
+ transformFile(filename: string, skipLinker?: boolean, sideEffects?: boolean, instrumentForCoverage?: boolean): Promise<Uint8Array>;
39
39
  /**
40
40
  * Performs JavaScript transformations on the provided data of a file. The file does not need
41
41
  * to exist on the filesystem.
@@ -45,7 +45,7 @@ export declare class JavaScriptTransformer {
45
45
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
46
46
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
47
47
  */
48
- transformData(filename: string, data: string, skipLinker: boolean, sideEffects?: boolean): Promise<Uint8Array>;
48
+ transformData(filename: string, data: string, skipLinker: boolean, sideEffects?: boolean, instrumentForCoverage?: boolean): Promise<Uint8Array>;
49
49
  /**
50
50
  * Stops all active transformation tasks and shuts down all workers.
51
51
  * @returns A void promise that resolves when closing is complete.
@@ -6,14 +6,11 @@
6
6
  * Use of this source code is governed by an MIT-style license that can be
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
- var __importDefault = (this && this.__importDefault) || function (mod) {
10
- return (mod && mod.__esModule) ? mod : { "default": mod };
11
- };
12
9
  Object.defineProperty(exports, "__esModule", { value: true });
13
10
  exports.JavaScriptTransformer = void 0;
14
11
  const node_crypto_1 = require("node:crypto");
15
12
  const promises_1 = require("node:fs/promises");
16
- const piscina_1 = __importDefault(require("piscina"));
13
+ const worker_pool_1 = require("../../utils/worker-pool");
17
14
  /**
18
15
  * A class that performs transformation of JavaScript files and raw data.
19
16
  * A worker pool is used to distribute the transformation actions and allow
@@ -41,13 +38,9 @@ class JavaScriptTransformer {
41
38
  this.#fileCacheKeyBase = Buffer.from(JSON.stringify(this.#commonOptions), 'utf-8');
42
39
  }
43
40
  #ensureWorkerPool() {
44
- this.#workerPool ??= new piscina_1.default({
41
+ this.#workerPool ??= new worker_pool_1.WorkerPool({
45
42
  filename: require.resolve('./javascript-transformer-worker'),
46
- minThreads: 1,
47
43
  maxThreads: this.maxThreads,
48
- // Shutdown idle threads after 1 second of inactivity
49
- idleTimeout: 1000,
50
- recordTiming: false,
51
44
  });
52
45
  return this.#workerPool;
53
46
  }
@@ -59,7 +52,7 @@ class JavaScriptTransformer {
59
52
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
60
53
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
61
54
  */
62
- async transformFile(filename, skipLinker, sideEffects) {
55
+ async transformFile(filename, skipLinker, sideEffects, instrumentForCoverage) {
63
56
  const data = await (0, promises_1.readFile)(filename);
64
57
  let result;
65
58
  let cacheKey;
@@ -86,6 +79,7 @@ class JavaScriptTransformer {
86
79
  data,
87
80
  skipLinker,
88
81
  sideEffects,
82
+ instrumentForCoverage,
89
83
  ...this.#commonOptions,
90
84
  }, {
91
85
  // The below is disable as with Yarn PNP this causes build failures with the below message
@@ -113,10 +107,10 @@ class JavaScriptTransformer {
113
107
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
114
108
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
115
109
  */
116
- async transformData(filename, data, skipLinker, sideEffects) {
110
+ async transformData(filename, data, skipLinker, sideEffects, instrumentForCoverage) {
117
111
  // Perform a quick test to determine if the data needs any transformations.
118
112
  // This allows directly returning the data without the worker communication overhead.
119
- if (skipLinker && !this.#commonOptions.advancedOptimizations) {
113
+ if (skipLinker && !this.#commonOptions.advancedOptimizations && !instrumentForCoverage) {
120
114
  const keepSourcemap = this.#commonOptions.sourcemap &&
121
115
  (!!this.#commonOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
122
116
  return Buffer.from(keepSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''), 'utf-8');
@@ -126,6 +120,7 @@ class JavaScriptTransformer {
126
120
  data,
127
121
  skipLinker,
128
122
  sideEffects,
123
+ instrumentForCoverage,
129
124
  ...this.#commonOptions,
130
125
  });
131
126
  }
@@ -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;
@@ -47,3 +47,12 @@ export declare function logMessages(logger: BuilderContext['logger'], executionR
47
47
  */
48
48
  export declare function isZonelessApp(polyfills: string[] | undefined): boolean;
49
49
  export declare function getEntryPointName(entryPoint: string): string;
50
+ /**
51
+ * A set of server-generated dependencies that are treated as external.
52
+ *
53
+ * These dependencies are marked as external because they are produced by a
54
+ * separate bundling process and are not included in the primary bundle. This
55
+ * ensures that these generated files are resolved from an external source rather
56
+ * than being part of the main bundle.
57
+ */
58
+ export declare const SERVER_GENERATED_EXTERNALS: Set<string>;
@@ -7,6 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SERVER_GENERATED_EXTERNALS = void 0;
10
11
  exports.logBuildStats = logBuildStats;
11
12
  exports.getChunkNameFromMetafile = getChunkNameFromMetafile;
12
13
  exports.calculateEstimatedTransferSizes = calculateEstimatedTransferSizes;
@@ -29,12 +30,15 @@ const node_path_1 = require("node:path");
29
30
  const node_url_1 = require("node:url");
30
31
  const node_zlib_1 = require("node:zlib");
31
32
  const semver_1 = require("semver");
33
+ const schema_1 = require("../../builders/application/schema");
34
+ const manifest_1 = require("../../utils/server-rendering/manifest");
32
35
  const stats_table_1 = require("../../utils/stats-table");
33
36
  const bundler_context_1 = require("./bundler-context");
34
37
  function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
35
38
  const browserStats = [];
36
39
  const serverStats = [];
37
40
  let unchangedCount = 0;
41
+ let componentStyleChange = false;
38
42
  for (const { path: file, size, type } of outputFiles) {
39
43
  // Only display JavaScript and CSS files
40
44
  if (!/\.(?:css|m?js)$/.test(file)) {
@@ -45,11 +49,16 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
45
49
  ++unchangedCount;
46
50
  continue;
47
51
  }
48
- const isPlatformServer = type === bundler_context_1.BuildOutputFileType.Server;
52
+ const isPlatformServer = type === bundler_context_1.BuildOutputFileType.ServerApplication || type === bundler_context_1.BuildOutputFileType.ServerRoot;
49
53
  if (isPlatformServer && !ssrOutputEnabled) {
50
54
  // Only log server build stats when SSR is enabled.
51
55
  continue;
52
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
+ }
53
62
  const name = initial.get(file)?.name ?? getChunkNameFromMetafile(metafile, file);
54
63
  const stat = {
55
64
  initial: initial.has(file),
@@ -67,7 +76,12 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
67
76
  return tableText + '\n';
68
77
  }
69
78
  else if (changedFiles !== undefined) {
70
- 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
+ }
71
85
  }
72
86
  if (unchangedCount > 0) {
73
87
  return `Unchanged output files: ${unchangedCount}`;
@@ -340,7 +354,7 @@ function getSupportedNodeTargets() {
340
354
  return SUPPORTED_NODE_VERSIONS.split('||').map((v) => 'node' + (0, semver_1.coerce)(v)?.version);
341
355
  }
342
356
  async function createJsonBuildManifest(result, normalizedOptions) {
343
- const { colors: color, outputOptions: { base, server, browser }, ssrOptions, } = normalizedOptions;
357
+ const { colors: color, outputOptions: { base, server, browser }, ssrOptions, outputMode, } = normalizedOptions;
344
358
  const { warnings, errors, prerenderedRoutes } = result;
345
359
  const manifest = {
346
360
  errors: errors.length ? await (0, esbuild_1.formatMessages)(errors, { kind: 'error', color }) : [],
@@ -348,7 +362,9 @@ async function createJsonBuildManifest(result, normalizedOptions) {
348
362
  outputPaths: {
349
363
  root: (0, node_url_1.pathToFileURL)(base),
350
364
  browser: (0, node_url_1.pathToFileURL)((0, node_path_1.join)(base, browser)),
351
- server: ssrOptions ? (0, node_url_1.pathToFileURL)((0, node_path_1.join)(base, server)) : undefined,
365
+ server: outputMode !== schema_1.OutputMode.Static && ssrOptions
366
+ ? (0, node_url_1.pathToFileURL)((0, node_path_1.join)(base, server))
367
+ : undefined,
352
368
  },
353
369
  prerenderedRoutes,
354
370
  };
@@ -385,3 +401,16 @@ function getEntryPointName(entryPoint) {
385
401
  .replace(/\.[cm]?[jt]s$/, '')
386
402
  .replace(/[\\/.]/g, '-');
387
403
  }
404
+ /**
405
+ * A set of server-generated dependencies that are treated as external.
406
+ *
407
+ * These dependencies are marked as external because they are produced by a
408
+ * separate bundling process and are not included in the primary bundle. This
409
+ * ensures that these generated files are resolved from an external source rather
410
+ * than being part of the main bundle.
411
+ */
412
+ exports.SERVER_GENERATED_EXTERNALS = new Set([
413
+ './polyfills.server.mjs',
414
+ './' + manifest_1.SERVER_APP_MANIFEST_FILENAME,
415
+ './' + manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME,
416
+ ]);
@@ -34,17 +34,22 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
34
34
  env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
35
35
  env.hasError = true;
36
36
  }
37
+ var r, s = 0;
37
38
  function next() {
38
- while (env.stack.length) {
39
- var rec = env.stack.pop();
39
+ while (r = env.stack.pop()) {
40
40
  try {
41
- var result = rec.dispose && rec.dispose.call(rec.value);
42
- if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
41
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
42
+ if (r.dispose) {
43
+ var result = r.dispose.call(r.value);
44
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
45
+ }
46
+ else s |= 1;
43
47
  }
44
48
  catch (e) {
45
49
  fail(e);
46
50
  }
47
51
  }
52
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
48
53
  if (env.hasError) throw env.error;
49
54
  }
50
55
  return next();
@@ -61,8 +66,8 @@ exports.SassWorkerImplementation = void 0;
61
66
  const node_assert_1 = __importDefault(require("node:assert"));
62
67
  const node_url_1 = require("node:url");
63
68
  const node_worker_threads_1 = require("node:worker_threads");
64
- const piscina_1 = require("piscina");
65
69
  const environment_options_1 = require("../../utils/environment-options");
70
+ const worker_pool_1 = require("../../utils/worker-pool");
66
71
  // Polyfill Symbol.dispose if not present
67
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
73
  Symbol.dispose ??= Symbol('Symbol Dispose');
@@ -85,16 +90,9 @@ class SassWorkerImplementation {
85
90
  this.maxThreads = maxThreads;
86
91
  }
87
92
  #ensureWorkerPool() {
88
- this.#workerPool ??= new piscina_1.Piscina({
93
+ this.#workerPool ??= new worker_pool_1.WorkerPool({
89
94
  filename: require.resolve('./worker'),
90
- minThreads: 1,
91
95
  maxThreads: this.maxThreads,
92
- // Web containers do not support transferable objects with receiveOnMessagePort which
93
- // is used when the Atomics based wait loop is enable.
94
- useAtomics: !process.versions.webcontainer,
95
- // Shutdown idle threads after 1 second of inactivity
96
- idleTimeout: 1000,
97
- recordTiming: false,
98
96
  });
99
97
  return this.#workerPool;
100
98
  }
@@ -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 { RawSourceMap } from '@ampproject/remapping';
8
9
  import { MessagePort } from 'node:worker_threads';
9
10
  import { SourceSpan, StringOptions } from 'sass';
10
11
  import type { SerializableWarningMessage } from './sass-service';
@@ -38,45 +39,25 @@ interface RenderRequestMessage {
38
39
  */
39
40
  rebase: boolean;
40
41
  }
41
- export default function renderSassStylesheet(request: RenderRequestMessage): Promise<{
42
+ interface RenderResult {
42
43
  warnings: SerializableWarningMessage[] | undefined;
43
44
  result: {
44
- loadedUrls: string[];
45
45
  css: string;
46
- sourceMap?: import("source-map-js").RawSourceMap;
46
+ loadedUrls: string[];
47
+ sourceMap?: RawSourceMap;
47
48
  };
48
- error?: undefined;
49
- } | {
49
+ }
50
+ interface RenderError {
50
51
  warnings: SerializableWarningMessage[] | undefined;
51
52
  error: {
52
- span: Omit<SourceSpan, "url"> & {
53
+ message: string;
54
+ stack?: string;
55
+ span?: Omit<SourceSpan, 'url'> & {
53
56
  url?: string;
54
57
  };
55
- message: string;
56
- stack: string | undefined;
57
- sassMessage: string;
58
- sassStack: string;
59
- };
60
- result?: undefined;
61
- } | {
62
- warnings: SerializableWarningMessage[] | undefined;
63
- error: {
64
- message: string;
65
- stack: string | undefined;
66
- span?: undefined;
67
- sassMessage?: undefined;
68
- sassStack?: undefined;
58
+ sassMessage?: string;
59
+ sassStack?: string;
69
60
  };
70
- result?: undefined;
71
- } | {
72
- warnings: SerializableWarningMessage[] | undefined;
73
- error: {
74
- message: string;
75
- span?: undefined;
76
- stack?: undefined;
77
- sassMessage?: undefined;
78
- sassStack?: undefined;
79
- };
80
- result?: undefined;
81
- }>;
61
+ }
62
+ export default function renderSassStylesheet(request: RenderRequestMessage): Promise<RenderResult | RenderError>;
82
63
  export {};
@@ -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;
@@ -95,6 +114,7 @@ async function renderSassStylesheet(request) {
95
114
  warnings,
96
115
  result: {
97
116
  ...result,
117
+ sourceMap: result.sourceMap,
98
118
  // URL is not serializable so to convert to string here and back to URL in the parent.
99
119
  loadedUrls: result.loadedUrls.map((p) => (0, node_url_1.fileURLToPath)(p)),
100
120
  },
@@ -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): Connect.NextHandleFunction;
10
+ export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles, usedComponentStyles: Map<string, Set<string>>): Connect.NextHandleFunction;
@@ -10,9 +10,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createAngularAssetsMiddleware = createAngularAssetsMiddleware;
11
11
  const mrmime_1 = require("mrmime");
12
12
  const node_path_1 = require("node:path");
13
+ const load_esm_1 = require("../../../utils/load-esm");
13
14
  const utils_1 = require("../utils");
14
- function createAngularAssetsMiddleware(server, assets, outputFiles) {
15
- return function (req, res, next) {
15
+ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponentStyles) {
16
+ return function angularAssetsMiddleware(req, res, next) {
16
17
  if (req.url === undefined || res.writableEnded) {
17
18
  return;
18
19
  }
@@ -47,19 +48,57 @@ function createAngularAssetsMiddleware(server, assets, outputFiles) {
47
48
  next();
48
49
  return;
49
50
  }
51
+ // Support HTTP HEAD requests for the virtual output files from the Angular build
52
+ if (req.method === 'HEAD' && outputFiles.get(pathname)?.servable) {
53
+ // While a GET will also generate content, the rest of the response is equivalent
54
+ req.method = 'GET';
55
+ }
50
56
  // Resource files are handled directly.
51
57
  // Global stylesheets (CSS files) are currently considered resources to workaround
52
58
  // dev server sourcemap issues with stylesheets.
53
59
  if (extension !== '.js' && extension !== '.html') {
54
60
  const outputFile = outputFiles.get(pathname);
55
61
  if (outputFile?.servable) {
62
+ const data = outputFile.contents;
63
+ if (extension === '.css') {
64
+ // Inject component ID for view encapsulation if requested
65
+ const componentId = new URL(req.url, 'http://localhost').searchParams.get('ngcomp');
66
+ if (componentId !== null) {
67
+ // Record the component style usage for HMR updates
68
+ const usedIds = usedComponentStyles.get(pathname);
69
+ if (usedIds === undefined) {
70
+ usedComponentStyles.set(pathname, new Set([componentId]));
71
+ }
72
+ else {
73
+ usedIds.add(componentId);
74
+ }
75
+ // Shim the stylesheet if a component ID is provided
76
+ if (componentId.length > 0) {
77
+ // Validate component ID
78
+ if (/^[_.\-\p{Letter}\d]+-c\d{9}$/u.test(componentId)) {
79
+ (0, load_esm_1.loadEsmModule)('@angular/compiler')
80
+ .then((compilerModule) => {
81
+ const encapsulatedData = compilerModule.encapsulateStyle(new TextDecoder().decode(data), componentId);
82
+ res.setHeader('Content-Type', 'text/css');
83
+ res.setHeader('Cache-Control', 'no-cache');
84
+ res.end(encapsulatedData);
85
+ })
86
+ .catch((e) => next(e));
87
+ return;
88
+ }
89
+ else {
90
+ // eslint-disable-next-line no-console
91
+ console.error('Invalid component stylesheet ID request: ' + componentId);
92
+ }
93
+ }
94
+ }
95
+ }
56
96
  const mimeType = (0, mrmime_1.lookup)(extension);
57
97
  if (mimeType) {
58
98
  res.setHeader('Content-Type', mimeType);
59
99
  }
60
100
  res.setHeader('Cache-Control', 'no-cache');
61
- (0, utils_1.appendServerConfiguredHeaders)(server, res);
62
- res.end(outputFile.contents);
101
+ res.end(data);
63
102
  return;
64
103
  }
65
104
  }
@@ -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;