@angular-devkit/build-angular 17.0.0-rc.2 → 17.0.0-rc.4

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 (46) hide show
  1. package/package.json +9 -8
  2. package/src/builders/app-shell/render-worker.d.ts +1 -1
  3. package/src/builders/app-shell/render-worker.js +16 -9
  4. package/src/builders/application/build-action.js +5 -2
  5. package/src/builders/application/execute-build.js +49 -30
  6. package/src/builders/dev-server/builder.d.ts +12 -1
  7. package/src/builders/dev-server/builder.js +13 -4
  8. package/src/builders/dev-server/vite-server.d.ts +9 -6
  9. package/src/builders/dev-server/vite-server.js +167 -63
  10. package/src/builders/extract-i18n/application-extraction.js +1 -0
  11. package/src/builders/prerender/routes-extractor-worker.js +1 -1
  12. package/src/builders/ssr-dev-server/index.d.ts +1 -1
  13. package/src/index.d.ts +1 -0
  14. package/src/index.js +3 -1
  15. package/src/tools/babel/plugins/elide-angular-metadata.js +14 -2
  16. package/src/tools/esbuild/angular/compiler-plugin.js +104 -57
  17. package/src/tools/esbuild/application-code-bundle.d.ts +3 -2
  18. package/src/tools/esbuild/application-code-bundle.js +7 -19
  19. package/src/tools/esbuild/bundler-context.d.ts +4 -1
  20. package/src/tools/esbuild/bundler-context.js +13 -8
  21. package/src/tools/esbuild/bundler-execution-result.d.ts +16 -14
  22. package/src/tools/esbuild/bundler-execution-result.js +18 -3
  23. package/src/tools/esbuild/commonjs-checker.js +12 -7
  24. package/src/tools/esbuild/global-scripts.d.ts +1 -1
  25. package/src/tools/esbuild/global-scripts.js +2 -1
  26. package/src/tools/esbuild/javascript-transformer.d.ts +2 -1
  27. package/src/tools/esbuild/javascript-transformer.js +44 -20
  28. package/src/tools/esbuild/utils.d.ts +1 -1
  29. package/src/tools/esbuild/utils.js +18 -4
  30. package/src/utils/bundle-calculator.js +1 -1
  31. package/src/utils/environment-options.d.ts +1 -0
  32. package/src/utils/environment-options.js +7 -2
  33. package/src/utils/load-esm.js +6 -1
  34. package/src/utils/routes-extractor/extractor.d.ts +1 -1
  35. package/src/utils/routes-extractor/extractor.js +2 -2
  36. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.d.ts +0 -4
  37. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +78 -28
  38. package/src/utils/server-rendering/fetch-patch.d.ts +8 -0
  39. package/src/utils/server-rendering/fetch-patch.js +66 -0
  40. package/src/utils/server-rendering/prerender.js +25 -30
  41. package/src/utils/server-rendering/render-worker.d.ts +4 -2
  42. package/src/utils/server-rendering/render-worker.js +10 -4
  43. package/src/utils/server-rendering/routes-extractor-worker.d.ts +5 -3
  44. package/src/utils/server-rendering/routes-extractor-worker.js +12 -6
  45. package/src/utils/server-rendering/prerender-server.d.ts +0 -21
  46. package/src/utils/server-rendering/prerender-server.js +0 -102
@@ -20,16 +20,12 @@ const piscina_1 = __importDefault(require("piscina"));
20
20
  * and advanced optimizations.
21
21
  */
22
22
  class JavaScriptTransformer {
23
+ maxThreads;
23
24
  #workerPool;
24
25
  #commonOptions;
25
- constructor(options, maxThreads) {
26
- this.#workerPool = new piscina_1.default({
27
- filename: require.resolve('./javascript-transformer-worker'),
28
- minThreads: 1,
29
- maxThreads,
30
- // Shutdown idle threads after 1 second of inactivity
31
- idleTimeout: 1000,
32
- });
26
+ #pendingfileResults;
27
+ constructor(options, maxThreads, reuseResults) {
28
+ this.maxThreads = maxThreads;
33
29
  // Extract options to ensure only the named options are serialized and sent to the worker
34
30
  const { sourcemap, thirdPartySourcemaps = false, advancedOptimizations = false, jit = false, } = options;
35
31
  this.#commonOptions = {
@@ -38,6 +34,20 @@ class JavaScriptTransformer {
38
34
  advancedOptimizations,
39
35
  jit,
40
36
  };
37
+ // Currently only tracks pending file transform results
38
+ if (reuseResults) {
39
+ this.#pendingfileResults = new Map();
40
+ }
41
+ }
42
+ #ensureWorkerPool() {
43
+ this.#workerPool ??= new piscina_1.default({
44
+ filename: require.resolve('./javascript-transformer-worker'),
45
+ minThreads: 1,
46
+ maxThreads: this.maxThreads,
47
+ // Shutdown idle threads after 1 second of inactivity
48
+ idleTimeout: 1000,
49
+ });
50
+ return this.#workerPool;
41
51
  }
42
52
  /**
43
53
  * Performs JavaScript transformations on a file from the filesystem.
@@ -47,13 +57,19 @@ class JavaScriptTransformer {
47
57
  * @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
48
58
  */
49
59
  transformFile(filename, skipLinker) {
50
- // Always send the request to a worker. Files are almost always from node modules which means
51
- // they may need linking. The data is also not yet available to perform most transformation checks.
52
- return this.#workerPool.run({
53
- filename,
54
- skipLinker,
55
- ...this.#commonOptions,
56
- });
60
+ const pendingKey = `${!!skipLinker}--${filename}`;
61
+ let pending = this.#pendingfileResults?.get(pendingKey);
62
+ if (pending === undefined) {
63
+ // Always send the request to a worker. Files are almost always from node modules which means
64
+ // they may need linking. The data is also not yet available to perform most transformation checks.
65
+ pending = this.#ensureWorkerPool().run({
66
+ filename,
67
+ skipLinker,
68
+ ...this.#commonOptions,
69
+ });
70
+ this.#pendingfileResults?.set(pendingKey, pending);
71
+ }
72
+ return pending;
57
73
  }
58
74
  /**
59
75
  * Performs JavaScript transformations on the provided data of a file. The file does not need
@@ -71,7 +87,7 @@ class JavaScriptTransformer {
71
87
  (!!this.#commonOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
72
88
  return Buffer.from(keepSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''), 'utf-8');
73
89
  }
74
- return this.#workerPool.run({
90
+ return this.#ensureWorkerPool().run({
75
91
  filename,
76
92
  data,
77
93
  skipLinker,
@@ -82,10 +98,18 @@ class JavaScriptTransformer {
82
98
  * Stops all active transformation tasks and shuts down all workers.
83
99
  * @returns A void promise that resolves when closing is complete.
84
100
  */
85
- close() {
86
- // Workaround piscina bug where a worker thread will be recreated after destroy to meet the minimum.
87
- this.#workerPool.options.minThreads = 0;
88
- return this.#workerPool.destroy();
101
+ async close() {
102
+ this.#pendingfileResults?.clear();
103
+ if (this.#workerPool) {
104
+ // Workaround piscina bug where a worker thread will be recreated after destroy to meet the minimum.
105
+ this.#workerPool.options.minThreads = 0;
106
+ try {
107
+ await this.#workerPool.destroy();
108
+ }
109
+ finally {
110
+ this.#workerPool = undefined;
111
+ }
112
+ }
89
113
  }
90
114
  }
91
115
  exports.JavaScriptTransformer = JavaScriptTransformer;
@@ -10,7 +10,7 @@ import { BuildOptions, Metafile, OutputFile, PartialMessage } from 'esbuild';
10
10
  import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
11
11
  import { BuildOutputFile, BuildOutputFileType, InitialFileRecord } from './bundler-context';
12
12
  import { BuildOutputAsset } from './bundler-execution-result';
13
- export declare function logBuildStats(context: BuilderContext, metafile: Metafile, initial: Map<string, InitialFileRecord>, budgetFailures: BudgetCalculatorResult[] | undefined, estimatedTransferSizes?: Map<string, number>): void;
13
+ export declare function logBuildStats(context: BuilderContext, metafile: Metafile, initial: Map<string, InitialFileRecord>, budgetFailures: BudgetCalculatorResult[] | undefined, changedFiles?: Set<string>, estimatedTransferSizes?: Map<string, number>): void;
14
14
  export declare function calculateEstimatedTransferSizes(outputFiles: OutputFile[]): Promise<Map<string, number>>;
15
15
  export declare function withSpinner<T>(text: string, action: () => T | Promise<T>): Promise<T>;
16
16
  export declare function withNoProgress<T>(text: string, action: () => T | Promise<T>): Promise<T>;
@@ -46,8 +46,9 @@ const spinner_1 = require("../../utils/spinner");
46
46
  const stats_1 = require("../webpack/utils/stats");
47
47
  const bundler_context_1 = require("./bundler-context");
48
48
  const compressAsync = (0, node_util_1.promisify)(node_zlib_1.brotliCompress);
49
- function logBuildStats(context, metafile, initial, budgetFailures, estimatedTransferSizes) {
49
+ function logBuildStats(context, metafile, initial, budgetFailures, changedFiles, estimatedTransferSizes) {
50
50
  const stats = [];
51
+ let unchangedCount = 0;
51
52
  for (const [file, output] of Object.entries(metafile.outputs)) {
52
53
  // Only display JavaScript and CSS files
53
54
  if (!file.endsWith('.js') && !file.endsWith('.css')) {
@@ -58,6 +59,11 @@ function logBuildStats(context, metafile, initial, budgetFailures, estimatedTran
58
59
  if (output['ng-component']) {
59
60
  continue;
60
61
  }
62
+ // Show only changed files if a changed list is provided
63
+ if (changedFiles && !changedFiles.has(file)) {
64
+ ++unchangedCount;
65
+ continue;
66
+ }
61
67
  let name = initial.get(file)?.name;
62
68
  if (name === undefined && output.entryPoint) {
63
69
  name = node_path_1.default
@@ -70,8 +76,16 @@ function logBuildStats(context, metafile, initial, budgetFailures, estimatedTran
70
76
  stats: [file, name ?? '-', output.bytes, estimatedTransferSizes?.get(file) ?? '-'],
71
77
  });
72
78
  }
73
- const tableText = (0, stats_1.generateBuildStatsTable)(stats, true, true, !!estimatedTransferSizes, budgetFailures);
74
- context.logger.info('\n' + tableText + '\n');
79
+ if (stats.length > 0) {
80
+ const tableText = (0, stats_1.generateBuildStatsTable)(stats, true, unchangedCount === 0, !!estimatedTransferSizes, budgetFailures);
81
+ context.logger.info('\n' + tableText + '\n');
82
+ }
83
+ else if (changedFiles !== undefined) {
84
+ context.logger.info('\nNo output file changes.\n');
85
+ }
86
+ if (unchangedCount > 0) {
87
+ context.logger.info(`Unchanged output files: ${unchangedCount}`);
88
+ }
75
89
  }
76
90
  exports.logBuildStats = logBuildStats;
77
91
  async function calculateEstimatedTransferSizes(outputFiles) {
@@ -327,7 +341,7 @@ function transformSupportedBrowsersToTargets(supportedBrowsers) {
327
341
  return transformed;
328
342
  }
329
343
  exports.transformSupportedBrowsersToTargets = transformSupportedBrowsersToTargets;
330
- const SUPPORTED_NODE_VERSIONS = '>=18.13.0';
344
+ const SUPPORTED_NODE_VERSIONS = '^18.13.0 || >=20.9.0';
331
345
  /**
332
346
  * Transform supported Node.js versions to esbuild target.
333
347
  * @see https://esbuild.github.io/api/#target
@@ -228,7 +228,7 @@ class AnyComponentStyleCalculator extends Calculator {
228
228
  * Calculate the bytes given a string value.
229
229
  */
230
230
  function calculateBytes(input, baseline, factor = 1) {
231
- const matches = input.match(/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/);
231
+ const matches = input.trim().match(/^(\d+(?:\.\d+)?)[ \t]*(%|[kmg]?b)?$/i);
232
232
  if (!matches) {
233
233
  return NaN;
234
234
  }
@@ -12,3 +12,4 @@ export declare const maxWorkers: number;
12
12
  export declare const useParallelTs: boolean;
13
13
  export declare const useLegacySass: boolean;
14
14
  export declare const debugPerformance: boolean;
15
+ export declare const shouldWatchRoot: boolean;
@@ -7,7 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.io/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.debugPerformance = exports.useLegacySass = exports.useParallelTs = exports.maxWorkers = exports.allowMinify = exports.shouldBeautify = exports.allowMangle = void 0;
10
+ exports.shouldWatchRoot = exports.debugPerformance = exports.useLegacySass = exports.useParallelTs = exports.maxWorkers = exports.allowMinify = exports.shouldBeautify = exports.allowMangle = void 0;
11
11
  const color_1 = require("./color");
12
12
  function isDisabled(variable) {
13
13
  return variable === '0' || variable.toLowerCase() === 'false';
@@ -68,8 +68,11 @@ exports.allowMinify = debugOptimize.minify;
68
68
  */
69
69
  const maxWorkersVariable = process.env['NG_BUILD_MAX_WORKERS'];
70
70
  exports.maxWorkers = isPresent(maxWorkersVariable) ? +maxWorkersVariable : 4;
71
+ // Default to enabled unless inside a Web Container which currently fails when transferring MessagePort objects
71
72
  const parallelTsVariable = process.env['NG_BUILD_PARALLEL_TS'];
72
- exports.useParallelTs = !isPresent(parallelTsVariable) || !isDisabled(parallelTsVariable);
73
+ exports.useParallelTs = isPresent(parallelTsVariable)
74
+ ? !isDisabled(parallelTsVariable)
75
+ : !process.versions.webcontainer;
73
76
  const legacySassVariable = process.env['NG_BUILD_LEGACY_SASS'];
74
77
  exports.useLegacySass = (() => {
75
78
  if (!isPresent(legacySassVariable)) {
@@ -81,3 +84,5 @@ exports.useLegacySass = (() => {
81
84
  })();
82
85
  const debugPerfVariable = process.env['NG_BUILD_DEBUG_PERF'];
83
86
  exports.debugPerformance = isPresent(debugPerfVariable) && isEnabled(debugPerfVariable);
87
+ const watchRootVariable = process.env['NG_BUILD_WATCH_ROOT'];
88
+ exports.shouldWatchRoot = isPresent(watchRootVariable) && isEnabled(watchRootVariable);
@@ -8,6 +8,10 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.loadEsmModule = void 0;
11
+ /**
12
+ * Lazily compiled dynamic import loader function.
13
+ */
14
+ let load;
11
15
  /**
12
16
  * This uses a dynamic import to load a module which may be ESM.
13
17
  * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
@@ -21,6 +25,7 @@ exports.loadEsmModule = void 0;
21
25
  * @returns A Promise that resolves to the dynamically imported module.
22
26
  */
23
27
  function loadEsmModule(modulePath) {
24
- return new Function('modulePath', `return import(modulePath);`)(modulePath);
28
+ load ??= new Function('modulePath', `return import(modulePath);`);
29
+ return load(modulePath);
25
30
  }
26
31
  exports.loadEsmModule = loadEsmModule;
@@ -11,5 +11,5 @@ interface RouterResult {
11
11
  success: boolean;
12
12
  redirect: boolean;
13
13
  }
14
- export declare function extractRoutes(bootstrapAppFnOrModule: (() => Promise<ApplicationRef>) | Type<unknown>, document: string, url: string): AsyncIterableIterator<RouterResult>;
14
+ export declare function extractRoutes(bootstrapAppFnOrModule: (() => Promise<ApplicationRef>) | Type<unknown>, document: string): AsyncIterableIterator<RouterResult>;
15
15
  export {};
@@ -38,11 +38,11 @@ async function* getRoutesFromRouterConfig(routes, compiler, parentInjector, pare
38
38
  }
39
39
  }
40
40
  }
41
- export async function* extractRoutes(bootstrapAppFnOrModule, document, url) {
41
+ export async function* extractRoutes(bootstrapAppFnOrModule, document) {
42
42
  const platformRef = createPlatformFactory(platformCore, 'server', [
43
43
  {
44
44
  provide: INITIAL_CONFIG,
45
- useValue: { document, url },
45
+ useValue: { document, url: '' },
46
46
  },
47
47
  {
48
48
  provide: ɵConsole,
@@ -5,10 +5,6 @@
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
- /**
9
- * Node.js ESM loader to redirect imports to in memory files.
10
- * @see: https://nodejs.org/api/esm.html#loaders for more information about loaders.
11
- */
12
8
  export interface ESMInMemoryFileLoaderWorkerData {
13
9
  outputFiles: Record<string, string>;
14
10
  workspaceRoot: string;
@@ -6,16 +6,24 @@
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.load = exports.resolve = exports.initialize = void 0;
14
+ const node_assert_1 = __importDefault(require("node:assert"));
15
+ const node_crypto_1 = require("node:crypto");
11
16
  const node_path_1 = require("node:path");
12
17
  const node_url_1 = require("node:url");
13
18
  const url_1 = require("url");
14
19
  const javascript_transformer_1 = require("../../../tools/esbuild/javascript-transformer");
15
20
  const node_18_utils_1 = require("./node-18-utils");
16
- const TRANSFORMED_FILES = {};
17
- const CHUNKS_REGEXP = /file:\/\/\/(main\.server|chunk-\w+)\.mjs/;
18
- let workspaceRootFile;
21
+ /**
22
+ * Node.js ESM loader to redirect imports to in memory files.
23
+ * @see: https://nodejs.org/api/esm.html#loaders for more information about loaders.
24
+ */
25
+ const MEMORY_URL_SCHEME = 'memory://';
26
+ let memoryVirtualRootUrl;
19
27
  let outputFiles;
20
28
  const javascriptTransformer = new javascript_transformer_1.JavaScriptTransformer(
21
29
  // Always enable JIT linking to support applications built with and without AOT.
@@ -24,44 +32,89 @@ const javascriptTransformer = new javascript_transformer_1.JavaScriptTransformer
24
32
  { sourcemap: true, jit: true }, 1);
25
33
  (0, node_18_utils_1.callInitializeIfNeeded)(initialize);
26
34
  function initialize(data) {
27
- workspaceRootFile = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(data.workspaceRoot, 'index.mjs')).href;
35
+ // This path does not actually exist but is used to overlay the in memory files with the
36
+ // actual filesystem for resolution purposes.
37
+ // A custom URL schema (such as `memory://`) cannot be used for the resolve output because
38
+ // the in-memory files may use `import.meta.url` in ways that assume a file URL.
39
+ // `createRequire` is one example of this usage.
40
+ memoryVirtualRootUrl = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(data.workspaceRoot, `.angular/prerender-root/${(0, node_crypto_1.randomUUID)()}/`)).href;
28
41
  outputFiles = data.outputFiles;
29
42
  }
30
43
  exports.initialize = initialize;
31
44
  function resolve(specifier, context, nextResolve) {
32
- if (!isFileProtocol(specifier)) {
33
- const normalizedSpecifier = specifier.replace(/^\.\//, '');
34
- if (normalizedSpecifier in outputFiles) {
45
+ // In-memory files loaded from external code will contain a memory scheme
46
+ if (specifier.startsWith(MEMORY_URL_SCHEME)) {
47
+ let memoryUrl;
48
+ try {
49
+ memoryUrl = new URL(specifier);
50
+ }
51
+ catch {
52
+ node_assert_1.default.fail('External code attempted to use malformed memory scheme: ' + specifier);
53
+ }
54
+ // Resolve with a URL based from the virtual filesystem root
55
+ return {
56
+ format: 'module',
57
+ shortCircuit: true,
58
+ url: new URL(memoryUrl.pathname.slice(1), memoryVirtualRootUrl).href,
59
+ };
60
+ }
61
+ // Use next/default resolve if the parent is not from the virtual root
62
+ if (!context.parentURL?.startsWith(memoryVirtualRootUrl)) {
63
+ return nextResolve(specifier, context);
64
+ }
65
+ // Check for `./` and `../` relative specifiers
66
+ const isRelative = specifier[0] === '.' &&
67
+ (specifier[1] === '/' || (specifier[1] === '.' && specifier[2] === '/'));
68
+ // Relative specifiers from memory file should be based from the parent memory location
69
+ if (isRelative) {
70
+ let specifierUrl;
71
+ try {
72
+ specifierUrl = new URL(specifier, context.parentURL);
73
+ }
74
+ catch { }
75
+ if (specifierUrl?.pathname &&
76
+ Object.hasOwn(outputFiles, specifierUrl.href.slice(memoryVirtualRootUrl.length))) {
35
77
  return {
36
78
  format: 'module',
37
79
  shortCircuit: true,
38
- // File URLs need to absolute. In Windows these also need to include the drive.
39
- // The `/` will be resolved to the drive letter.
40
- url: (0, node_url_1.pathToFileURL)('/' + normalizedSpecifier).href,
80
+ url: specifierUrl.href,
41
81
  };
42
82
  }
83
+ node_assert_1.default.fail(`In-memory ESM relative file should always exist: '${context.parentURL}' --> '${specifier}'`);
43
84
  }
85
+ // Update the parent URL to allow for module resolution for the workspace.
86
+ // This handles bare specifiers (npm packages) and absolute paths.
44
87
  // Defer to the next hook in the chain, which would be the
45
88
  // Node.js default resolve if this is the last user-specified loader.
46
- return nextResolve(specifier, isBundleEntryPointOrChunk(context) ? { ...context, parentURL: workspaceRootFile } : context);
89
+ return nextResolve(specifier, {
90
+ ...context,
91
+ parentURL: new URL('index.js', memoryVirtualRootUrl).href,
92
+ });
47
93
  }
48
94
  exports.resolve = resolve;
49
95
  async function load(url, context, nextLoad) {
50
- if (isFileProtocol(url)) {
96
+ const { format } = context;
97
+ // Load the file from memory if the URL is based in the virtual root
98
+ if (url.startsWith(memoryVirtualRootUrl)) {
99
+ const source = outputFiles[url.slice(memoryVirtualRootUrl.length)];
100
+ (0, node_assert_1.default)(source !== undefined, 'Resolved in-memory ESM file should always exist: ' + url);
101
+ // In-memory files have already been transformer during bundling and can be returned directly
102
+ return {
103
+ format,
104
+ shortCircuit: true,
105
+ source,
106
+ };
107
+ }
108
+ // Only module files potentially require transformation. Angular libraries that would
109
+ // need linking are ESM only.
110
+ if (format === 'module' && isFileProtocol(url)) {
51
111
  const filePath = (0, url_1.fileURLToPath)(url);
52
- // Remove '/' or drive letter for Windows that was added in the above 'resolve'.
53
- let source = outputFiles[(0, node_path_1.relative)('/', filePath)] ?? TRANSFORMED_FILES[filePath];
54
- if (source === undefined) {
55
- source = TRANSFORMED_FILES[filePath] = Buffer.from(await javascriptTransformer.transformFile(filePath)).toString('utf-8');
56
- }
57
- if (source !== undefined) {
58
- const { format } = context;
59
- return {
60
- format,
61
- shortCircuit: true,
62
- source,
63
- };
64
- }
112
+ const source = await javascriptTransformer.transformFile(filePath);
113
+ return {
114
+ format,
115
+ shortCircuit: true,
116
+ source,
117
+ };
65
118
  }
66
119
  // Let Node.js handle all other URLs.
67
120
  return nextLoad(url);
@@ -73,9 +126,6 @@ function isFileProtocol(url) {
73
126
  function handleProcessExit() {
74
127
  void javascriptTransformer.close();
75
128
  }
76
- function isBundleEntryPointOrChunk(context) {
77
- return !!context.parentURL && CHUNKS_REGEXP.test(context.parentURL);
78
- }
79
129
  process.once('exit', handleProcessExit);
80
130
  process.once('SIGINT', handleProcessExit);
81
131
  process.once('uncaughtException', handleProcessExit);
@@ -0,0 +1,8 @@
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
+ export declare function patchFetchToLoadInMemoryAssets(): void;
@@ -0,0 +1,66 @@
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.patchFetchToLoadInMemoryAssets = void 0;
11
+ const mrmime_1 = require("mrmime");
12
+ const promises_1 = require("node:fs/promises");
13
+ const node_path_1 = require("node:path");
14
+ const node_worker_threads_1 = require("node:worker_threads");
15
+ const undici_1 = require("undici");
16
+ /**
17
+ * This is passed as workerData when setting up the worker via the `piscina` package.
18
+ */
19
+ const { assetFiles } = node_worker_threads_1.workerData;
20
+ const assetsCache = new Map();
21
+ const RESOLVE_PROTOCOL = 'resolve:';
22
+ function patchFetchToLoadInMemoryAssets() {
23
+ const global = globalThis;
24
+ const originalFetch = global.fetch;
25
+ const patchedFetch = async (input, init) => {
26
+ let url;
27
+ if (input instanceof URL) {
28
+ url = input;
29
+ }
30
+ else if (typeof input === 'string') {
31
+ url = new URL(input, RESOLVE_PROTOCOL + '//');
32
+ }
33
+ else if (typeof input === 'object' && 'url' in input) {
34
+ url = new URL(input.url, RESOLVE_PROTOCOL + '//');
35
+ }
36
+ else {
37
+ return originalFetch(input, init);
38
+ }
39
+ const { pathname, protocol } = url;
40
+ if (protocol !== RESOLVE_PROTOCOL || !assetFiles[pathname]) {
41
+ // Only handle relative requests or files that are in assets.
42
+ return originalFetch(input, init);
43
+ }
44
+ const cachedAsset = assetsCache.get(pathname);
45
+ if (cachedAsset) {
46
+ const { content, headers } = cachedAsset;
47
+ return new undici_1.Response(content, {
48
+ headers,
49
+ });
50
+ }
51
+ const extension = (0, node_path_1.extname)(pathname);
52
+ const mimeType = (0, mrmime_1.lookup)(extension);
53
+ const content = await (0, promises_1.readFile)(assetFiles[pathname]);
54
+ const headers = mimeType
55
+ ? {
56
+ 'Content-Type': mimeType,
57
+ }
58
+ : undefined;
59
+ assetsCache.set(pathname, { headers, content });
60
+ return new undici_1.Response(content, {
61
+ headers,
62
+ });
63
+ };
64
+ global.fetch = patchedFetch;
65
+ }
66
+ exports.patchFetchToLoadInMemoryAssets = patchFetchToLoadInMemoryAssets;
@@ -16,7 +16,6 @@ const node_path_1 = require("node:path");
16
16
  const piscina_1 = __importDefault(require("piscina"));
17
17
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
18
18
  const node_18_utils_1 = require("./esm-in-memory-loader/node-18-utils");
19
- const prerender_server_1 = require("./prerender-server");
20
19
  async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOptions = {}, outputFiles, assets, document, sourcemap = false, inlineCriticalCss = false, maxThreads = 1, verbose = false) {
21
20
  const outputFilesForWorker = {};
22
21
  const serverBundlesSourceMaps = new Map();
@@ -45,37 +44,33 @@ async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOpti
45
44
  }
46
45
  }
47
46
  serverBundlesSourceMaps.clear();
48
- // Start server to handle HTTP requests to assets.
49
- // TODO: consider starting this is a seperate process to avoid any blocks to the main thread.
50
- const { address: assetsServerAddress, close: closeAssetsServer } = await (0, prerender_server_1.startServer)(assets);
51
- try {
52
- // Get routes to prerender
53
- const { routes: allRoutes, warnings: routesWarnings } = await getAllRoutes(workspaceRoot, outputFilesForWorker, document, appShellOptions, prerenderOptions, sourcemap, verbose, assetsServerAddress);
54
- if (routesWarnings?.length) {
55
- warnings.push(...routesWarnings);
56
- }
57
- if (allRoutes.size < 1) {
58
- return {
59
- errors,
60
- warnings,
61
- output: {},
62
- prerenderedRoutes: allRoutes,
63
- };
64
- }
65
- // Render routes
66
- const { warnings: renderingWarnings, errors: renderingErrors, output, } = await renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, inlineCriticalCss, document, assetsServerAddress, appShellOptions);
67
- errors.push(...renderingErrors);
68
- warnings.push(...renderingWarnings);
47
+ const assetsReversed = {};
48
+ for (const { source, destination } of assets) {
49
+ assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source;
50
+ }
51
+ // Get routes to prerender
52
+ const { routes: allRoutes, warnings: routesWarnings } = await getAllRoutes(workspaceRoot, outputFilesForWorker, assetsReversed, document, appShellOptions, prerenderOptions, sourcemap, verbose);
53
+ if (routesWarnings?.length) {
54
+ warnings.push(...routesWarnings);
55
+ }
56
+ if (allRoutes.size < 1) {
69
57
  return {
70
58
  errors,
71
59
  warnings,
72
- output,
60
+ output: {},
73
61
  prerenderedRoutes: allRoutes,
74
62
  };
75
63
  }
76
- finally {
77
- void closeAssetsServer?.();
78
- }
64
+ // Render routes
65
+ const { warnings: renderingWarnings, errors: renderingErrors, output, } = await renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, inlineCriticalCss, document, appShellOptions);
66
+ errors.push(...renderingErrors);
67
+ warnings.push(...renderingWarnings);
68
+ return {
69
+ errors,
70
+ warnings,
71
+ output,
72
+ prerenderedRoutes: allRoutes,
73
+ };
79
74
  }
80
75
  exports.prerenderPages = prerenderPages;
81
76
  class RoutesSet extends Set {
@@ -83,7 +78,7 @@ class RoutesSet extends Set {
83
78
  return super.add(addLeadingSlash(value));
84
79
  }
85
80
  }
86
- async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, inlineCriticalCss, document, baseUrl, appShellOptions) {
81
+ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, inlineCriticalCss, document, appShellOptions) {
87
82
  const output = {};
88
83
  const warnings = [];
89
84
  const errors = [];
@@ -97,9 +92,9 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
97
92
  workerData: {
98
93
  workspaceRoot,
99
94
  outputFiles: outputFilesForWorker,
95
+ assetFiles: assetFilesForWorker,
100
96
  inlineCriticalCss,
101
97
  document,
102
- baseUrl,
103
98
  },
104
99
  execArgv: workerExecArgv,
105
100
  });
@@ -139,7 +134,7 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
139
134
  output,
140
135
  };
141
136
  }
142
- async function getAllRoutes(workspaceRoot, outputFilesForWorker, document, appShellOptions, prerenderOptions, sourcemap, verbose, assetsServerAddress) {
137
+ async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWorker, document, appShellOptions, prerenderOptions, sourcemap, verbose) {
143
138
  const { routesFile, discoverRoutes } = prerenderOptions;
144
139
  const routes = new RoutesSet();
145
140
  const { route: appShellRoute } = appShellOptions;
@@ -165,9 +160,9 @@ async function getAllRoutes(workspaceRoot, outputFilesForWorker, document, appSh
165
160
  workerData: {
166
161
  workspaceRoot,
167
162
  outputFiles: outputFilesForWorker,
163
+ assetFiles: assetFilesForWorker,
168
164
  document,
169
165
  verbose,
170
- url: assetsServerAddress,
171
166
  },
172
167
  execArgv: workerExecArgv,
173
168
  });
@@ -10,11 +10,13 @@ import { RenderResult, ServerContext } from './render-page';
10
10
  export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
11
11
  document: string;
12
12
  inlineCriticalCss?: boolean;
13
- baseUrl: string;
13
+ assetFiles: Record</** Destination */ string, /** Source */ string>;
14
14
  }
15
15
  export interface RenderOptions {
16
16
  route: string;
17
17
  serverContext: ServerContext;
18
18
  }
19
19
  /** Renders an application based on a provided options. */
20
- export default function (options: RenderOptions): Promise<RenderResult>;
20
+ declare function render(options: RenderOptions): Promise<RenderResult>;
21
+ declare const _default: typeof render;
22
+ export default _default;
@@ -8,19 +8,25 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  const node_worker_threads_1 = require("node:worker_threads");
11
+ const load_esm_1 = require("../load-esm");
12
+ const fetch_patch_1 = require("./fetch-patch");
11
13
  const render_page_1 = require("./render-page");
12
14
  /**
13
15
  * This is passed as workerData when setting up the worker via the `piscina` package.
14
16
  */
15
- const { outputFiles, document, inlineCriticalCss, baseUrl } = node_worker_threads_1.workerData;
17
+ const { outputFiles, document, inlineCriticalCss } = node_worker_threads_1.workerData;
16
18
  /** Renders an application based on a provided options. */
17
- function default_1(options) {
19
+ function render(options) {
18
20
  return (0, render_page_1.renderPage)({
19
21
  ...options,
20
- route: baseUrl + options.route,
21
22
  outputFiles,
22
23
  document,
23
24
  inlineCriticalCss,
25
+ loadBundle: async (path) => await (0, load_esm_1.loadEsmModule)(new URL(path, 'memory://')),
24
26
  });
25
27
  }
26
- exports.default = default_1;
28
+ function initialize() {
29
+ (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
30
+ return render;
31
+ }
32
+ exports.default = initialize();