@angular-devkit/build-angular 17.1.2 → 17.2.0-next.1

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 (51) hide show
  1. package/package.json +31 -32
  2. package/src/builders/application/build-action.js +0 -4
  3. package/src/builders/application/execute-build.js +8 -15
  4. package/src/builders/application/index.js +19 -3
  5. package/src/builders/application/options.d.ts +3 -0
  6. package/src/builders/application/options.js +12 -1
  7. package/src/builders/application/setup-bundling.js +2 -2
  8. package/src/builders/dev-server/options.js +3 -2
  9. package/src/builders/dev-server/schema.json +1 -1
  10. package/src/builders/extract-i18n/options.js +3 -2
  11. package/src/builders/extract-i18n/schema.json +1 -1
  12. package/src/builders/jest/index.js +44 -5
  13. package/src/builders/jest/jest.config.mjs +11 -0
  14. package/src/tools/babel/plugins/elide-angular-metadata.d.ts +1 -1
  15. package/src/tools/babel/plugins/elide-angular-metadata.js +38 -30
  16. package/src/tools/babel/plugins/pure-toplevel-functions.d.ts +1 -1
  17. package/src/tools/babel/plugins/pure-toplevel-functions.js +3 -4
  18. package/src/tools/esbuild/angular/compilation/angular-compilation.d.ts +9 -2
  19. package/src/tools/esbuild/angular/compilation/angular-compilation.js +11 -3
  20. package/src/tools/esbuild/angular/compilation/aot-compilation.d.ts +2 -2
  21. package/src/tools/esbuild/angular/compilation/aot-compilation.js +19 -8
  22. package/src/tools/esbuild/angular/compilation/index.d.ts +1 -1
  23. package/src/tools/esbuild/angular/compilation/index.js +2 -1
  24. package/src/tools/esbuild/angular/compilation/jit-compilation.d.ts +2 -2
  25. package/src/tools/esbuild/angular/compilation/jit-compilation.js +12 -6
  26. package/src/tools/esbuild/angular/compilation/parallel-compilation.d.ts +2 -2
  27. package/src/tools/esbuild/angular/compilation/parallel-compilation.js +2 -2
  28. package/src/tools/esbuild/angular/compilation/parallel-worker.d.ts +2 -1
  29. package/src/tools/esbuild/angular/compilation/parallel-worker.js +2 -2
  30. package/src/tools/esbuild/angular/compiler-plugin.js +7 -7
  31. package/src/tools/esbuild/application-code-bundle.js +2 -2
  32. package/src/tools/esbuild/budget-stats.js +5 -0
  33. package/src/tools/esbuild/bundler-context.js +6 -0
  34. package/src/tools/esbuild/bundler-execution-result.d.ts +2 -0
  35. package/src/tools/esbuild/bundler-execution-result.js +6 -0
  36. package/src/tools/esbuild/compiler-plugin-options.js +2 -1
  37. package/src/tools/esbuild/global-scripts.js +3 -4
  38. package/src/tools/esbuild/global-styles.js +2 -1
  39. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  40. package/src/tools/esbuild/stylesheets/bundle-options.js +1 -0
  41. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +7 -0
  42. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.js +24 -8
  43. package/src/tools/esbuild/utils.d.ts +5 -8
  44. package/src/tools/esbuild/utils.js +64 -31
  45. package/src/tools/vite/angular-memory-plugin.js +6 -5
  46. package/src/tools/webpack/utils/stats.d.ts +1 -0
  47. package/src/tools/webpack/utils/stats.js +98 -47
  48. package/src/utils/environment-options.d.ts +2 -0
  49. package/src/utils/environment-options.js +5 -1
  50. package/src/utils/postcss-configuration.d.ts +11 -0
  51. package/src/utils/postcss-configuration.js +77 -0
package/package.json CHANGED
@@ -1,90 +1,89 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "17.1.2",
3
+ "version": "17.2.0-next.1",
4
4
  "description": "Angular Webpack Build Facade",
5
5
  "main": "src/index.js",
6
6
  "typings": "src/index.d.ts",
7
7
  "builders": "builders.json",
8
8
  "dependencies": {
9
9
  "@ampproject/remapping": "2.2.1",
10
- "@angular-devkit/architect": "0.1701.2",
11
- "@angular-devkit/build-webpack": "0.1701.2",
12
- "@angular-devkit/core": "17.1.2",
13
- "@babel/core": "7.23.7",
10
+ "@angular-devkit/architect": "0.1702.0-next.1",
11
+ "@angular-devkit/build-webpack": "0.1702.0-next.1",
12
+ "@angular-devkit/core": "17.2.0-next.1",
13
+ "@babel/core": "7.23.9",
14
14
  "@babel/generator": "7.23.6",
15
15
  "@babel/helper-annotate-as-pure": "7.22.5",
16
16
  "@babel/helper-split-export-declaration": "7.22.6",
17
- "@babel/plugin-transform-async-generator-functions": "7.23.7",
17
+ "@babel/plugin-transform-async-generator-functions": "7.23.9",
18
18
  "@babel/plugin-transform-async-to-generator": "7.23.3",
19
- "@babel/plugin-transform-runtime": "7.23.7",
20
- "@babel/preset-env": "7.23.7",
21
- "@babel/runtime": "7.23.7",
19
+ "@babel/plugin-transform-runtime": "7.23.9",
20
+ "@babel/preset-env": "7.23.9",
21
+ "@babel/runtime": "7.23.9",
22
22
  "@discoveryjs/json-ext": "0.5.7",
23
- "@ngtools/webpack": "17.1.2",
24
- "@vitejs/plugin-basic-ssl": "1.0.2",
23
+ "@ngtools/webpack": "17.2.0-next.1",
24
+ "@vitejs/plugin-basic-ssl": "1.1.0",
25
25
  "ansi-colors": "4.1.3",
26
- "autoprefixer": "10.4.16",
26
+ "autoprefixer": "10.4.17",
27
27
  "babel-loader": "9.1.3",
28
28
  "babel-plugin-istanbul": "6.1.1",
29
29
  "browserslist": "^4.21.5",
30
- "copy-webpack-plugin": "11.0.0",
30
+ "copy-webpack-plugin": "12.0.2",
31
31
  "critters": "0.0.20",
32
- "css-loader": "6.8.1",
33
- "esbuild-wasm": "0.19.11",
32
+ "css-loader": "6.10.0",
33
+ "esbuild-wasm": "0.20.0",
34
34
  "fast-glob": "3.3.2",
35
35
  "https-proxy-agent": "7.0.2",
36
36
  "http-proxy-middleware": "2.0.6",
37
- "inquirer": "9.2.12",
38
- "jsonc-parser": "3.2.0",
37
+ "inquirer": "9.2.13",
38
+ "jsonc-parser": "3.2.1",
39
39
  "karma-source-map-support": "1.4.0",
40
40
  "less": "4.2.0",
41
41
  "less-loader": "11.1.0",
42
42
  "license-webpack-plugin": "4.0.2",
43
43
  "loader-utils": "3.2.1",
44
44
  "magic-string": "0.30.5",
45
- "mini-css-extract-plugin": "2.7.6",
45
+ "mini-css-extract-plugin": "2.7.7",
46
46
  "mrmime": "2.0.0",
47
47
  "open": "8.4.2",
48
48
  "ora": "5.4.1",
49
49
  "parse5-html-rewriting-stream": "7.0.0",
50
50
  "picomatch": "3.0.1",
51
- "piscina": "4.2.1",
51
+ "piscina": "4.3.1",
52
52
  "postcss": "8.4.33",
53
- "postcss-loader": "7.3.4",
53
+ "postcss-loader": "8.1.0",
54
54
  "resolve-url-loader": "5.0.0",
55
55
  "rxjs": "7.8.1",
56
- "sass": "1.69.7",
57
- "sass-loader": "13.3.3",
56
+ "sass": "1.70.0",
57
+ "sass-loader": "14.1.0",
58
58
  "semver": "7.5.4",
59
59
  "source-map-loader": "5.0.0",
60
60
  "source-map-support": "0.5.21",
61
- "terser": "5.26.0",
62
- "text-table": "0.2.0",
61
+ "terser": "5.27.0",
63
62
  "tree-kill": "1.2.2",
64
63
  "tslib": "2.6.2",
65
- "undici": "6.2.1",
64
+ "undici": "6.5.0",
66
65
  "vite": "5.0.12",
67
66
  "watchpack": "2.4.0",
68
- "webpack": "5.89.0",
67
+ "webpack": "5.90.0",
69
68
  "webpack-dev-middleware": "6.1.1",
70
69
  "webpack-dev-server": "4.15.1",
71
70
  "webpack-merge": "5.10.0",
72
71
  "webpack-subresource-integrity": "5.1.0"
73
72
  },
74
73
  "optionalDependencies": {
75
- "esbuild": "0.19.11"
74
+ "esbuild": "0.20.0"
76
75
  },
77
76
  "peerDependencies": {
78
- "@angular/compiler-cli": "^17.0.0",
79
- "@angular/localize": "^17.0.0",
80
- "@angular/platform-server": "^17.0.0",
81
- "@angular/service-worker": "^17.0.0",
77
+ "@angular/compiler-cli": "^17.0.0 || ^17.2.0-next.0",
78
+ "@angular/localize": "^17.0.0 || ^17.2.0-next.0",
79
+ "@angular/platform-server": "^17.0.0 || ^17.2.0-next.0",
80
+ "@angular/service-worker": "^17.0.0 || ^17.2.0-next.0",
82
81
  "@web/test-runner": "^0.18.0",
83
82
  "browser-sync": "^3.0.2",
84
83
  "jest": "^29.5.0",
85
84
  "jest-environment-jsdom": "^29.5.0",
86
85
  "karma": "^6.3.0",
87
- "ng-packagr": "^17.0.0",
86
+ "ng-packagr": "^17.0.0 || ^17.2.0-next.0",
88
87
  "protractor": "^7.0.0",
89
88
  "tailwindcss": "^2.0.0 || ^3.0.0",
90
89
  "typescript": ">=5.2 <5.4"
@@ -54,8 +54,6 @@ async function* runEsBuildBuildAction(action, options) {
54
54
  try {
55
55
  // Perform the build action
56
56
  result = await withProgress('Building...', () => action());
57
- // Log all diagnostic (error/warning) messages from the build
58
- await (0, utils_1.logMessages)(logger, result);
59
57
  }
60
58
  finally {
61
59
  // Ensure Sass workers are shutdown if not watching
@@ -142,8 +140,6 @@ async function* runEsBuildBuildAction(action, options) {
142
140
  logger.info(changes.toDebugString());
143
141
  }
144
142
  result = await withProgress('Changes detected. Rebuilding...', () => action(result.createRebuildState(changes)));
145
- // Log all diagnostic (error/warning) messages from the rebuild
146
- await (0, utils_1.logMessages)(logger, result);
147
143
  // Update watched locations provided by the new build result.
148
144
  // Keep watching all previous files if there are any errors; otherwise consider all
149
145
  // files stale until confirmed present in the new result's watch files.
@@ -16,14 +16,13 @@ const commonjs_checker_1 = require("../../tools/esbuild/commonjs-checker");
16
16
  const license_extractor_1 = require("../../tools/esbuild/license-extractor");
17
17
  const utils_1 = require("../../tools/esbuild/utils");
18
18
  const bundle_calculator_1 = require("../../utils/bundle-calculator");
19
- const color_1 = require("../../utils/color");
20
19
  const copy_assets_1 = require("../../utils/copy-assets");
21
20
  const supported_browsers_1 = require("../../utils/supported-browsers");
22
21
  const execute_post_bundle_1 = require("./execute-post-bundle");
23
22
  const i18n_1 = require("./i18n");
24
23
  const setup_bundling_1 = require("./setup-bundling");
25
24
  async function executeBuild(options, context, rebuildState) {
26
- const { projectRoot, workspaceRoot, i18nOptions, optimizationOptions, assets, cacheOptions, prerenderOptions, } = options;
25
+ const { projectRoot, workspaceRoot, i18nOptions, optimizationOptions, assets, cacheOptions, prerenderOptions, ssrOptions, verbose, colors, jsonLogs, } = options;
27
26
  // TODO: Consider integrating into watch mode. Would require full rebuild on target changes.
28
27
  const browsers = (0, supported_browsers_1.getSupportedBrowsers)(projectRoot, context.logger);
29
28
  // Load active translations if inlining
@@ -96,12 +95,11 @@ async function executeBuild(options, context, rebuildState) {
96
95
  executionResult.addOutputFile('3rdpartylicenses.txt', await (0, license_extractor_1.extractLicenses)(metafile, workspaceRoot), bundler_context_1.BuildOutputFileType.Root);
97
96
  }
98
97
  // Perform i18n translation inlining if enabled
99
- let prerenderedRoutes;
100
98
  if (i18nOptions.shouldInline) {
101
99
  const result = await (0, i18n_1.inlineI18n)(options, executionResult, initialFiles);
102
100
  executionResult.addErrors(result.errors);
103
101
  executionResult.addWarnings(result.warnings);
104
- prerenderedRoutes = result.prerenderedRoutes;
102
+ executionResult.addPrerenderedRoutes(result.prerenderedRoutes);
105
103
  }
106
104
  else {
107
105
  const result = await (0, execute_post_bundle_1.executePostBundleSteps)(options, executionResult.outputFiles, executionResult.assetFiles, initialFiles,
@@ -109,26 +107,21 @@ async function executeBuild(options, context, rebuildState) {
109
107
  i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined);
110
108
  executionResult.addErrors(result.errors);
111
109
  executionResult.addWarnings(result.warnings);
112
- prerenderedRoutes = result.prerenderedRoutes;
110
+ executionResult.addPrerenderedRoutes(result.prerenderedRoutes);
113
111
  executionResult.outputFiles.push(...result.additionalOutputFiles);
114
112
  executionResult.assetFiles.push(...result.additionalAssets);
115
113
  }
116
114
  if (prerenderOptions) {
117
- executionResult.addOutputFile('prerendered-routes.json', JSON.stringify({ routes: prerenderedRoutes.sort((a, b) => a.localeCompare(b)) }, null, 2), bundler_context_1.BuildOutputFileType.Root);
118
- let prerenderMsg = `Prerendered ${prerenderedRoutes.length} static route`;
119
- if (prerenderedRoutes.length > 1) {
120
- prerenderMsg += 's.';
121
- }
122
- else {
123
- prerenderMsg += '.';
124
- }
125
- context.logger.info(color_1.colors.magenta(prerenderMsg) + '\n');
115
+ const prerenderedRoutes = executionResult.prerenderedRoutes;
116
+ executionResult.addOutputFile('prerendered-routes.json', JSON.stringify({ routes: prerenderedRoutes }, null, 2), bundler_context_1.BuildOutputFileType.Root);
126
117
  }
127
- (0, utils_1.logBuildStats)(context.logger, metafile, initialFiles, budgetFailures, changedFiles, estimatedTransferSizes);
128
118
  // Write metafile if stats option is enabled
129
119
  if (options.stats) {
130
120
  executionResult.addOutputFile('stats.json', JSON.stringify(metafile, null, 2), bundler_context_1.BuildOutputFileType.Root);
131
121
  }
122
+ if (!jsonLogs) {
123
+ context.logger.info((0, utils_1.logBuildStats)(metafile, initialFiles, budgetFailures, colors, changedFiles, estimatedTransferSizes, !!ssrOptions, verbose));
124
+ }
132
125
  return executionResult;
133
126
  }
134
127
  exports.executeBuild = executeBuild;
@@ -10,6 +10,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.buildApplication = exports.buildApplicationInternal = void 0;
11
11
  const architect_1 = require("@angular-devkit/architect");
12
12
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
13
+ const utils_1 = require("../../tools/esbuild/utils");
14
+ const color_1 = require("../../utils/color");
13
15
  const purge_cache_1 = require("../../utils/purge-cache");
14
16
  const version_1 = require("../../utils/version");
15
17
  const build_action_1 = require("./build-action");
@@ -57,11 +59,25 @@ context, infrastructureSettings, extensions) {
57
59
  context.addTeardown(() => controller.abort('builder-teardown'));
58
60
  }
59
61
  yield* (0, build_action_1.runEsBuildBuildAction)(async (rebuildState) => {
62
+ const { prerenderOptions, outputOptions, jsonLogs } = normalizedOptions;
60
63
  const startTime = process.hrtime.bigint();
61
64
  const result = await (0, execute_build_1.executeBuild)(normalizedOptions, context, rebuildState);
62
- const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
63
- const status = result.errors.length > 0 ? 'failed' : 'complete';
64
- logger.info(`Application bundle generation ${status}. [${buildTime.toFixed(3)} seconds]`);
65
+ if (!jsonLogs) {
66
+ if (prerenderOptions) {
67
+ const prerenderedRoutesLength = result.prerenderedRoutes.length;
68
+ let prerenderMsg = `Prerendered ${prerenderedRoutesLength} static route`;
69
+ prerenderMsg += prerenderedRoutesLength !== 1 ? 's.' : '.';
70
+ logger.info(color_1.colors.magenta(prerenderMsg));
71
+ }
72
+ const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
73
+ const hasError = result.errors.length > 0;
74
+ if (writeToFileSystem && !hasError) {
75
+ logger.info(`Output location: ${outputOptions.base}\n`);
76
+ }
77
+ logger.info(`Application bundle generation ${hasError ? 'failed' : 'complete'}. [${buildTime.toFixed(3)} seconds]`);
78
+ }
79
+ // Log all diagnostic (error/warning) messages
80
+ await (0, utils_1.logMessages)(logger, result, normalizedOptions);
65
81
  return result;
66
82
  }, {
67
83
  watch: normalizedOptions.watch,
@@ -127,6 +127,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
127
127
  file: string;
128
128
  package: string;
129
129
  } | undefined;
130
+ postcssConfiguration: import("../../utils/postcss-configuration").PostcssConfiguration | undefined;
130
131
  i18nOptions: I18nOptions & {
131
132
  duplicateTranslationBehavior?: I18NTranslation | undefined;
132
133
  missingTranslationBehavior?: I18NTranslation | undefined;
@@ -136,5 +137,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
136
137
  publicPath: string | undefined;
137
138
  plugins: Plugin[] | undefined;
138
139
  loaderExtensions: Record<string, "binary" | "file" | "text"> | undefined;
140
+ jsonLogs: boolean;
141
+ colors: boolean;
139
142
  }>;
140
143
  export {};
@@ -17,9 +17,12 @@ const node_module_1 = require("node:module");
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const helpers_1 = require("../../tools/webpack/utils/helpers");
19
19
  const utils_1 = require("../../utils");
20
+ const color_1 = require("../../utils/color");
21
+ const environment_options_1 = require("../../utils/environment-options");
20
22
  const i18n_options_1 = require("../../utils/i18n-options");
21
23
  const normalize_cache_1 = require("../../utils/normalize-cache");
22
24
  const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
25
+ const postcss_configuration_1 = require("../../utils/postcss-configuration");
23
26
  const tailwind_1 = require("../../utils/tailwind");
24
27
  const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
25
28
  const schema_1 = require("./schema");
@@ -108,6 +111,11 @@ async function normalizeOptions(context, projectName, options, extensions) {
108
111
  loaderExtensions[extension] = value;
109
112
  }
110
113
  }
114
+ const postcssConfiguration = await (0, postcss_configuration_1.loadPostcssConfiguration)(workspaceRoot, projectRoot);
115
+ // Skip tailwind configuration if postcss is customized
116
+ const tailwindConfiguration = postcssConfiguration
117
+ ? undefined
118
+ : await getTailwindConfig(workspaceRoot, projectRoot, context);
111
119
  const globalStyles = [];
112
120
  if (options.styles?.length) {
113
121
  const { entryPoints: stylesheetEntrypoints, noInjectNames } = (0, helpers_1.normalizeGlobalStyles)(options.styles || []);
@@ -212,13 +220,16 @@ async function normalizeOptions(context, projectName, options, extensions) {
212
220
  globalScripts,
213
221
  serviceWorker: typeof serviceWorker === 'string' ? node_path_1.default.join(workspaceRoot, serviceWorker) : undefined,
214
222
  indexHtmlOptions,
215
- tailwindConfiguration: await getTailwindConfig(workspaceRoot, projectRoot, context),
223
+ tailwindConfiguration,
224
+ postcssConfiguration,
216
225
  i18nOptions,
217
226
  namedChunks,
218
227
  budgets: budgets?.length ? budgets : undefined,
219
228
  publicPath: deployUrl ? deployUrl : undefined,
220
229
  plugins: extensions?.codePlugins?.length ? extensions?.codePlugins : undefined,
221
230
  loaderExtensions,
231
+ jsonLogs: environment_options_1.useJSONBuildLogs,
232
+ colors: color_1.colors.enabled,
222
233
  };
223
234
  }
224
235
  exports.normalizeOptions = normalizeOptions;
@@ -59,11 +59,11 @@ function setupBundlerContexts(options, browsers, codeBundleCache) {
59
59
  // Disable external deps for server bundles.
60
60
  // This is because it breaks Vite 'optimizeDeps' for SSR.
61
61
  externalPackages: false,
62
- }, nodeTargets, codeBundleCache), () => false));
62
+ }, nodeTargets, codeBundleCache)));
63
63
  // Server polyfills code
64
64
  const serverPolyfillBundleOptions = (0, application_code_bundle_1.createServerPolyfillBundleOptions)(options, nodeTargets, codeBundleCache);
65
65
  if (serverPolyfillBundleOptions) {
66
- bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, serverPolyfillBundleOptions, () => false));
66
+ bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, serverPolyfillBundleOptions));
67
67
  }
68
68
  }
69
69
  return bundlerContexts;
@@ -29,8 +29,9 @@ async function normalizeOptions(context, projectName, options) {
29
29
  const projectMetadata = await context.getProjectMetadata(projectName);
30
30
  const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
31
31
  const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
32
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33
- const buildTarget = (0, architect_1.targetFromTargetString)(options.buildTarget ?? options.browserTarget);
32
+ // Target specifier defaults to the current project's build target using a development configuration
33
+ const buildTargetSpecifier = options.buildTarget ?? options.browserTarget ?? `::development`;
34
+ const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
34
35
  // Initial options to keep
35
36
  const { host, port, poll, open, verbose, watch, allowedHosts, disableHostCheck, liveReload, hmr, headers, proxyConfig, servePath, publicHost, ssl, sslCert, sslKey, forceEsbuild, } = options;
36
37
  // Return all the normalized options
@@ -13,7 +13,7 @@
13
13
  "buildTarget": {
14
14
  "type": "string",
15
15
  "description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
16
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
16
+ "pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
17
17
  },
18
18
  "port": {
19
19
  "type": "number",
@@ -30,8 +30,9 @@ async function normalizeOptions(context, projectName, options) {
30
30
  const workspaceRoot = context.workspaceRoot;
31
31
  const projectMetadata = await context.getProjectMetadata(projectName);
32
32
  const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
33
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
34
- const buildTarget = (0, architect_1.targetFromTargetString)(options.buildTarget ?? options.browserTarget);
33
+ // Target specifier defaults to the current project's build target with no specified configuration
34
+ const buildTargetSpecifier = options.buildTarget ?? options.browserTarget ?? ':';
35
+ const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
35
36
  const i18nOptions = (0, i18n_options_1.createI18nOptions)(projectMetadata);
36
37
  // Normalize xliff format extensions
37
38
  let format = options.format;
@@ -13,7 +13,7 @@
13
13
  "buildTarget": {
14
14
  "type": "string",
15
15
  "description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
16
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
16
+ "pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
17
17
  },
18
18
  "format": {
19
19
  "type": "string",
@@ -31,15 +31,16 @@ var __importStar = (this && this.__importStar) || function (mod) {
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  const architect_1 = require("@angular-devkit/architect");
34
- const child_process_1 = require("child_process");
35
- const path = __importStar(require("path"));
36
- const util_1 = require("util");
34
+ const node_child_process_1 = require("node:child_process");
35
+ const fs = __importStar(require("node:fs/promises"));
36
+ const path = __importStar(require("node:path"));
37
+ const node_util_1 = require("node:util");
37
38
  const color_1 = require("../../utils/color");
38
39
  const test_files_1 = require("../../utils/test-files");
39
40
  const application_1 = require("../application");
40
41
  const schema_1 = require("../browser-esbuild/schema");
41
42
  const options_1 = require("./options");
42
- const execFile = (0, util_1.promisify)(child_process_1.execFile);
43
+ const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
43
44
  /** Main execution function for the Jest builder. */
44
45
  exports.default = (0, architect_1.createBuilder)(async (schema, context) => {
45
46
  context.logger.warn('NOTE: The Jest builder is currently EXPERIMENTAL and not ready for production use.');
@@ -65,8 +66,21 @@ exports.default = (0, architect_1.createBuilder)(async (schema, context) => {
65
66
  error: '`jest-environment-jsdom` is not installed. Install it with `npm install jest-environment-jsdom --save-dev`.',
66
67
  };
67
68
  }
69
+ const [testFiles, customConfig] = await Promise.all([
70
+ (0, test_files_1.findTestFiles)(options.include, options.exclude, context.workspaceRoot),
71
+ findCustomJestConfig(context.workspaceRoot),
72
+ ]);
73
+ // Warn if a custom Jest configuration is found. We won't use it, so if a developer is trying to use a custom config, this hopefully
74
+ // makes a better experience than silently ignoring the configuration.
75
+ // Ideally, this would be a hard error. However a Jest config could exist for testing other files in the workspace outside of Angular
76
+ // CLI, so we likely can't produce a hard error in this situation without an opt-out.
77
+ if (customConfig) {
78
+ context.logger.warn('A custom Jest config was found, but this is not supported by `@angular-devkit/build-angular:jest` and will be' +
79
+ ` ignored: ${customConfig}. This is an experiment to see if completely abstracting away Jest's configuration is viable. Please` +
80
+ ` consider if your use case can be met without directly modifying the Jest config. If this is a major obstacle for your use` +
81
+ ` case, please post it in this issue so we can collect feedback and evaluate: https://github.com/angular/angular-cli/issues/25434.`);
82
+ }
68
83
  // Build all the test files.
69
- const testFiles = await (0, test_files_1.findTestFiles)(options.include, options.exclude, context.workspaceRoot);
70
84
  const jestGlobal = path.join(__dirname, 'jest-global.mjs');
71
85
  const initTestBed = path.join(__dirname, 'init-test-bed.mjs');
72
86
  const buildResult = await build(context, {
@@ -94,6 +108,7 @@ exports.default = (0, architect_1.createBuilder)(async (schema, context) => {
94
108
  '--experimental-vm-modules',
95
109
  jest,
96
110
  `--rootDir="${path.join(testOut, 'browser')}"`,
111
+ `--config=${path.join(__dirname, 'jest.config.mjs')}`,
97
112
  '--testEnvironment=jsdom',
98
113
  // TODO(dgp1130): Enable cache once we have a mechanism for properly clearing / disabling it.
99
114
  '--no-cache',
@@ -158,3 +173,27 @@ function resolveModule(module) {
158
173
  return undefined;
159
174
  }
160
175
  }
176
+ /** Returns whether or not the provided directory includes a Jest configuration file. */
177
+ async function findCustomJestConfig(dir) {
178
+ const entries = await fs.readdir(dir, { withFileTypes: true });
179
+ // Jest supports many file extensions (`js`, `ts`, `cjs`, `cts`, `json`, etc.) Just look
180
+ // for anything with that prefix.
181
+ const config = entries.find((entry) => entry.isFile() && entry.name.startsWith('jest.config.'));
182
+ if (config) {
183
+ return path.join(dir, config.name);
184
+ }
185
+ // Jest also supports a `jest` key in `package.json`, look for a config there.
186
+ const packageJsonPath = path.join(dir, 'package.json');
187
+ let packageJson;
188
+ try {
189
+ packageJson = await fs.readFile(packageJsonPath, 'utf8');
190
+ }
191
+ catch {
192
+ return undefined; // No package.json, therefore no Jest configuration in it.
193
+ }
194
+ const json = JSON.parse(packageJson);
195
+ if ('jest' in json) {
196
+ return packageJsonPath;
197
+ }
198
+ return undefined;
199
+ }
@@ -0,0 +1,11 @@
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.io/license
7
+ */
8
+
9
+ // Empty config file, everything is specified via CLI options right now.
10
+ // This file is used just so Jest doesn't accidentally inherit a custom user-specified Jest config.
11
+ export default {};
@@ -7,7 +7,7 @@
7
7
  */
8
8
  /// <reference path="../../../../../../../../../../packages/angular_devkit/build_angular/src/babel-bazel.d.ts" />
9
9
  /// <reference types="@angular/compiler-cli/private/babel" />
10
- import { PluginObj } from '@babel/core';
10
+ import type { PluginObj } from '@babel/core';
11
11
  /**
12
12
  * Provides one or more keywords that if found within the content of a source file indicate
13
13
  * that this plugin should be used with a source file.
@@ -8,7 +8,6 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.getKeywords = void 0;
11
- const core_1 = require("@babel/core");
12
11
  /**
13
12
  * The name of the Angular class metadata function created by the Angular compiler.
14
13
  */
@@ -28,9 +27,18 @@ const SET_CLASS_DEBUG_INFO_NAME = 'ɵsetClassDebugInfo';
28
27
  * @returns An a string iterable containing one or more keywords.
29
28
  */
30
29
  function getKeywords() {
31
- return [SET_CLASS_METADATA_NAME, SET_CLASS_METADATA_ASYNC_NAME, SET_CLASS_DEBUG_INFO_NAME];
30
+ return Object.keys(angularMetadataFunctions);
32
31
  }
33
32
  exports.getKeywords = getKeywords;
33
+ /**
34
+ * An object map of function names and related value checks for discovery of Angular generated
35
+ * metadata calls.
36
+ */
37
+ const angularMetadataFunctions = {
38
+ [SET_CLASS_METADATA_NAME]: isSetClassMetadataCall,
39
+ [SET_CLASS_METADATA_ASYNC_NAME]: isSetClassMetadataAsyncCall,
40
+ [SET_CLASS_DEBUG_INFO_NAME]: isSetClassDebugInfoCall,
41
+ };
34
42
  /**
35
43
  * A babel plugin factory function for eliding the Angular class metadata function (`ɵsetClassMetadata`).
36
44
  *
@@ -40,20 +48,23 @@ function default_1() {
40
48
  return {
41
49
  visitor: {
42
50
  CallExpression(path) {
43
- const callee = path.node.callee;
44
- const callArguments = path.node.arguments;
51
+ const callee = path.get('callee');
45
52
  // The function being called must be the metadata function name
46
53
  let calleeName;
47
- if (core_1.types.isMemberExpression(callee) && core_1.types.isIdentifier(callee.property)) {
48
- calleeName = callee.property.name;
54
+ if (callee.isMemberExpression()) {
55
+ const calleeProperty = callee.get('property');
56
+ if (calleeProperty.isIdentifier()) {
57
+ calleeName = calleeProperty.node.name;
58
+ }
59
+ }
60
+ else if (callee.isIdentifier()) {
61
+ calleeName = callee.node.name;
49
62
  }
50
- else if (core_1.types.isIdentifier(callee)) {
51
- calleeName = callee.name;
63
+ if (!calleeName) {
64
+ return;
52
65
  }
53
- if (calleeName !== undefined &&
54
- (isRemoveClassMetadataCall(calleeName, callArguments) ||
55
- isRemoveClassmetadataAsyncCall(calleeName, callArguments) ||
56
- isSetClassDebugInfoCall(calleeName, callArguments))) {
66
+ if (Object.hasOwn(angularMetadataFunctions, calleeName) &&
67
+ angularMetadataFunctions[calleeName](path.get('arguments'))) {
57
68
  // The metadata function is always emitted inside a function expression
58
69
  const parent = path.getFunctionParent();
59
70
  if (parent && (parent.isFunctionExpression() || parent.isArrowFunctionExpression())) {
@@ -68,35 +79,32 @@ function default_1() {
68
79
  }
69
80
  exports.default = default_1;
70
81
  /** Determines if a function call is a call to `setClassMetadata`. */
71
- function isRemoveClassMetadataCall(name, args) {
82
+ function isSetClassMetadataCall(callArguments) {
72
83
  // `setClassMetadata` calls have to meet the following criteria:
73
84
  // * First must be an identifier
74
85
  // * Second must be an array literal
75
- return (name === SET_CLASS_METADATA_NAME &&
76
- args.length === 4 &&
77
- core_1.types.isIdentifier(args[0]) &&
78
- core_1.types.isArrayExpression(args[1]));
86
+ return (callArguments.length === 4 &&
87
+ callArguments[0].isIdentifier() &&
88
+ callArguments[1].isArrayExpression());
79
89
  }
80
90
  /** Determines if a function call is a call to `setClassMetadataAsync`. */
81
- function isRemoveClassmetadataAsyncCall(name, args) {
91
+ function isSetClassMetadataAsyncCall(callArguments) {
82
92
  // `setClassMetadataAsync` calls have to meet the following criteria:
83
93
  // * First argument must be an identifier.
84
94
  // * Second argument must be an inline function.
85
95
  // * Third argument must be an inline function.
86
- return (name === SET_CLASS_METADATA_ASYNC_NAME &&
87
- args.length === 3 &&
88
- core_1.types.isIdentifier(args[0]) &&
89
- isInlineFunction(args[1]) &&
90
- isInlineFunction(args[2]));
96
+ return (callArguments.length === 3 &&
97
+ callArguments[0].isIdentifier() &&
98
+ isInlineFunction(callArguments[1]) &&
99
+ isInlineFunction(callArguments[2]));
91
100
  }
92
101
  /** Determines if a function call is a call to `setClassDebugInfo`. */
93
- function isSetClassDebugInfoCall(name, args) {
94
- return (name === SET_CLASS_DEBUG_INFO_NAME &&
95
- args.length === 2 &&
96
- core_1.types.isIdentifier(args[0]) &&
97
- core_1.types.isObjectExpression(args[1]));
102
+ function isSetClassDebugInfoCall(callArguments) {
103
+ return (callArguments.length === 2 &&
104
+ callArguments[0].isIdentifier() &&
105
+ callArguments[1].isObjectExpression());
98
106
  }
99
107
  /** Determines if a node is an inline function expression. */
100
- function isInlineFunction(node) {
101
- return core_1.types.isFunctionExpression(node) || core_1.types.isArrowFunctionExpression(node);
108
+ function isInlineFunction(path) {
109
+ return path.isFunctionExpression() || path.isArrowFunctionExpression();
102
110
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  /// <reference path="../../../../../../../../../../packages/angular_devkit/build_angular/src/babel-bazel.d.ts" />
9
9
  /// <reference types="@angular/compiler-cli/private/babel" />
10
- import { PluginObj } from '@babel/core';
10
+ import type { PluginObj } from '@babel/core';
11
11
  /**
12
12
  * A babel plugin factory function for adding the PURE annotation to top-level new and call expressions.
13
13
  *
@@ -33,7 +33,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
33
33
  return (mod && mod.__esModule) ? mod : { "default": mod };
34
34
  };
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- const core_1 = require("@babel/core");
37
36
  const helper_annotate_as_pure_1 = __importDefault(require("@babel/helper-annotate-as-pure"));
38
37
  const tslib = __importStar(require("tslib"));
39
38
  /**
@@ -67,14 +66,14 @@ function default_1() {
67
66
  if (path.getFunctionParent()) {
68
67
  return;
69
68
  }
70
- const callee = path.node.callee;
71
- if ((core_1.types.isFunctionExpression(callee) || core_1.types.isArrowFunctionExpression(callee)) &&
69
+ const callee = path.get('callee');
70
+ if ((callee.isFunctionExpression() || callee.isArrowFunctionExpression()) &&
72
71
  path.node.arguments.length !== 0) {
73
72
  return;
74
73
  }
75
74
  // Do not annotate TypeScript helpers emitted by the TypeScript compiler.
76
75
  // TypeScript helpers are intended to cause side effects.
77
- if (core_1.types.isIdentifier(callee) && isTslibHelperName(callee.name)) {
76
+ if (callee.isIdentifier() && isTslibHelperName(callee.node.name)) {
78
77
  return;
79
78
  }
80
79
  (0, helper_annotate_as_pure_1.default)(path);