@angular-devkit/build-angular 17.1.0 → 17.2.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +27 -27
  2. package/src/builders/dev-server/options.js +3 -2
  3. package/src/builders/dev-server/schema.json +1 -1
  4. package/src/builders/dev-server/vite-server.js +14 -213
  5. package/src/builders/extract-i18n/options.js +3 -2
  6. package/src/builders/extract-i18n/schema.json +1 -1
  7. package/src/builders/jest/index.js +44 -5
  8. package/src/builders/jest/jest.config.mjs +11 -0
  9. package/src/builders/prerender/routes-extractor-worker.js +1 -1
  10. package/src/tools/esbuild/angular/compilation/angular-compilation.d.ts +9 -2
  11. package/src/tools/esbuild/angular/compilation/angular-compilation.js +11 -3
  12. package/src/tools/esbuild/angular/compilation/aot-compilation.d.ts +2 -2
  13. package/src/tools/esbuild/angular/compilation/aot-compilation.js +19 -8
  14. package/src/tools/esbuild/angular/compilation/index.d.ts +1 -1
  15. package/src/tools/esbuild/angular/compilation/index.js +2 -1
  16. package/src/tools/esbuild/angular/compilation/jit-compilation.d.ts +2 -2
  17. package/src/tools/esbuild/angular/compilation/jit-compilation.js +12 -6
  18. package/src/tools/esbuild/angular/compilation/parallel-compilation.d.ts +2 -2
  19. package/src/tools/esbuild/angular/compilation/parallel-compilation.js +2 -2
  20. package/src/tools/esbuild/angular/compilation/parallel-worker.d.ts +2 -1
  21. package/src/tools/esbuild/angular/compilation/parallel-worker.js +2 -2
  22. package/src/tools/esbuild/angular/compiler-plugin.js +7 -7
  23. package/src/tools/esbuild/angular/component-stylesheets.js +10 -8
  24. package/src/tools/esbuild/application-code-bundle.js +12 -3
  25. package/src/tools/esbuild/bundler-context.js +12 -8
  26. package/src/tools/esbuild/external-packages-plugin.d.ts +16 -0
  27. package/src/tools/esbuild/external-packages-plugin.js +66 -0
  28. package/src/tools/esbuild/stylesheets/css-resource-plugin.js +3 -2
  29. package/src/tools/vite/angular-memory-plugin.d.ts +24 -0
  30. package/src/tools/vite/angular-memory-plugin.js +252 -0
  31. package/src/utils/environment-options.d.ts +1 -0
  32. package/src/utils/environment-options.js +3 -1
  33. package/src/utils/index-file/inline-critical-css.js +28 -20
  34. package/src/utils/server-rendering/render-page.js +5 -0
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "17.1.0",
3
+ "version": "17.2.0-next.0",
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.0",
11
- "@angular-devkit/build-webpack": "0.1701.0",
12
- "@angular-devkit/core": "17.1.0",
10
+ "@angular-devkit/architect": "0.1702.0-next.0",
11
+ "@angular-devkit/build-webpack": "0.1702.0-next.0",
12
+ "@angular-devkit/core": "17.2.0-next.0",
13
13
  "@babel/core": "7.23.7",
14
14
  "@babel/generator": "7.23.6",
15
15
  "@babel/helper-annotate-as-pure": "7.22.5",
@@ -17,53 +17,53 @@
17
17
  "@babel/plugin-transform-async-generator-functions": "7.23.7",
18
18
  "@babel/plugin-transform-async-to-generator": "7.23.3",
19
19
  "@babel/plugin-transform-runtime": "7.23.7",
20
- "@babel/preset-env": "7.23.7",
21
- "@babel/runtime": "7.23.7",
20
+ "@babel/preset-env": "7.23.8",
21
+ "@babel/runtime": "7.23.8",
22
22
  "@discoveryjs/json-ext": "0.5.7",
23
- "@ngtools/webpack": "17.1.0",
24
- "@vitejs/plugin-basic-ssl": "1.0.2",
23
+ "@ngtools/webpack": "17.2.0-next.0",
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.9.1",
33
+ "esbuild-wasm": "0.19.12",
34
34
  "fast-glob": "3.3.2",
35
35
  "https-proxy-agent": "7.0.2",
36
36
  "http-proxy-middleware": "2.0.6",
37
37
  "inquirer": "9.2.12",
38
- "jsonc-parser": "3.2.0",
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.0",
52
52
  "postcss": "8.4.33",
53
- "postcss-loader": "7.3.4",
53
+ "postcss-loader": "8.0.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.0.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",
61
+ "terser": "5.27.0",
62
62
  "text-table": "0.2.0",
63
63
  "tree-kill": "1.2.2",
64
64
  "tslib": "2.6.2",
65
- "undici": "6.2.1",
66
- "vite": "5.0.11",
65
+ "undici": "6.4.0",
66
+ "vite": "5.0.12",
67
67
  "watchpack": "2.4.0",
68
68
  "webpack": "5.89.0",
69
69
  "webpack-dev-middleware": "6.1.1",
@@ -72,19 +72,19 @@
72
72
  "webpack-subresource-integrity": "5.1.0"
73
73
  },
74
74
  "optionalDependencies": {
75
- "esbuild": "0.19.11"
75
+ "esbuild": "0.19.12"
76
76
  },
77
77
  "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",
78
+ "@angular/compiler-cli": "^17.0.0 || ^17.2.0-next.0",
79
+ "@angular/localize": "^17.0.0 || ^17.2.0-next.0",
80
+ "@angular/platform-server": "^17.0.0 || ^17.2.0-next.0",
81
+ "@angular/service-worker": "^17.0.0 || ^17.2.0-next.0",
82
82
  "@web/test-runner": "^0.18.0",
83
83
  "browser-sync": "^3.0.2",
84
84
  "jest": "^29.5.0",
85
85
  "jest-environment-jsdom": "^29.5.0",
86
86
  "karma": "^6.3.0",
87
- "ng-packagr": "^17.0.0",
87
+ "ng-packagr": "^17.0.0 || ^17.2.0-next.0",
88
88
  "protractor": "^7.0.0",
89
89
  "tailwindcss": "^2.0.0 || ^3.0.0",
90
90
  "typescript": ">=5.2 <5.4"
@@ -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",
@@ -34,8 +34,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34
34
  };
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.setupServer = exports.serveWithVite = void 0;
37
- const remapping_1 = __importDefault(require("@ampproject/remapping"));
38
- const mrmime_1 = require("mrmime");
39
37
  const node_assert_1 = __importDefault(require("node:assert"));
40
38
  const promises_1 = require("node:fs/promises");
41
39
  const node_path_1 = require("node:path");
@@ -43,10 +41,10 @@ const bundler_context_1 = require("../../tools/esbuild/bundler-context");
43
41
  const javascript_transformer_1 = require("../../tools/esbuild/javascript-transformer");
44
42
  const rxjs_esm_resolution_plugin_1 = require("../../tools/esbuild/rxjs-esm-resolution-plugin");
45
43
  const utils_1 = require("../../tools/esbuild/utils");
44
+ const angular_memory_plugin_1 = require("../../tools/vite/angular-memory-plugin");
46
45
  const i18n_locale_plugin_1 = require("../../tools/vite/i18n-locale-plugin");
47
46
  const utils_2 = require("../../utils");
48
47
  const load_esm_1 = require("../../utils/load-esm");
49
- const render_page_1 = require("../../utils/server-rendering/render-page");
50
48
  const supported_browsers_1 = require("../../utils/supported-browsers");
51
49
  const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
52
50
  const application_1 = require("../application");
@@ -324,7 +322,6 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
324
322
  }
325
323
  }
326
324
  }
327
- // eslint-disable-next-line max-lines-per-function
328
325
  async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
329
326
  const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig, true);
330
327
  // dynamically import Vite for ESM compatibility
@@ -344,7 +341,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
344
341
  publicDir: false,
345
342
  esbuild: false,
346
343
  mode: 'development',
347
- appType: 'spa',
344
+ appType: 'mpa',
348
345
  css: {
349
346
  devSourcemap: true,
350
347
  },
@@ -409,189 +406,18 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
409
406
  },
410
407
  plugins: [
411
408
  (0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)(),
412
- {
413
- name: 'vite:angular-memory',
414
- // Ensures plugin hooks run before built-in Vite hooks
415
- enforce: 'pre',
416
- async resolveId(source, importer) {
417
- // Prevent vite from resolving an explicit external dependency (`externalDependencies` option)
418
- if (externalMetadata.explicit.includes(source)) {
419
- // This is still not ideal since Vite will still transform the import specifier to
420
- // `/@id/${source}` but is currently closer to a raw external than a resolved file path.
421
- return source;
422
- }
423
- if (importer && source[0] === '.' && importer.startsWith(virtualProjectRoot)) {
424
- // Remove query if present
425
- const [importerFile] = importer.split('?', 1);
426
- source =
427
- '/' +
428
- normalizePath((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source));
429
- }
430
- const [file] = source.split('?', 1);
431
- if (outputFiles.has(file)) {
432
- return (0, node_path_1.join)(virtualProjectRoot, source);
433
- }
434
- },
435
- load(id) {
436
- const [file] = id.split('?', 1);
437
- const relativeFile = '/' + normalizePath((0, node_path_1.relative)(virtualProjectRoot, file));
438
- const codeContents = outputFiles.get(relativeFile)?.contents;
439
- if (codeContents === undefined) {
440
- if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
441
- return loadViteClientCode(file);
442
- }
443
- return;
444
- }
445
- const code = Buffer.from(codeContents).toString('utf-8');
446
- const mapContents = outputFiles.get(relativeFile + '.map')?.contents;
447
- return {
448
- // Remove source map URL comments from the code if a sourcemap is present.
449
- // Vite will inline and add an additional sourcemap URL for the sourcemap.
450
- code: mapContents ? code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '') : code,
451
- map: mapContents && Buffer.from(mapContents).toString('utf-8'),
452
- };
453
- },
454
- configureServer(server) {
455
- const originalssrTransform = server.ssrTransform;
456
- server.ssrTransform = async (code, map, url, originalCode) => {
457
- const result = await originalssrTransform(code, null, url, originalCode);
458
- if (!result || !result.map || !map) {
459
- return result;
460
- }
461
- const remappedMap = (0, remapping_1.default)([result.map, map], () => null);
462
- // Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root.
463
- remappedMap.sourceRoot = normalizePath(serverOptions.workspaceRoot) + '/';
464
- return {
465
- ...result,
466
- map: remappedMap,
467
- };
468
- };
469
- // Assets and resources get handled first
470
- server.middlewares.use(function angularAssetsMiddleware(req, res, next) {
471
- if (req.url === undefined || res.writableEnded) {
472
- return;
473
- }
474
- // Parse the incoming request.
475
- // The base of the URL is unused but required to parse the URL.
476
- const pathname = pathnameWithoutBasePath(req.url, server.config.base);
477
- const extension = (0, node_path_1.extname)(pathname);
478
- // Rewrite all build assets to a vite raw fs URL
479
- const assetSourcePath = assets.get(pathname);
480
- if (assetSourcePath !== undefined) {
481
- // Workaround to disable Vite transformer middleware.
482
- // See: https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/middlewares/transform.ts#L201 and
483
- // https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/transformRequest.ts#L204-L206
484
- req.headers.accept = 'text/html';
485
- // The encoding needs to match what happens in the vite static middleware.
486
- // ref: https://github.com/vitejs/vite/blob/d4f13bd81468961c8c926438e815ab6b1c82735e/packages/vite/src/node/server/middlewares/static.ts#L163
487
- req.url = `${server.config.base}@fs/${encodeURI(assetSourcePath)}`;
488
- next();
489
- return;
490
- }
491
- // Resource files are handled directly.
492
- // Global stylesheets (CSS files) are currently considered resources to workaround
493
- // dev server sourcemap issues with stylesheets.
494
- if (extension !== '.js' && extension !== '.html') {
495
- const outputFile = outputFiles.get(pathname);
496
- if (outputFile?.servable) {
497
- const mimeType = (0, mrmime_1.lookup)(extension);
498
- if (mimeType) {
499
- res.setHeader('Content-Type', mimeType);
500
- }
501
- res.setHeader('Cache-Control', 'no-cache');
502
- if (serverOptions.headers) {
503
- Object.entries(serverOptions.headers).forEach(([name, value]) => res.setHeader(name, value));
504
- }
505
- res.end(outputFile.contents);
506
- return;
507
- }
508
- }
509
- next();
510
- });
511
- if (extensionMiddleware?.length) {
512
- extensionMiddleware.forEach((middleware) => server.middlewares.use(middleware));
513
- }
514
- // Returning a function, installs middleware after the main transform middleware but
515
- // before the built-in HTML middleware
516
- return () => {
517
- function angularSSRMiddleware(req, res, next) {
518
- const url = req.originalUrl;
519
- if (
520
- // Skip if path is not defined.
521
- !url ||
522
- // Skip if path is like a file.
523
- // NOTE: We use a regexp to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
524
- /^\.[a-z]{2,4}$/i.test((0, node_path_1.extname)(url.split('?')[0]))) {
525
- next();
526
- return;
527
- }
528
- const rawHtml = outputFiles.get('/index.server.html')?.contents;
529
- if (!rawHtml) {
530
- next();
531
- return;
532
- }
533
- transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, async (html) => {
534
- const { content } = await (0, render_page_1.renderPage)({
535
- document: html,
536
- route: new URL(req.originalUrl ?? '/', server.resolvedUrls?.local[0]).toString(),
537
- serverContext: 'ssr',
538
- loadBundle: (uri) =>
539
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
540
- server.ssrLoadModule(uri.slice(1)),
541
- // Files here are only needed for critical CSS inlining.
542
- outputFiles: {},
543
- // TODO: add support for critical css inlining.
544
- inlineCriticalCss: false,
545
- });
546
- return indexHtmlTransformer && content
547
- ? await indexHtmlTransformer(content)
548
- : content;
549
- });
550
- }
551
- if (ssr) {
552
- server.middlewares.use(angularSSRMiddleware);
553
- }
554
- server.middlewares.use(function angularIndexMiddleware(req, res, next) {
555
- if (!req.url) {
556
- next();
557
- return;
558
- }
559
- // Parse the incoming request.
560
- // The base of the URL is unused but required to parse the URL.
561
- const pathname = pathnameWithoutBasePath(req.url, server.config.base);
562
- if (pathname === '/' || pathname === `/index.html`) {
563
- const rawHtml = outputFiles.get('/index.html')?.contents;
564
- if (rawHtml) {
565
- transformIndexHtmlAndAddHeaders(req.url, rawHtml, res, next, indexHtmlTransformer);
566
- return;
567
- }
568
- }
569
- next();
570
- });
571
- };
572
- function transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, additionalTransformer) {
573
- server
574
- .transformIndexHtml(url, Buffer.from(rawHtml).toString('utf-8'))
575
- .then(async (processedHtml) => {
576
- if (additionalTransformer) {
577
- const content = await additionalTransformer(processedHtml);
578
- if (!content) {
579
- next();
580
- return;
581
- }
582
- processedHtml = content;
583
- }
584
- res.setHeader('Content-Type', 'text/html');
585
- res.setHeader('Cache-Control', 'no-cache');
586
- if (serverOptions.headers) {
587
- Object.entries(serverOptions.headers).forEach(([name, value]) => res.setHeader(name, value));
588
- }
589
- res.end(processedHtml);
590
- })
591
- .catch((error) => next(error));
592
- }
593
- },
594
- },
409
+ (0, angular_memory_plugin_1.createAngularMemoryPlugin)({
410
+ workspaceRoot: serverOptions.workspaceRoot,
411
+ virtualProjectRoot,
412
+ outputFiles,
413
+ assets,
414
+ ssr,
415
+ external: externalMetadata.explicit,
416
+ indexHtmlTransformer,
417
+ extensionMiddleware,
418
+ extraHeaders: serverOptions.headers,
419
+ normalizePath,
420
+ }),
595
421
  ],
596
422
  // Browser only optimizeDeps. (This does not run for SSR dependencies).
597
423
  optimizeDeps: getDepOptimizationConfig({
@@ -627,31 +453,6 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
627
453
  return configuration;
628
454
  }
629
455
  exports.setupServer = setupServer;
630
- /**
631
- * Reads the resolved Vite client code from disk and updates the content to remove
632
- * an unactionable suggestion to update the Vite configuration file to disable the
633
- * error overlay. The Vite configuration file is not present when used in the Angular
634
- * CLI.
635
- * @param file The absolute path to the Vite client code.
636
- * @returns
637
- */
638
- async function loadViteClientCode(file) {
639
- const originalContents = await (0, promises_1.readFile)(file, 'utf-8');
640
- let contents = originalContents.replace('You can also disable this overlay by setting', '');
641
- contents = contents.replace(
642
- // eslint-disable-next-line max-len
643
- '<code part="config-option-name">server.hmr.overlay</code> to <code part="config-option-value">false</code> in <code part="config-file-name">vite.config.js.</code>', '');
644
- (0, node_assert_1.default)(originalContents !== contents, 'Failed to update Vite client error overlay text.');
645
- return contents;
646
- }
647
- function pathnameWithoutBasePath(url, basePath) {
648
- const parsedUrl = new URL(url, 'http://localhost');
649
- const pathname = decodeURIComponent(parsedUrl.pathname);
650
- // slice(basePath.length - 1) to retain the trailing slash
651
- return basePath !== '/' && pathname.startsWith(basePath)
652
- ? pathname.slice(basePath.length - 1)
653
- : pathname;
654
- }
655
456
  function getDepOptimizationConfig({ disabled, exclude, include, target, prebundleTransformer, ssr, loader, thirdPartySourcemaps, }) {
656
457
  const plugins = [
657
458
  {
@@ -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 {};
@@ -43,7 +43,7 @@ async function extract() {
43
43
  const browserIndexInputPath = path.join(outputPath, indexFile);
44
44
  const document = await fs.promises.readFile(browserIndexInputPath, 'utf8');
45
45
  const bootstrapAppFnOrModule = bootstrapAppFn || AppServerModule;
46
- (0, node_assert_1.default)(bootstrapAppFnOrModule, `Neither an AppServerModule nor a bootstrapping function was exported from: ${serverBundlePath}.`);
46
+ (0, node_assert_1.default)(bootstrapAppFnOrModule, `The file "${serverBundlePath}" does not have a default export for an AppServerModule or a bootstrapping function.`);
47
47
  const routes = [];
48
48
  for await (const { route, success } of extractRoutes(bootstrapAppFnOrModule, document)) {
49
49
  if (success) {
@@ -14,6 +14,13 @@ export interface EmitFileResult {
14
14
  contents: string;
15
15
  dependencies?: readonly string[];
16
16
  }
17
+ export declare enum DiagnosticModes {
18
+ None = 0,
19
+ Option = 1,
20
+ Syntactic = 2,
21
+ Semantic = 4,
22
+ All = 7
23
+ }
17
24
  export declare abstract class AngularCompilation {
18
25
  #private;
19
26
  static loadCompilerCli(): Promise<typeof ng>;
@@ -25,8 +32,8 @@ export declare abstract class AngularCompilation {
25
32
  referencedFiles: readonly string[];
26
33
  }>;
27
34
  abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;
28
- protected abstract collectDiagnostics(): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
29
- diagnoseFiles(): Promise<{
35
+ protected abstract collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
36
+ diagnoseFiles(modes?: DiagnosticModes): Promise<{
30
37
  errors?: PartialMessage[];
31
38
  warnings?: PartialMessage[];
32
39
  }>;
@@ -30,10 +30,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
30
30
  return result;
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.AngularCompilation = void 0;
33
+ exports.AngularCompilation = exports.DiagnosticModes = void 0;
34
34
  const load_esm_1 = require("../../../../utils/load-esm");
35
35
  const profiling_1 = require("../../profiling");
36
36
  const diagnostics_1 = require("../diagnostics");
37
+ var DiagnosticModes;
38
+ (function (DiagnosticModes) {
39
+ DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
40
+ DiagnosticModes[DiagnosticModes["Option"] = 1] = "Option";
41
+ DiagnosticModes[DiagnosticModes["Syntactic"] = 2] = "Syntactic";
42
+ DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
43
+ DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
44
+ })(DiagnosticModes || (exports.DiagnosticModes = DiagnosticModes = {}));
37
45
  class AngularCompilation {
38
46
  static #angularCompilerCliModule;
39
47
  static #typescriptModule;
@@ -64,13 +72,13 @@ class AngularCompilation {
64
72
  supportJitMode: false,
65
73
  }));
66
74
  }
67
- async diagnoseFiles() {
75
+ async diagnoseFiles(modes = DiagnosticModes.All) {
68
76
  const result = {};
69
77
  // Avoid loading typescript until actually needed.
70
78
  // This allows for avoiding the load of typescript in the main thread when using the parallel compilation.
71
79
  const typescript = await AngularCompilation.loadTypescript();
72
80
  await (0, profiling_1.profileAsync)('NG_DIAGNOSTICS_TOTAL', async () => {
73
- for (const diagnostic of await this.collectDiagnostics()) {
81
+ for (const diagnostic of await this.collectDiagnostics(modes)) {
74
82
  const message = (0, diagnostics_1.convertTypeScriptDiagnostic)(typescript, diagnostic);
75
83
  if (diagnostic.category === typescript.DiagnosticCategory.Error) {
76
84
  (result.errors ??= []).push(message);
@@ -8,7 +8,7 @@
8
8
  import type ng from '@angular/compiler-cli';
9
9
  import ts from 'typescript';
10
10
  import { AngularHostOptions } from '../angular-host';
11
- import { AngularCompilation, EmitFileResult } from './angular-compilation';
11
+ import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
12
12
  export declare class AotCompilation extends AngularCompilation {
13
13
  #private;
14
14
  initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions): Promise<{
@@ -16,6 +16,6 @@ export declare class AotCompilation extends AngularCompilation {
16
16
  compilerOptions: ng.CompilerOptions;
17
17
  referencedFiles: readonly string[];
18
18
  }>;
19
- collectDiagnostics(): Iterable<ts.Diagnostic>;
19
+ collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
20
20
  emitAffectedFiles(): Iterable<EmitFileResult>;
21
21
  }