@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.
- package/package.json +9 -8
- package/src/builders/app-shell/render-worker.d.ts +1 -1
- package/src/builders/app-shell/render-worker.js +16 -9
- package/src/builders/application/build-action.js +5 -2
- package/src/builders/application/execute-build.js +49 -30
- package/src/builders/dev-server/builder.d.ts +12 -1
- package/src/builders/dev-server/builder.js +13 -4
- package/src/builders/dev-server/vite-server.d.ts +9 -6
- package/src/builders/dev-server/vite-server.js +167 -63
- package/src/builders/extract-i18n/application-extraction.js +1 -0
- package/src/builders/prerender/routes-extractor-worker.js +1 -1
- package/src/builders/ssr-dev-server/index.d.ts +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -1
- package/src/tools/babel/plugins/elide-angular-metadata.js +14 -2
- package/src/tools/esbuild/angular/compiler-plugin.js +104 -57
- package/src/tools/esbuild/application-code-bundle.d.ts +3 -2
- package/src/tools/esbuild/application-code-bundle.js +7 -19
- package/src/tools/esbuild/bundler-context.d.ts +4 -1
- package/src/tools/esbuild/bundler-context.js +13 -8
- package/src/tools/esbuild/bundler-execution-result.d.ts +16 -14
- package/src/tools/esbuild/bundler-execution-result.js +18 -3
- package/src/tools/esbuild/commonjs-checker.js +12 -7
- package/src/tools/esbuild/global-scripts.d.ts +1 -1
- package/src/tools/esbuild/global-scripts.js +2 -1
- package/src/tools/esbuild/javascript-transformer.d.ts +2 -1
- package/src/tools/esbuild/javascript-transformer.js +44 -20
- package/src/tools/esbuild/utils.d.ts +1 -1
- package/src/tools/esbuild/utils.js +18 -4
- package/src/utils/bundle-calculator.js +1 -1
- package/src/utils/environment-options.d.ts +1 -0
- package/src/utils/environment-options.js +7 -2
- package/src/utils/load-esm.js +6 -1
- package/src/utils/routes-extractor/extractor.d.ts +1 -1
- package/src/utils/routes-extractor/extractor.js +2 -2
- package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.d.ts +0 -4
- package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +78 -28
- package/src/utils/server-rendering/fetch-patch.d.ts +8 -0
- package/src/utils/server-rendering/fetch-patch.js +66 -0
- package/src/utils/server-rendering/prerender.js +25 -30
- package/src/utils/server-rendering/render-worker.d.ts +4 -2
- package/src/utils/server-rendering/render-worker.js +10 -4
- package/src/utils/server-rendering/routes-extractor-worker.d.ts +5 -3
- package/src/utils/server-rendering/routes-extractor-worker.js +12 -6
- package/src/utils/server-rendering/prerender-server.d.ts +0 -21
- 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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.#
|
|
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
|
-
|
|
87
|
-
this.#workerPool
|
|
88
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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 = '
|
|
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(
|
|
231
|
+
const matches = input.trim().match(/^(\d+(?:\.\d+)?)[ \t]*(%|[kmg]?b)?$/i);
|
|
232
232
|
if (!matches) {
|
|
233
233
|
return NaN;
|
|
234
234
|
}
|
|
@@ -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 =
|
|
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);
|
package/src/utils/load-esm.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
17
|
+
const { outputFiles, document, inlineCriticalCss } = node_worker_threads_1.workerData;
|
|
16
18
|
/** Renders an application based on a provided options. */
|
|
17
|
-
function
|
|
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
|
-
|
|
28
|
+
function initialize() {
|
|
29
|
+
(0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
|
|
30
|
+
return render;
|
|
31
|
+
}
|
|
32
|
+
exports.default = initialize();
|