@angular/build 21.0.0-next.1 → 21.0.0-next.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/build",
3
- "version": "21.0.0-next.1",
3
+ "version": "21.0.0-next.3",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,8 +23,8 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/architect": "0.2100.0-next.1",
27
- "@babel/core": "7.28.3",
26
+ "@angular-devkit/architect": "0.2100.0-next.3",
27
+ "@babel/core": "7.28.4",
28
28
  "@babel/helper-annotate-as-pure": "7.27.3",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
30
30
  "@inquirer/confirm": "5.1.16",
@@ -35,18 +35,18 @@
35
35
  "https-proxy-agent": "7.0.6",
36
36
  "istanbul-lib-instrument": "6.0.3",
37
37
  "jsonc-parser": "3.3.1",
38
- "listr2": "9.0.2",
39
- "magic-string": "0.30.18",
38
+ "listr2": "9.0.3",
39
+ "magic-string": "0.30.19",
40
40
  "mrmime": "2.0.1",
41
41
  "parse5-html-rewriting-stream": "8.0.0",
42
42
  "picomatch": "4.0.3",
43
43
  "piscina": "5.1.3",
44
- "rolldown": "1.0.0-beta.34",
45
- "sass": "1.91.0",
44
+ "rolldown": "1.0.0-beta.36",
45
+ "sass": "1.92.1",
46
46
  "semver": "7.7.2",
47
47
  "source-map-support": "0.5.21",
48
- "tinyglobby": "0.2.14",
49
- "vite": "7.1.3",
48
+ "tinyglobby": "0.2.15",
49
+ "vite": "7.1.5",
50
50
  "watchpack": "2.4.4"
51
51
  },
52
52
  "optionalDependencies": {
@@ -60,14 +60,14 @@
60
60
  "@angular/platform-browser": "^21.0.0-next.0",
61
61
  "@angular/platform-server": "^21.0.0-next.0",
62
62
  "@angular/service-worker": "^21.0.0-next.0",
63
- "@angular/ssr": "^21.0.0-next.1",
63
+ "@angular/ssr": "^21.0.0-next.3",
64
64
  "karma": "^6.4.0",
65
65
  "less": "^4.2.0",
66
66
  "ng-packagr": "^21.0.0-next.0",
67
67
  "postcss": "^8.4.0",
68
68
  "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0",
69
69
  "tslib": "^2.3.0",
70
- "typescript": ">=5.8 <6.0",
70
+ "typescript": ">=5.9 <6.0",
71
71
  "vitest": "^3.1.1"
72
72
  },
73
73
  "peerDependenciesMeta": {
@@ -112,7 +112,7 @@
112
112
  "type": "git",
113
113
  "url": "https://github.com/angular/angular-cli.git"
114
114
  },
115
- "packageManager": "pnpm@10.15.0",
115
+ "packageManager": "pnpm@10.15.1",
116
116
  "engines": {
117
117
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
118
118
  "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
@@ -177,7 +177,10 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
177
177
  file: string;
178
178
  package: string;
179
179
  } | undefined;
180
- postcssConfiguration: import("../../utils/postcss-configuration").PostcssConfiguration | undefined;
180
+ postcssConfiguration: {
181
+ configPath: string;
182
+ config: import("../../utils/postcss-configuration").PostcssConfiguration;
183
+ } | undefined;
181
184
  i18nOptions: I18nOptions & {
182
185
  duplicateTranslationBehavior?: I18NTranslation;
183
186
  missingTranslationBehavior?: I18NTranslation;
@@ -317,6 +317,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
317
317
  // Setup server and start listening
318
318
  const serverConfiguration = await (0, server_1.setupServer)(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), componentStyles, templateUpdates, browserOptions.loader, {
319
319
  ...browserOptions.define,
320
+ 'ngJitMode': browserOptions.aot ? 'false' : 'true',
320
321
  'ngHmrMode': browserOptions.templateUpdates ? 'true' : 'false',
321
322
  }, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
322
323
  server = await createServer(serverConfiguration);
@@ -9,7 +9,8 @@ export declare function findTests(include: string[], exclude: string[], workspac
9
9
  interface TestEntrypointsOptions {
10
10
  projectSourceRoot: string;
11
11
  workspaceRoot: string;
12
+ removeTestExtension?: boolean;
12
13
  }
13
14
  /** Generate unique bundle names for a set of test files. */
14
- export declare function getTestEntrypoints(testFiles: string[], { projectSourceRoot, workspaceRoot }: TestEntrypointsOptions): Map<string, string>;
15
+ export declare function getTestEntrypoints(testFiles: string[], { projectSourceRoot, workspaceRoot, removeTestExtension }: TestEntrypointsOptions): Map<string, string>;
15
16
  export {};
@@ -21,7 +21,7 @@ async function findTests(include, exclude, workspaceRoot, projectSourceRoot) {
21
21
  return [...new Set(files.flat())];
22
22
  }
23
23
  /** Generate unique bundle names for a set of test files. */
24
- function getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot }) {
24
+ function getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot, removeTestExtension }) {
25
25
  const seen = new Set();
26
26
  return new Map(Array.from(testFiles, (testFile) => {
27
27
  const relativePath = removeRoots(testFile, [projectSourceRoot, workspaceRoot])
@@ -29,7 +29,11 @@ function getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot }) {
29
29
  .replace(/^[./\\]+/, '')
30
30
  // Replace any path separators with dashes.
31
31
  .replace(/[/\\]/g, '-');
32
- const baseName = `spec-${(0, node_path_1.basename)(relativePath, (0, node_path_1.extname)(relativePath))}`;
32
+ let fileName = (0, node_path_1.basename)(relativePath, (0, node_path_1.extname)(relativePath));
33
+ if (removeTestExtension) {
34
+ fileName = fileName.replace(/\.(spec|test)$/, '');
35
+ }
36
+ const baseName = `spec-${fileName}`;
33
37
  let uniqueName = baseName;
34
38
  let suffix = 2;
35
39
  while (seen.has(uniqueName)) {
@@ -15,7 +15,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
15
15
  cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
16
16
  buildTarget: import("@angular-devkit/architect").Target;
17
17
  include: string[];
18
- exclude: string[];
18
+ exclude: string[] | undefined;
19
19
  runnerName: import("./schema").Runner;
20
20
  codeCoverage: {
21
21
  exclude: string[] | undefined;
@@ -38,7 +38,7 @@ async function normalizeOptions(context, projectName, options) {
38
38
  // Target/configuration specified options
39
39
  buildTarget,
40
40
  include: options.include ?? ['**/*.spec.ts'],
41
- exclude: options.exclude ?? [],
41
+ exclude: options.exclude,
42
42
  runnerName: runner,
43
43
  codeCoverage: options.codeCoverage
44
44
  ? {
@@ -43,8 +43,18 @@ function createTestBedInitVirtualFile(providersFile, projectSourceRoot) {
43
43
  });
44
44
  `;
45
45
  }
46
+ function adjustOutputHashing(hashing) {
47
+ switch (hashing) {
48
+ case schema_1.OutputHashing.All:
49
+ case schema_1.OutputHashing.Media:
50
+ // Ensure media is continued to be hashed to avoid overwriting of output media files
51
+ return schema_1.OutputHashing.Media;
52
+ default:
53
+ return schema_1.OutputHashing.None;
54
+ }
55
+ }
46
56
  async function getVitestBuildOptions(options, baseBuildOptions) {
47
- const { workspaceRoot, projectSourceRoot, include, exclude, watch, tsConfig, providersFile } = options;
57
+ const { workspaceRoot, projectSourceRoot, include, exclude = [], watch, tsConfig, providersFile, } = options;
48
58
  // Find test files
49
59
  const testFiles = await (0, test_discovery_1.findTests)(include, exclude, workspaceRoot, projectSourceRoot);
50
60
  if (testFiles.length === 0) {
@@ -53,7 +63,11 @@ async function getVitestBuildOptions(options, baseBuildOptions) {
53
63
  (exclude.length ? `- Excluded: ${exclude.join(', ')}\n` : '') +
54
64
  `\nPlease check the 'test' target configuration in your project's 'angular.json' file.`);
55
65
  }
56
- const entryPoints = (0, test_discovery_1.getTestEntrypoints)(testFiles, { projectSourceRoot, workspaceRoot });
66
+ const entryPoints = (0, test_discovery_1.getTestEntrypoints)(testFiles, {
67
+ projectSourceRoot,
68
+ workspaceRoot,
69
+ removeTestExtension: true,
70
+ });
57
71
  entryPoints.set('init-testbed', 'angular:test-bed-init');
58
72
  const buildOptions = {
59
73
  ...baseBuildOptions,
@@ -70,7 +84,7 @@ async function getVitestBuildOptions(options, baseBuildOptions) {
70
84
  ssr: false,
71
85
  prerender: false,
72
86
  sourceMap: { scripts: true, vendor: false, styles: false },
73
- outputHashing: schema_1.OutputHashing.None,
87
+ outputHashing: adjustOutputHashing(baseBuildOptions.outputHashing),
74
88
  optimization: false,
75
89
  tsConfig,
76
90
  entryPoints,
@@ -13,11 +13,13 @@ export declare class VitestExecutor implements TestExecutor {
13
13
  private vitest;
14
14
  private readonly projectName;
15
15
  private readonly options;
16
- private readonly outputPath;
17
- private latestBuildResult;
18
- private readonly sigintListener;
16
+ private buildResultFiles;
17
+ private testFileToEntryPoint;
18
+ private entryPointToTestFile;
19
19
  constructor(projectName: string, options: NormalizedUnitTestBuilderOptions);
20
20
  execute(buildResult: FullResult | IncrementalResult): AsyncIterable<BuilderOutput>;
21
21
  [Symbol.asyncDispose](): Promise<void>;
22
+ private prepareSetupFiles;
23
+ private createVitestPlugins;
22
24
  private initializeVitest;
23
25
  }
@@ -12,73 +12,219 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.VitestExecutor = void 0;
14
14
  const node_assert_1 = __importDefault(require("node:assert"));
15
- const node_crypto_1 = require("node:crypto");
16
- const node_fs_1 = require("node:fs");
17
15
  const promises_1 = require("node:fs/promises");
18
16
  const node_path_1 = __importDefault(require("node:path"));
19
17
  const error_1 = require("../../../../utils/error");
20
18
  const load_esm_1 = require("../../../../utils/load-esm");
21
19
  const path_1 = require("../../../../utils/path");
22
20
  const results_1 = require("../../../application/results");
23
- const application_builder_1 = require("../../../karma/application_builder");
21
+ const test_discovery_1 = require("../../test-discovery");
24
22
  const browser_provider_1 = require("./browser-provider");
25
23
  class VitestExecutor {
26
24
  vitest;
27
25
  projectName;
28
26
  options;
29
- outputPath;
30
- latestBuildResult;
31
- // Graceful shutdown signal handler
32
- // This is needed to remove the temporary output directory on Ctrl+C
33
- sigintListener = () => {
34
- (0, node_fs_1.rmSync)(this.outputPath, { recursive: true, force: true });
35
- };
27
+ buildResultFiles = new Map();
28
+ // This is a reverse map of the entry points created in `build-options.ts`.
29
+ // It is used by the in-memory provider plugin to map the requested test file
30
+ // path back to its bundled output path.
31
+ // Example: `Map<'/path/to/src/app.spec.ts', 'spec-src-app-spec'>`
32
+ testFileToEntryPoint = new Map();
33
+ entryPointToTestFile = new Map();
36
34
  constructor(projectName, options) {
37
35
  this.projectName = projectName;
38
36
  this.options = options;
39
- this.outputPath = (0, path_1.toPosixPath)(node_path_1.default.join(options.workspaceRoot, generateOutputPath()));
40
- process.on('SIGINT', this.sigintListener);
41
37
  }
42
38
  async *execute(buildResult) {
43
- await (0, application_builder_1.writeTestFiles)(buildResult.files, this.outputPath);
44
- this.latestBuildResult = buildResult;
39
+ if (buildResult.kind === results_1.ResultKind.Full) {
40
+ this.buildResultFiles.clear();
41
+ for (const [path, file] of Object.entries(buildResult.files)) {
42
+ this.buildResultFiles.set(path, file);
43
+ }
44
+ }
45
+ else {
46
+ for (const file of buildResult.removed) {
47
+ this.buildResultFiles.delete(file.path);
48
+ }
49
+ for (const [path, file] of Object.entries(buildResult.files)) {
50
+ this.buildResultFiles.set(path, file);
51
+ }
52
+ }
53
+ // The `getTestEntrypoints` function is used here to create the same mapping
54
+ // that was used in `build-options.ts` to generate the build entry points.
55
+ // This is a deliberate duplication to avoid a larger refactoring of the
56
+ // builder's core interfaces to pass the entry points from the build setup
57
+ // phase to the execution phase.
58
+ if (this.testFileToEntryPoint.size === 0) {
59
+ const { include, exclude = [], workspaceRoot, projectSourceRoot } = this.options;
60
+ const testFiles = await (0, test_discovery_1.findTests)(include, exclude, workspaceRoot, projectSourceRoot);
61
+ const entryPoints = (0, test_discovery_1.getTestEntrypoints)(testFiles, {
62
+ projectSourceRoot,
63
+ workspaceRoot,
64
+ removeTestExtension: true,
65
+ });
66
+ for (const [entryPoint, testFile] of entryPoints) {
67
+ this.testFileToEntryPoint.set(testFile, entryPoint);
68
+ this.entryPointToTestFile.set(entryPoint + '.js', testFile);
69
+ }
70
+ }
45
71
  // Initialize Vitest if not already present.
46
72
  this.vitest ??= await this.initializeVitest();
47
73
  const vitest = this.vitest;
48
74
  let testResults;
49
75
  if (buildResult.kind === results_1.ResultKind.Incremental) {
50
- const addedFiles = buildResult.added.map((file) => node_path_1.default.join(this.outputPath, file));
51
- const modifiedFiles = buildResult.modified.map((file) => node_path_1.default.join(this.outputPath, file));
52
- if (addedFiles.length === 0 && modifiedFiles.length === 0) {
53
- yield { success: true };
54
- return;
55
- }
56
- // If new files are added, use `start` to trigger test discovery.
57
- // Also pass modified files to `start` to ensure they are re-run.
58
- if (addedFiles.length > 0) {
59
- await vitest.start([...addedFiles, ...modifiedFiles]);
76
+ // To rerun tests, Vitest needs the original test file paths, not the output paths.
77
+ const modifiedSourceFiles = new Set();
78
+ for (const modifiedFile of buildResult.modified) {
79
+ // The `modified` files in the build result are the output paths.
80
+ // We need to find the original source file path to pass to Vitest.
81
+ const source = this.entryPointToTestFile.get(modifiedFile);
82
+ if (source) {
83
+ modifiedSourceFiles.add(source);
84
+ }
85
+ vitest.invalidateFile((0, path_1.toPosixPath)(node_path_1.default.join(this.options.workspaceRoot, modifiedFile)));
60
86
  }
61
- else {
62
- // For modified files only, use the more efficient `rerunTestSpecifications`
63
- const specsToRerun = modifiedFiles.flatMap((file) => vitest.getModuleSpecifications(file));
64
- if (specsToRerun.length > 0) {
65
- modifiedFiles.forEach((file) => vitest.invalidateFile(file));
66
- testResults = await vitest.rerunTestSpecifications(specsToRerun);
87
+ const specsToRerun = [];
88
+ for (const file of modifiedSourceFiles) {
89
+ vitest.invalidateFile(file);
90
+ const specs = vitest.getModuleSpecifications(file);
91
+ if (specs) {
92
+ specsToRerun.push(...specs);
67
93
  }
68
94
  }
95
+ if (specsToRerun.length > 0) {
96
+ testResults = await vitest.rerunTestSpecifications(specsToRerun);
97
+ }
69
98
  }
70
99
  // Check if all the tests pass to calculate the result
71
- const testModules = testResults?.testModules;
72
- yield { success: testModules?.every((testModule) => testModule.ok()) ?? true };
100
+ const testModules = testResults?.testModules ?? this.vitest.state.getTestModules();
101
+ yield { success: testModules.every((testModule) => testModule.ok()) };
73
102
  }
74
103
  async [Symbol.asyncDispose]() {
75
- process.off('SIGINT', this.sigintListener);
76
104
  await this.vitest?.close();
77
- await (0, promises_1.rm)(this.outputPath, { recursive: true, force: true });
105
+ }
106
+ prepareSetupFiles() {
107
+ const { setupFiles } = this.options;
108
+ // Add setup file entries for TestBed initialization and project polyfills
109
+ const testSetupFiles = ['init-testbed.js', ...setupFiles];
110
+ // TODO: Provide additional result metadata to avoid needing to extract based on filename
111
+ if (this.buildResultFiles.has('polyfills.js')) {
112
+ testSetupFiles.unshift('polyfills.js');
113
+ }
114
+ return testSetupFiles;
115
+ }
116
+ createVitestPlugins(testSetupFiles, browserOptions) {
117
+ const { workspaceRoot } = this.options;
118
+ return [
119
+ {
120
+ name: 'angular:project-init',
121
+ // Type is incorrect. This allows a Promise<void>.
122
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
123
+ configureVitest: async (context) => {
124
+ // Create a subproject that can be configured with plugins for browser mode.
125
+ // Plugins defined directly in the vite overrides will not be present in the
126
+ // browser specific Vite instance.
127
+ await context.injectTestProjects({
128
+ test: {
129
+ name: this.projectName,
130
+ root: workspaceRoot,
131
+ globals: true,
132
+ setupFiles: testSetupFiles,
133
+ // Use `jsdom` if no browsers are explicitly configured.
134
+ // `node` is effectively no "environment" and the default.
135
+ environment: browserOptions.browser ? 'node' : 'jsdom',
136
+ browser: browserOptions.browser,
137
+ include: this.options.include,
138
+ ...(this.options.exclude ? { exclude: this.options.exclude } : {}),
139
+ },
140
+ plugins: [
141
+ {
142
+ name: 'angular:test-in-memory-provider',
143
+ enforce: 'pre',
144
+ resolveId: (id, importer) => {
145
+ if (importer && (id[0] === '.' || id[0] === '/')) {
146
+ let fullPath;
147
+ if (this.testFileToEntryPoint.has(importer)) {
148
+ fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(this.options.workspaceRoot, id));
149
+ }
150
+ else {
151
+ fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(node_path_1.default.dirname(importer), id));
152
+ }
153
+ const relativePath = node_path_1.default.relative(this.options.workspaceRoot, fullPath);
154
+ if (this.buildResultFiles.has((0, path_1.toPosixPath)(relativePath))) {
155
+ return fullPath;
156
+ }
157
+ }
158
+ if (this.testFileToEntryPoint.has(id)) {
159
+ return id;
160
+ }
161
+ (0, node_assert_1.default)(this.buildResultFiles.size > 0, 'buildResult must be available for resolving.');
162
+ const relativePath = node_path_1.default.relative(this.options.workspaceRoot, id);
163
+ if (this.buildResultFiles.has((0, path_1.toPosixPath)(relativePath))) {
164
+ return id;
165
+ }
166
+ },
167
+ load: async (id) => {
168
+ (0, node_assert_1.default)(this.buildResultFiles.size > 0, 'buildResult must be available for in-memory loading.');
169
+ // Attempt to load as a source test file.
170
+ const entryPoint = this.testFileToEntryPoint.get(id);
171
+ let outputPath;
172
+ if (entryPoint) {
173
+ outputPath = entryPoint + '.js';
174
+ // To support coverage exclusion of the actual test file, the virtual
175
+ // test entry point only references the built and bundled intermediate file.
176
+ return {
177
+ code: `import "./${outputPath}";`,
178
+ };
179
+ }
180
+ else {
181
+ // Attempt to load as a built artifact.
182
+ const relativePath = node_path_1.default.relative(this.options.workspaceRoot, id);
183
+ outputPath = (0, path_1.toPosixPath)(relativePath);
184
+ }
185
+ const outputFile = this.buildResultFiles.get(outputPath);
186
+ if (outputFile) {
187
+ const sourceMapPath = outputPath + '.map';
188
+ const sourceMapFile = this.buildResultFiles.get(sourceMapPath);
189
+ const code = outputFile.origin === 'memory'
190
+ ? Buffer.from(outputFile.contents).toString('utf-8')
191
+ : await (0, promises_1.readFile)(outputFile.inputPath, 'utf-8');
192
+ const map = sourceMapFile
193
+ ? sourceMapFile.origin === 'memory'
194
+ ? Buffer.from(sourceMapFile.contents).toString('utf-8')
195
+ : await (0, promises_1.readFile)(sourceMapFile.inputPath, 'utf-8')
196
+ : undefined;
197
+ return {
198
+ code,
199
+ map: map ? JSON.parse(map) : undefined,
200
+ };
201
+ }
202
+ },
203
+ },
204
+ {
205
+ name: 'angular:html-index',
206
+ transformIndexHtml: () => {
207
+ // Add all global stylesheets
208
+ if (this.buildResultFiles.has('styles.css')) {
209
+ return [
210
+ {
211
+ tag: 'link',
212
+ attrs: { href: 'styles.css', rel: 'stylesheet' },
213
+ injectTo: 'head',
214
+ },
215
+ ];
216
+ }
217
+ return [];
218
+ },
219
+ },
220
+ ],
221
+ });
222
+ },
223
+ },
224
+ ];
78
225
  }
79
226
  async initializeVitest() {
80
- const { codeCoverage, reporters, workspaceRoot, setupFiles, browsers, debug, watch } = this.options;
81
- const { outputPath, projectName, latestBuildResult } = this;
227
+ const { codeCoverage, reporters, workspaceRoot, browsers, debug, watch } = this.options;
82
228
  let vitestNodeModule;
83
229
  try {
84
230
  vitestNodeModule = await (0, load_esm_1.loadEsmModule)('vitest/node');
@@ -96,14 +242,9 @@ class VitestExecutor {
96
242
  if (browserOptions.errors?.length) {
97
243
  throw new Error(browserOptions.errors.join('\n'));
98
244
  }
99
- (0, node_assert_1.default)(latestBuildResult, 'buildResult must be available before initializing vitest');
100
- // Add setup file entries for TestBed initialization and project polyfills
101
- const testSetupFiles = ['init-testbed.js', ...setupFiles];
102
- // TODO: Provide additional result metadata to avoid needing to extract based on filename
103
- const polyfillsFile = Object.keys(latestBuildResult.files).find((f) => f === 'polyfills.js');
104
- if (polyfillsFile) {
105
- testSetupFiles.unshift(polyfillsFile);
106
- }
245
+ (0, node_assert_1.default)(this.buildResultFiles.size > 0, 'buildResult must be available before initializing vitest');
246
+ const testSetupFiles = this.prepareSetupFiles();
247
+ const plugins = this.createVitestPlugins(testSetupFiles, browserOptions);
107
248
  const debugOptions = debug
108
249
  ? {
109
250
  inspectBrk: true,
@@ -115,12 +256,12 @@ class VitestExecutor {
115
256
  // Disable configuration file resolution/loading
116
257
  config: false,
117
258
  root: workspaceRoot,
118
- project: ['base', projectName],
259
+ project: ['base', this.projectName],
119
260
  name: 'base',
120
261
  include: [],
121
262
  reporters: reporters ?? ['default'],
122
263
  watch,
123
- coverage: generateCoverageOption(codeCoverage, workspaceRoot, this.outputPath),
264
+ coverage: generateCoverageOption(codeCoverage),
124
265
  ...debugOptions,
125
266
  }, {
126
267
  server: {
@@ -128,61 +269,12 @@ class VitestExecutor {
128
269
  // be enabled as it controls other internal behavior related to rerunning tests.
129
270
  watch: null,
130
271
  },
131
- plugins: [
132
- {
133
- name: 'angular:project-init',
134
- async configureVitest(context) {
135
- // Create a subproject that can be configured with plugins for browser mode.
136
- // Plugins defined directly in the vite overrides will not be present in the
137
- // browser specific Vite instance.
138
- const [project] = await context.injectTestProjects({
139
- test: {
140
- name: projectName,
141
- root: outputPath,
142
- globals: true,
143
- setupFiles: testSetupFiles,
144
- // Use `jsdom` if no browsers are explicitly configured.
145
- // `node` is effectively no "environment" and the default.
146
- environment: browserOptions.browser ? 'node' : 'jsdom',
147
- browser: browserOptions.browser,
148
- },
149
- plugins: [
150
- {
151
- name: 'angular:html-index',
152
- transformIndexHtml: () => {
153
- (0, node_assert_1.default)(latestBuildResult, 'buildResult must be available for HTML index transformation.');
154
- // Add all global stylesheets
155
- const styleFiles = Object.entries(latestBuildResult.files).filter(([file]) => file === 'styles.css');
156
- return styleFiles.map(([href]) => ({
157
- tag: 'link',
158
- attrs: { href, rel: 'stylesheet' },
159
- injectTo: 'head',
160
- }));
161
- },
162
- },
163
- ],
164
- });
165
- // Adjust coverage excludes to not include the otherwise automatically inserted included unit tests.
166
- // Vite does this as a convenience but is problematic for the bundling strategy employed by the
167
- // builder's test setup. To workaround this, the excludes are adjusted here to only automatically
168
- // exclude the TypeScript source test files.
169
- project.config.coverage.exclude = [
170
- ...(codeCoverage?.exclude ?? []),
171
- '**/*.{test,spec}.?(c|m)ts',
172
- ];
173
- },
174
- },
175
- ],
272
+ plugins,
176
273
  });
177
274
  }
178
275
  }
179
276
  exports.VitestExecutor = VitestExecutor;
180
- function generateOutputPath() {
181
- const datePrefix = new Date().toISOString().replaceAll(/[-:.]/g, '');
182
- const uuidSuffix = (0, node_crypto_1.randomUUID)().slice(0, 8);
183
- return node_path_1.default.join('dist', 'test-out', `${datePrefix}-${uuidSuffix}`);
184
- }
185
- function generateCoverageOption(codeCoverage, workspaceRoot, outputPath) {
277
+ function generateCoverageOption(codeCoverage) {
186
278
  if (!codeCoverage) {
187
279
  return {
188
280
  enabled: false,
@@ -191,8 +283,8 @@ function generateCoverageOption(codeCoverage, workspaceRoot, outputPath) {
191
283
  return {
192
284
  enabled: true,
193
285
  excludeAfterRemap: true,
194
- include: [`${(0, path_1.toPosixPath)(node_path_1.default.relative(workspaceRoot, outputPath))}/**`],
195
- // Special handling for `reporter` due to an undefined value causing upstream failures
286
+ // Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures
287
+ ...(codeCoverage.exclude ? { exclude: codeCoverage.exclude } : {}),
196
288
  ...(codeCoverage.reporters
197
289
  ? { reporter: codeCoverage.reporters }
198
290
  : {}),
@@ -39,7 +39,6 @@
39
39
  "items": {
40
40
  "type": "string"
41
41
  },
42
- "default": [],
43
42
  "description": "Globs of files to exclude, relative to the project root."
44
43
  },
45
44
  "watch": {
@@ -61,8 +60,7 @@
61
60
  "description": "Globs to exclude from code coverage.",
62
61
  "items": {
63
62
  "type": "string"
64
- },
65
- "default": []
63
+ }
66
64
  },
67
65
  "codeCoverageReporters": {
68
66
  "type": "array",
@@ -418,6 +418,8 @@ function getEsBuildCommonOptions(options) {
418
418
  packages = 'external';
419
419
  }
420
420
  }
421
+ const minifySyntax = optimizationOptions.scripts;
422
+ const minifyIdentifiers = minifySyntax && environment_options_1.allowMangle;
421
423
  return {
422
424
  absWorkingDir: workspaceRoot,
423
425
  format: 'esm',
@@ -429,9 +431,10 @@ function getEsBuildCommonOptions(options) {
429
431
  metafile: true,
430
432
  legalComments: options.extractLicenses ? 'none' : 'eof',
431
433
  logLevel: options.verbose && !jsonLogs ? 'debug' : 'silent',
432
- minifyIdentifiers: optimizationOptions.scripts && environment_options_1.allowMangle,
433
- minifySyntax: optimizationOptions.scripts,
434
- minifyWhitespace: optimizationOptions.scripts,
434
+ keepNames: !minifyIdentifiers,
435
+ minifyIdentifiers,
436
+ minifySyntax,
437
+ minifyWhitespace: minifySyntax,
435
438
  pure: ['forwardRef'],
436
439
  outdir: workspaceRoot,
437
440
  outExtension: outExtension ? { '.js': `.${outExtension}` } : undefined,
@@ -448,7 +451,7 @@ function getEsBuildCommonOptions(options) {
448
451
  // Only set to false when script optimizations are enabled. It should not be set to true because
449
452
  // Angular turns `ngDevMode` into an object for development debugging purposes when not defined
450
453
  // which a constant true value would break.
451
- ...(optimizationOptions.scripts ? { 'ngDevMode': 'false' } : undefined),
454
+ ...(minifySyntax ? { 'ngDevMode': 'false' } : undefined),
452
455
  'ngJitMode': jit ? 'true' : 'false',
453
456
  'ngServerMode': 'false',
454
457
  'ngHmrMode': options.templateUpdates ? 'true' : 'false',
@@ -29,7 +29,10 @@ export interface BundleStylesheetOptions {
29
29
  file: string;
30
30
  package: string;
31
31
  };
32
- postcssConfiguration?: PostcssConfiguration;
32
+ postcssConfiguration?: {
33
+ config: PostcssConfiguration;
34
+ configPath: string;
35
+ };
33
36
  publicPath?: string;
34
37
  cacheOptions: NormalizedCachedOptions;
35
38
  }
@@ -46,7 +46,10 @@ export interface StylesheetPluginOptions {
46
46
  * initialized and used for every stylesheet. This overrides the tailwind integration
47
47
  * and any tailwind usage must be manually configured in the custom postcss usage.
48
48
  */
49
- postcssConfiguration?: PostcssConfiguration;
49
+ postcssConfiguration?: {
50
+ config: PostcssConfiguration;
51
+ configPath: string;
52
+ };
50
53
  /**
51
54
  * Optional Options for configuring Sass behavior.
52
55
  */
@@ -46,6 +46,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.StylesheetPluginFactory = void 0;
47
47
  const node_assert_1 = __importDefault(require("node:assert"));
48
48
  const promises_1 = require("node:fs/promises");
49
+ const node_module_1 = require("node:module");
49
50
  const node_path_1 = require("node:path");
50
51
  const tinyglobby_1 = require("tinyglobby");
51
52
  const error_1 = require("../../../utils/error");
@@ -143,13 +144,15 @@ class StylesheetPluginFactory {
143
144
  node_assert_1.default.equal(++this.initPostcssCallCount, 1, '`initPostcss` was called more than once.');
144
145
  const { options } = this;
145
146
  if (options.postcssConfiguration) {
146
- const postCssInstanceKey = JSON.stringify(options.postcssConfiguration);
147
+ const { config, configPath } = options.postcssConfiguration;
148
+ const postCssInstanceKey = JSON.stringify(config);
147
149
  let postcssProcessor = postcssProcessors.get(postCssInstanceKey)?.deref();
148
150
  if (!postcssProcessor) {
149
151
  postcss ??= (await Promise.resolve().then(() => __importStar(require('postcss')))).default;
150
152
  postcssProcessor = postcss();
151
- for (const [pluginName, pluginOptions] of options.postcssConfiguration.plugins) {
152
- const { default: plugin } = await Promise.resolve(`${pluginName}`).then(s => __importStar(require(s)));
153
+ const postCssPluginRequire = (0, node_module_1.createRequire)((0, node_path_1.dirname)(configPath) + '/');
154
+ for (const [pluginName, pluginOptions] of config.plugins) {
155
+ const plugin = postCssPluginRequire(pluginName);
153
156
  if (typeof plugin !== 'function' || plugin.postcss !== true) {
154
157
  throw new Error(`Attempted to load invalid Postcss plugin: "${pluginName}"`);
155
158
  }
@@ -53,6 +53,7 @@ function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless
53
53
  esbuildOptions: {
54
54
  // Set esbuild supported targets.
55
55
  target,
56
+ keepNames: true,
56
57
  supported: (0, utils_1.getFeatureSupport)(target, zoneless),
57
58
  plugins,
58
59
  loader,
@@ -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 = '21.0.0-next.1';
13
+ const VERSION = '21.0.0-next.3';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -14,4 +14,7 @@ export interface SearchDirectory {
14
14
  }
15
15
  export declare function generateSearchDirectories(roots: string[]): Promise<SearchDirectory[]>;
16
16
  export declare function findTailwindConfiguration(searchDirectories: SearchDirectory[]): string | undefined;
17
- export declare function loadPostcssConfiguration(searchDirectories: SearchDirectory[]): Promise<PostcssConfiguration | undefined>;
17
+ export declare function loadPostcssConfiguration(searchDirectories: SearchDirectory[]): Promise<{
18
+ configPath: string;
19
+ config: PostcssConfiguration;
20
+ } | undefined>;
@@ -67,7 +67,7 @@ async function loadPostcssConfiguration(searchDirectories) {
67
67
  config.plugins.push(element);
68
68
  }
69
69
  }
70
- return config;
70
+ return { config, configPath };
71
71
  }
72
72
  // Normalize plugin object map form
73
73
  const entries = Object.entries(raw.plugins);
@@ -81,5 +81,5 @@ async function loadPostcssConfiguration(searchDirectories) {
81
81
  }
82
82
  config.plugins.push([name, options]);
83
83
  }
84
- return config;
84
+ return { config, configPath };
85
85
  }
@@ -6,12 +6,13 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { ApplicationRef, Type } from '@angular/core';
9
+ import type { BootstrapContext } from '@angular/platform-browser';
9
10
  import type { ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp } from '@angular/ssr';
10
11
  /**
11
12
  * Represents the exports available from the main server bundle.
12
13
  */
13
14
  interface MainServerBundleExports {
14
- default: (() => Promise<ApplicationRef>) | Type<unknown>;
15
+ default: ((context: BootstrapContext) => Promise<ApplicationRef>) | Type<unknown>;
15
16
  ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree;
16
17
  ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp;
17
18
  }
package/src/typings.d.ts DELETED
@@ -1,19 +0,0 @@
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
-
9
- // The `bundled_beasties` causes issues with module mappings in Bazel,
10
- // leading to unexpected behavior with esbuild. Specifically, the problem occurs
11
- // when esbuild resolves to a different module or version than expected, due to
12
- // how Bazel handles module mappings.
13
- //
14
- // This change aims to resolve esbuild types correctly and maintain consistency
15
- // in the Bazel build process.
16
-
17
- declare module 'esbuild' {
18
- export * from 'esbuild-wasm';
19
- }