@angular-devkit/build-angular 17.1.0-next.2 → 17.1.0-rc.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 (57) hide show
  1. package/builders.json +5 -0
  2. package/package.json +24 -20
  3. package/src/builders/app-shell/index.js +7 -0
  4. package/src/builders/application/build-action.d.ts +4 -3
  5. package/src/builders/application/build-action.js +8 -5
  6. package/src/builders/application/execute-build.js +10 -4
  7. package/src/builders/application/index.d.ts +20 -10
  8. package/src/builders/application/index.js +38 -26
  9. package/src/builders/application/options.d.ts +11 -3
  10. package/src/builders/application/options.js +42 -30
  11. package/src/builders/application/schema.d.ts +32 -2
  12. package/src/builders/application/schema.json +40 -2
  13. package/src/builders/browser-esbuild/index.js +8 -4
  14. package/src/builders/dev-server/vite-server.js +7 -13
  15. package/src/builders/jest/index.js +2 -2
  16. package/src/builders/prerender/index.js +7 -0
  17. package/src/builders/ssr-dev-server/index.js +17 -31
  18. package/src/builders/web-test-runner/builder-status-warnings.d.ts +11 -0
  19. package/src/builders/web-test-runner/builder-status-warnings.js +46 -0
  20. package/src/builders/web-test-runner/index.d.ts +10 -0
  21. package/src/builders/web-test-runner/index.js +151 -0
  22. package/src/builders/web-test-runner/jasmine_runner.js +88 -0
  23. package/src/builders/web-test-runner/options.d.ts +24 -0
  24. package/src/builders/web-test-runner/options.js +26 -0
  25. package/src/builders/web-test-runner/schema.d.ts +188 -0
  26. package/src/builders/web-test-runner/schema.js +15 -0
  27. package/src/builders/web-test-runner/schema.json +291 -0
  28. package/src/builders/web-test-runner/test_page.html +40 -0
  29. package/src/tools/esbuild/angular/angular-host.js +1 -1
  30. package/src/tools/esbuild/angular/compiler-plugin.js +10 -26
  31. package/src/tools/esbuild/angular/component-stylesheets.d.ts +3 -6
  32. package/src/tools/esbuild/angular/component-stylesheets.js +46 -60
  33. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +2 -2
  34. package/src/tools/esbuild/bundler-context.d.ts +1 -1
  35. package/src/tools/esbuild/bundler-context.js +18 -2
  36. package/src/tools/esbuild/cache.d.ts +88 -0
  37. package/src/tools/esbuild/cache.js +92 -0
  38. package/src/tools/esbuild/compiler-plugin-options.js +1 -1
  39. package/src/tools/esbuild/index-html-generator.js +3 -1
  40. package/src/tools/esbuild/javascript-transformer-worker.d.ts +2 -2
  41. package/src/tools/esbuild/javascript-transformer-worker.js +12 -5
  42. package/src/tools/esbuild/javascript-transformer.d.ts +3 -1
  43. package/src/tools/esbuild/javascript-transformer.js +42 -17
  44. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +1 -1
  45. package/src/tools/esbuild/stylesheets/sass-language.js +3 -12
  46. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.js +9 -1
  47. package/src/tools/esbuild/utils.d.ts +2 -2
  48. package/src/tools/esbuild/utils.js +61 -74
  49. package/src/tools/sass/lexer.d.ts +0 -11
  50. package/src/tools/sass/lexer.js +1 -87
  51. package/src/utils/index-file/index-html-generator.js +15 -28
  52. package/src/utils/index.d.ts +1 -0
  53. package/src/utils/index.js +1 -0
  54. package/src/{builders/dev-server → utils}/load-proxy-config.js +2 -2
  55. package/src/{builders/jest → utils}/test-files.d.ts +1 -2
  56. package/src/{builders/jest → utils}/test-files.js +3 -3
  57. /package/src/{builders/dev-server → utils}/load-proxy-config.d.ts +0 -0
@@ -14,17 +14,8 @@ exports.ComponentStylesheetBundler = void 0;
14
14
  const node_crypto_1 = require("node:crypto");
15
15
  const node_path_1 = __importDefault(require("node:path"));
16
16
  const bundler_context_1 = require("../bundler-context");
17
+ const cache_1 = require("../cache");
17
18
  const bundle_options_1 = require("../stylesheets/bundle-options");
18
- class BundlerContextCache extends Map {
19
- getOrCreate(key, creator) {
20
- let value = this.get(key);
21
- if (value === undefined) {
22
- value = creator();
23
- this.set(key, value);
24
- }
25
- return value;
26
- }
27
- }
28
19
  /**
29
20
  * Bundles component stylesheets. A stylesheet can be either an inline stylesheet that
30
21
  * is contained within the Component's metadata definition or an external file referenced
@@ -33,8 +24,8 @@ class BundlerContextCache extends Map {
33
24
  class ComponentStylesheetBundler {
34
25
  options;
35
26
  incremental;
36
- #fileContexts = new BundlerContextCache();
37
- #inlineContexts = new BundlerContextCache();
27
+ #fileContexts = new cache_1.MemoryCache();
28
+ #inlineContexts = new cache_1.MemoryCache();
38
29
  /**
39
30
  *
40
31
  * @param options An object containing the stylesheet bundling options.
@@ -45,14 +36,14 @@ class ComponentStylesheetBundler {
45
36
  this.incremental = incremental;
46
37
  }
47
38
  async bundleFile(entry) {
48
- const bundlerContext = this.#fileContexts.getOrCreate(entry, () => {
39
+ const bundlerContext = await this.#fileContexts.getOrCreate(entry, () => {
49
40
  return new bundler_context_1.BundlerContext(this.options.workspaceRoot, this.incremental, (loadCache) => {
50
41
  const buildOptions = (0, bundle_options_1.createStylesheetBundleOptions)(this.options, loadCache);
51
42
  buildOptions.entryPoints = [entry];
52
43
  return buildOptions;
53
44
  });
54
45
  });
55
- return extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles);
46
+ return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles);
56
47
  }
57
48
  async bundleInline(data, filename, language) {
58
49
  // Use a hash of the inline stylesheet content to ensure a consistent identifier. External stylesheets will resolve
@@ -60,7 +51,7 @@ class ComponentStylesheetBundler {
60
51
  // TODO: Consider xxhash instead for hashing
61
52
  const id = (0, node_crypto_1.createHash)('sha256').update(data).digest('hex');
62
53
  const entry = [language, id, filename].join(';');
63
- const bundlerContext = this.#inlineContexts.getOrCreate(entry, () => {
54
+ const bundlerContext = await this.#inlineContexts.getOrCreate(entry, () => {
64
55
  const namespace = 'angular:styles/component';
65
56
  return new bundler_context_1.BundlerContext(this.options.workspaceRoot, this.incremental, (loadCache) => {
66
57
  const buildOptions = (0, bundle_options_1.createStylesheetBundleOptions)(this.options, loadCache, {
@@ -92,7 +83,7 @@ class ComponentStylesheetBundler {
92
83
  });
93
84
  });
94
85
  // Extract the result of the bundling from the output files
95
- return extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles);
86
+ return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles);
96
87
  }
97
88
  invalidate(files) {
98
89
  if (!this.incremental) {
@@ -112,51 +103,46 @@ class ComponentStylesheetBundler {
112
103
  this.#inlineContexts.clear();
113
104
  await Promise.allSettled(contexts.map((context) => context.dispose()));
114
105
  }
115
- }
116
- exports.ComponentStylesheetBundler = ComponentStylesheetBundler;
117
- function extractResult(result, referencedFiles) {
118
- let contents = '';
119
- let map;
120
- let outputPath;
121
- const resourceFiles = [];
122
- if (!result.errors) {
123
- for (const outputFile of result.outputFiles) {
124
- const filename = node_path_1.default.basename(outputFile.path);
125
- if (outputFile.type === bundler_context_1.BuildOutputFileType.Media) {
126
- // The output files could also contain resources (images/fonts/etc.) that were referenced
127
- resourceFiles.push(outputFile);
128
- }
129
- else if (filename.endsWith('.css')) {
130
- outputPath = outputFile.path;
131
- contents = outputFile.text;
132
- }
133
- else if (filename.endsWith('.css.map')) {
134
- map = outputFile.text;
135
- }
136
- else {
137
- throw new Error(`Unexpected non CSS/Media file "${filename}" outputted during component stylesheet processing.`);
106
+ extractResult(result, referencedFiles) {
107
+ let contents = '';
108
+ let metafile;
109
+ const outputFiles = [];
110
+ if (!result.errors) {
111
+ for (const outputFile of result.outputFiles) {
112
+ const filename = node_path_1.default.basename(outputFile.path);
113
+ // Needed for Bazel as otherwise the files will not be written in the correct place.
114
+ outputFile.path = node_path_1.default.join(this.options.workspaceRoot, outputFile.path);
115
+ if (outputFile.type === bundler_context_1.BuildOutputFileType.Media) {
116
+ // The output files could also contain resources (images/fonts/etc.) that were referenced
117
+ outputFiles.push(outputFile);
118
+ }
119
+ else if (filename.endsWith('.css')) {
120
+ contents = outputFile.text;
121
+ }
122
+ else if (filename.endsWith('.css.map')) {
123
+ outputFiles.push(outputFile);
124
+ }
125
+ else {
126
+ throw new Error(`Unexpected non CSS/Media file "${filename}" outputted during component stylesheet processing.`);
127
+ }
138
128
  }
129
+ metafile = result.metafile;
130
+ // Remove entryPoint fields from outputs to prevent the internal component styles from being
131
+ // treated as initial files. Also mark the entry as a component resource for stat reporting.
132
+ Object.values(metafile.outputs).forEach((output) => {
133
+ delete output.entryPoint;
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ output['ng-component'] = true;
136
+ });
139
137
  }
138
+ return {
139
+ errors: result.errors,
140
+ warnings: result.warnings,
141
+ contents,
142
+ outputFiles,
143
+ metafile,
144
+ referencedFiles,
145
+ };
140
146
  }
141
- let metafile;
142
- if (!result.errors) {
143
- metafile = result.metafile;
144
- // Remove entryPoint fields from outputs to prevent the internal component styles from being
145
- // treated as initial files. Also mark the entry as a component resource for stat reporting.
146
- Object.values(metafile.outputs).forEach((output) => {
147
- delete output.entryPoint;
148
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
- output['ng-component'] = true;
150
- });
151
- }
152
- return {
153
- errors: result.errors,
154
- warnings: result.warnings,
155
- contents,
156
- map,
157
- path: outputPath,
158
- resourceFiles,
159
- metafile,
160
- referencedFiles,
161
- };
162
147
  }
148
+ exports.ComponentStylesheetBundler = ComponentStylesheetBundler;
@@ -92,8 +92,8 @@ function setupJitPluginCallbacks(build, stylesheetBundler, additionalResultFiles
92
92
  else {
93
93
  stylesheetResult = await stylesheetBundler.bundleInline(entry.contents, entry.path, inlineStyleLanguage);
94
94
  }
95
- const { contents, resourceFiles, errors, warnings, metafile, referencedFiles } = stylesheetResult;
96
- additionalResultFiles.set(entry.path, { outputFiles: resourceFiles, metafile });
95
+ const { contents, outputFiles, errors, warnings, metafile, referencedFiles } = stylesheetResult;
96
+ additionalResultFiles.set(entry.path, { outputFiles, metafile });
97
97
  return {
98
98
  errors,
99
99
  warnings,
@@ -20,6 +20,7 @@ export type BundleContextResult = {
20
20
  server?: Set<string>;
21
21
  browser?: Set<string>;
22
22
  };
23
+ externalConfiguration?: string[];
23
24
  };
24
25
  export interface InitialFileRecord {
25
26
  entrypoint: boolean;
@@ -35,7 +36,6 @@ export declare enum BuildOutputFileType {
35
36
  }
36
37
  export interface BuildOutputFile extends OutputFile {
37
38
  type: BuildOutputFileType;
38
- fullOutputPath: string;
39
39
  clone: () => BuildOutputFile;
40
40
  }
41
41
  export type BundlerOptionsFactory<T extends BuildOptions = BuildOptions> = (loadCache: LoadResultCache | undefined) => T;
@@ -6,9 +6,13 @@
6
6
  * Use of this source code is governed by an MIT-style license that can be
7
7
  * found in the LICENSE file at https://angular.io/license
8
8
  */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
9
12
  Object.defineProperty(exports, "__esModule", { value: true });
10
13
  exports.BundlerContext = exports.BuildOutputFileType = void 0;
11
14
  const esbuild_1 = require("esbuild");
15
+ const node_assert_1 = __importDefault(require("node:assert"));
12
16
  const node_path_1 = require("node:path");
13
17
  const load_result_cache_1 = require("./load-result-cache");
14
18
  const utils_1 = require("./utils");
@@ -71,6 +75,7 @@ class BundlerContext {
71
75
  const externalImportsBrowser = new Set();
72
76
  const externalImportsServer = new Set();
73
77
  const outputFiles = [];
78
+ let externalConfiguration;
74
79
  for (const result of individualResults) {
75
80
  warnings.push(...result.warnings);
76
81
  if (result.errors) {
@@ -87,6 +92,12 @@ class BundlerContext {
87
92
  outputFiles.push(...result.outputFiles);
88
93
  result.externalImports.browser?.forEach((value) => externalImportsBrowser.add(value));
89
94
  result.externalImports.server?.forEach((value) => externalImportsServer.add(value));
95
+ if (result.externalConfiguration) {
96
+ externalConfiguration ??= new Set();
97
+ for (const value of result.externalConfiguration) {
98
+ externalConfiguration.add(value);
99
+ }
100
+ }
90
101
  }
91
102
  if (errors !== undefined) {
92
103
  return { errors, warnings };
@@ -101,6 +112,7 @@ class BundlerContext {
101
112
  browser: externalImportsBrowser,
102
113
  server: externalImportsServer,
103
114
  },
115
+ externalConfiguration: externalConfiguration ? [...externalConfiguration] : undefined,
104
116
  };
105
117
  }
106
118
  /**
@@ -250,10 +262,13 @@ class BundlerContext {
250
262
  externalImports.add(importData.path);
251
263
  }
252
264
  }
253
- const platformIsServer = this.#esbuildOptions?.platform === 'node';
265
+ (0, node_assert_1.default)(this.#esbuildOptions, 'esbuild options cannot be undefined.');
266
+ const { platform, assetNames = '' } = this.#esbuildOptions;
267
+ const platformIsServer = platform === 'node';
268
+ const mediaDirname = (0, node_path_1.dirname)(assetNames);
254
269
  const outputFiles = result.outputFiles.map((file) => {
255
270
  let fileType;
256
- if ((0, node_path_1.dirname)(file.path) === 'media') {
271
+ if ((0, node_path_1.dirname)(file.path) === mediaDirname) {
257
272
  fileType = BuildOutputFileType.Media;
258
273
  }
259
274
  else {
@@ -269,6 +284,7 @@ class BundlerContext {
269
284
  externalImports: {
270
285
  [platformIsServer ? 'server' : 'browser']: externalImports,
271
286
  },
287
+ externalConfiguration: this.#esbuildOptions.external,
272
288
  errors: undefined,
273
289
  };
274
290
  }
@@ -0,0 +1,88 @@
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
+ * @fileoverview
10
+ * Provides infrastructure for common caching functionality within the build system.
11
+ */
12
+ /**
13
+ * A backing data store for one or more Cache instances.
14
+ * The interface is intentionally designed to support using a JavaScript
15
+ * Map instance as a potential cache store.
16
+ */
17
+ export interface CacheStore<V> {
18
+ /**
19
+ * Returns the specified value from the cache store or `undefined` if not found.
20
+ * @param key The key to retrieve from the store.
21
+ */
22
+ get(key: string): V | undefined | Promise<V | undefined>;
23
+ /**
24
+ * Returns whether the provided key is present in the cache store.
25
+ * @param key The key to check from the store.
26
+ */
27
+ has(key: string): boolean | Promise<boolean>;
28
+ /**
29
+ * Adds a new value to the cache store if the key is not present.
30
+ * Updates the value for the key if already present.
31
+ * @param key The key to associate with the value in the cache store.
32
+ * @param value The value to add to the cache store.
33
+ */
34
+ set(key: string, value: V): this | Promise<this>;
35
+ }
36
+ /**
37
+ * A cache object that allows accessing and storing key/value pairs in
38
+ * an underlying CacheStore. This class is the primary method for consumers
39
+ * to use a cache.
40
+ */
41
+ export declare class Cache<V, S extends CacheStore<V> = CacheStore<V>> {
42
+ protected readonly store: S;
43
+ readonly namespace?: string | undefined;
44
+ constructor(store: S, namespace?: string | undefined);
45
+ /**
46
+ * Prefixes a key with the cache namespace if present.
47
+ * @param key A key string to prefix.
48
+ * @returns A prefixed key if a namespace is present. Otherwise the provided key.
49
+ */
50
+ protected withNamespace(key: string): string;
51
+ /**
52
+ * Gets the value associated with a provided key if available.
53
+ * Otherwise, creates a value using the factory creator function, puts the value
54
+ * in the cache, and returns the new value.
55
+ * @param key A key associated with the value.
56
+ * @param creator A factory function for the value if no value is present.
57
+ * @returns A value associated with the provided key.
58
+ */
59
+ getOrCreate(key: string, creator: () => V | Promise<V>): Promise<V>;
60
+ /**
61
+ * Gets the value associated with a provided key if available.
62
+ * @param key A key associated with the value.
63
+ * @returns A value associated with the provided key if present. Otherwise, `undefined`.
64
+ */
65
+ get(key: string): Promise<V | undefined>;
66
+ /**
67
+ * Puts a value in the cache and associates it with the provided key.
68
+ * If the key is already present, the value is updated instead.
69
+ * @param key A key associated with the value.
70
+ * @param value A value to put in the cache.
71
+ */
72
+ put(key: string, value: V): Promise<void>;
73
+ }
74
+ /**
75
+ * A lightweight in-memory cache implementation based on a JavaScript Map object.
76
+ */
77
+ export declare class MemoryCache<V> extends Cache<V, Map<string, V>> {
78
+ constructor();
79
+ /**
80
+ * Removes all entries from the cache instance.
81
+ */
82
+ clear(): void;
83
+ /**
84
+ * Provides all the values currently present in the cache instance.
85
+ * @returns An iterable of all values in the cache.
86
+ */
87
+ values(): IterableIterator<V>;
88
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.io/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.MemoryCache = exports.Cache = void 0;
11
+ /**
12
+ * A cache object that allows accessing and storing key/value pairs in
13
+ * an underlying CacheStore. This class is the primary method for consumers
14
+ * to use a cache.
15
+ */
16
+ class Cache {
17
+ store;
18
+ namespace;
19
+ constructor(store, namespace) {
20
+ this.store = store;
21
+ this.namespace = namespace;
22
+ }
23
+ /**
24
+ * Prefixes a key with the cache namespace if present.
25
+ * @param key A key string to prefix.
26
+ * @returns A prefixed key if a namespace is present. Otherwise the provided key.
27
+ */
28
+ withNamespace(key) {
29
+ if (this.namespace) {
30
+ return `${this.namespace}:${key}`;
31
+ }
32
+ return key;
33
+ }
34
+ /**
35
+ * Gets the value associated with a provided key if available.
36
+ * Otherwise, creates a value using the factory creator function, puts the value
37
+ * in the cache, and returns the new value.
38
+ * @param key A key associated with the value.
39
+ * @param creator A factory function for the value if no value is present.
40
+ * @returns A value associated with the provided key.
41
+ */
42
+ async getOrCreate(key, creator) {
43
+ const namespacedKey = this.withNamespace(key);
44
+ let value = await this.store.get(namespacedKey);
45
+ if (value === undefined) {
46
+ value = await creator();
47
+ await this.store.set(namespacedKey, value);
48
+ }
49
+ return value;
50
+ }
51
+ /**
52
+ * Gets the value associated with a provided key if available.
53
+ * @param key A key associated with the value.
54
+ * @returns A value associated with the provided key if present. Otherwise, `undefined`.
55
+ */
56
+ async get(key) {
57
+ const value = await this.store.get(this.withNamespace(key));
58
+ return value;
59
+ }
60
+ /**
61
+ * Puts a value in the cache and associates it with the provided key.
62
+ * If the key is already present, the value is updated instead.
63
+ * @param key A key associated with the value.
64
+ * @param value A value to put in the cache.
65
+ */
66
+ async put(key, value) {
67
+ await this.store.set(this.withNamespace(key), value);
68
+ }
69
+ }
70
+ exports.Cache = Cache;
71
+ /**
72
+ * A lightweight in-memory cache implementation based on a JavaScript Map object.
73
+ */
74
+ class MemoryCache extends Cache {
75
+ constructor() {
76
+ super(new Map());
77
+ }
78
+ /**
79
+ * Removes all entries from the cache instance.
80
+ */
81
+ clear() {
82
+ this.store.clear();
83
+ }
84
+ /**
85
+ * Provides all the values currently present in the cache instance.
86
+ * @returns An iterable of all values in the cache.
87
+ */
88
+ values() {
89
+ return this.store.values();
90
+ }
91
+ }
92
+ exports.MemoryCache = MemoryCache;
@@ -32,7 +32,7 @@ function createCompilerPluginOptions(options, target, sourceFileCache) {
32
32
  // Hidden component stylesheet sourcemaps are inaccessible which is effectively
33
33
  // the same as being disabled. Disabling has the advantage of avoiding the overhead
34
34
  // of sourcemap processing.
35
- !!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'),
35
+ sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
36
36
  outputNames,
37
37
  includePaths: stylePreprocessorOptions?.includePaths,
38
38
  externalDependencies,
@@ -45,7 +45,7 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
45
45
  const hints = [];
46
46
  const { indexHtmlOptions, externalPackages, optimizationOptions, crossOrigin, subresourceIntegrity, baseHref, } = buildOptions;
47
47
  (0, node_assert_1.default)(indexHtmlOptions, 'indexHtmlOptions cannot be undefined.');
48
- if (!externalPackages) {
48
+ if (!externalPackages && indexHtmlOptions.preloadInitial) {
49
49
  for (const [key, value] of initialFiles) {
50
50
  if (value.entrypoint) {
51
51
  // Entry points are already referenced in the HTML
@@ -87,6 +87,7 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
87
87
  },
88
88
  crossOrigin: crossOrigin,
89
89
  deployUrl: buildOptions.publicPath,
90
+ postTransform: indexHtmlOptions.transformer,
90
91
  });
91
92
  indexHtmlGenerator.readAsset = readAsset;
92
93
  const transformResult = await indexHtmlGenerator.process({
@@ -110,6 +111,7 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
110
111
  const { InlineCriticalCssProcessor } = await Promise.resolve().then(() => __importStar(require('../../utils/index-file/inline-critical-css')));
111
112
  const inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
112
113
  minify: false, // CSS has already been minified during the build.
114
+ deployUrl: buildOptions.publicPath,
113
115
  readAsset,
114
116
  });
115
117
  const { content, errors, warnings } = await inlineCriticalCssProcessor.process(contentWithoutCriticalCssInlined, {
@@ -7,7 +7,7 @@
7
7
  */
8
8
  interface JavaScriptTransformRequest {
9
9
  filename: string;
10
- data: string;
10
+ data: string | Uint8Array;
11
11
  sourcemap: boolean;
12
12
  thirdPartySourcemaps: boolean;
13
13
  advancedOptimizations: boolean;
@@ -15,5 +15,5 @@ interface JavaScriptTransformRequest {
15
15
  sideEffects?: boolean;
16
16
  jit: boolean;
17
17
  }
18
- export default function transformJavaScript(request: JavaScriptTransformRequest): Promise<Uint8Array>;
18
+ export default function transformJavaScript(request: JavaScriptTransformRequest): Promise<unknown>;
19
19
  export {};
@@ -29,19 +29,26 @@ var __importStar = (this && this.__importStar) || function (mod) {
29
29
  __setModuleDefault(result, mod);
30
30
  return result;
31
31
  };
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
32
35
  Object.defineProperty(exports, "__esModule", { value: true });
33
36
  const core_1 = require("@babel/core");
34
- const promises_1 = require("node:fs/promises");
37
+ const piscina_1 = __importDefault(require("piscina"));
35
38
  const application_1 = __importStar(require("../../tools/babel/presets/application"));
36
39
  const load_esm_1 = require("../../utils/load-esm");
40
+ const textDecoder = new TextDecoder();
41
+ const textEncoder = new TextEncoder();
37
42
  async function transformJavaScript(request) {
38
- request.data ??= await (0, promises_1.readFile)(request.filename, 'utf-8');
39
- const transformedData = await transformWithBabel(request);
40
- return Buffer.from(transformedData, 'utf-8');
43
+ const { filename, data, ...options } = request;
44
+ const textData = typeof data === 'string' ? data : textDecoder.decode(data);
45
+ const transformedData = await transformWithBabel(filename, textData, options);
46
+ // Transfer the data via `move` instead of cloning
47
+ return piscina_1.default.move(textEncoder.encode(transformedData));
41
48
  }
42
49
  exports.default = transformJavaScript;
43
50
  let linkerPluginCreator;
44
- async function transformWithBabel({ filename, data, ...options }) {
51
+ async function transformWithBabel(filename, data, options) {
45
52
  const shouldLink = !options.skipLinker && (await (0, application_1.requiresLinking)(filename, data));
46
53
  const useInputSourcemap = options.sourcemap &&
47
54
  (!!options.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
@@ -5,6 +5,7 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
+ import { Cache } from './cache';
8
9
  /**
9
10
  * Transformation options that should apply to all transformed files and data.
10
11
  */
@@ -24,7 +25,8 @@ export interface JavaScriptTransformerOptions {
24
25
  export declare class JavaScriptTransformer {
25
26
  #private;
26
27
  readonly maxThreads: number;
27
- constructor(options: JavaScriptTransformerOptions, maxThreads: number, reuseResults?: boolean);
28
+ private readonly cache?;
29
+ constructor(options: JavaScriptTransformerOptions, maxThreads: number, cache?: Cache<Uint8Array, import("./cache").CacheStore<Uint8Array>> | undefined);
28
30
  /**
29
31
  * Performs JavaScript transformations on a file from the filesystem.
30
32
  * If no transformations are required, the data for the original file will be returned.
@@ -11,6 +11,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.JavaScriptTransformer = void 0;
14
+ const node_crypto_1 = require("node:crypto");
15
+ const promises_1 = require("node:fs/promises");
14
16
  const piscina_1 = __importDefault(require("piscina"));
15
17
  /**
16
18
  * A class that performs transformation of JavaScript files and raw data.
@@ -21,11 +23,13 @@ const piscina_1 = __importDefault(require("piscina"));
21
23
  */
22
24
  class JavaScriptTransformer {
23
25
  maxThreads;
26
+ cache;
24
27
  #workerPool;
25
28
  #commonOptions;
26
- #pendingfileResults;
27
- constructor(options, maxThreads, reuseResults) {
29
+ #fileCacheKeyBase;
30
+ constructor(options, maxThreads, cache) {
28
31
  this.maxThreads = maxThreads;
32
+ this.cache = cache;
29
33
  // Extract options to ensure only the named options are serialized and sent to the worker
30
34
  const { sourcemap, thirdPartySourcemaps = false, advancedOptimizations = false, jit = false, } = options;
31
35
  this.#commonOptions = {
@@ -34,10 +38,7 @@ class JavaScriptTransformer {
34
38
  advancedOptimizations,
35
39
  jit,
36
40
  };
37
- // Currently only tracks pending file transform results
38
- if (reuseResults) {
39
- this.#pendingfileResults = new Map();
40
- }
41
+ this.#fileCacheKeyBase = Buffer.from(JSON.stringify(this.#commonOptions), 'utf-8');
41
42
  }
42
43
  #ensureWorkerPool() {
43
44
  this.#workerPool ??= new piscina_1.default({
@@ -57,21 +58,46 @@ class JavaScriptTransformer {
57
58
  * @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
58
59
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
59
60
  */
60
- transformFile(filename, skipLinker, sideEffects) {
61
- const pendingKey = `${!!skipLinker}--${filename}`;
62
- let pending = this.#pendingfileResults?.get(pendingKey);
63
- if (pending === undefined) {
64
- // Always send the request to a worker. Files are almost always from node modules which means
65
- // they may need linking. The data is also not yet available to perform most transformation checks.
66
- pending = this.#ensureWorkerPool().run({
61
+ async transformFile(filename, skipLinker, sideEffects) {
62
+ const data = await (0, promises_1.readFile)(filename);
63
+ let result;
64
+ let cacheKey;
65
+ if (this.cache) {
66
+ // Create a cache key from the file data and options that effect the output.
67
+ // NOTE: If additional options are added, this may need to be updated.
68
+ // TODO: Consider xxhash or similar instead of SHA256
69
+ const hash = (0, node_crypto_1.createHash)('sha256');
70
+ hash.update(`${!!skipLinker}--${!!sideEffects}`);
71
+ hash.update(data);
72
+ hash.update(this.#fileCacheKeyBase);
73
+ cacheKey = hash.digest('hex');
74
+ try {
75
+ result = await this.cache?.get(cacheKey);
76
+ }
77
+ catch {
78
+ // Failure to get the value should not fail the transform
79
+ }
80
+ }
81
+ if (result === undefined) {
82
+ // If there is no cache or no cached entry, process the file
83
+ result = (await this.#ensureWorkerPool().run({
67
84
  filename,
85
+ data,
68
86
  skipLinker,
69
87
  sideEffects,
70
88
  ...this.#commonOptions,
71
- });
72
- this.#pendingfileResults?.set(pendingKey, pending);
89
+ }, { transferList: [data.buffer] }));
90
+ // If there is a cache then store the result
91
+ if (this.cache && cacheKey) {
92
+ try {
93
+ await this.cache.put(cacheKey, result);
94
+ }
95
+ catch {
96
+ // Failure to store the value in the cache should not fail the transform
97
+ }
98
+ }
73
99
  }
74
- return pending;
100
+ return result;
75
101
  }
76
102
  /**
77
103
  * Performs JavaScript transformations on the provided data of a file. The file does not need
@@ -103,7 +129,6 @@ class JavaScriptTransformer {
103
129
  * @returns A void promise that resolves when closing is complete.
104
130
  */
105
131
  async close() {
106
- this.#pendingfileResults?.clear();
107
132
  if (this.#workerPool) {
108
133
  try {
109
134
  await this.#workerPool.destroy();
@@ -13,7 +13,7 @@ export interface BundleStylesheetOptions {
13
13
  optimization: boolean;
14
14
  inlineFonts: boolean;
15
15
  preserveSymlinks?: boolean;
16
- sourcemap: boolean | 'external' | 'inline';
16
+ sourcemap: boolean | 'external' | 'inline' | 'linked';
17
17
  outputNames: {
18
18
  bundles: string;
19
19
  media: string;