This commit is contained in:
CHEVALLIER Abel
2025-11-13 16:23:22 +01:00
parent de9c515a47
commit cb235644dc
34924 changed files with 3811102 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedCachedOptions } from '../../utils/normalize-cache';
import { NormalizedOutputOptions } from './options';
import { Result } from './results';
export declare function runEsBuildBuildAction(action: (rebuildState?: RebuildState) => Promise<ExecutionResult>, options: {
workspaceRoot: string;
projectRoot: string;
outputOptions: NormalizedOutputOptions;
logger: BuilderContext['logger'];
cacheOptions: NormalizedCachedOptions;
watch?: boolean;
verbose?: boolean;
progress?: boolean;
poll?: number;
signal?: AbortSignal;
preserveSymlinks?: boolean;
clearScreen?: boolean;
colors?: boolean;
jsonLogs?: boolean;
incrementalResults?: boolean;
}): AsyncIterable<Result>;

View File

@@ -0,0 +1,354 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runEsBuildBuildAction = runEsBuildBuildAction;
const node_fs_1 = require("node:fs");
const node_path_1 = __importDefault(require("node:path"));
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const sass_language_1 = require("../../tools/esbuild/stylesheets/sass-language");
const utils_1 = require("../../tools/esbuild/utils");
const environment_options_1 = require("../../utils/environment-options");
const path_1 = require("../../utils/path");
const results_1 = require("./results");
// Watch workspace for package manager changes
const packageWatchFiles = [
// manifest can affect module resolution
'package.json',
// npm lock file
'package-lock.json',
// pnpm lock file
'pnpm-lock.yaml',
// yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
'yarn.lock',
'.pnp.cjs',
'.pnp.data.json',
];
async function* runEsBuildBuildAction(action, options) {
const { watch, poll, clearScreen, logger, cacheOptions, outputOptions, verbose, projectRoot, workspaceRoot, progress, preserveSymlinks, colors, jsonLogs, incrementalResults, } = options;
const withProgress = progress ? utils_1.withSpinner : utils_1.withNoProgress;
// Initial build
let result;
try {
// Perform the build action
result = await withProgress('Building...', () => action());
// Log all diagnostic (error/warning/logs) messages
await (0, utils_1.logMessages)(logger, result, colors, jsonLogs);
}
finally {
// Ensure Sass workers are shutdown if not watching
if (!watch) {
(0, sass_language_1.shutdownSassWorkerPool)();
}
}
// Setup watcher if watch mode enabled
let watcher;
if (watch) {
if (progress) {
logger.info('Watch mode enabled. Watching for file changes...');
}
const ignored = [
// Ignore the output and cache paths to avoid infinite rebuild cycles
outputOptions.base,
cacheOptions.basePath,
`${(0, path_1.toPosixPath)(workspaceRoot)}/**/.*/**`,
];
// Setup a watcher
const { createWatcher } = await Promise.resolve().then(() => __importStar(require('../../tools/esbuild/watcher')));
watcher = createWatcher({
polling: typeof poll === 'number',
interval: poll,
followSymlinks: preserveSymlinks,
ignored,
});
// Setup abort support
options.signal?.addEventListener('abort', () => void watcher?.close());
// Watch the entire project root if 'NG_BUILD_WATCH_ROOT' environment variable is set
if (environment_options_1.shouldWatchRoot) {
if (!preserveSymlinks) {
// Ignore all node modules directories to avoid excessive file watchers.
// Package changes are handled below by watching manifest and lock files.
// NOTE: this is not enable when preserveSymlinks is true as this would break `npm link` usages.
ignored.push('**/node_modules/**');
watcher.add(packageWatchFiles
.map((file) => node_path_1.default.join(workspaceRoot, file))
.filter((file) => (0, node_fs_1.existsSync)(file)));
}
watcher.add(projectRoot);
}
// Watch locations provided by the initial build result
watcher.add(result.watchFiles);
}
// Output the first build results after setting up the watcher to ensure that any code executed
// higher in the iterator call stack will trigger the watcher. This is particularly relevant for
// unit tests which execute the builder and modify the file system programmatically.
yield* emitOutputResults(result, outputOptions);
// Finish if watch mode is not enabled
if (!watcher) {
return;
}
// Used to force a full result on next rebuild if there were initial errors.
// This ensures at least one full result is emitted.
let hasInitialErrors = result.errors.length > 0;
// Wait for changes and rebuild as needed
const currentWatchFiles = new Set(result.watchFiles);
try {
for await (const changes of watcher) {
if (options.signal?.aborted) {
break;
}
if (clearScreen) {
// eslint-disable-next-line no-console
console.clear();
}
if (verbose) {
logger.info(changes.toDebugString());
}
// Clear removed files from current watch files
changes.removed.forEach((removedPath) => currentWatchFiles.delete(removedPath));
const rebuildState = result.createRebuildState(changes);
result = await withProgress('Changes detected. Rebuilding...', () => action(rebuildState));
// Log all diagnostic (error/warning/logs) messages
await (0, utils_1.logMessages)(logger, result, colors, jsonLogs);
// Update watched locations provided by the new build result.
// Keep watching all previous files if there are any errors; otherwise consider all
// files stale until confirmed present in the new result's watch files.
const staleWatchFiles = result.errors.length > 0 ? undefined : new Set(currentWatchFiles);
for (const watchFile of result.watchFiles) {
if (!currentWatchFiles.has(watchFile)) {
// Add new watch location
watcher.add(watchFile);
currentWatchFiles.add(watchFile);
}
// Present so remove from stale locations
staleWatchFiles?.delete(watchFile);
}
// Remove any stale locations if the build was successful
if (staleWatchFiles?.size) {
watcher.remove([...staleWatchFiles]);
}
for (const outputResult of emitOutputResults(result, outputOptions, changes, incrementalResults && !hasInitialErrors ? rebuildState : undefined)) {
yield outputResult;
}
// Clear initial build errors flag if no errors are now present
hasInitialErrors &&= result.errors.length > 0;
}
}
finally {
// Stop the watcher and cleanup incremental rebuild state
await Promise.allSettled([watcher.close(), result.dispose()]);
(0, sass_language_1.shutdownSassWorkerPool)();
}
}
function* emitOutputResults({ outputFiles, assetFiles, errors, warnings, externalMetadata, htmlIndexPath, htmlBaseHref, templateUpdates, }, outputOptions, changes, rebuildState) {
if (errors.length > 0) {
yield {
kind: results_1.ResultKind.Failure,
errors: errors,
warnings: warnings,
detail: {
outputOptions,
},
};
return;
}
// Use a full result if there is no rebuild state (no prior build result)
if (!rebuildState || !changes) {
const result = {
kind: results_1.ResultKind.Full,
warnings: warnings,
files: {},
detail: {
externalMetadata,
htmlIndexPath,
htmlBaseHref,
outputOptions,
},
};
for (const file of assetFiles) {
result.files[file.destination] = {
type: bundler_context_1.BuildOutputFileType.Browser,
inputPath: file.source,
origin: 'disk',
};
}
for (const file of outputFiles) {
result.files[file.path] = {
type: file.type,
contents: file.contents,
origin: 'memory',
hash: file.hash,
};
}
yield result;
return;
}
// Template updates only exist if no other JS changes have occurred.
// A full page reload may be required based on the following incremental output change analysis.
const hasTemplateUpdates = !!templateUpdates?.size;
// Use an incremental result if previous output information is available
const { previousAssetsInfo, previousOutputInfo } = rebuildState;
const incrementalResult = {
kind: results_1.ResultKind.Incremental,
warnings: warnings,
// Initially attempt to use a background update of files to support component updates.
background: hasTemplateUpdates,
added: [],
removed: [],
modified: [],
files: {},
detail: {
externalMetadata,
htmlIndexPath,
htmlBaseHref,
outputOptions,
},
};
let hasCssUpdates = false;
// Initially assume all previous output files have been removed
const removedOutputFiles = new Map(previousOutputInfo);
for (const file of outputFiles) {
removedOutputFiles.delete(file.path);
const previousHash = previousOutputInfo.get(file.path)?.hash;
let needFile = false;
if (previousHash === undefined) {
needFile = true;
incrementalResult.added.push(file.path);
}
else if (previousHash !== file.hash) {
needFile = true;
incrementalResult.modified.push(file.path);
}
if (needFile) {
if (file.path.endsWith('.css')) {
hasCssUpdates = true;
}
else if (!canBackgroundUpdate(file)) {
incrementalResult.background = false;
}
incrementalResult.files[file.path] = {
type: file.type,
contents: file.contents,
origin: 'memory',
hash: file.hash,
};
}
}
// Initially assume all previous assets files have been removed
const removedAssetFiles = new Map(previousAssetsInfo);
for (const { source, destination } of assetFiles) {
removedAssetFiles.delete(source);
if (!previousAssetsInfo.has(source)) {
incrementalResult.added.push(destination);
incrementalResult.background = false;
}
else if (changes.modified.has(source)) {
incrementalResult.modified.push(destination);
incrementalResult.background = false;
}
else {
continue;
}
hasCssUpdates ||= destination.endsWith('.css');
incrementalResult.files[destination] = {
type: bundler_context_1.BuildOutputFileType.Browser,
inputPath: source,
origin: 'disk',
};
}
// Do not remove stale files yet if there are template updates.
// Component chunk files may still be referenced in running browser code.
// Module evaluation time component updates will update any of these files.
// This typically occurs when a lazy component is changed that has not yet
// been accessed at runtime.
if (hasTemplateUpdates && incrementalResult.background) {
removedOutputFiles.clear();
}
// Include the removed output and asset files
incrementalResult.removed.push(...Array.from(removedOutputFiles, ([file, { type }]) => ({
path: file,
type,
})), ...Array.from(removedAssetFiles.values(), (file) => ({
path: file,
type: bundler_context_1.BuildOutputFileType.Browser,
})));
yield incrementalResult;
// If there are template updates and the incremental update was background only, a component
// update is possible.
if (hasTemplateUpdates && incrementalResult.background) {
// Template changes may be accompanied by stylesheet changes and these should also be updated hot when possible.
if (hasCssUpdates) {
const styleResult = {
kind: results_1.ResultKind.Incremental,
added: incrementalResult.added.filter(isCssFilePath),
removed: incrementalResult.removed.filter(({ path }) => isCssFilePath(path)),
modified: incrementalResult.modified.filter(isCssFilePath),
files: Object.fromEntries(Object.entries(incrementalResult.files).filter(([path]) => isCssFilePath(path))),
};
yield styleResult;
}
const updateResult = {
kind: results_1.ResultKind.ComponentUpdate,
updates: Array.from(templateUpdates, ([id, content]) => ({
type: 'template',
id,
content,
})),
};
yield updateResult;
}
}
function isCssFilePath(filePath) {
return /\.css(?:\.map)?$/i.test(filePath);
}
function canBackgroundUpdate(file) {
// Files in the output root are not served and do not affect the
// application available with the development server.
if (file.type === bundler_context_1.BuildOutputFileType.Root) {
return true;
}
// Updates to non-JS files must signal an update with the dev server
// except the service worker configuration which is special cased.
return /(?:\.m?js|\.map)$/.test(file.path) || file.path === 'ngsw.json';
}

View File

@@ -0,0 +1,21 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BundleContextResult } from '../../tools/esbuild/bundler-context';
/**
* Optimizes the chunks of a build result using rolldown.
*
* This function takes the output of an esbuild build, identifies the main browser entry point,
* and uses rolldown to bundle and optimize the JavaScript chunks. The optimized chunks
* replace the original ones in the build result, and the metafile is updated to reflect
* the changes.
*
* @param original The original build result from esbuild.
* @param sourcemap A boolean or 'hidden' to control sourcemap generation.
* @returns A promise that resolves to the updated build result with optimized chunks.
*/
export declare function optimizeChunks(original: BundleContextResult, sourcemap: boolean | 'hidden'): Promise<BundleContextResult>;

View File

@@ -0,0 +1,277 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.optimizeChunks = optimizeChunks;
const node_assert_1 = __importDefault(require("node:assert"));
const rollup_1 = require("rollup");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const utils_1 = require("../../tools/esbuild/utils");
const error_1 = require("../../utils/error");
/**
* Converts the output of a rolldown build into an esbuild-compatible metafile.
* @param rolldownOutput The output of a rolldown build.
* @param originalMetafile The original esbuild metafile from the build.
* @returns An esbuild-compatible metafile.
*/
function rolldownToEsbuildMetafile(rolldownOutput, originalMetafile) {
const newMetafile = {
inputs: originalMetafile.inputs,
outputs: {},
};
const intermediateChunkSizes = {};
for (const [path, output] of Object.entries(originalMetafile.outputs)) {
intermediateChunkSizes[path] = Object.values(output.inputs).reduce((s, i) => s + i.bytesInOutput, 0);
}
for (const chunk of rolldownOutput) {
if (chunk.type === 'asset') {
newMetafile.outputs[chunk.fileName] = {
bytes: typeof chunk.source === 'string'
? Buffer.byteLength(chunk.source, 'utf8')
: chunk.source.length,
inputs: {},
imports: [],
exports: [],
};
continue;
}
const newOutputInputs = {};
if (chunk.modules) {
for (const [moduleId, renderedModule] of Object.entries(chunk.modules)) {
const originalOutputEntry = originalMetafile.outputs[moduleId];
if (!originalOutputEntry?.inputs) {
continue;
}
const totalOriginalBytesInModule = intermediateChunkSizes[moduleId];
if (totalOriginalBytesInModule === 0) {
continue;
}
for (const [originalInputPath, originalInputInfo] of Object.entries(originalOutputEntry.inputs)) {
const proportion = originalInputInfo.bytesInOutput / totalOriginalBytesInModule;
const newBytesInOutput = Math.floor(renderedModule.renderedLength * proportion);
const existing = newOutputInputs[originalInputPath];
if (existing) {
existing.bytesInOutput += newBytesInOutput;
}
else {
newOutputInputs[originalInputPath] = { bytesInOutput: newBytesInOutput };
}
if (!newMetafile.inputs[originalInputPath]) {
newMetafile.inputs[originalInputPath] = originalMetafile.inputs[originalInputPath];
}
}
}
}
const imports = [
...chunk.imports.map((path) => ({ path, kind: 'import-statement' })),
...(chunk.dynamicImports?.map((path) => ({ path, kind: 'dynamic-import' })) ?? []),
];
newMetafile.outputs[chunk.fileName] = {
bytes: Buffer.byteLength(chunk.code, 'utf8'),
inputs: newOutputInputs,
imports,
exports: chunk.exports ?? [],
entryPoint: chunk.isEntry && chunk.facadeModuleId
? originalMetafile.outputs[chunk.facadeModuleId]?.entryPoint
: undefined,
};
}
return newMetafile;
}
/**
* Creates an InitialFileRecord object with a specified depth.
* @param depth The depth of the file in the dependency graph.
* @returns An InitialFileRecord object.
*/
function createInitialFileRecord(depth) {
return {
type: 'script',
entrypoint: false,
external: false,
serverFile: false,
depth,
};
}
/**
* Creates an esbuild message object for a chunk optimization failure.
* @param message The error message detailing the cause of the failure.
* @returns A partial esbuild message object.
*/
function createChunkOptimizationFailureMessage(message) {
// Most of these fields are not actually needed for printing the error
return {
id: '',
text: 'Chunk optimization failed',
detail: undefined,
pluginName: '',
location: null,
notes: [
{
text: message,
location: null,
},
],
};
}
/**
* Optimizes the chunks of a build result using rolldown.
*
* This function takes the output of an esbuild build, identifies the main browser entry point,
* and uses rolldown to bundle and optimize the JavaScript chunks. The optimized chunks
* replace the original ones in the build result, and the metafile is updated to reflect
* the changes.
*
* @param original The original build result from esbuild.
* @param sourcemap A boolean or 'hidden' to control sourcemap generation.
* @returns A promise that resolves to the updated build result with optimized chunks.
*/
async function optimizeChunks(original, sourcemap) {
// Failed builds cannot be optimized
if (original.errors) {
return original;
}
// Find the main browser entrypoint
let mainFile;
for (const [file, record] of original.initialFiles) {
if (record.name === 'main' &&
record.entrypoint &&
!record.serverFile &&
record.type === 'script') {
mainFile = file;
break;
}
}
// No action required if no browser main entrypoint or metafile for stats
if (!mainFile || !original.metafile) {
return original;
}
const chunks = {};
const maps = {};
for (const originalFile of original.outputFiles) {
if (originalFile.type !== bundler_context_1.BuildOutputFileType.Browser) {
continue;
}
if (originalFile.path.endsWith('.js')) {
chunks[originalFile.path] = originalFile;
}
else if (originalFile.path.endsWith('.js.map')) {
// Create mapping of JS file to sourcemap content
maps[originalFile.path.slice(0, -4)] = originalFile;
}
}
const usedChunks = new Set();
let bundle;
let optimizedOutput;
try {
bundle = await (0, rollup_1.rollup)({
input: mainFile,
plugins: [
{
name: 'angular-bundle',
resolveId(source) {
// Remove leading `./` if present
const file = source[0] === '.' && source[1] === '/' ? source.slice(2) : source;
if (chunks[file]) {
return file;
}
// All other identifiers are considered external to maintain behavior
return { id: source, external: true };
},
load(id) {
(0, node_assert_1.default)(chunks[id], `Angular chunk content should always be present in chunk optimizer [${id}].`);
usedChunks.add(id);
const result = {
code: chunks[id].text,
map: maps[id]?.text,
};
return result;
},
},
],
});
const result = await bundle.generate({
compact: true,
sourcemap,
chunkFileNames: (chunkInfo) => `${chunkInfo.name.replace(/-[a-zA-Z0-9]{8}$/, '')}-[hash].js`,
});
optimizedOutput = result.output;
}
catch (e) {
(0, error_1.assertIsError)(e);
return {
errors: [createChunkOptimizationFailureMessage(e.message)],
warnings: original.warnings,
};
}
finally {
await bundle?.close();
}
// Update metafile
const newMetafile = rolldownToEsbuildMetafile(optimizedOutput, original.metafile);
// Add back the outputs that were not part of the optimization
for (const [path, output] of Object.entries(original.metafile.outputs)) {
if (usedChunks.has(path)) {
continue;
}
newMetafile.outputs[path] = output;
for (const inputPath of Object.keys(output.inputs)) {
if (!newMetafile.inputs[inputPath]) {
newMetafile.inputs[inputPath] = original.metafile.inputs[inputPath];
}
}
}
original.metafile = newMetafile;
// Remove used chunks and associated sourcemaps from the original result
original.outputFiles = original.outputFiles.filter((file) => !usedChunks.has(file.path) &&
!(file.path.endsWith('.map') && usedChunks.has(file.path.slice(0, -4))));
// Add new optimized chunks
const importsPerFile = {};
for (const optimizedFile of optimizedOutput) {
if (optimizedFile.type !== 'chunk') {
continue;
}
importsPerFile[optimizedFile.fileName] = optimizedFile.imports;
original.outputFiles.push((0, utils_1.createOutputFile)(optimizedFile.fileName, optimizedFile.code, bundler_context_1.BuildOutputFileType.Browser));
if (optimizedFile.map && optimizedFile.sourcemapFileName) {
original.outputFiles.push((0, utils_1.createOutputFile)(optimizedFile.sourcemapFileName, optimizedFile.map.toString(), bundler_context_1.BuildOutputFileType.Browser));
}
}
// Update initial files to reflect optimized chunks
const entriesToAnalyze = [];
for (const usedFile of usedChunks) {
// Leave the main file since its information did not change
if (usedFile === mainFile) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
entriesToAnalyze.push([mainFile, original.initialFiles.get(mainFile)]);
continue;
}
// Remove all other used chunks
original.initialFiles.delete(usedFile);
}
// Analyze for transitive initial files
let currentEntry;
while ((currentEntry = entriesToAnalyze.pop())) {
const [entryPath, entryRecord] = currentEntry;
for (const importPath of importsPerFile[entryPath]) {
const existingRecord = original.initialFiles.get(importPath);
if (existingRecord) {
// Store the smallest value depth
if (existingRecord.depth > entryRecord.depth + 1) {
existingRecord.depth = entryRecord.depth + 1;
}
continue;
}
const record = createInitialFileRecord(entryRecord.depth + 1);
entriesToAnalyze.push([importPath, record]);
}
}
return original;
}

View File

@@ -0,0 +1,11 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedApplicationBuildOptions } from './options';
export declare function executeBuild(options: NormalizedApplicationBuildOptions, context: BuilderContext, rebuildState?: RebuildState): Promise<ExecutionResult>;

View File

@@ -0,0 +1,249 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeBuild = executeBuild;
const compilation_1 = require("../../tools/angular/compilation");
const source_file_cache_1 = require("../../tools/esbuild/angular/source-file-cache");
const budget_stats_1 = require("../../tools/esbuild/budget-stats");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const bundler_execution_result_1 = require("../../tools/esbuild/bundler-execution-result");
const commonjs_checker_1 = require("../../tools/esbuild/commonjs-checker");
const license_extractor_1 = require("../../tools/esbuild/license-extractor");
const profiling_1 = require("../../tools/esbuild/profiling");
const utils_1 = require("../../tools/esbuild/utils");
const bundle_calculator_1 = require("../../utils/bundle-calculator");
const environment_options_1 = require("../../utils/environment-options");
const resolve_assets_1 = require("../../utils/resolve-assets");
const manifest_1 = require("../../utils/server-rendering/manifest");
const supported_browsers_1 = require("../../utils/supported-browsers");
const execute_post_bundle_1 = require("./execute-post-bundle");
const i18n_1 = require("./i18n");
const setup_bundling_1 = require("./setup-bundling");
// eslint-disable-next-line max-lines-per-function
async function executeBuild(options, context, rebuildState) {
const { projectRoot, workspaceRoot, i18nOptions, optimizationOptions, assets, cacheOptions, serverEntryPoint, baseHref, ssrOptions, verbose, colors, jsonLogs, } = options;
// TODO: Consider integrating into watch mode. Would require full rebuild on target changes.
const browsers = (0, supported_browsers_1.getSupportedBrowsers)(projectRoot, context.logger);
// Load active translations if inlining
// TODO: Integrate into watch mode and only load changed translations
if (i18nOptions.shouldInline) {
await (0, i18n_1.loadActiveTranslations)(context, i18nOptions);
}
// Reuse rebuild state or create new bundle contexts for code and global stylesheets
let bundlerContexts;
let componentStyleBundler;
let codeBundleCache;
let bundlingResult;
let templateUpdates;
if (rebuildState) {
bundlerContexts = rebuildState.rebuildContexts;
componentStyleBundler = rebuildState.componentStyleBundler;
codeBundleCache = rebuildState.codeBundleCache;
templateUpdates = rebuildState.templateUpdates;
// Reset template updates for new rebuild
templateUpdates?.clear();
const allFileChanges = rebuildState.fileChanges.all;
// Bundle all contexts that do not require TypeScript changed file checks.
// These will automatically use cached results based on the changed files.
bundlingResult = await bundler_context_1.BundlerContext.bundleAll(bundlerContexts.otherContexts, allFileChanges);
// Check the TypeScript code bundling cache for changes. If invalid, force a rebundle of
// all TypeScript related contexts.
const forceTypeScriptRebuild = codeBundleCache?.invalidate(allFileChanges);
const typescriptResults = [];
for (const typescriptContext of bundlerContexts.typescriptContexts) {
typescriptContext.invalidate(allFileChanges);
const result = await typescriptContext.bundle(forceTypeScriptRebuild);
typescriptResults.push(result);
}
bundlingResult = bundler_context_1.BundlerContext.mergeResults([bundlingResult, ...typescriptResults]);
}
else {
const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
codeBundleCache = new source_file_cache_1.SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
componentStyleBundler = (0, setup_bundling_1.createComponentStyleBundler)(options, target);
if (options.templateUpdates) {
templateUpdates = new Map();
}
bundlerContexts = (0, setup_bundling_1.setupBundlerContexts)(options, target, codeBundleCache, componentStyleBundler,
// Create new reusable compilation for the appropriate mode based on the `jit` plugin option
await (0, compilation_1.createAngularCompilation)(!!options.jit, !options.serverEntryPoint), templateUpdates);
// Bundle everything on initial build
bundlingResult = await bundler_context_1.BundlerContext.bundleAll([
...bundlerContexts.typescriptContexts,
...bundlerContexts.otherContexts,
]);
}
// Update any external component styles if enabled and rebuilding.
// TODO: Only attempt rebundling of invalidated styles once incremental build results are supported.
if (rebuildState && options.externalRuntimeStyles) {
componentStyleBundler.invalidate(rebuildState.fileChanges.all);
const componentResults = await componentStyleBundler.bundleAllFiles(true, true);
bundlingResult = bundler_context_1.BundlerContext.mergeResults([bundlingResult, ...componentResults]);
}
if (options.optimizationOptions.scripts && environment_options_1.shouldOptimizeChunks) {
const { optimizeChunks } = await Promise.resolve().then(() => __importStar(require('./chunk-optimizer')));
bundlingResult = await (0, profiling_1.profileAsync)('OPTIMIZE_CHUNKS', () => optimizeChunks(bundlingResult, options.sourcemapOptions.scripts ? !options.sourcemapOptions.hidden || 'hidden' : false));
}
const executionResult = new bundler_execution_result_1.ExecutionResult(bundlerContexts, componentStyleBundler, codeBundleCache, templateUpdates);
executionResult.addWarnings(bundlingResult.warnings);
// Add used external component style referenced files to be watched
if (options.externalRuntimeStyles) {
executionResult.extraWatchFiles.push(...componentStyleBundler.collectReferencedFiles());
}
// Return if the bundling has errors
if (bundlingResult.errors) {
executionResult.addErrors(bundlingResult.errors);
return executionResult;
}
// Analyze external imports if external options are enabled
if (options.externalPackages || bundlingResult.externalConfiguration) {
const { externalConfiguration = [], externalImports: { browser = [], server = [] }, } = bundlingResult;
// Similar to esbuild, --external:@foo/bar automatically implies --external:@foo/bar/*,
// which matches import paths like @foo/bar/baz.
// This means all paths within the @foo/bar package are also marked as external.
const exclusionsPrefixes = externalConfiguration.map((exclusion) => exclusion + '/');
const exclusions = new Set(externalConfiguration);
const explicitExternal = new Set();
const isExplicitExternal = (dep) => {
if (exclusions.has(dep)) {
return true;
}
for (const prefix of exclusionsPrefixes) {
if (dep.startsWith(prefix)) {
return true;
}
}
return false;
};
const implicitBrowser = [];
for (const dep of browser) {
if (isExplicitExternal(dep)) {
explicitExternal.add(dep);
}
else {
implicitBrowser.push(dep);
}
}
const implicitServer = [];
for (const dep of server) {
if (isExplicitExternal(dep)) {
explicitExternal.add(dep);
}
else {
implicitServer.push(dep);
}
}
executionResult.setExternalMetadata(implicitBrowser, implicitServer, [...explicitExternal]);
}
const { metafile, initialFiles, outputFiles } = bundlingResult;
executionResult.outputFiles.push(...outputFiles);
// Analyze files for bundle budget failures if present
let budgetFailures;
if (options.budgets) {
const compatStats = (0, budget_stats_1.generateBudgetStats)(metafile, outputFiles, initialFiles);
budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(options.budgets, compatStats, true)];
for (const { message, severity } of budgetFailures) {
if (severity === 'error') {
executionResult.addError(message);
}
else {
executionResult.addWarning(message);
}
}
}
// Calculate estimated transfer size if scripts are optimized
let estimatedTransferSizes;
if (optimizationOptions.scripts || optimizationOptions.styles.minify) {
estimatedTransferSizes = await (0, utils_1.calculateEstimatedTransferSizes)(executionResult.outputFiles);
}
// Check metafile for CommonJS module usage if optimizing scripts
if (optimizationOptions.scripts) {
const messages = (0, commonjs_checker_1.checkCommonJSModules)(metafile, options.allowedCommonJsDependencies);
executionResult.addWarnings(messages);
}
// Copy assets
if (assets) {
executionResult.addAssets(await (0, resolve_assets_1.resolveAssets)(assets, workspaceRoot));
}
// Extract and write licenses for used packages
if (options.extractLicenses) {
executionResult.addOutputFile('3rdpartylicenses.txt', await (0, license_extractor_1.extractLicenses)(metafile, workspaceRoot), bundler_context_1.BuildOutputFileType.Root);
}
// Watch input index HTML file if configured
if (options.indexHtmlOptions) {
executionResult.extraWatchFiles.push(options.indexHtmlOptions.input);
executionResult.htmlIndexPath = options.indexHtmlOptions.output;
executionResult.htmlBaseHref = options.baseHref;
}
// Create server app engine manifest
if (serverEntryPoint) {
executionResult.addOutputFile(manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME, (0, manifest_1.generateAngularServerAppEngineManifest)(i18nOptions, baseHref), bundler_context_1.BuildOutputFileType.ServerRoot);
}
// Perform i18n translation inlining if enabled
if (i18nOptions.shouldInline) {
const result = await (0, i18n_1.inlineI18n)(metafile, options, executionResult, initialFiles);
executionResult.addErrors(result.errors);
executionResult.addWarnings(result.warnings);
executionResult.addPrerenderedRoutes(result.prerenderedRoutes);
}
else {
const result = await (0, execute_post_bundle_1.executePostBundleSteps)(metafile, options, executionResult.outputFiles, executionResult.assetFiles, initialFiles,
// Set lang attribute to the defined source locale if present
i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined);
// Deduplicate and add errors and warnings
executionResult.addErrors([...new Set(result.errors)]);
executionResult.addWarnings([...new Set(result.warnings)]);
executionResult.addPrerenderedRoutes(result.prerenderedRoutes);
executionResult.outputFiles.push(...result.additionalOutputFiles);
executionResult.assetFiles.push(...result.additionalAssets);
}
executionResult.addOutputFile('prerendered-routes.json', JSON.stringify({ routes: executionResult.prerenderedRoutes }, null, 2), bundler_context_1.BuildOutputFileType.Root);
// Write metafile if stats option is enabled
if (options.stats) {
executionResult.addOutputFile('stats.json', JSON.stringify(metafile, null, 2), bundler_context_1.BuildOutputFileType.Root);
}
if (!jsonLogs) {
const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo);
executionResult.addLog((0, utils_1.logBuildStats)(metafile, outputFiles, initialFiles, budgetFailures, colors, changedFiles, estimatedTransferSizes, !!ssrOptions, verbose));
}
return executionResult;
}

View File

@@ -0,0 +1,27 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { Metafile } from 'esbuild';
import { BuildOutputFile, InitialFileRecord } from '../../tools/esbuild/bundler-context';
import { BuildOutputAsset, PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedApplicationBuildOptions } from './options';
/**
* Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation.
* @param metafile An esbuild metafile object.
* @param options The normalized application builder options used to create the build.
* @param outputFiles The output files of an executed build.
* @param assetFiles The assets of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
* @param locale A language locale to insert in the index.html.
*/
export declare function executePostBundleSteps(metafile: Metafile, options: NormalizedApplicationBuildOptions, outputFiles: BuildOutputFile[], assetFiles: BuildOutputAsset[], initialFiles: Map<string, InitialFileRecord>, locale: string | undefined): Promise<{
errors: string[];
warnings: string[];
additionalOutputFiles: BuildOutputFile[];
additionalAssets: BuildOutputAsset[];
prerenderedRoutes: PrerenderedRoutesRecord;
}>;

View File

@@ -0,0 +1,135 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executePostBundleSteps = executePostBundleSteps;
const node_assert_1 = __importDefault(require("node:assert"));
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const index_html_generator_1 = require("../../tools/esbuild/index-html-generator");
const utils_1 = require("../../tools/esbuild/utils");
const environment_options_1 = require("../../utils/environment-options");
const manifest_1 = require("../../utils/server-rendering/manifest");
const models_1 = require("../../utils/server-rendering/models");
const prerender_1 = require("../../utils/server-rendering/prerender");
const service_worker_1 = require("../../utils/service-worker");
const options_1 = require("./options");
const schema_1 = require("./schema");
/**
* Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation.
* @param metafile An esbuild metafile object.
* @param options The normalized application builder options used to create the build.
* @param outputFiles The output files of an executed build.
* @param assetFiles The assets of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
* @param locale A language locale to insert in the index.html.
*/
// eslint-disable-next-line max-lines-per-function
async function executePostBundleSteps(metafile, options, outputFiles, assetFiles, initialFiles, locale) {
const additionalAssets = [];
const additionalOutputFiles = [];
const allErrors = [];
const allWarnings = [];
const prerenderedRoutes = {};
const { baseHref = '/', serviceWorker, ssrOptions, indexHtmlOptions, optimizationOptions, sourcemapOptions, outputMode, serverEntryPoint, prerenderOptions, appShellOptions, publicPath, workspaceRoot, partialSSRBuild, } = options;
// Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR).
// NOTE: Critical CSS inlining is deliberately omitted here, as it will be handled during server rendering.
// Additionally, when using prerendering or AppShell, the index HTML file may be regenerated.
// To prevent generating duplicate files with the same filename, a `Map` is used to store and manage the files.
const additionalHtmlOutputFiles = new Map();
// Generate index HTML file
// If localization is enabled, index generation is handled in the inlining process.
if (indexHtmlOptions) {
const { csrContent, ssrContent, errors, warnings } = await (0, index_html_generator_1.generateIndexHtml)(initialFiles, outputFiles, options, locale);
allErrors.push(...errors);
allWarnings.push(...warnings);
additionalHtmlOutputFiles.set(indexHtmlOptions.output, (0, utils_1.createOutputFile)(indexHtmlOptions.output, csrContent, bundler_context_1.BuildOutputFileType.Browser));
if (ssrContent) {
additionalHtmlOutputFiles.set(options_1.INDEX_HTML_SERVER, (0, utils_1.createOutputFile)(options_1.INDEX_HTML_SERVER, ssrContent, bundler_context_1.BuildOutputFileType.ServerApplication));
}
}
// Create server manifest
const initialFilesPaths = new Set(initialFiles.keys());
if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) {
const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, undefined, locale, baseHref, initialFilesPaths, metafile, publicPath);
additionalOutputFiles.push(...serverAssetsChunks, (0, utils_1.createOutputFile)(manifest_1.SERVER_APP_MANIFEST_FILENAME, manifestContent, bundler_context_1.BuildOutputFileType.ServerApplication));
}
// Pre-render (SSG) and App-shell
// If localization is enabled, prerendering is handled in the inlining process.
if (!partialSSRBuild &&
(prerenderOptions || appShellOptions || (outputMode && serverEntryPoint)) &&
!allErrors.length) {
(0, node_assert_1.default)(indexHtmlOptions, 'The "index" option is required when using the "ssg" or "appShell" options.');
const { output, warnings, errors, serializableRouteTreeNode } = await (0, prerender_1.prerenderPages)(workspaceRoot, baseHref, appShellOptions, prerenderOptions, [...outputFiles, ...additionalOutputFiles], assetFiles, outputMode, sourcemapOptions.scripts, environment_options_1.maxWorkers);
allErrors.push(...errors);
allWarnings.push(...warnings);
const indexHasBeenPrerendered = output[indexHtmlOptions.output];
for (const [path, { content, appShellRoute }] of Object.entries(output)) {
// Update the index contents with the app shell under these conditions:
// - Replace 'index.html' with the app shell only if it hasn't been prerendered yet.
// - Always replace 'index.csr.html' with the app shell.
let filePath = path;
if (appShellRoute && !indexHasBeenPrerendered) {
if (outputMode !== schema_1.OutputMode.Server && indexHtmlOptions.output === options_1.INDEX_HTML_CSR) {
filePath = 'index.html';
}
else {
filePath = indexHtmlOptions.output;
}
}
additionalHtmlOutputFiles.set(filePath, (0, utils_1.createOutputFile)(filePath, content, bundler_context_1.BuildOutputFileType.Browser));
}
const serializableRouteTreeNodeForManifest = [];
for (const metadata of serializableRouteTreeNode) {
serializableRouteTreeNodeForManifest.push(metadata);
if (metadata.renderMode === models_1.RouteRenderMode.Prerender && !metadata.route.includes('*')) {
prerenderedRoutes[metadata.route] = { headers: metadata.headers };
}
}
if (outputMode === schema_1.OutputMode.Server) {
// Regenerate the manifest to append route tree. This is only needed if SSR is enabled.
const manifest = additionalOutputFiles.find((f) => f.path === manifest_1.SERVER_APP_MANIFEST_FILENAME);
(0, node_assert_1.default)(manifest, `${manifest_1.SERVER_APP_MANIFEST_FILENAME} was not found in output files.`);
const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, serializableRouteTreeNodeForManifest, locale, baseHref, initialFilesPaths, metafile, publicPath);
for (const chunk of serverAssetsChunks) {
const idx = additionalOutputFiles.findIndex(({ path }) => path === chunk.path);
if (idx === -1) {
additionalOutputFiles.push(chunk);
}
else {
additionalOutputFiles[idx] = chunk;
}
}
manifest.contents = new TextEncoder().encode(manifestContent);
}
}
additionalOutputFiles.push(...additionalHtmlOutputFiles.values());
// Augment the application with service worker support
// If localization is enabled, service worker is handled in the inlining process.
if (serviceWorker) {
try {
const serviceWorkerResult = await (0, service_worker_1.augmentAppWithServiceWorkerEsbuild)(workspaceRoot, serviceWorker, baseHref, options.indexHtmlOptions?.output,
// Ensure additional files recently added are used
[...outputFiles, ...additionalOutputFiles], assetFiles);
additionalOutputFiles.push((0, utils_1.createOutputFile)('ngsw.json', serviceWorkerResult.manifest, bundler_context_1.BuildOutputFileType.Browser));
additionalAssets.push(...serviceWorkerResult.assetFiles);
}
catch (error) {
allErrors.push(error instanceof Error ? error.message : `${error}`);
}
}
return {
errors: allErrors,
warnings: allWarnings,
additionalAssets,
prerenderedRoutes,
additionalOutputFiles,
};
}

View File

@@ -0,0 +1,31 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import type { Metafile } from 'esbuild';
import { InitialFileRecord } from '../../tools/esbuild/bundler-context';
import { ExecutionResult, PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedApplicationBuildOptions } from './options';
/**
* Inlines all active locales as specified by the application build options into all
* application JavaScript files created during the build.
* @param metafile An esbuild metafile object.
* @param options The normalized application builder options used to create the build.
* @param executionResult The result of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
*/
export declare function inlineI18n(metafile: Metafile, options: NormalizedApplicationBuildOptions, executionResult: ExecutionResult, initialFiles: Map<string, InitialFileRecord>): Promise<{
errors: string[];
warnings: string[];
prerenderedRoutes: PrerenderedRoutesRecord;
}>;
/**
* Loads all active translations using the translation loaders from the `@angular/localize` package.
* @param context The architect builder context for the current build.
* @param i18n The normalized i18n options to use.
*/
export declare function loadActiveTranslations(context: BuilderContext, i18n: NormalizedApplicationBuildOptions['i18nOptions']): Promise<void>;

137
node_modules/@angular/build/src/builders/application/i18n.js generated vendored Executable file
View File

@@ -0,0 +1,137 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.inlineI18n = inlineI18n;
exports.loadActiveTranslations = loadActiveTranslations;
const node_path_1 = require("node:path");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const i18n_inliner_1 = require("../../tools/esbuild/i18n-inliner");
const environment_options_1 = require("../../utils/environment-options");
const i18n_options_1 = require("../../utils/i18n-options");
const load_translations_1 = require("../../utils/load-translations");
const execute_post_bundle_1 = require("./execute-post-bundle");
const options_1 = require("./options");
/**
* Inlines all active locales as specified by the application build options into all
* application JavaScript files created during the build.
* @param metafile An esbuild metafile object.
* @param options The normalized application builder options used to create the build.
* @param executionResult The result of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
*/
async function inlineI18n(metafile, options, executionResult, initialFiles) {
const { i18nOptions, optimizationOptions, baseHref, cacheOptions } = options;
// Create the multi-threaded inliner with common options and the files generated from the build.
const inliner = new i18n_inliner_1.I18nInliner({
missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning',
outputFiles: executionResult.outputFiles,
shouldOptimize: optimizationOptions.scripts,
persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined,
}, environment_options_1.maxWorkers);
const inlineResult = {
errors: [],
warnings: [],
prerenderedRoutes: {},
};
// For each active locale, use the inliner to process the output files of the build.
const updatedOutputFiles = [];
const updatedAssetFiles = [];
// Root and SSR entry files are not modified.
const unModifiedOutputFiles = executionResult.outputFiles.filter(({ type }) => type === bundler_context_1.BuildOutputFileType.Root || type === bundler_context_1.BuildOutputFileType.ServerRoot);
try {
for (const locale of i18nOptions.inlineLocales) {
// A locale specific set of files is returned from the inliner.
const localeInlineResult = await inliner.inlineForLocale(locale, i18nOptions.locales[locale].translation);
const localeOutputFiles = localeInlineResult.outputFiles;
inlineResult.errors.push(...localeInlineResult.errors);
inlineResult.warnings.push(...localeInlineResult.warnings);
const { errors, warnings, additionalAssets, additionalOutputFiles, prerenderedRoutes: generatedRoutes, } = await (0, execute_post_bundle_1.executePostBundleSteps)(metafile, {
...options,
baseHref: (0, options_1.getLocaleBaseHref)(baseHref, i18nOptions, locale) ?? baseHref,
}, [...unModifiedOutputFiles, ...localeOutputFiles], executionResult.assetFiles, initialFiles, locale);
localeOutputFiles.push(...additionalOutputFiles);
inlineResult.errors.push(...errors);
inlineResult.warnings.push(...warnings);
// Update directory with locale base or subPath
const subPath = i18nOptions.locales[locale].subPath;
if (i18nOptions.flatOutput !== true) {
localeOutputFiles.forEach((file) => {
file.path = (0, node_path_1.join)(subPath, file.path);
});
for (const assetFile of [...executionResult.assetFiles, ...additionalAssets]) {
updatedAssetFiles.push({
source: assetFile.source,
destination: (0, node_path_1.join)(subPath, assetFile.destination),
});
}
}
else {
executionResult.assetFiles.push(...additionalAssets);
}
inlineResult.prerenderedRoutes = { ...inlineResult.prerenderedRoutes, ...generatedRoutes };
updatedOutputFiles.push(...localeOutputFiles);
}
}
finally {
await inliner.close();
}
// Update the result with all localized files.
executionResult.outputFiles = [
// Root and SSR entry files are not modified.
...unModifiedOutputFiles,
// Updated files for each locale.
...updatedOutputFiles,
];
// Assets are only changed if not using the flat output option
if (!i18nOptions.flatOutput) {
executionResult.assetFiles = updatedAssetFiles;
}
// Inline any template updates if present
if (executionResult.templateUpdates?.size) {
// The development server only allows a single locale but issue a warning if used programmatically (experimental)
// with multiple locales and template HMR.
if (i18nOptions.inlineLocales.size > 1) {
inlineResult.warnings.push(`Component HMR updates can only be inlined with a single locale. The first locale will be used.`);
}
const firstLocale = [...i18nOptions.inlineLocales][0];
for (const [id, content] of executionResult.templateUpdates) {
const templateUpdateResult = await inliner.inlineTemplateUpdate(firstLocale, i18nOptions.locales[firstLocale].translation, content, id);
executionResult.templateUpdates.set(id, templateUpdateResult.code);
inlineResult.errors.push(...templateUpdateResult.errors);
inlineResult.warnings.push(...templateUpdateResult.warnings);
}
}
return inlineResult;
}
/**
* Loads all active translations using the translation loaders from the `@angular/localize` package.
* @param context The architect builder context for the current build.
* @param i18n The normalized i18n options to use.
*/
async function loadActiveTranslations(context, i18n) {
// Load locale data and translations (if present)
let loader;
for (const [locale, desc] of Object.entries(i18n.locales)) {
if (!i18n.inlineLocales.has(locale) && locale !== i18n.sourceLocale) {
continue;
}
if (!desc.files.length) {
continue;
}
loader ??= await (0, load_translations_1.createTranslationLoader)();
(0, i18n_options_1.loadTranslations)(locale, desc, context.workspaceRoot, loader, {
warn(message) {
context.logger.warn(message);
},
error(message) {
throw new Error(message);
},
}, undefined, i18n.duplicateTranslationBehavior);
}
}

View File

@@ -0,0 +1,32 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { Builder, BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import { ApplicationBuilderExtensions, ApplicationBuilderInternalOptions } from './options';
import { Result } from './results';
import { Schema as ApplicationBuilderOptions } from './schema';
export type { ApplicationBuilderOptions };
export declare function buildApplicationInternal(options: ApplicationBuilderInternalOptions, context: BuilderContext & {
signal?: AbortSignal;
}, extensions?: ApplicationBuilderExtensions): AsyncIterable<Result>;
/**
* Builds an application using the `application` builder with the provided
* options.
*
* Usage of the `extensions` parameter is NOT supported and may cause unexpected
* build output or build failures.
*
* @experimental Direct usage of this function is considered experimental.
*
* @param options The options defined by the builder's schema to use.
* @param context An Architect builder context instance.
* @param extensions An object contain extension points for the build.
* @returns The build output results of the build.
*/
export declare function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable<BuilderOutput>;
declare const builder: Builder<ApplicationBuilderOptions>;
export default builder;

252
node_modules/@angular/build/src/builders/application/index.js generated vendored Executable file
View File

@@ -0,0 +1,252 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildApplicationInternal = buildApplicationInternal;
exports.buildApplication = buildApplication;
const architect_1 = require("@angular-devkit/architect");
const node_assert_1 = __importDefault(require("node:assert"));
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const utils_1 = require("../../tools/esbuild/utils");
const color_1 = require("../../utils/color");
const delete_output_dir_1 = require("../../utils/delete-output-dir");
const environment_options_1 = require("../../utils/environment-options");
const purge_cache_1 = require("../../utils/purge-cache");
const version_1 = require("../../utils/version");
const build_action_1 = require("./build-action");
const execute_build_1 = require("./execute-build");
const options_1 = require("./options");
const results_1 = require("./results");
const isNodeV22orHigher = Number(process.versions.node.split('.', 1)[0]) >= 22;
async function* buildApplicationInternal(options,
// TODO: Integrate abort signal support into builder system
context, extensions) {
const { workspaceRoot, logger, target } = context;
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(workspaceRoot);
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
// Determine project name from builder context target
const projectName = target?.project;
if (!projectName) {
context.logger.error(`The 'application' builder requires a target to be specified.`);
// Only the vite-based dev server current uses the errors value
yield { kind: results_1.ResultKind.Failure, errors: [] };
return;
}
if (environment_options_1.bazelEsbuildPluginPath) {
extensions ??= {};
extensions.codePlugins ??= [];
const { default: bazelEsbuildPlugin } = await Promise.resolve(`${environment_options_1.bazelEsbuildPluginPath}`).then(s => __importStar(require(s)));
extensions.codePlugins.push(bazelEsbuildPlugin);
}
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options, extensions);
if (!normalizedOptions.outputOptions.ignoreServer) {
const { browser, server } = normalizedOptions.outputOptions;
if (browser === '') {
context.logger.error(`'outputPath.browser' cannot be configured to an empty string when SSR is enabled.`);
yield { kind: results_1.ResultKind.Failure, errors: [] };
return;
}
if (browser === server) {
context.logger.error(`'outputPath.browser' and 'outputPath.server' cannot be configured to the same value.`);
yield { kind: results_1.ResultKind.Failure, errors: [] };
return;
}
}
// Setup an abort controller with a builder teardown if no signal is present
let signal = context.signal;
if (!signal) {
const controller = new AbortController();
signal = controller.signal;
context.addTeardown(() => controller.abort('builder-teardown'));
}
yield* (0, build_action_1.runEsBuildBuildAction)(async (rebuildState) => {
const { serverEntryPoint, jsonLogs, partialSSRBuild } = normalizedOptions;
const startTime = process.hrtime.bigint();
const result = await (0, execute_build_1.executeBuild)(normalizedOptions, context, rebuildState);
if (jsonLogs) {
result.addLog(await (0, utils_1.createJsonBuildManifest)(result, normalizedOptions));
}
else {
if (serverEntryPoint && !partialSSRBuild) {
const prerenderedRoutesLength = Object.keys(result.prerenderedRoutes).length;
let prerenderMsg = `Prerendered ${prerenderedRoutesLength} static route`;
prerenderMsg += prerenderedRoutesLength !== 1 ? 's.' : '.';
result.addLog(color_1.colors.magenta(prerenderMsg));
}
const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
const hasError = result.errors.length > 0;
result.addLog(`Application bundle generation ${hasError ? 'failed' : 'complete'}.` +
` [${buildTime.toFixed(3)} seconds] - ${new Date().toISOString()}\n`);
}
return result;
}, {
watch: normalizedOptions.watch,
preserveSymlinks: normalizedOptions.preserveSymlinks,
poll: normalizedOptions.poll,
cacheOptions: normalizedOptions.cacheOptions,
outputOptions: normalizedOptions.outputOptions,
verbose: normalizedOptions.verbose,
projectRoot: normalizedOptions.projectRoot,
workspaceRoot: normalizedOptions.workspaceRoot,
progress: normalizedOptions.progress,
clearScreen: normalizedOptions.clearScreen,
colors: normalizedOptions.colors,
jsonLogs: normalizedOptions.jsonLogs,
incrementalResults: normalizedOptions.incrementalResults,
logger,
signal,
});
}
/**
* Builds an application using the `application` builder with the provided
* options.
*
* Usage of the `extensions` parameter is NOT supported and may cause unexpected
* build output or build failures.
*
* @experimental Direct usage of this function is considered experimental.
*
* @param options The options defined by the builder's schema to use.
* @param context An Architect builder context instance.
* @param extensions An object contain extension points for the build.
* @returns The build output results of the build.
*/
async function* buildApplication(options, context, extensions) {
let initial = true;
const internalOptions = { ...options, incrementalResults: true };
for await (const result of buildApplicationInternal(internalOptions, context, extensions)) {
const outputOptions = result.detail?.['outputOptions'];
if (initial) {
initial = false;
// Clean the output location if requested.
// Output options may not be present if the build failed.
if (outputOptions?.clean) {
await (0, delete_output_dir_1.deleteOutputDir)(context.workspaceRoot, outputOptions.base, [
outputOptions.browser,
outputOptions.server,
]);
}
}
if (result.kind === results_1.ResultKind.Failure) {
yield { success: false };
continue;
}
(0, node_assert_1.default)(outputOptions, 'Application output options are required for builder usage.');
(0, node_assert_1.default)(result.kind === results_1.ResultKind.Full || result.kind === results_1.ResultKind.Incremental, 'Application build did not provide a file result output.');
// TODO: Restructure output logging to better handle stdout JSON piping
if (!environment_options_1.useJSONBuildLogs) {
context.logger.info(`Output location: ${outputOptions.base}\n`);
}
// Writes the output files to disk and ensures the containing directories are present
const directoryExists = new Set();
await (0, utils_1.emitFilesToDisk)(Object.entries(result.files), async ([filePath, file]) => {
if (outputOptions.ignoreServer &&
(file.type === bundler_context_1.BuildOutputFileType.ServerApplication ||
file.type === bundler_context_1.BuildOutputFileType.ServerRoot)) {
return;
}
const fullFilePath = generateFullPath(filePath, file.type, outputOptions);
// Ensure output subdirectories exist
const fileBasePath = node_path_1.default.dirname(fullFilePath);
if (fileBasePath && !directoryExists.has(fileBasePath)) {
await promises_1.default.mkdir(fileBasePath, { recursive: true });
directoryExists.add(fileBasePath);
}
if (file.origin === 'memory') {
// Write file contents
await promises_1.default.writeFile(fullFilePath, file.contents);
}
else {
// Copy file contents
if (isNodeV22orHigher) {
// Use newer `cp` API on Node.js 22+ (minimum v22 for CLI is 22.11)
await promises_1.default.cp(file.inputPath, fullFilePath, {
mode: promises_1.default.constants.COPYFILE_FICLONE,
preserveTimestamps: true,
});
}
else {
// For Node.js 20 use `copyFile` (`cp` is not stable for v20)
// TODO: Remove when Node.js 20 is no longer supported
await promises_1.default.copyFile(file.inputPath, fullFilePath, promises_1.default.constants.COPYFILE_FICLONE);
}
}
});
// Delete any removed files if incremental
if (result.kind === results_1.ResultKind.Incremental && result.removed?.length) {
await Promise.all(result.removed.map((file) => {
const fullFilePath = generateFullPath(file.path, file.type, outputOptions);
return promises_1.default.rm(fullFilePath, { force: true, maxRetries: 3 });
}));
}
yield { success: true };
}
}
function generateFullPath(filePath, type, outputOptions) {
let typeDirectory;
switch (type) {
case bundler_context_1.BuildOutputFileType.Browser:
case bundler_context_1.BuildOutputFileType.Media:
typeDirectory = outputOptions.browser;
break;
case bundler_context_1.BuildOutputFileType.ServerApplication:
case bundler_context_1.BuildOutputFileType.ServerRoot:
typeDirectory = outputOptions.server;
break;
case bundler_context_1.BuildOutputFileType.Root:
typeDirectory = '';
break;
default:
throw new Error(`Unhandled write for file "${filePath}" with type "${bundler_context_1.BuildOutputFileType[type]}".`);
}
// NOTE: 'base' is a fully resolved path at this point
const fullFilePath = node_path_1.default.join(outputOptions.base, typeDirectory, filePath);
return fullFilePath;
}
const builder = (0, architect_1.createBuilder)(buildApplication);
exports.default = builder;

View File

@@ -0,0 +1,210 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import { I18nOptions } from '../../utils/i18n-options';
import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import { Schema as ApplicationBuilderOptions, ExperimentalPlatform, I18NTranslation, OutputMode, OutputPathClass } from './schema';
/**
* The filename for the client-side rendered HTML template.
* This template is used for client-side rendering (CSR) in a web application.
*/
export declare const INDEX_HTML_CSR = "index.csr.html";
/**
* The filename for the server-side rendered HTML template.
* This template is used for server-side rendering (SSR) in a web application.
*/
export declare const INDEX_HTML_SERVER = "index.server.html";
export type NormalizedOutputOptions = Required<OutputPathClass> & {
clean: boolean;
ignoreServer: boolean;
};
export type NormalizedApplicationBuildOptions = Awaited<ReturnType<typeof normalizeOptions>>;
export interface ApplicationBuilderExtensions {
codePlugins?: Plugin[];
indexHtmlTransformer?: IndexHtmlTransform;
}
/** Internal options hidden from builder schema but available when invoked programmatically. */
interface InternalOptions {
/**
* Entry points to use for the compilation. Incompatible with `browser`, which must not be provided. May be relative or absolute paths.
* If given a relative path, it is resolved relative to the current workspace and will generate an output at the same relative location
* in the output directory. If given an absolute path, the output will be generated in the root of the output directory with the same base
* name.
*
* If provided a Map, the key is the name of the output bundle and the value is the entry point file.
*/
entryPoints?: Set<string> | Map<string, string>;
/** File extension to use for the generated output files. */
outExtension?: 'js' | 'mjs';
/**
* Indicates whether all node packages should be marked as external.
* Currently used by the dev-server to support prebundling.
*/
externalPackages?: boolean | {
exclude: string[];
};
/**
* Forces the output from the localize post-processing to not create nested directories per locale output.
* This is only used by the development server which currently only supports a single locale per build.
*/
forceI18nFlatOutput?: boolean;
/**
* When set to `true`, enables fast SSR in development mode by disabling the full manifest generation and prerendering.
*
* This option is intended to optimize performance during development by skipping prerendering and route extraction when not required.
* @default false
*/
partialSSRBuild?: boolean;
/**
* Enables the use of AOT compiler emitted external runtime styles.
* External runtime styles use `link` elements instead of embedded style content in the output JavaScript.
* This option is only intended to be used with a development server that can process and serve component
* styles.
*/
externalRuntimeStyles?: boolean;
/**
* Enables the AOT compiler to generate template component update functions.
* This option is only intended to be used with a development server that can process and serve component
* template updates.
*/
templateUpdates?: boolean;
/**
* Enables emitting incremental build results when in watch mode. A full build result will only be emitted
* for the initial build. This option also requires watch to be enabled to have an effect.
*/
incrementalResults?: boolean;
/**
* Enables instrumentation to collect code coverage data for specific files.
*
* Used exclusively for tests and shouldn't be used for other kinds of builds.
*/
instrumentForCoverage?: (filename: string) => boolean;
}
/** Full set of options for `application` builder. */
export type ApplicationBuilderInternalOptions = Omit<ApplicationBuilderOptions & InternalOptions, 'browser'> & {
browser?: string;
};
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @param plugins An optional array of programmatically supplied build plugins.
* @returns An object containing normalized options required to perform the build.
*/
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: ApplicationBuilderInternalOptions, extensions?: ApplicationBuilderExtensions): Promise<{
advancedOptimizations: boolean;
allowedCommonJsDependencies: string[] | undefined;
baseHref: string | undefined;
cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
crossOrigin: import("./schema").CrossOrigin | undefined;
externalDependencies: string[] | undefined;
externalPackages: boolean | {
exclude: string[] | undefined;
} | undefined;
extractLicenses: boolean | undefined;
inlineStyleLanguage: string;
jit: boolean;
stats: boolean;
polyfills: string[] | undefined;
poll: number | undefined;
progress: boolean;
preserveSymlinks: boolean;
stylePreprocessorOptions: import("./schema").StylePreprocessorOptions | undefined;
subresourceIntegrity: boolean | undefined;
serverEntryPoint: string | undefined;
prerenderOptions: {
discoverRoutes: boolean;
routesFile: string | undefined;
} | undefined;
appShellOptions: {
route: string;
} | undefined;
outputMode: OutputMode | undefined;
ssrOptions: {
entry?: undefined;
platform?: undefined;
} | {
entry: string | undefined;
platform: ExperimentalPlatform;
} | undefined;
verbose: boolean | undefined;
watch: boolean | undefined;
workspaceRoot: string;
entryPoints: Record<string, string>;
optimizationOptions: import("../../utils").NormalizedOptimizationOptions;
outputOptions: NormalizedOutputOptions;
outExtension: "js" | "mjs" | undefined;
sourcemapOptions: import("./schema").SourceMapClass;
tsconfig: string;
projectRoot: string;
assets: (import("./schema").AssetPatternClass & {
output: string;
})[] | undefined;
outputNames: {
bundles: string;
media: string;
};
fileReplacements: Record<string, string> | undefined;
globalStyles: {
name: string;
files: string[];
initial: boolean;
}[];
globalScripts: {
name: string;
files: string[];
initial: boolean;
}[];
serviceWorker: string | undefined;
indexHtmlOptions: {
input: string;
output: string;
insertionOrder: [string, boolean][];
transformer: IndexHtmlTransform | undefined;
preloadInitial: boolean;
} | undefined;
tailwindConfiguration: {
file: string;
package: string;
} | undefined;
postcssConfiguration: import("../../utils/postcss-configuration").PostcssConfiguration | undefined;
i18nOptions: I18nOptions & {
duplicateTranslationBehavior?: I18NTranslation;
missingTranslationBehavior?: I18NTranslation;
};
namedChunks: boolean | undefined;
budgets: import("./schema").Budget[] | undefined;
publicPath: string | undefined;
plugins: Plugin[] | undefined;
loaderExtensions: Record<string, "file" | "base64" | "binary" | "text" | "dataurl"> | undefined;
jsonLogs: boolean;
colors: boolean;
clearScreen: boolean | undefined;
define: {
[key: string]: string;
} | undefined;
partialSSRBuild: boolean;
externalRuntimeStyles: boolean | undefined;
instrumentForCoverage: ((filename: string) => boolean) | undefined;
security: {
autoCsp: {
unsafeEval: boolean;
} | undefined;
};
templateUpdates: boolean;
incrementalResults: boolean;
customConditions: string[] | undefined;
frameworkVersion: string;
}>;
export declare function getLocaleBaseHref(baseHref: string | undefined, i18n: NormalizedApplicationBuildOptions['i18nOptions'], locale: string): string | undefined;
export {};

View File

@@ -0,0 +1,490 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.INDEX_HTML_SERVER = exports.INDEX_HTML_CSR = void 0;
exports.normalizeOptions = normalizeOptions;
exports.getLocaleBaseHref = getLocaleBaseHref;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_module_1 = require("node:module");
const node_path_1 = __importDefault(require("node:path"));
const utils_1 = require("../../utils");
const color_1 = require("../../utils/color");
const environment_options_1 = require("../../utils/environment-options");
const i18n_options_1 = require("../../utils/i18n-options");
const normalize_cache_1 = require("../../utils/normalize-cache");
const postcss_configuration_1 = require("../../utils/postcss-configuration");
const project_metadata_1 = require("../../utils/project-metadata");
const url_1 = require("../../utils/url");
const schema_1 = require("./schema");
/**
* The filename for the client-side rendered HTML template.
* This template is used for client-side rendering (CSR) in a web application.
*/
exports.INDEX_HTML_CSR = 'index.csr.html';
/**
* The filename for the server-side rendered HTML template.
* This template is used for server-side rendering (SSR) in a web application.
*/
exports.INDEX_HTML_SERVER = 'index.server.html';
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @param plugins An optional array of programmatically supplied build plugins.
* @returns An object containing normalized options required to perform the build.
*/
// eslint-disable-next-line max-lines-per-function
async function normalizeOptions(context, projectName, options, extensions) {
// If not explicitly set, default to the Node.js process argument
const preserveSymlinks = options.preserveSymlinks ?? process.execArgv.includes('--preserve-symlinks');
// Setup base paths based on workspace root and project information
const workspaceRoot = preserveSymlinks
? context.workspaceRoot
: // NOTE: promises.realpath should not be used here since it uses realpath.native which
// can cause case conversion and other undesirable behavior on Windows systems.
// ref: https://github.com/nodejs/node/issues/7726
(0, node_fs_1.realpathSync)(context.workspaceRoot);
const projectMetadata = await context.getProjectMetadata(projectName);
const { projectRoot, projectSourceRoot } = (0, project_metadata_1.getProjectRootPaths)(workspaceRoot, projectMetadata);
// Gather persistent caching option and provide a project specific cache location
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
cacheOptions.path = node_path_1.default.join(cacheOptions.path, projectName);
const i18nOptions = (0, i18n_options_1.createI18nOptions)(projectMetadata, options.localize, context.logger, !!options.ssr);
i18nOptions.duplicateTranslationBehavior = options.i18nDuplicateTranslation;
i18nOptions.missingTranslationBehavior = options.i18nMissingTranslation;
if (options.forceI18nFlatOutput) {
i18nOptions.flatOutput = true;
}
const entryPoints = normalizeEntryPoints(workspaceRoot, projectSourceRoot, options.browser, options.entryPoints);
const tsconfig = node_path_1.default.join(workspaceRoot, options.tsConfig);
const optimizationOptions = (0, utils_1.normalizeOptimization)(options.optimization);
const sourcemapOptions = (0, utils_1.normalizeSourceMaps)(options.sourceMap ?? false);
const assets = options.assets?.length
? (0, utils_1.normalizeAssetPatterns)(options.assets, workspaceRoot, projectRoot, projectSourceRoot)
: undefined;
let fileReplacements;
if (options.fileReplacements) {
for (const replacement of options.fileReplacements) {
const fileReplaceWith = node_path_1.default.join(workspaceRoot, replacement.with);
try {
await (0, promises_1.access)(fileReplaceWith, promises_1.constants.F_OK);
}
catch {
throw new Error(`The ${fileReplaceWith} path in file replacements does not exist.`);
}
fileReplacements ??= {};
fileReplacements[node_path_1.default.join(workspaceRoot, replacement.replace)] = fileReplaceWith;
}
}
let loaderExtensions;
if (options.loader) {
for (const [extension, value] of Object.entries(options.loader)) {
if (extension[0] !== '.' || /\.[cm]?[jt]sx?$/.test(extension)) {
continue;
}
if (value !== 'text' &&
value !== 'binary' &&
value !== 'file' &&
value !== 'dataurl' &&
value !== 'base64' &&
value !== 'empty') {
continue;
}
loaderExtensions ??= {};
loaderExtensions[extension] = value;
}
}
// Validate prerender and ssr options when using the outputMode
if (options.outputMode === schema_1.OutputMode.Server) {
if (!options.server) {
throw new Error('The "server" option is required when "outputMode" is set to "server".');
}
if (typeof options.ssr === 'boolean' || !options.ssr?.entry) {
throw new Error('The "ssr.entry" option is required when "outputMode" is set to "server".');
}
}
if (options.outputMode) {
if (!options.server) {
options.ssr = false;
}
if (options.prerender !== undefined) {
context.logger.warn('The "prerender" option is not considered when "outputMode" is specified.');
}
options.prerender = !!options.server;
if (options.appShell !== undefined) {
context.logger.warn('The "appShell" option is not considered when "outputMode" is specified.');
}
}
// A configuration file can exist in the project or workspace root
const searchDirectories = await (0, postcss_configuration_1.generateSearchDirectories)([projectRoot, workspaceRoot]);
const postcssConfiguration = await (0, postcss_configuration_1.loadPostcssConfiguration)(searchDirectories);
// Skip tailwind configuration if postcss is customized
const tailwindConfiguration = postcssConfiguration
? undefined
: await getTailwindConfig(searchDirectories, workspaceRoot, context);
let serverEntryPoint;
if (typeof options.server === 'string') {
if (options.server === '') {
throw new Error('The "server" option cannot be an empty string.');
}
serverEntryPoint = node_path_1.default.join(workspaceRoot, options.server);
}
let prerenderOptions;
if (options.prerender) {
const { discoverRoutes = true, routesFile = undefined } = options.prerender === true ? {} : options.prerender;
prerenderOptions = {
discoverRoutes,
routesFile: routesFile && node_path_1.default.join(workspaceRoot, routesFile),
};
}
let ssrOptions;
if (options.ssr === true) {
ssrOptions = {};
}
else if (typeof options.ssr === 'object') {
const { entry, experimentalPlatform = schema_1.ExperimentalPlatform.Node } = options.ssr;
ssrOptions = {
entry: entry && node_path_1.default.join(workspaceRoot, entry),
platform: experimentalPlatform,
};
}
let appShellOptions;
if (options.appShell) {
appShellOptions = {
route: 'shell',
};
}
const outputPath = options.outputPath ?? node_path_1.default.join(workspaceRoot, 'dist', projectName);
const outputOptions = {
browser: 'browser',
server: 'server',
media: 'media',
...(typeof outputPath === 'string' ? undefined : outputPath),
base: (0, project_metadata_1.normalizeDirectoryPath)(node_path_1.default.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base)),
clean: options.deleteOutputPath ?? true,
// For app-shell and SSG server files are not required by users.
// Omit these when SSR is not enabled.
ignoreServer: ((ssrOptions === undefined || serverEntryPoint === undefined) &&
options.outputMode === undefined) ||
options.outputMode === schema_1.OutputMode.Static,
};
const outputNames = {
bundles: options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Bundles
? '[name]-[hash]'
: '[name]',
media: outputOptions.media +
(options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Media
? '/[name]-[hash]'
: '/[name]'),
};
const globalStyles = normalizeGlobalEntries(options.styles, 'styles');
const globalScripts = normalizeGlobalEntries(options.scripts, 'scripts');
let indexHtmlOptions;
// index can never have a value of `true` but in the schema it's of type `boolean`.
if (typeof options.index !== 'boolean') {
let indexInput;
let indexOutput;
// The output file will be created within the configured output path
if (typeof options.index === 'string') {
indexInput = indexOutput = node_path_1.default.join(workspaceRoot, options.index);
}
else if (typeof options.index === 'undefined') {
indexInput = node_path_1.default.join(projectSourceRoot, 'index.html');
indexOutput = 'index.html';
}
else {
indexInput = node_path_1.default.join(workspaceRoot, options.index.input);
indexOutput = options.index.output || 'index.html';
}
/**
* If SSR is activated, create a distinct entry file for the `index.html`.
* This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file
* if it exists (handling SSG).
*
* For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server.
*
* This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`.
*/
const indexBaseName = node_path_1.default.basename(indexOutput);
indexOutput =
(ssrOptions || prerenderOptions) && indexBaseName === 'index.html'
? exports.INDEX_HTML_CSR
: indexBaseName;
indexHtmlOptions = {
input: indexInput,
output: indexOutput,
insertionOrder: [
['polyfills', true],
...globalStyles.filter((s) => s.initial).map((s) => [s.name, false]),
...globalScripts.filter((s) => s.initial).map((s) => [s.name, false]),
['main', true],
// [name, esm]
],
transformer: extensions?.indexHtmlTransformer,
// Preload initial defaults to true
preloadInitial: typeof options.index !== 'object' || (options.index.preloadInitial ?? true),
};
}
if (appShellOptions || ssrOptions || prerenderOptions) {
if (!serverEntryPoint) {
throw new Error('The "server" option is required when enabling "ssr", "prerender" or "app-shell".');
}
if (!indexHtmlOptions) {
throw new Error('The "index" option cannot be set to false when enabling "ssr", "prerender" or "app-shell".');
}
}
const autoCsp = options.security?.autoCsp;
const security = {
autoCsp: autoCsp
? {
unsafeEval: autoCsp === true ? false : !!autoCsp.unsafeEval,
}
: undefined,
};
// Initial options to keep
const { allowedCommonJsDependencies, aot = true, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, } = options;
// Return all the normalized options
return {
advancedOptimizations: !!aot && optimizationOptions.scripts,
allowedCommonJsDependencies,
baseHref,
cacheOptions,
crossOrigin,
externalDependencies: normalizeExternals(externalDependencies),
externalPackages: typeof externalPackages === 'object'
? {
...externalPackages,
exclude: normalizeExternals(externalPackages.exclude),
}
: externalPackages,
extractLicenses,
inlineStyleLanguage,
jit: !aot,
stats: !!statsJson,
polyfills: polyfills === undefined || Array.isArray(polyfills) ? polyfills : [polyfills],
poll,
progress,
preserveSymlinks,
stylePreprocessorOptions,
subresourceIntegrity,
serverEntryPoint,
prerenderOptions,
appShellOptions,
outputMode,
ssrOptions,
verbose,
watch,
workspaceRoot,
entryPoints,
optimizationOptions,
outputOptions,
outExtension,
sourcemapOptions,
tsconfig,
projectRoot,
assets,
outputNames,
fileReplacements,
globalStyles,
globalScripts,
serviceWorker: serviceWorker
? node_path_1.default.join(workspaceRoot, typeof serviceWorker === 'string' ? serviceWorker : 'src/ngsw-config.json')
: undefined,
indexHtmlOptions,
tailwindConfiguration,
postcssConfiguration,
i18nOptions,
namedChunks,
budgets: budgets?.length ? budgets : undefined,
publicPath: deployUrl,
plugins: extensions?.codePlugins?.length ? extensions?.codePlugins : undefined,
loaderExtensions,
jsonLogs: environment_options_1.useJSONBuildLogs,
colors: (0, color_1.supportColor)(),
clearScreen,
define,
partialSSRBuild: environment_options_1.usePartialSsrBuild || partialSSRBuild,
externalRuntimeStyles: aot && externalRuntimeStyles,
instrumentForCoverage,
security,
templateUpdates: !!options.templateUpdates,
incrementalResults: !!options.incrementalResults,
customConditions: options.conditions,
frameworkVersion: await findFrameworkVersion(projectRoot),
};
}
async function getTailwindConfig(searchDirectories, workspaceRoot, context) {
const tailwindConfigurationPath = (0, postcss_configuration_1.findTailwindConfiguration)(searchDirectories);
if (!tailwindConfigurationPath) {
return undefined;
}
// Create a node resolver from the configuration file
const resolver = (0, node_module_1.createRequire)(tailwindConfigurationPath);
try {
return {
file: tailwindConfigurationPath,
package: resolver.resolve('tailwindcss'),
};
}
catch {
const relativeTailwindConfigPath = node_path_1.default.relative(workspaceRoot, tailwindConfigurationPath);
context.logger.warn(`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
` but the 'tailwindcss' package is not installed.` +
` To enable Tailwind CSS, please install the 'tailwindcss' package.`);
}
return undefined;
}
/**
* Normalize entry point options. To maintain compatibility with the legacy browser builder, we need a single `browser`
* option which defines a single entry point. However, we also want to support multiple entry points as an internal option.
* The two options are mutually exclusive and if `browser` is provided it will be used as the sole entry point.
* If `entryPoints` are provided, they will be used as the set of entry points.
*
* @param workspaceRoot Path to the root of the Angular workspace.
* @param browser The `browser` option pointing at the application entry point. While required per the schema file, it may be omitted by
* programmatic usages of `browser-esbuild`.
* @param entryPoints Set of entry points to use if provided.
* @returns An object mapping entry point names to their file paths.
*/
function normalizeEntryPoints(workspaceRoot, projectSourceRoot, browser, entryPoints) {
if (browser === '') {
throw new Error('`browser` option cannot be an empty string.');
}
// `browser` and `entryPoints` are mutually exclusive.
if (browser && entryPoints) {
throw new Error('Only one of `browser` or `entryPoints` may be provided.');
}
if (browser) {
// Use `browser` alone.
return { 'main': node_path_1.default.join(workspaceRoot, browser) };
}
else if (!entryPoints) {
// Default browser entry if no explicit entry points
return { 'main': node_path_1.default.join(projectSourceRoot, 'main.ts') };
}
else if (entryPoints instanceof Map) {
return Object.fromEntries(Array.from(entryPoints.entries(), ([name, entryPoint]) => {
// Get the full file path to a relative entry point input. Leave bare specifiers alone so they are resolved as modules.
const isRelativePath = entryPoint.startsWith('.');
const entryPointPath = isRelativePath ? node_path_1.default.join(workspaceRoot, entryPoint) : entryPoint;
return [name, entryPointPath];
}));
}
else {
// Use `entryPoints` alone.
const entryPointPaths = {};
for (const entryPoint of entryPoints) {
const parsedEntryPoint = node_path_1.default.parse(entryPoint);
// Use the input file path without an extension as the "name" of the entry point dictating its output location.
// Relative entry points are generated at the same relative path in the output directory.
// Absolute entry points are always generated with the same file name in the root of the output directory. This includes absolute
// paths pointing at files actually within the workspace root.
const entryPointName = node_path_1.default.isAbsolute(entryPoint)
? parsedEntryPoint.name
: node_path_1.default.join(parsedEntryPoint.dir, parsedEntryPoint.name);
// Get the full file path to a relative entry point input. Leave bare specifiers alone so they are resolved as modules.
const isRelativePath = entryPoint.startsWith('.');
const entryPointPath = isRelativePath ? node_path_1.default.join(workspaceRoot, entryPoint) : entryPoint;
// Check for conflicts with previous entry points.
const existingEntryPointPath = entryPointPaths[entryPointName];
if (existingEntryPointPath) {
throw new Error(`\`${existingEntryPointPath}\` and \`${entryPointPath}\` both output to the same location \`${entryPointName}\`.` +
' Rename or move one of the files to fix the conflict.');
}
entryPointPaths[entryPointName] = entryPointPath;
}
return entryPointPaths;
}
}
function normalizeGlobalEntries(rawEntries, defaultName) {
if (!rawEntries?.length) {
return [];
}
const bundles = new Map();
for (const rawEntry of rawEntries) {
let entry;
if (typeof rawEntry === 'string') {
// string entries use default bundle name and inject values
entry = { input: rawEntry };
}
else {
entry = rawEntry;
}
const { bundleName, input, inject = true } = entry;
// Non-injected entries default to the file name
const name = bundleName || (inject ? defaultName : node_path_1.default.basename(input, node_path_1.default.extname(input)));
const existing = bundles.get(name);
if (!existing) {
bundles.set(name, { name, files: [input], initial: inject });
continue;
}
if (existing.initial !== inject) {
throw new Error(`The "${name}" bundle is mixing injected and non-injected entries. ` +
'Verify that the project options are correct.');
}
existing.files.push(input);
}
return [...bundles.values()];
}
function getLocaleBaseHref(baseHref = '', i18n, locale) {
if (i18n.flatOutput) {
return undefined;
}
const localeData = i18n.locales[locale];
if (!localeData) {
return undefined;
}
const baseHrefSuffix = localeData.baseHref ?? localeData.subPath + '/';
return baseHrefSuffix !== '' ? (0, url_1.urlJoin)(baseHref, baseHrefSuffix) : undefined;
}
/**
* Normalizes an array of external dependency paths by ensuring that
* wildcard patterns (`/*`) are removed from package names.
*
* This avoids the need to handle this normalization repeatedly in our plugins,
* as esbuild already treats `--external:@foo/bar` as implicitly including
* `--external:@foo/bar/*`. By standardizing the input, we ensure consistency
* and reduce redundant checks across our plugins.
*
* @param value - An optional array of dependency paths to normalize.
* @returns A new array with wildcard patterns removed from package names, or `undefined` if input is `undefined`.
*/
function normalizeExternals(value) {
if (!value) {
return undefined;
}
return [
...new Set(value.map((d) =>
// remove "/*" wildcard in the end if provided string is not path-like
d.endsWith('/*') && !/^\.{0,2}\//.test(d) ? d.slice(0, -2) : d)),
];
}
async function findFrameworkVersion(projectRoot) {
// Create a custom require function for ESM compliance.
// NOTE: The trailing slash is significant.
const projectResolve = (0, node_module_1.createRequire)(projectRoot + '/').resolve;
try {
const manifestPath = projectResolve('@angular/core/package.json');
const manifestData = await (0, promises_1.readFile)(manifestPath, 'utf-8');
const manifestObject = JSON.parse(manifestData);
const version = manifestObject.version;
return version;
}
catch {
throw new Error('Error: It appears that "@angular/core" is missing as a dependency. Please ensure it is included in your project.');
}
}

View File

@@ -0,0 +1,73 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BuildOutputFileType } from '../../tools/esbuild/bundler-context';
export declare enum ResultKind {
Failure = 0,
Full = 1,
Incremental = 2,
ComponentUpdate = 3
}
export type Result = FailureResult | FullResult | IncrementalResult | ComponentUpdateResult;
export interface BaseResult {
kind: ResultKind;
warnings?: ResultMessage[];
duration?: number;
detail?: Record<string, unknown>;
}
export interface FailureResult extends BaseResult {
kind: ResultKind.Failure;
errors: ResultMessage[];
}
export interface FullResult extends BaseResult {
kind: ResultKind.Full;
files: Record<string, ResultFile>;
}
export interface IncrementalResult extends BaseResult {
kind: ResultKind.Incremental;
background?: boolean;
added: string[];
removed: {
path: string;
type: BuildOutputFileType;
}[];
modified: string[];
files: Record<string, ResultFile>;
}
export type ResultFile = DiskFile | MemoryFile;
export interface BaseResultFile {
origin: 'memory' | 'disk';
type: BuildOutputFileType;
}
export interface DiskFile extends BaseResultFile {
origin: 'disk';
inputPath: string;
}
export interface MemoryFile extends BaseResultFile {
origin: 'memory';
hash: string;
contents: Uint8Array;
}
export interface ResultMessage {
text: string;
location?: {
file: string;
line: number;
column: number;
} | null;
notes?: {
text: string;
}[];
}
export interface ComponentUpdateResult extends BaseResult {
kind: ResultKind.ComponentUpdate;
updates: {
id: string;
type: 'style' | 'template';
content: string;
}[];
}

View File

@@ -0,0 +1,17 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResultKind = void 0;
var ResultKind;
(function (ResultKind) {
ResultKind[ResultKind["Failure"] = 0] = "Failure";
ResultKind[ResultKind["Full"] = 1] = "Full";
ResultKind[ResultKind["Incremental"] = 2] = "Incremental";
ResultKind[ResultKind["ComponentUpdate"] = 3] = "ComponentUpdate";
})(ResultKind || (exports.ResultKind = ResultKind = {}));

View File

@@ -0,0 +1,644 @@
/**
* Application builder target options
*/
export type Schema = {
/**
* A list of CommonJS or AMD packages that are allowed to be used without a build time
* warning. Use `'*'` to allow all.
*/
allowedCommonJsDependencies?: string[];
/**
* Build using Ahead of Time compilation.
*/
aot?: boolean;
/**
* Generates an application shell during build time.
*/
appShell?: boolean;
/**
* Define the assets to be copied to the output directory. These assets are copied as-is
* without any further processing or hashing.
*/
assets?: AssetPattern[];
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* The full path for the browser entry point to the application, relative to the current
* workspace.
*/
browser?: string;
/**
* Budget thresholds to ensure parts of your application stay within boundaries which you
* set.
*/
budgets?: Budget[];
/**
* Automatically clear the terminal screen during rebuilds.
*/
clearScreen?: boolean;
/**
* Custom package resolution conditions used to resolve conditional exports/imports.
* Defaults to ['module', 'development'/'production']. The following special conditions are
* always present if the requirements are satisfied: 'default', 'import', 'require',
* 'browser', 'node'.
*/
conditions?: string[];
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
crossOrigin?: CrossOrigin;
/**
* Defines global identifiers that will be replaced with a specified constant value when
* found in any JavaScript or TypeScript code including libraries. The value will be used
* directly. String values must be put in quotes. Identifiers within Angular metadata such
* as Component Decorators will not be replaced.
*/
define?: {
[key: string]: string;
};
/**
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* Customize the base path for the URLs of resources in 'index.html' and component
* stylesheets. This option is only necessary for specific deployment scenarios, such as
* with Angular Elements or when utilizing different CDN locations.
*/
deployUrl?: string;
/**
* Exclude the listed external dependencies from being bundled into the bundle. Instead, the
* created bundle relies on these dependencies to be available during runtime. Note:
* `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths
* like `@foo/bar/baz`.
*/
externalDependencies?: string[];
/**
* Extract all licenses in a separate file.
*/
extractLicenses?: boolean;
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* How to handle duplicate translations for i18n.
*/
i18nDuplicateTranslation?: I18NTranslation;
/**
* How to handle missing translations for i18n.
*/
i18nMissingTranslation?: I18NTranslation;
/**
* Configures the generation of the application's HTML index.
*/
index?: IndexUnion;
/**
* The stylesheet language to use for the application's inline component styles.
*/
inlineStyleLanguage?: InlineStyleLanguage;
/**
* Defines the type of loader to use with a specified file extension when used with a
* JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content
* as a Uint8Array; `file` emits the file and provides the runtime location of the file;
* `dataurl` inlines the content as a data URL with best guess of MIME type; `base64`
* inlines the content as a Base64-encoded string; `empty` considers the content to be empty
* and not include it in bundles.
*/
loader?: {
[key: string]: any;
};
/**
* Translate the bundles in one or more locales.
*/
localize?: Localize;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.dev/reference/configs/workspace-config#optimization-configuration.
*/
optimization?: OptimizationUnion;
/**
* Define the output filename cache-busting hashing mode.
*
* - `none`: No hashing.
* - `all`: Hash for all output bundles.
* - `media`: Hash for all output media (e.g., images, fonts, etc. that are referenced in
* CSS files).
* - `bundles`: Hash for output of lazy and main bundles.
*/
outputHashing?: OutputHashing;
/**
* Defines the type of build output artifact. 'static': Generates a static site build
* artifact for deployment on any static hosting service. 'server': Generates a server
* application build artifact, required for applications using hybrid rendering or APIs.
*/
outputMode?: OutputMode;
/**
* Specify the output path relative to workspace root.
*/
outputPath?: OutputPathUnion;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* A list of polyfills to include in the build. Can be a full path for a file, relative to
* the current workspace or module specifier. Example: 'zone.js'.
*/
polyfills?: string[];
/**
* Prerender (SSG) pages of your application during build time.
*/
prerender?: PrerenderUnion;
/**
* Do not use the real path when resolving modules. If unset then will default to `true` if
* NodeJS option --preserve-symlinks is set.
*/
preserveSymlinks?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* Global scripts to be included in the build.
*/
scripts?: ScriptElement[];
/**
* Security features to protect against XSS and other common attacks
*/
security?: Security;
/**
* The full path for the server entry point to the application, relative to the current
* workspace.
*/
server?: Serv;
/**
* Generates a service worker configuration.
*/
serviceWorker?: Serv;
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.dev/reference/configs/workspace-config#source-map-configuration.
*/
sourceMap?: SourceMapUnion;
/**
* Server side render (SSR) pages of your application during runtime.
*/
ssr?: SsrUnion;
/**
* Generates a 'stats.json' file which can be analyzed with
* https://esbuild.github.io/analyze/.
*/
statsJson?: boolean;
/**
* Options to pass to style preprocessors.
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: StyleElement[];
/**
* Enables the use of subresource integrity validation.
*/
subresourceIntegrity?: boolean;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig: string;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Run build when files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
};
export type AssetPattern = AssetPatternClass | string;
export type AssetPatternClass = {
/**
* Allow glob patterns to follow symlink directories. This allows subdirectories of the
* symlink to be searched.
*/
followSymlinks?: boolean;
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output?: string;
};
export type Budget = {
/**
* The baseline size for comparison.
*/
baseline?: string;
/**
* The threshold for error relative to the baseline (min & max).
*/
error?: string;
/**
* The maximum threshold for error relative to the baseline.
*/
maximumError?: string;
/**
* The maximum threshold for warning relative to the baseline.
*/
maximumWarning?: string;
/**
* The minimum threshold for error relative to the baseline.
*/
minimumError?: string;
/**
* The minimum threshold for warning relative to the baseline.
*/
minimumWarning?: string;
/**
* The name of the bundle.
*/
name?: string;
/**
* The type of budget.
*/
type: Type;
/**
* The threshold for warning relative to the baseline (min & max).
*/
warning?: string;
};
/**
* The type of budget.
*/
export declare enum Type {
All = "all",
AllScript = "allScript",
Any = "any",
AnyComponentStyle = "anyComponentStyle",
AnyScript = "anyScript",
Bundle = "bundle",
Initial = "initial"
}
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
export declare enum CrossOrigin {
Anonymous = "anonymous",
None = "none",
UseCredentials = "use-credentials"
}
export type FileReplacement = {
replace: string;
with: string;
};
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
export declare enum I18NTranslation {
Error = "error",
Ignore = "ignore",
Warning = "warning"
}
/**
* Configures the generation of the application's HTML index.
*/
export type IndexUnion = boolean | IndexObject | string;
export type IndexObject = {
/**
* The path of a file to use for the application's generated HTML index.
*/
input: string;
/**
* The output path of the application's generated HTML index file. The full provided path
* will be used and will be considered relative to the application's configured output path.
*/
output?: string;
/**
* Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial
* application files and resources.
*/
preloadInitial?: boolean;
[property: string]: any;
};
/**
* The stylesheet language to use for the application's inline component styles.
*/
export declare enum InlineStyleLanguage {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
/**
* Translate the bundles in one or more locales.
*/
export type Localize = string[] | boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.dev/reference/configs/workspace-config#optimization-configuration.
*/
export type OptimizationUnion = boolean | OptimizationClass;
export type OptimizationClass = {
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
fonts?: FontsUnion;
/**
* Enables optimization of the scripts output.
*/
scripts?: boolean;
/**
* Enables optimization of the styles output.
*/
styles?: StylesUnion;
};
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
export type FontsUnion = boolean | FontsClass;
export type FontsClass = {
/**
* Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS
* definitions in the application's HTML index file. This option requires internet access.
* `HTTPS_PROXY` environment variable can be used to specify a proxy server.
*/
inline?: boolean;
};
/**
* Enables optimization of the styles output.
*/
export type StylesUnion = boolean | StylesClass;
export type StylesClass = {
/**
* Extract and inline critical CSS definitions to improve first paint time.
*/
inlineCritical?: boolean;
/**
* Minify CSS definitions by removing extraneous whitespace and comments, merging
* identifiers and minimizing values.
*/
minify?: boolean;
/**
* Remove comments in global CSS that contains '@license' or '@preserve' or that starts with
* '//!' or '/*!'.
*/
removeSpecialComments?: boolean;
};
/**
* Define the output filename cache-busting hashing mode.
*
* - `none`: No hashing.
* - `all`: Hash for all output bundles.
* - `media`: Hash for all output media (e.g., images, fonts, etc. that are referenced in
* CSS files).
* - `bundles`: Hash for output of lazy and main bundles.
*/
export declare enum OutputHashing {
All = "all",
Bundles = "bundles",
Media = "media",
None = "none"
}
/**
* Defines the type of build output artifact. 'static': Generates a static site build
* artifact for deployment on any static hosting service. 'server': Generates a server
* application build artifact, required for applications using hybrid rendering or APIs.
*/
export declare enum OutputMode {
Server = "server",
Static = "static"
}
/**
* Specify the output path relative to workspace root.
*/
export type OutputPathUnion = OutputPathClass | string;
export type OutputPathClass = {
/**
* Specify the output path relative to workspace root.
*/
base: string;
/**
* The output directory name of your browser build within the output path base. Defaults to
* 'browser'.
*/
browser?: string;
/**
* The output directory name of your media files within the output browser directory.
* Defaults to 'media'.
*/
media?: string;
/**
* The output directory name of your server build within the output path base. Defaults to
* 'server'.
*/
server?: string;
};
/**
* Prerender (SSG) pages of your application during build time.
*/
export type PrerenderUnion = boolean | PrerenderClass;
export type PrerenderClass = {
/**
* Whether the builder should process the Angular Router configuration to find all
* unparameterized routes and prerender them.
*/
discoverRoutes?: boolean;
/**
* The path to a file that contains a list of all routes to prerender, separated by
* newlines. This option is useful if you want to prerender routes with parameterized URLs.
*/
routesFile?: string;
};
export type ScriptElement = ScriptClass | string;
export type ScriptClass = {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
};
/**
* Security features to protect against XSS and other common attacks
*/
export type Security = {
/**
* Enables automatic generation of a hash-based Strict Content Security Policy
* (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will
* default to true once we are out of experimental/preview phases.
*/
autoCsp?: AutoCspUnion;
};
/**
* Enables automatic generation of a hash-based Strict Content Security Policy
* (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will
* default to true once we are out of experimental/preview phases.
*/
export type AutoCspUnion = boolean | AutoCspClass;
export type AutoCspClass = {
/**
* Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in
* the auto-CSP. Please only enable this if you are absolutely sure that you need to, as
* allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.
*/
unsafeEval?: boolean;
};
/**
* The full path for the server entry point to the application, relative to the current
* workspace.
*
* Generates a service worker configuration.
*/
export type Serv = boolean | string;
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.dev/reference/configs/workspace-config#source-map-configuration.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export type SourceMapClass = {
/**
* Output source maps used for error reporting tools.
*/
hidden?: boolean;
/**
* Output source maps for all scripts.
*/
scripts?: boolean;
/**
* Output original source content for files within the source map.
*/
sourcesContent?: boolean;
/**
* Output source maps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages source maps.
*/
vendor?: boolean;
};
/**
* Server side render (SSR) pages of your application during runtime.
*/
export type SsrUnion = boolean | SsrClass;
export type SsrClass = {
/**
* The server entry-point that when executed will spawn the web server.
*/
entry?: string;
/**
* Specifies the platform for which the server bundle is generated. This affects the APIs
* and modules available in the server-side code.
*
* - `node`: (Default) Generates a bundle optimized for Node.js environments.
* - `neutral`: Generates a platform-neutral bundle suitable for environments like edge
* workers, and other serverless platforms. This option avoids using Node.js-specific APIs,
* making the bundle more portable.
*
* Please note that this feature does not provide polyfills for Node.js modules.
* Additionally, it is experimental, and the schematics may undergo changes in future
* versions.
*/
experimentalPlatform?: ExperimentalPlatform;
};
/**
* Specifies the platform for which the server bundle is generated. This affects the APIs
* and modules available in the server-side code.
*
* - `node`: (Default) Generates a bundle optimized for Node.js environments.
* - `neutral`: Generates a platform-neutral bundle suitable for environments like edge
* workers, and other serverless platforms. This option avoids using Node.js-specific APIs,
* making the bundle more portable.
*
* Please note that this feature does not provide polyfills for Node.js modules.
* Additionally, it is experimental, and the schematics may undergo changes in future
* versions.
*/
export declare enum ExperimentalPlatform {
Neutral = "neutral",
Node = "node"
}
/**
* Options to pass to style preprocessors.
*/
export type StylePreprocessorOptions = {
/**
* Paths to include. Paths will be resolved to workspace root.
*/
includePaths?: string[];
/**
* Options to pass to the sass preprocessor.
*/
sass?: Sass;
};
/**
* Options to pass to the sass preprocessor.
*/
export type Sass = {
/**
* A set of deprecations to treat as fatal. If a deprecation warning of any provided type is
* encountered during compilation, the compiler will error instead. If a Version is
* provided, then all deprecations that were active in that compiler version will be treated
* as fatal.
*/
fatalDeprecations?: string[];
/**
* A set of future deprecations to opt into early. Future deprecations passed here will be
* treated as active by the compiler, emitting warnings as necessary.
*/
futureDeprecations?: string[];
/**
* A set of active deprecations to ignore. If a deprecation warning of any provided type is
* encountered during compilation, the compiler will ignore it instead.
*/
silenceDeprecations?: string[];
};
export type StyleElement = StyleClass | string;
export type StyleClass = {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
};

View File

@@ -0,0 +1,92 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExperimentalPlatform = exports.OutputMode = exports.OutputHashing = exports.InlineStyleLanguage = exports.I18NTranslation = exports.CrossOrigin = exports.Type = void 0;
/**
* The type of budget.
*/
var Type;
(function (Type) {
Type["All"] = "all";
Type["AllScript"] = "allScript";
Type["Any"] = "any";
Type["AnyComponentStyle"] = "anyComponentStyle";
Type["AnyScript"] = "anyScript";
Type["Bundle"] = "bundle";
Type["Initial"] = "initial";
})(Type || (exports.Type = Type = {}));
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
var CrossOrigin;
(function (CrossOrigin) {
CrossOrigin["Anonymous"] = "anonymous";
CrossOrigin["None"] = "none";
CrossOrigin["UseCredentials"] = "use-credentials";
})(CrossOrigin || (exports.CrossOrigin = CrossOrigin = {}));
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
var I18NTranslation;
(function (I18NTranslation) {
I18NTranslation["Error"] = "error";
I18NTranslation["Ignore"] = "ignore";
I18NTranslation["Warning"] = "warning";
})(I18NTranslation || (exports.I18NTranslation = I18NTranslation = {}));
/**
* The stylesheet language to use for the application's inline component styles.
*/
var InlineStyleLanguage;
(function (InlineStyleLanguage) {
InlineStyleLanguage["Css"] = "css";
InlineStyleLanguage["Less"] = "less";
InlineStyleLanguage["Sass"] = "sass";
InlineStyleLanguage["Scss"] = "scss";
})(InlineStyleLanguage || (exports.InlineStyleLanguage = InlineStyleLanguage = {}));
/**
* Define the output filename cache-busting hashing mode.
*
* - `none`: No hashing.
* - `all`: Hash for all output bundles.
* - `media`: Hash for all output media (e.g., images, fonts, etc. that are referenced in
* CSS files).
* - `bundles`: Hash for output of lazy and main bundles.
*/
var OutputHashing;
(function (OutputHashing) {
OutputHashing["All"] = "all";
OutputHashing["Bundles"] = "bundles";
OutputHashing["Media"] = "media";
OutputHashing["None"] = "none";
})(OutputHashing || (exports.OutputHashing = OutputHashing = {}));
/**
* Defines the type of build output artifact. 'static': Generates a static site build
* artifact for deployment on any static hosting service. 'server': Generates a server
* application build artifact, required for applications using hybrid rendering or APIs.
*/
var OutputMode;
(function (OutputMode) {
OutputMode["Server"] = "server";
OutputMode["Static"] = "static";
})(OutputMode || (exports.OutputMode = OutputMode = {}));
/**
* Specifies the platform for which the server bundle is generated. This affects the APIs
* and modules available in the server-side code.
*
* - `node`: (Default) Generates a bundle optimized for Node.js environments.
* - `neutral`: Generates a platform-neutral bundle suitable for environments like edge
* workers, and other serverless platforms. This option avoids using Node.js-specific APIs,
* making the bundle more portable.
*
* Please note that this feature does not provide polyfills for Node.js modules.
* Additionally, it is experimental, and the schematics may undergo changes in future
* versions.
*/
var ExperimentalPlatform;
(function (ExperimentalPlatform) {
ExperimentalPlatform["Neutral"] = "neutral";
ExperimentalPlatform["Node"] = "node";
})(ExperimentalPlatform || (exports.ExperimentalPlatform = ExperimentalPlatform = {}));

View File

@@ -0,0 +1,720 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Application schema for Build Facade.",
"description": "Application builder target options",
"type": "object",
"properties": {
"assets": {
"type": "array",
"description": "Define the assets to be copied to the output directory. These assets are copied as-is without any further processing or hashing.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"browser": {
"type": "string",
"description": "The full path for the browser entry point to the application, relative to the current workspace."
},
"server": {
"description": "The full path for the server entry point to the application, relative to the current workspace.",
"oneOf": [
{
"type": "string",
"description": "The full path for the server entry point to the application, relative to the current workspace."
},
{
"const": false,
"type": "boolean",
"description": "Indicates that a server entry point is not provided."
}
]
},
"polyfills": {
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"type": "array",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
},
"deployUrl": {
"type": "string",
"description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations."
},
"security": {
"description": "Security features to protect against XSS and other common attacks",
"type": "object",
"additionalProperties": false,
"properties": {
"autoCsp": {
"description": "Enables automatic generation of a hash-based Strict Content Security Policy (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will default to true once we are out of experimental/preview phases.",
"default": false,
"oneOf": [
{
"type": "object",
"properties": {
"unsafeEval": {
"type": "boolean",
"description": "Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.",
"default": false
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
}
}
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The JavaScript/TypeScript file or package containing the file to include."
}
]
}
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": ["css", "less", "sass", "scss"]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"sass": {
"description": "Options to pass to the sass preprocessor.",
"type": "object",
"properties": {
"fatalDeprecations": {
"description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.",
"type": "array",
"items": {
"type": "string"
}
},
"silenceDeprecations": {
"description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.",
"type": "array",
"items": {
"type": "string"
}
},
"futureDeprecations": {
"description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.",
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"externalDependencies": {
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime. Note: `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths like `@foo/bar/baz`.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"clearScreen": {
"type": "boolean",
"default": false,
"description": "Automatically clear the terminal screen during rebuilds."
},
"optimization": {
"description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.dev/reference/configs/workspace-config#optimization-configuration.",
"default": true,
"x-user-analytics": "ep.ng_optimization",
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Enables optimization of the scripts output.",
"default": true
},
"styles": {
"description": "Enables optimization of the styles output.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"minify": {
"type": "boolean",
"description": "Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers and minimizing values.",
"default": true
},
"inlineCritical": {
"type": "boolean",
"description": "Extract and inline critical CSS definitions to improve first paint time.",
"default": true
},
"removeSpecialComments": {
"type": "boolean",
"description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"fonts": {
"description": "Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"inline": {
"type": "boolean",
"description": "Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"loader": {
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `dataurl` inlines the content as a data URL with best guess of MIME type; `base64` inlines the content as a Base64-encoded string; `empty` considers the content to be empty and not include it in bundles.",
"type": "object",
"patternProperties": {
"^\\.\\S+$": { "enum": ["text", "binary", "file", "dataurl", "base64", "empty"] }
}
},
"define": {
"description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"conditions": {
"description": "Custom package resolution conditions used to resolve conditional exports/imports. Defaults to ['module', 'development'/'production']. The following special conditions are always present if the requirements are satisfied: 'default', 'import', 'require', 'browser', 'node'.",
"type": "array",
"items": {
"type": "string"
}
},
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
"items": {
"$ref": "#/definitions/fileReplacement"
},
"default": []
},
"outputPath": {
"description": "Specify the output path relative to workspace root.",
"oneOf": [
{
"type": "object",
"properties": {
"base": {
"type": "string",
"description": "Specify the output path relative to workspace root."
},
"browser": {
"type": "string",
"pattern": "^[-\\w\\.]*$",
"default": "browser",
"description": "The output directory name of your browser build within the output path base. Defaults to 'browser'."
},
"server": {
"type": "string",
"pattern": "^[-\\w\\.]*$",
"default": "server",
"description": "The output directory name of your server build within the output path base. Defaults to 'server'."
},
"media": {
"type": "string",
"pattern": "^[-\\w\\.]+$",
"default": "media",
"description": "The output directory name of your media files within the output browser directory. Defaults to 'media'."
}
},
"required": ["base"],
"additionalProperties": false
},
{
"type": "string"
}
]
},
"aot": {
"type": "boolean",
"description": "Build using Ahead of Time compilation.",
"x-user-analytics": "ep.ng_aot",
"default": true
},
"sourceMap": {
"description": "Output source maps for scripts and styles. For more information, see https://angular.dev/reference/configs/workspace-config#source-map-configuration.",
"default": false,
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Output source maps for all scripts.",
"default": true
},
"styles": {
"type": "boolean",
"description": "Output source maps for all styles.",
"default": true
},
"hidden": {
"type": "boolean",
"description": "Output source maps used for error reporting tools.",
"default": false
},
"vendor": {
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
},
"sourcesContent": {
"type": "boolean",
"description": "Output original source content for files within the source map.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"baseHref": {
"type": "string",
"description": "Base url for the application being built."
},
"verbose": {
"type": "boolean",
"description": "Adds more details to output logging.",
"default": false
},
"progress": {
"type": "boolean",
"description": "Log progress to the console while building.",
"default": true
},
"i18nMissingTranslation": {
"type": "string",
"description": "How to handle missing translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"localize": {
"description": "Translate the bundles in one or more locales.",
"oneOf": [
{
"type": "boolean",
"description": "Translate all locales."
},
{
"type": "array",
"description": "List of locales ID's to translate.",
"minItems": 1,
"items": {
"type": "string",
"pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$"
}
}
]
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
},
"outputHashing": {
"type": "string",
"description": "Define the output filename cache-busting hashing mode.\n\n- `none`: No hashing.\n- `all`: Hash for all output bundles. \n- `media`: Hash for all output media (e.g., images, fonts, etc. that are referenced in CSS files).\n- `bundles`: Hash for output of lazy and main bundles.",
"default": "none",
"enum": ["none", "all", "media", "bundles"]
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"deleteOutputPath": {
"type": "boolean",
"description": "Delete the output path before building.",
"default": true
},
"preserveSymlinks": {
"type": "boolean",
"description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set."
},
"extractLicenses": {
"type": "boolean",
"description": "Extract all licenses in a separate file.",
"default": true
},
"namedChunks": {
"type": "boolean",
"description": "Use file name for lazy loaded chunks.",
"default": false
},
"subresourceIntegrity": {
"type": "boolean",
"description": "Enables the use of subresource integrity validation.",
"default": false
},
"serviceWorker": {
"description": "Generates a service worker configuration.",
"default": false,
"oneOf": [
{
"type": "string",
"description": "Path to ngsw-config.json."
},
{
"const": false,
"type": "boolean",
"description": "Does not generate a service worker configuration."
}
]
},
"index": {
"description": "Configures the generation of the application's HTML index.",
"oneOf": [
{
"type": "string",
"description": "The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path."
},
{
"type": "object",
"description": "",
"properties": {
"input": {
"type": "string",
"minLength": 1,
"description": "The path of a file to use for the application's generated HTML index."
},
"output": {
"type": "string",
"minLength": 1,
"default": "index.html",
"description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
},
"preloadInitial": {
"type": "boolean",
"default": true,
"description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources."
}
},
"required": ["input"]
},
{
"const": false,
"type": "boolean",
"description": "Does not generate an `index.html` file."
}
]
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed with https://esbuild.github.io/analyze/.",
"default": false
},
"budgets": {
"description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.",
"type": "array",
"items": {
"$ref": "#/definitions/budget"
},
"default": []
},
"webWorkerTsConfig": {
"type": "string",
"description": "TypeScript configuration for Web Worker modules."
},
"crossOrigin": {
"type": "string",
"description": "Define the crossorigin attribute setting of elements that provide CORS support.",
"default": "none",
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"prerender": {
"description": "Prerender (SSG) pages of your application during build time.",
"oneOf": [
{
"type": "boolean",
"description": "Enable prerending of pages of your application during build time."
},
{
"type": "object",
"properties": {
"routesFile": {
"type": "string",
"description": "The path to a file that contains a list of all routes to prerender, separated by newlines. This option is useful if you want to prerender routes with parameterized URLs."
},
"discoverRoutes": {
"type": "boolean",
"description": "Whether the builder should process the Angular Router configuration to find all unparameterized routes and prerender them.",
"default": true
}
},
"additionalProperties": false
}
]
},
"ssr": {
"description": "Server side render (SSR) pages of your application during runtime.",
"default": false,
"oneOf": [
{
"type": "boolean",
"description": "Enable the server bundles to be written to disk."
},
{
"type": "object",
"properties": {
"entry": {
"type": "string",
"description": "The server entry-point that when executed will spawn the web server."
},
"experimentalPlatform": {
"description": "Specifies the platform for which the server bundle is generated. This affects the APIs and modules available in the server-side code. \n\n- `node`: (Default) Generates a bundle optimized for Node.js environments. \n- `neutral`: Generates a platform-neutral bundle suitable for environments like edge workers, and other serverless platforms. This option avoids using Node.js-specific APIs, making the bundle more portable. \n\nPlease note that this feature does not provide polyfills for Node.js modules. Additionally, it is experimental, and the schematics may undergo changes in future versions.",
"default": "node",
"enum": ["node", "neutral"]
}
},
"additionalProperties": false
}
]
},
"appShell": {
"type": "boolean",
"description": "Generates an application shell during build time."
},
"outputMode": {
"type": "string",
"description": "Defines the type of build output artifact. 'static': Generates a static site build artifact for deployment on any static hosting service. 'server': Generates a server application build artifact, required for applications using hybrid rendering or APIs.",
"enum": ["static", "server"]
}
},
"additionalProperties": false,
"required": ["tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"followSymlinks": {
"type": "boolean",
"default": false,
"description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched."
},
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
},
"output": {
"type": "string",
"default": "",
"description": "Absolute path within the output."
}
},
"additionalProperties": false,
"required": ["glob", "input"]
},
{
"type": "string"
}
]
},
"fileReplacement": {
"type": "object",
"properties": {
"replace": {
"type": "string",
"pattern": "\\.(([cm]?[jt])sx?|json)$"
},
"with": {
"type": "string",
"pattern": "\\.(([cm]?[jt])sx?|json)$"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
},
"budget": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "The type of budget.",
"enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"]
},
"name": {
"type": "string",
"description": "The name of the bundle."
},
"baseline": {
"type": "string",
"description": "The baseline size for comparison."
},
"maximumWarning": {
"type": "string",
"description": "The maximum threshold for warning relative to the baseline."
},
"maximumError": {
"type": "string",
"description": "The maximum threshold for error relative to the baseline."
},
"minimumWarning": {
"type": "string",
"description": "The minimum threshold for warning relative to the baseline."
},
"minimumError": {
"type": "string",
"description": "The minimum threshold for error relative to the baseline."
},
"warning": {
"type": "string",
"description": "The threshold for warning relative to the baseline (min & max)."
},
"error": {
"type": "string",
"description": "The threshold for error relative to the baseline (min & max)."
}
},
"additionalProperties": false,
"required": ["type"]
}
}
}

View File

@@ -0,0 +1,25 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { AngularCompilation } from '../../tools/angular/compilation';
import { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets';
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
import { BundlerContext } from '../../tools/esbuild/bundler-context';
import { NormalizedApplicationBuildOptions } from './options';
/**
* Generates one or more BundlerContext instances based on the builder provided
* configuration.
* @param options The normalized application builder options to use.
* @param browsers An string array of browserslist browsers to support.
* @param codeBundleCache An instance of the TypeScript source file cache.
* @returns An array of BundlerContext objects.
*/
export declare function setupBundlerContexts(options: NormalizedApplicationBuildOptions, target: string[], codeBundleCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler, angularCompilation: AngularCompilation, templateUpdates: Map<string, string> | undefined): {
typescriptContexts: BundlerContext[];
otherContexts: BundlerContext[];
};
export declare function createComponentStyleBundler(options: NormalizedApplicationBuildOptions, target: string[]): ComponentStylesheetBundler;

View File

@@ -0,0 +1,103 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupBundlerContexts = setupBundlerContexts;
exports.createComponentStyleBundler = createComponentStyleBundler;
const component_stylesheets_1 = require("../../tools/esbuild/angular/component-stylesheets");
const application_code_bundle_1 = require("../../tools/esbuild/application-code-bundle");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const global_scripts_1 = require("../../tools/esbuild/global-scripts");
const global_styles_1 = require("../../tools/esbuild/global-styles");
const utils_1 = require("../../tools/esbuild/utils");
/**
* Generates one or more BundlerContext instances based on the builder provided
* configuration.
* @param options The normalized application builder options to use.
* @param browsers An string array of browserslist browsers to support.
* @param codeBundleCache An instance of the TypeScript source file cache.
* @returns An array of BundlerContext objects.
*/
function setupBundlerContexts(options, target, codeBundleCache, stylesheetBundler, angularCompilation, templateUpdates) {
const { outputMode, serverEntryPoint, appShellOptions, prerenderOptions, ssrOptions, workspaceRoot, watch = false, } = options;
const typescriptContexts = [];
const otherContexts = [];
// Browser application code
typescriptContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createBrowserCodeBundleOptions)(options, target, codeBundleCache, stylesheetBundler, angularCompilation, templateUpdates)));
// Browser polyfills code
const browserPolyfillBundleOptions = (0, application_code_bundle_1.createBrowserPolyfillBundleOptions)(options, target, codeBundleCache, stylesheetBundler);
if (browserPolyfillBundleOptions) {
const browserPolyfillContext = new bundler_context_1.BundlerContext(workspaceRoot, watch, browserPolyfillBundleOptions);
if (typeof browserPolyfillBundleOptions === 'function') {
otherContexts.push(browserPolyfillContext);
}
else {
typescriptContexts.push(browserPolyfillContext);
}
}
// Global Stylesheets
if (options.globalStyles.length > 0) {
for (const initial of [true, false]) {
const bundleOptions = (0, global_styles_1.createGlobalStylesBundleOptions)(options, target, initial);
if (bundleOptions) {
otherContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, bundleOptions, () => initial));
}
}
}
// Global Scripts
if (options.globalScripts.length > 0) {
for (const initial of [true, false]) {
const bundleOptions = (0, global_scripts_1.createGlobalScriptsBundleOptions)(options, target, initial);
if (bundleOptions) {
otherContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, bundleOptions, () => initial));
}
}
}
// Skip server build when none of the features are enabled.
if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) {
const nodeTargets = [...target, ...(0, utils_1.getSupportedNodeTargets)()];
typescriptContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createServerMainCodeBundleOptions)(options, nodeTargets, codeBundleCache, stylesheetBundler)));
if (outputMode && ssrOptions?.entry) {
// New behavior introduced: 'server.ts' is now bundled separately from 'main.server.ts'.
typescriptContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createSsrEntryCodeBundleOptions)(options, nodeTargets, codeBundleCache, stylesheetBundler)));
}
// Server polyfills code
const serverPolyfillBundleOptions = (0, application_code_bundle_1.createServerPolyfillBundleOptions)(options, nodeTargets, codeBundleCache.loadResultCache);
if (serverPolyfillBundleOptions) {
otherContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, serverPolyfillBundleOptions));
}
}
return { typescriptContexts, otherContexts };
}
function createComponentStyleBundler(options, target) {
const { workspaceRoot, optimizationOptions, sourcemapOptions, outputNames, externalDependencies, preserveSymlinks, stylePreprocessorOptions, inlineStyleLanguage, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, } = options;
const incremental = !!options.watch;
return new component_stylesheets_1.ComponentStylesheetBundler({
workspaceRoot,
inlineFonts: !!optimizationOptions.fonts.inline,
optimization: !!optimizationOptions.styles.minify,
sourcemap:
// Hidden component stylesheet sourcemaps are inaccessible which is effectively
// the same as being disabled. Disabling has the advantage of avoiding the overhead
// of sourcemap processing.
sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
sourcesContent: sourcemapOptions.sourcesContent,
outputNames,
includePaths: stylePreprocessorOptions?.includePaths,
// string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sass: stylePreprocessorOptions?.sass,
externalDependencies,
target,
preserveSymlinks,
tailwindConfiguration,
postcssConfiguration,
cacheOptions,
publicPath,
}, inlineStyleLanguage, incremental);
}

View File

@@ -0,0 +1,31 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import type http from 'node:http';
import { type IndexHtmlTransform } from './internal';
import type { DevServerBuilderOutput } from './output';
import type { Schema as DevServerBuilderOptions } from './schema';
/**
* A Builder that executes a development server based on the provided browser target option.
*
* Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
* unexpected build output or build failures.
*
* @param options Dev Server options.
* @param context The build context.
* @param extensions An optional object containing an array of build plugins (esbuild-based)
* and/or HTTP request middleware.
*
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: DevServerBuilderOptions, context: BuilderContext, extensions?: {
buildPlugins?: Plugin[];
middleware?: ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[];
indexHtmlTransformer?: IndexHtmlTransform;
}): AsyncIterable<DevServerBuilderOutput>;

View File

@@ -0,0 +1,60 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = execute;
const check_port_1 = require("../../utils/check-port");
const internal_1 = require("./internal");
const options_1 = require("./options");
const vite_server_1 = require("./vite-server");
/**
* A Builder that executes a development server based on the provided browser target option.
*
* Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
* unexpected build output or build failures.
*
* @param options Dev Server options.
* @param context The build context.
* @param extensions An optional object containing an array of build plugins (esbuild-based)
* and/or HTTP request middleware.
*
* @experimental Direct usage of this function is considered experimental.
*/
async function* execute(options, context, extensions) {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
context.logger.error(`The "dev-server" builder requires a target to be specified.`);
return;
}
const { builderName, normalizedOptions } = await initialize(options, projectName, context);
yield* (0, vite_server_1.serveWithVite)(normalizedOptions, builderName, (options, context, plugins) => (0, internal_1.buildApplicationInternal)(options, context, { codePlugins: plugins }), context, { indexHtml: extensions?.indexHtmlTransformer }, extensions);
}
async function initialize(initialOptions, projectName, context) {
// Purge old build disk cache.
await (0, internal_1.purgeStaleBuildCache)(context);
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, initialOptions);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
if (!/^127\.\d+\.\d+\.\d+/g.test(normalizedOptions.host) &&
normalizedOptions.host !== '::1' &&
normalizedOptions.host !== 'localhost') {
context.logger.warn(`
Warning: This is a simple server for use in testing or debugging Angular applications
locally. It hasn't been reviewed for security issues.
Binding this server to an open connection can result in compromising your application or
computer. Using a different host than the one passed to the "--host" flag might result in
websocket connection issues.
`);
}
normalizedOptions.port = await (0, check_port_1.checkPort)(normalizedOptions.port, normalizedOptions.host);
return {
builderName,
normalizedOptions,
};
}

View File

@@ -0,0 +1,15 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { Builder } from '@angular-devkit/architect';
import { execute } from './builder';
import type { DevServerBuilderOutput } from './output';
import type { Schema as DevServerBuilderOptions } from './schema';
export { type DevServerBuilderOptions, type DevServerBuilderOutput, execute as executeDevServerBuilder, };
declare const builder: Builder<DevServerBuilderOptions>;
export default builder;
export { execute as executeDevServer };

16
node_modules/@angular/build/src/builders/dev-server/index.js generated vendored Executable file
View File

@@ -0,0 +1,16 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeDevServer = exports.executeDevServerBuilder = void 0;
const architect_1 = require("@angular-devkit/architect");
const builder_1 = require("./builder");
Object.defineProperty(exports, "executeDevServerBuilder", { enumerable: true, get: function () { return builder_1.execute; } });
Object.defineProperty(exports, "executeDevServer", { enumerable: true, get: function () { return builder_1.execute; } });
const builder = (0, architect_1.createBuilder)(builder_1.execute);
exports.default = builder;

View File

@@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export { type BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context';
export { createRxjsEsmResolutionPlugin } from '../../tools/esbuild/rxjs-esm-resolution-plugin';
export { JavaScriptTransformer } from '../../tools/esbuild/javascript-transformer';
export { getFeatureSupport, isZonelessApp } from '../../tools/esbuild/utils';
export { type IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
export { purgeStaleBuildCache } from '../../utils/purge-cache';
export { getSupportedBrowsers } from '../../utils/supported-browsers';
export { transformSupportedBrowsersToTargets } from '../../tools/esbuild/utils';
export { buildApplicationInternal } from '../../builders/application';
export type { ApplicationBuilderInternalOptions } from '../../builders/application/options';
export type { ExternalResultMetadata } from '../../tools/esbuild/bundler-execution-result';

View File

@@ -0,0 +1,27 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildApplicationInternal = exports.transformSupportedBrowsersToTargets = exports.getSupportedBrowsers = exports.purgeStaleBuildCache = exports.isZonelessApp = exports.getFeatureSupport = exports.JavaScriptTransformer = exports.createRxjsEsmResolutionPlugin = exports.BuildOutputFileType = void 0;
var bundler_context_1 = require("../../tools/esbuild/bundler-context");
Object.defineProperty(exports, "BuildOutputFileType", { enumerable: true, get: function () { return bundler_context_1.BuildOutputFileType; } });
var rxjs_esm_resolution_plugin_1 = require("../../tools/esbuild/rxjs-esm-resolution-plugin");
Object.defineProperty(exports, "createRxjsEsmResolutionPlugin", { enumerable: true, get: function () { return rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin; } });
var javascript_transformer_1 = require("../../tools/esbuild/javascript-transformer");
Object.defineProperty(exports, "JavaScriptTransformer", { enumerable: true, get: function () { return javascript_transformer_1.JavaScriptTransformer; } });
var utils_1 = require("../../tools/esbuild/utils");
Object.defineProperty(exports, "getFeatureSupport", { enumerable: true, get: function () { return utils_1.getFeatureSupport; } });
Object.defineProperty(exports, "isZonelessApp", { enumerable: true, get: function () { return utils_1.isZonelessApp; } });
var purge_cache_1 = require("../../utils/purge-cache");
Object.defineProperty(exports, "purgeStaleBuildCache", { enumerable: true, get: function () { return purge_cache_1.purgeStaleBuildCache; } });
var supported_browsers_1 = require("../../utils/supported-browsers");
Object.defineProperty(exports, "getSupportedBrowsers", { enumerable: true, get: function () { return supported_browsers_1.getSupportedBrowsers; } });
var utils_2 = require("../../tools/esbuild/utils");
Object.defineProperty(exports, "transformSupportedBrowsersToTargets", { enumerable: true, get: function () { return utils_2.transformSupportedBrowsersToTargets; } });
var application_1 = require("../../builders/application");
Object.defineProperty(exports, "buildApplicationInternal", { enumerable: true, get: function () { return application_1.buildApplicationInternal; } });

View File

@@ -0,0 +1,48 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { Schema as DevServerOptions } from './schema';
export type NormalizedDevServerOptions = Awaited<ReturnType<typeof normalizeOptions>>;
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: DevServerOptions): Promise<{
buildTarget: import("@angular-devkit/architect").Target;
host: string;
port: number;
poll: number | undefined;
open: boolean | undefined;
verbose: boolean | undefined;
watch: boolean | undefined;
liveReload: boolean;
hmr: boolean;
headers: {
[key: string]: string;
} | undefined;
workspaceRoot: string;
projectRoot: string;
cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
proxyConfig: string | undefined;
servePath: string | undefined;
ssl: boolean | undefined;
sslCert: string | undefined;
sslKey: string | undefined;
prebundle: import("./schema").PrebundleUnion | undefined;
inspect: boolean | {
host?: string;
port?: number;
};
allowedHosts: true | string[];
}>;

View File

@@ -0,0 +1,102 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = normalizeOptions;
const architect_1 = require("@angular-devkit/architect");
const node_path_1 = __importDefault(require("node:path"));
const utils_1 = require("../../utils");
const normalize_cache_1 = require("../../utils/normalize-cache");
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
async function normalizeOptions(context, projectName, options) {
const { workspaceRoot, logger } = context;
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
// Target specifier defaults to the current project's build target using a development configuration
const buildTargetSpecifier = options.buildTarget ?? `::development`;
const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
// Get the application builder options.
const browserBuilderName = await context.getBuilderNameForTarget(buildTarget);
const rawBuildOptions = await context.getTargetOptions(buildTarget);
const buildOptions = (await context.validateOptions(rawBuildOptions, browserBuilderName));
const optimization = (0, utils_1.normalizeOptimization)(buildOptions.optimization);
if (options.prebundle) {
if (!cacheOptions.enabled) {
// Warn if the initial options provided by the user enable prebundling but caching is disabled
logger.warn('Prebundling has been configured but will not be used because caching has been disabled.');
}
else if (optimization.scripts) {
// Warn if the initial options provided by the user enable prebundling but script optimization is enabled.
logger.warn('Prebundling has been configured but will not be used because scripts optimization is enabled.');
}
}
let inspect = false;
const inspectRaw = options.inspect;
if (inspectRaw === true || inspectRaw === '' || inspectRaw === 'true') {
inspect = {
host: undefined,
port: undefined,
};
}
else if (typeof inspectRaw === 'string' && inspectRaw !== 'false') {
const port = +inspectRaw;
if (isFinite(port)) {
inspect = {
host: undefined,
port,
};
}
else {
const [host, port] = inspectRaw.split(':');
inspect = {
host,
port: isNaN(+port) ? undefined : +port,
};
}
}
// Initial options to keep
const { host, port, poll, open, verbose, watch, liveReload, hmr, headers, proxyConfig, servePath, ssl, sslCert, sslKey, prebundle, allowedHosts, } = options;
// Return all the normalized options
return {
buildTarget,
host: host ?? 'localhost',
port: port ?? 4200,
poll,
open,
verbose,
watch,
liveReload: !!liveReload,
hmr: hmr ?? !!liveReload,
headers,
workspaceRoot,
projectRoot,
cacheOptions,
proxyConfig,
servePath,
ssl,
sslCert,
sslKey,
// Prebundling defaults to true but requires caching to function
prebundle: cacheOptions.enabled && !optimization.scripts && prebundle,
inspect,
allowedHosts: allowedHosts ? allowedHosts : [],
};
}

View File

@@ -0,0 +1,16 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { BuilderOutput } from '@angular-devkit/architect';
/**
* @experimental Direct usage of this type is considered experimental.
*/
export interface DevServerBuilderOutput extends BuilderOutput {
baseUrl: string;
port?: number;
address?: string;
}

View File

@@ -0,0 +1,9 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,112 @@
/**
* Dev Server target options for Build Facade.
*/
export type Schema = {
/**
* The hosts that the development server will respond to. This option sets the Vite option
* of the same name. For further details:
* https://vite.dev/config/server-options.html#server-allowedhosts
*/
allowedHosts?: AllowedHosts;
/**
* A build builder target to serve in the format of `project:target[:configuration]`. You
* can also pass in more than one configuration name as a comma-separated list. Example:
* `project:target:production,staging`.
*/
buildTarget: string;
/**
* Custom HTTP headers to be added to all responses.
*/
headers?: {
[key: string]: string;
};
/**
* Enable hot module replacement. Defaults to the value of 'liveReload'. Currently, only
* global and component stylesheets are supported.
*/
hmr?: boolean;
/**
* Host to listen on.
*/
host?: string;
/**
* Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are
* enabled.
*/
inspect?: Inspect;
/**
* Whether to reload the page on change, using live-reload.
*/
liveReload?: boolean;
/**
* Opens the url in default browser.
*/
open?: boolean;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* Port to listen on.
*/
port?: number;
/**
* Enable and control the Vite-based development server's prebundling capabilities. To
* enable prebundling, the Angular CLI cache must also be enabled.
*/
prebundle?: PrebundleUnion;
/**
* Proxy configuration file. For more information, see
* https://angular.dev/tools/cli/serve#proxying-to-a-backend-server.
*/
proxyConfig?: string;
/**
* The pathname where the application will be served.
*/
servePath?: string;
/**
* Serve using HTTPS.
*/
ssl?: boolean;
/**
* SSL certificate to use for serving HTTPS.
*/
sslCert?: string;
/**
* SSL key to use for serving HTTPS.
*/
sslKey?: string;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Rebuild on change.
*/
watch?: boolean;
};
/**
* The hosts that the development server will respond to. This option sets the Vite option
* of the same name. For further details:
* https://vite.dev/config/server-options.html#server-allowedhosts
*/
export type AllowedHosts = string[] | boolean;
/**
* Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are
* enabled.
*/
export type Inspect = boolean | string;
/**
* Enable and control the Vite-based development server's prebundling capabilities. To
* enable prebundling, the Angular CLI cache must also be enabled.
*/
export type PrebundleUnion = boolean | PrebundleClass;
export type PrebundleClass = {
/**
* List of package imports that should not be prebundled by the development server. The
* packages will be bundled into the application code itself. Note: specifying `@foo/bar`
* marks all paths within the `@foo/bar` package as excluded, including sub-paths like
* `@foo/bar/baz`.
*/
exclude: string[];
};

View File

@@ -0,0 +1,4 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,131 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Dev Server Target",
"description": "Dev Server target options for Build Facade.",
"type": "object",
"properties": {
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
},
"port": {
"type": "number",
"description": "Port to listen on.",
"default": 4200
},
"host": {
"type": "string",
"description": "Host to listen on.",
"default": "localhost"
},
"proxyConfig": {
"type": "string",
"description": "Proxy configuration file. For more information, see https://angular.dev/tools/cli/serve#proxying-to-a-backend-server."
},
"ssl": {
"type": "boolean",
"description": "Serve using HTTPS.",
"default": false
},
"sslKey": {
"type": "string",
"description": "SSL key to use for serving HTTPS."
},
"sslCert": {
"type": "string",
"description": "SSL certificate to use for serving HTTPS."
},
"allowedHosts": {
"description": "The hosts that the development server will respond to. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts",
"default": [],
"oneOf": [
{
"type": "array",
"description": "A list of hosts that the development server will respond to.",
"items": {
"type": "string"
}
},
{
"type": "boolean",
"description": "Indicates that all hosts are allowed. This is not recommended and a security risk."
}
]
},
"headers": {
"type": "object",
"description": "Custom HTTP headers to be added to all responses.",
"propertyNames": {
"pattern": "^[-_A-Za-z0-9]+$"
},
"additionalProperties": {
"type": "string"
}
},
"open": {
"type": "boolean",
"description": "Opens the url in default browser.",
"default": false,
"alias": "o"
},
"verbose": {
"type": "boolean",
"description": "Adds more details to output logging."
},
"liveReload": {
"type": "boolean",
"description": "Whether to reload the page on change, using live-reload.",
"default": true
},
"servePath": {
"type": "string",
"description": "The pathname where the application will be served."
},
"hmr": {
"type": "boolean",
"description": "Enable hot module replacement. Defaults to the value of 'liveReload'. Currently, only global and component stylesheets are supported."
},
"watch": {
"type": "boolean",
"description": "Rebuild on change.",
"default": true
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"inspect": {
"default": false,
"description": "Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are enabled.",
"oneOf": [
{
"type": "string",
"description": "Activate the inspector on host and port in the format of `[[host:]port]`. See the security warning in https://nodejs.org/docs/latest-v22.x/api/cli.html#warning-binding-inspector-to-a-public-ipport-combination-is-insecure regarding the host parameter usage."
},
{ "type": "boolean" }
]
},
"prebundle": {
"description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled.",
"default": true,
"oneOf": [
{ "type": "boolean" },
{
"type": "object",
"properties": {
"exclude": {
"description": "List of package imports that should not be prebundled by the development server. The packages will be bundled into the application code itself. Note: specifying `@foo/bar` marks all paths within the `@foo/bar` package as excluded, including sub-paths like `@foo/bar/baz`.",
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": false,
"required": ["exclude"]
}
]
}
},
"additionalProperties": false,
"required": ["buildTarget"]
}

View File

@@ -0,0 +1,42 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import type { Connect, InlineConfig } from 'vite';
import type { ComponentStyleRecord } from '../../tools/vite/middlewares';
import { ServerSsrMode } from '../../tools/vite/plugins';
import { EsbuildLoaderOption } from '../../tools/vite/utils';
import { Result } from '../application/results';
import { type ApplicationBuilderInternalOptions, BuildOutputFileType, type ExternalResultMetadata, JavaScriptTransformer } from './internal';
import type { NormalizedDevServerOptions } from './options';
import type { DevServerBuilderOutput } from './output';
interface OutputFileRecord {
contents: Uint8Array;
size: number;
hash: string;
updated: boolean;
servable: boolean;
type: BuildOutputFileType;
}
interface OutputAssetRecord {
source: string;
updated: boolean;
}
interface DevServerExternalResultMetadata extends Omit<ExternalResultMetadata, 'explicit'> {
explicitBrowser: string[];
explicitServer: string[];
}
export type BuilderAction = (options: ApplicationBuilderInternalOptions, context: BuilderContext, plugins?: Plugin[]) => AsyncIterable<Result>;
export declare function serveWithVite(serverOptions: NormalizedDevServerOptions, builderName: string, builderAction: BuilderAction, context: BuilderContext, transformers?: {
indexHtml?: (content: string) => Promise<string>;
}, extensions?: {
middleware?: Connect.NextHandleFunction[];
buildPlugins?: Plugin[];
}): AsyncIterableIterator<DevServerBuilderOutput>;
export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, OutputAssetRecord>, preserveSymlinks: boolean | undefined, externalMetadata: DevServerExternalResultMetadata, ssrMode: ServerSsrMode, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, componentStyles: Map<string, ComponentStyleRecord>, templateUpdates: Map<string, string>, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, define: ApplicationBuilderInternalOptions['define'], extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>, thirdPartySourcemaps?: boolean): Promise<InlineConfig>;
export {};

View File

@@ -0,0 +1,735 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.serveWithVite = serveWithVite;
exports.setupServer = setupServer;
const node_assert_1 = __importDefault(require("node:assert"));
const promises_1 = require("node:fs/promises");
const node_module_1 = require("node:module");
const node_path_1 = require("node:path");
const plugins_1 = require("../../tools/vite/plugins");
const utils_1 = require("../../tools/vite/utils");
const utils_2 = require("../../utils");
const environment_options_1 = require("../../utils/environment-options");
const load_esm_1 = require("../../utils/load-esm");
const results_1 = require("../application/results");
const schema_1 = require("../application/schema");
const internal_1 = require("./internal");
/**
* Build options that are also present on the dev server but are only passed
* to the build.
*/
const CONVENIENCE_BUILD_OPTIONS = ['watch', 'poll', 'verbose'];
// eslint-disable-next-line max-lines-per-function
async function* serveWithVite(serverOptions, builderName, builderAction, context, transformers, extensions) {
// Get the browser configuration from the target name.
const rawBrowserOptions = await context.getTargetOptions(serverOptions.buildTarget);
// Deploy url is not used in the dev-server.
delete rawBrowserOptions.deployUrl;
// Copy convenience options to build
for (const optionName of CONVENIENCE_BUILD_OPTIONS) {
const optionValue = serverOptions[optionName];
if (optionValue !== undefined) {
rawBrowserOptions[optionName] = optionValue;
}
}
// TODO: Adjust architect to not force a JsonObject derived return type
const browserOptions = (await context.validateOptions(rawBrowserOptions, builderName));
if (browserOptions.prerender || (browserOptions.outputMode && browserOptions.server)) {
// Disable prerendering if enabled and force SSR.
// This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested.
browserOptions.prerender = undefined;
browserOptions.ssr ||= true;
}
// Disable auto CSP.
browserOptions.security = {
autoCsp: false,
};
// Disable JSON build stats.
// These are not accessible with the dev server and can cause HMR fallbacks.
if (browserOptions.statsJson === true) {
context.logger.warn('Build JSON statistics output (`statsJson` option) has been disabled.' +
' The development server does not support this option.');
}
browserOptions.statsJson = false;
// Set all packages as external to support Vite's prebundle caching
browserOptions.externalPackages = serverOptions.prebundle;
// Disable generating a full manifest with routes.
// This is done during runtime when using the dev-server.
browserOptions.partialSSRBuild = true;
// The development server currently only supports a single locale when localizing.
// This matches the behavior of the Webpack-based development server but could be expanded in the future.
if (browserOptions.localize === true ||
(Array.isArray(browserOptions.localize) && browserOptions.localize.length > 1)) {
context.logger.warn('Localization (`localize` option) has been disabled. The development server only supports localizing a single locale per build.');
browserOptions.localize = false;
}
else if (browserOptions.localize) {
// When localization is enabled with a single locale, force a flat path to maintain behavior with the existing Webpack-based dev server.
browserOptions.forceI18nFlatOutput = true;
}
const { vendor: thirdPartySourcemaps, scripts: scriptsSourcemaps } = (0, utils_2.normalizeSourceMaps)(browserOptions.sourceMap ?? false);
if (scriptsSourcemaps && browserOptions.server) {
// https://nodejs.org/api/process.html#processsetsourcemapsenabledval
process.setSourceMapsEnabled(true);
}
if (serverOptions.hmr &&
(browserOptions.outputHashing === schema_1.OutputHashing.All ||
browserOptions.outputHashing === schema_1.OutputHashing.Bundles)) {
serverOptions.hmr = false;
context.logger.warn(`Hot Module Replacement (HMR) is disabled because the 'outputHashing' option is set to '${browserOptions.outputHashing}'. ` +
'HMR is incompatible with this setting.');
}
const componentsHmrCanBeUsed = browserOptions.aot && serverOptions.liveReload && serverOptions.hmr;
// Enable to support link-based component style hot reloading (`NG_HMR_CSTYLES=1` can be used to enable)
browserOptions.externalRuntimeStyles = componentsHmrCanBeUsed && environment_options_1.useComponentStyleHmr;
// Enable to support component template hot replacement (`NG_HMR_TEMPLATE=0` can be used to disable selectively)
// This will also replace file-based/inline styles as code if external runtime styles are not enabled.
browserOptions.templateUpdates = componentsHmrCanBeUsed && environment_options_1.useComponentTemplateHmr;
browserOptions.incrementalResults = true;
// Setup the prebundling transformer that will be shared across Vite prebundling requests
const prebundleTransformer = new internal_1.JavaScriptTransformer(
// Always enable JIT linking to support applications built with and without AOT.
// In a development environment the additional scope information does not
// have a negative effect unlike production where final output size is relevant.
{ sourcemap: true, jit: true, thirdPartySourcemaps }, 1);
// The index HTML path will be updated from the build results if provided by the builder
let htmlIndexPath = 'index.html';
// dynamically import Vite for ESM compatibility
const { createServer, normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
let server;
let serverUrl;
let hadError = false;
const generatedFiles = new Map();
const assetFiles = new Map();
const externalMetadata = {
implicitBrowser: [],
implicitServer: [],
explicitBrowser: [],
explicitServer: [],
};
const componentStyles = new Map();
const templateUpdates = new Map();
// Add cleanup logic via a builder teardown.
let deferred;
context.addTeardown(async () => {
await server?.close();
await prebundleTransformer.close();
deferred?.();
});
// TODO: Switch this to an architect schedule call when infrastructure settings are supported
for await (const result of builderAction(browserOptions, context, extensions?.buildPlugins)) {
if (result.kind === results_1.ResultKind.Failure) {
if (result.errors.length && server) {
hadError = true;
server.ws.send({
type: 'error',
err: {
message: result.errors[0].text,
stack: '',
loc: result.errors[0].location ?? undefined,
},
});
}
yield { baseUrl: '', success: false };
continue;
}
// Clear existing error overlay on successful result
if (hadError && server) {
hadError = false;
// Send an empty update to clear the error overlay
server.ws.send({
'type': 'update',
updates: [],
});
}
let needClientUpdate = true;
switch (result.kind) {
case results_1.ResultKind.Full:
if (result.detail?.['htmlIndexPath']) {
htmlIndexPath = result.detail['htmlIndexPath'];
}
if (serverOptions.servePath === undefined && result.detail?.['htmlBaseHref']) {
const baseHref = result.detail['htmlBaseHref'];
// Remove trailing slash
serverOptions.servePath =
baseHref !== './' && baseHref[baseHref.length - 1] === '/'
? baseHref.slice(0, -1)
: baseHref;
}
assetFiles.clear();
componentStyles.clear();
generatedFiles.clear();
for (const [outputPath, file] of Object.entries(result.files)) {
updateResultRecord(outputPath, file, normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles,
// The initial build will not yet have a server setup
!server);
}
// Clear stale template updates on code rebuilds
templateUpdates.clear();
break;
case results_1.ResultKind.Incremental:
(0, node_assert_1.default)(server, 'Builder must provide an initial full build before incremental results.');
// Background updates should only update server files/options
needClientUpdate = !result.background;
for (const removed of result.removed) {
const filePath = '/' + normalizePath(removed.path);
generatedFiles.delete(filePath);
assetFiles.delete(filePath);
}
for (const modified of result.modified) {
updateResultRecord(modified, result.files[modified], normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles);
}
for (const added of result.added) {
updateResultRecord(added, result.files[added], normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles);
}
break;
case results_1.ResultKind.ComponentUpdate:
(0, node_assert_1.default)(serverOptions.hmr, 'Component updates are only supported with HMR enabled.');
(0, node_assert_1.default)(server, 'Builder must provide an initial full build before component update results.');
for (const componentUpdate of result.updates) {
if (componentUpdate.type === 'template') {
templateUpdates.set(componentUpdate.id, componentUpdate.content);
server.ws.send('angular:component-update', {
id: componentUpdate.id,
timestamp: Date.now(),
});
}
}
context.logger.info('Component update sent to client(s).');
continue;
default:
context.logger.warn(`Unknown result kind [${result.kind}] provided by build.`);
continue;
}
// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
if (result.detail?.['externalMetadata']) {
const { implicitBrowser, implicitServer, explicit } = result.detail['externalMetadata'];
const implicitServerFiltered = implicitServer.filter((m) => !(0, node_module_1.isBuiltin)(m) && !isAbsoluteUrl(m));
const implicitBrowserFiltered = implicitBrowser.filter((m) => !isAbsoluteUrl(m));
// Empty Arrays to avoid growing unlimited with every re-build.
externalMetadata.explicitBrowser.length = 0;
externalMetadata.explicitServer.length = 0;
externalMetadata.implicitServer.length = 0;
externalMetadata.implicitBrowser.length = 0;
const externalDeps = browserOptions.externalDependencies ?? [];
externalMetadata.explicitBrowser.push(...explicit, ...externalDeps);
externalMetadata.explicitServer.push(...explicit, ...externalDeps, ...node_module_1.builtinModules);
externalMetadata.implicitServer.push(...implicitServerFiltered);
externalMetadata.implicitBrowser.push(...implicitBrowserFiltered);
// The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
// See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239
externalMetadata.explicitBrowser.sort();
externalMetadata.explicitServer.sort();
externalMetadata.implicitServer.sort();
externalMetadata.implicitBrowser.sort();
}
if (server) {
// Update fs allow list to include any new assets from the build option.
server.config.server.fs.allow = [
...new Set([
...server.config.server.fs.allow,
...[...assetFiles.values()].map(({ source }) => source),
]),
];
const updatedFiles = await invalidateUpdatedFiles(normalizePath, generatedFiles, assetFiles, server);
if (needClientUpdate) {
handleUpdate(server, serverOptions, context.logger, componentStyles, updatedFiles);
}
}
else {
const projectName = context.target?.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
context.logger.info('NOTE: Raw file sizes do not reflect development server per-request transformations.');
if (browserOptions.ssr && serverOptions.inspect) {
const { host, port } = serverOptions.inspect;
const { default: inspector } = await Promise.resolve().then(() => __importStar(require('node:inspector')));
inspector.open(port, host, true);
context.addTeardown(() => inspector.close());
}
const { root = '' } = await context.getProjectMetadata(projectName);
const projectRoot = (0, node_path_1.join)(context.workspaceRoot, root);
const browsers = (0, internal_1.getSupportedBrowsers)(projectRoot, context.logger);
const target = (0, internal_1.transformSupportedBrowsersToTargets)(browsers);
// Needed for browser-esbuild as polyfills can be a string.
const polyfills = Array.isArray((browserOptions.polyfills ??= []))
? browserOptions.polyfills
: [browserOptions.polyfills];
let ssrMode = plugins_1.ServerSsrMode.NoSsr;
if (browserOptions.outputMode &&
typeof browserOptions.ssr === 'object' &&
browserOptions.ssr.entry) {
ssrMode = plugins_1.ServerSsrMode.ExternalSsrMiddleware;
}
else if (browserOptions.ssr) {
ssrMode = plugins_1.ServerSsrMode.InternalSsrMiddleware;
}
if (browserOptions.progress !== false && ssrMode !== plugins_1.ServerSsrMode.NoSsr) {
// This is a workaround for https://github.com/angular/angular-cli/issues/28336, which is caused by the interaction between `zone.js` and `listr2`.
process.once('SIGINT', () => {
process.kill(process.pid);
});
}
// Setup server and start listening
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), componentStyles, templateUpdates, browserOptions.loader, {
...browserOptions.define,
'ngJitMode': browserOptions.aot ? 'false' : 'true',
'ngHmrMode': browserOptions.templateUpdates ? 'true' : 'false',
}, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
server = await createServer(serverConfiguration);
await server.listen();
// Setup builder context logging for browser clients
server.hot.on('angular:log', (data) => {
if (typeof data?.text !== 'string') {
context.logger.warn('Development server client sent invalid internal log event.');
}
switch (data.kind) {
case 'error':
context.logger.error(`[CLIENT ERROR]: ${data.text}`);
break;
case 'warning':
context.logger.warn(`[CLIENT WARNING]: ${data.text}`);
break;
default:
context.logger.info(`[CLIENT INFO]: ${data.text}`);
break;
}
});
// Setup component HMR invalidation
// Invalidation occurs when the runtime cannot update a component
server.hot.on('angular:invalidate', (data) => {
if (typeof data?.id !== 'string') {
context.logger.warn('Development server client sent invalid internal invalidate event.');
}
// Clear invalid template update
templateUpdates.delete(data.id);
// Some cases are expected unsupported update scenarios but some may be errors.
// If an error occurred, log the error in addition to the invalidation.
if (data.error) {
context.logger.error(`Component update failed${data.message ? `: ${data.message}` : '.'}` +
'\nPlease consider reporting the error at https://github.com/angular/angular-cli/issues');
}
else {
context.logger.warn(`Component update unsupported${data.message ? `: ${data.message}` : '.'}`);
}
server?.ws.send({
type: 'full-reload',
path: '*',
});
context.logger.info('Page reload sent to client(s).');
});
const urls = server.resolvedUrls;
if (urls && (urls.local.length || urls.network.length)) {
serverUrl = new URL(urls.local[0] ?? urls.network[0]);
}
// log connection information
server.printUrls();
server.bindCLIShortcuts({
print: true,
customShortcuts: [
{
key: 'r',
description: 'force reload browser',
action(server) {
componentStyles.forEach((record) => record.used?.clear());
server.ws.send({
type: 'full-reload',
path: '*',
});
},
},
],
});
}
// TODO: adjust output typings to reflect both development servers
yield {
success: true,
port: serverUrl?.port,
baseUrl: serverUrl?.href,
};
}
await new Promise((resolve) => (deferred = resolve));
}
/**
* Invalidates any updated asset or generated files and resets their `updated` state.
* This function also clears the server application cache when necessary.
*
* @returns A list of files that were updated and invalidated.
*/
async function invalidateUpdatedFiles(normalizePath, generatedFiles, assetFiles, server) {
const updatedFiles = [];
// Invalidate any updated asset
for (const [file, record] of assetFiles) {
if (!record.updated) {
continue;
}
record.updated = false;
updatedFiles.push(file);
}
// Invalidate any updated files
let serverApplicationChanged = false;
for (const [file, record] of generatedFiles) {
if (!record.updated) {
continue;
}
record.updated = false;
updatedFiles.push(file);
serverApplicationChanged ||= record.type === internal_1.BuildOutputFileType.ServerApplication;
const updatedModules = server.moduleGraph.getModulesByFile(normalizePath((0, node_path_1.join)(server.config.root, file)));
updatedModules?.forEach((m) => server.moduleGraph.invalidateModule(m));
}
if (serverApplicationChanged) {
// Clear the server app cache and
// trigger module evaluation before reload to initiate dependency optimization.
const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
ɵdestroyAngularServerApp();
}
return updatedFiles;
}
/**
* Handles updates for the client by sending HMR or full page reload commands
* based on the updated files. It also ensures proper tracking of component styles and determines if
* a full reload is needed.
*/
function handleUpdate(server, serverOptions, logger, componentStyles, updatedFiles) {
if (!updatedFiles.length) {
return;
}
if (serverOptions.hmr) {
if (updatedFiles.every((f) => f.endsWith('.css'))) {
let requiresReload = false;
const timestamp = Date.now();
const updates = updatedFiles.flatMap((filePath) => {
// For component styles, an HMR update must be sent for each one with the corresponding
// component identifier search parameter (`ngcomp`). The Vite client code will not keep
// the existing search parameters when it performs an update and each one must be
// specified explicitly. Typically, there is only one each though as specific style files
// are not typically reused across components.
const record = componentStyles.get(filePath);
if (record) {
if (record.reload) {
// Shadow DOM components currently require a full reload.
// Vite's CSS hot replacement does not support shadow root searching.
requiresReload = true;
return [];
}
return Array.from(record.used ?? []).map((id) => {
return {
type: 'css-update',
timestamp,
path: `${filePath}?ngcomp` + (typeof id === 'string' ? `=${id}` : ''),
acceptedPath: filePath,
};
});
}
return {
type: 'css-update',
timestamp,
path: filePath,
acceptedPath: filePath,
};
});
if (!requiresReload) {
server.ws.send({
type: 'update',
updates,
});
logger.info('Stylesheet update sent to client(s).');
return;
}
}
}
// Send reload command to clients
if (serverOptions.liveReload) {
// Clear used component tracking on full reload
componentStyles.forEach((record) => record.used?.clear());
server.ws.send({
type: 'full-reload',
path: '*',
});
logger.info('Page reload sent to client(s).');
}
}
function updateResultRecord(outputPath, file, normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles, initial = false) {
if (file.origin === 'disk') {
assetFiles.set('/' + normalizePath(outputPath), {
source: normalizePath(file.inputPath),
updated: !initial,
});
return;
}
let filePath;
if (outputPath === htmlIndexPath) {
// Convert custom index output path to standard index path for dev-server usage.
// This mimics the Webpack dev-server behavior.
filePath = '/index.html';
}
else {
filePath = '/' + normalizePath(outputPath);
}
const servable = file.type === internal_1.BuildOutputFileType.Browser || file.type === internal_1.BuildOutputFileType.Media;
// Skip analysis of sourcemaps
if (filePath.endsWith('.map')) {
generatedFiles.set(filePath, {
contents: file.contents,
servable,
size: file.contents.byteLength,
hash: file.hash,
type: file.type,
updated: false,
});
return;
}
// New or updated file
generatedFiles.set(filePath, {
contents: file.contents,
size: file.contents.byteLength,
hash: file.hash,
// Consider the files updated except on the initial build result
updated: !initial,
type: file.type,
servable,
});
// Record any external component styles
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
const componentStyle = componentStyles.get(filePath);
if (componentStyle) {
componentStyle.rawContent = file.contents;
}
else {
componentStyles.set(filePath, {
rawContent: file.contents,
});
}
}
}
// eslint-disable-next-line max-lines-per-function
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, componentStyles, templateUpdates, prebundleLoaderExtensions, define, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
// dynamically import Vite for ESM compatibility
const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
// Path will not exist on disk and only used to provide separate path for Vite requests
const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project));
// Files used for SSR warmup.
let ssrFiles;
switch (ssrMode) {
case plugins_1.ServerSsrMode.InternalSsrMiddleware:
ssrFiles = ['./main.server.mjs'];
break;
case plugins_1.ServerSsrMode.ExternalSsrMiddleware:
ssrFiles = ['./main.server.mjs', './server.mjs'];
break;
}
/**
* Required when using `externalDependencies` to prevent Vite load errors.
*
* @note Can be removed if Vite introduces native support for externals.
* @note Vite misresolves browser modules in SSR when accessing URLs with multiple segments
* (e.g., 'foo/bar'), as they are not correctly re-based from the base href.
*/
const preTransformRequests = externalMetadata.explicitBrowser.length === 0 && ssrMode === plugins_1.ServerSsrMode.NoSsr;
const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite');
const configuration = {
configFile: false,
envFile: false,
cacheDir,
root: virtualProjectRoot,
publicDir: false,
esbuild: false,
mode: 'development',
// We use custom as we do not rely on Vite's htmlFallbackMiddleware and indexHtmlMiddleware.
appType: 'custom',
css: {
devSourcemap: true,
},
// Ensure custom 'file' loader build option entries are handled by Vite in application code that
// reference third-party libraries. Relative usage is handled directly by the build and not Vite.
// Only 'file' loader entries are currently supported directly by Vite.
assetsInclude: prebundleLoaderExtensions &&
Object.entries(prebundleLoaderExtensions)
.filter(([, value]) => value === 'file')
// Create a file extension glob for each key
.map(([key]) => '*' + key),
// Vite will normalize the `base` option by adding a leading slash.
base: serverOptions.servePath,
resolve: {
mainFields: ['es2020', 'browser', 'module', 'main'],
preserveSymlinks,
},
dev: {
preTransformRequests,
},
server: {
preTransformRequests,
warmup: {
ssrFiles,
},
port: serverOptions.port,
strictPort: true,
host: serverOptions.host,
open: serverOptions.open,
allowedHosts: serverOptions.allowedHosts,
headers: serverOptions.headers,
// Disable the websocket if live reload is disabled (false/undefined are the only valid values)
ws: serverOptions.liveReload === false && serverOptions.hmr === false ? false : undefined,
// When server-side rendering (SSR) is enabled togather with SSL and Express is being used,
// we must configure Vite to use HTTP/1.1.
// This is necessary because Express does not support HTTP/2.
// We achieve this by defining an empty proxy.
// See: https://github.com/vitejs/vite/blob/c4b532cc900bf988073583511f57bd581755d5e3/packages/vite/src/node/http.ts#L106
proxy: serverOptions.ssl && ssrMode === plugins_1.ServerSsrMode.ExternalSsrMiddleware
? (proxy ?? {})
: proxy,
cors: {
// This will add the header `Access-Control-Allow-Origin: http://example.com`,
// where `http://example.com` is the requesting origin.
origin: true,
// Allow preflight requests to be proxied.
preflightContinue: true,
},
// File watching is handled by the build directly. `null` disables file watching for Vite.
watch: null,
fs: {
// Ensure cache directory, node modules, and all assets are accessible by the client.
// The first two are required for Vite to function in prebundling mode (the default) and to load
// the Vite client-side code for browser reloading. These would be available by default but when
// the `allow` option is explicitly configured, they must be included manually.
allow: [
cacheDir,
(0, node_path_1.join)(serverOptions.workspaceRoot, 'node_modules'),
...[...assets.values()].map(({ source }) => source),
],
},
},
ssr: {
// Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
noExternal: /.*/,
// Exclude any Node.js built in module and provided dependencies (currently build defined externals)
external: externalMetadata.explicitServer,
optimizeDeps: (0, utils_1.getDepOptimizationConfig)({
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: serverOptions.prebundle === false,
// Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
exclude: externalMetadata.explicitServer,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicitServer,
ssr: true,
prebundleTransformer,
zoneless,
target,
loader: prebundleLoaderExtensions,
thirdPartySourcemaps,
define,
}),
},
plugins: [
(0, plugins_1.createAngularSetupMiddlewaresPlugin)({
outputFiles,
assets,
indexHtmlTransformer,
extensionMiddleware,
componentStyles,
templateUpdates,
ssrMode,
resetComponentUpdates: () => templateUpdates.clear(),
projectRoot: serverOptions.projectRoot,
}),
(0, plugins_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
await (0, plugins_1.createAngularSsrTransformPlugin)(serverOptions.workspaceRoot),
await (0, plugins_1.createAngularMemoryPlugin)({
virtualProjectRoot,
outputFiles,
templateUpdates,
external: externalMetadata.explicitBrowser,
disableViteTransport: !serverOptions.liveReload,
}),
],
// Browser only optimizeDeps. (This does not run for SSR dependencies).
optimizeDeps: (0, utils_1.getDepOptimizationConfig)({
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: serverOptions.prebundle === false,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude: externalMetadata.explicitBrowser,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicitBrowser,
ssr: false,
prebundleTransformer,
target,
zoneless,
loader: prebundleLoaderExtensions,
thirdPartySourcemaps,
define,
}),
};
if (serverOptions.ssl) {
if (serverOptions.sslCert && serverOptions.sslKey) {
configuration.server ??= {};
// server configuration is defined above
configuration.server.https = {
cert: await (0, promises_1.readFile)(serverOptions.sslCert),
key: await (0, promises_1.readFile)(serverOptions.sslKey),
};
}
else {
const { default: basicSslPlugin } = await Promise.resolve().then(() => __importStar(require('@vitejs/plugin-basic-ssl')));
configuration.plugins ??= [];
configuration.plugins.push(basicSslPlugin());
}
}
return configuration;
}
/**
* Checks if the given value is an absolute URL.
*
* This function helps in avoiding Vite's prebundling from processing absolute URLs (http://, https://, //) as files.
*
* @param value - The URL or path to check.
* @returns `true` if the value is not an absolute URL; otherwise, `false`.
*/
function isAbsoluteUrl(value) {
return /^(?:https?:)?\/\//.test(value);
}

View File

@@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize';
import type { MessageExtractor } from '@angular/localize/tools';
import type { BuilderContext } from '@angular-devkit/architect';
import type { ApplicationBuilderExtensions } from '../application/options';
import type { NormalizedExtractI18nOptions } from './options';
export declare function extractMessages(options: NormalizedExtractI18nOptions, builderName: string, context: BuilderContext, extractorConstructor: typeof MessageExtractor, extensions?: ApplicationBuilderExtensions): Promise<{
success: boolean;
basePath: string;
messages: LocalizeMessage[];
useLegacyIds: boolean;
}>;

View File

@@ -0,0 +1,131 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractMessages = extractMessages;
const node_fs_1 = require("node:fs");
const node_path_1 = __importDefault(require("node:path"));
const application_1 = require("../application");
const results_1 = require("../application/results");
const schema_1 = require("../application/schema");
async function extractMessages(options, builderName, context, extractorConstructor, extensions) {
const messages = [];
// Setup the build options for the application based on the buildTarget option
const buildOptions = (await context.validateOptions(await context.getTargetOptions(options.buildTarget), builderName));
buildOptions.optimization = false;
buildOptions.sourceMap = { scripts: true, vendor: true, styles: false };
buildOptions.localize = false;
buildOptions.budgets = undefined;
buildOptions.index = false;
buildOptions.serviceWorker = false;
buildOptions.outputMode = schema_1.OutputMode.Static;
buildOptions.appShell = undefined;
buildOptions.ssr = undefined;
buildOptions.prerender = undefined;
buildOptions.server = undefined;
// Build the application with the build options
const builderResult = await first((0, application_1.buildApplicationInternal)(buildOptions, context, extensions));
let success = false;
if (!builderResult || builderResult.kind === results_1.ResultKind.Failure) {
context.logger.error('Application build failed.');
}
else if (builderResult.kind !== results_1.ResultKind.Full) {
context.logger.error('Application build did not provide a full output.');
}
else {
// Setup the localize message extractor based on the in-memory files
const extractor = setupLocalizeExtractor(extractorConstructor, builderResult.files, context);
// Extract messages from each output JavaScript file.
// Output files are only present on a successful build.
for (const filePath of Object.keys(builderResult.files)) {
if (!filePath.endsWith('.js')) {
continue;
}
const fileMessages = extractor.extractMessages(filePath);
messages.push(...fileMessages);
}
success = true;
}
return {
success,
basePath: context.workspaceRoot,
messages,
// Legacy i18n identifiers are not supported with the new application builder
useLegacyIds: false,
};
}
function setupLocalizeExtractor(extractorConstructor, files, context) {
const textDecoder = new TextDecoder();
// Setup a virtual file system instance for the extractor
// * MessageExtractor itself uses readFile, relative and resolve
// * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve
const filesystem = {
readFile(path) {
// Output files are stored as relative to the workspace root
const requestedPath = node_path_1.default.relative(context.workspaceRoot, path);
const file = files[requestedPath];
let content;
if (file?.origin === 'memory') {
content = textDecoder.decode(file.contents);
}
else if (file?.origin === 'disk') {
content = (0, node_fs_1.readFileSync)(file.inputPath, 'utf-8');
}
if (content === undefined) {
throw new Error('Unknown file requested: ' + requestedPath);
}
return content;
},
relative(from, to) {
return node_path_1.default.relative(from, to);
},
resolve(...paths) {
return node_path_1.default.resolve(...paths);
},
exists(path) {
// Output files are stored as relative to the workspace root
const requestedPath = node_path_1.default.relative(context.workspaceRoot, path);
return files[requestedPath] !== undefined;
},
dirname(path) {
return node_path_1.default.dirname(path);
},
};
const logger = {
// level 2 is warnings
level: 2,
debug(...args) {
// eslint-disable-next-line no-console
console.debug(...args);
},
info(...args) {
context.logger.info(args.join(''));
},
warn(...args) {
context.logger.warn(args.join(''));
},
error(...args) {
context.logger.error(args.join(''));
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extractor = new extractorConstructor(filesystem, logger, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath: context.workspaceRoot,
useSourceMaps: true,
});
return extractor;
}
async function first(iterable) {
for await (const value of iterable) {
return value;
}
}

View File

@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { ApplicationBuilderExtensions } from '../application/options';
import { Schema as ExtractI18nBuilderOptions } from './schema';
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: ExtractI18nBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): Promise<BuilderOutput>;

View File

@@ -0,0 +1,162 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = execute;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const load_esm_1 = require("../../utils/load-esm");
const version_1 = require("../../utils/version");
const options_1 = require("./options");
const schema_1 = require("./schema");
/**
* @experimental Direct usage of this function is considered experimental.
*/
async function execute(options, context, extensions) {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
context.logger.error(`The 'extract-i18n' builder requires a target to be specified.`);
return { success: false };
}
const { projectType } = (await context.getProjectMetadata(projectName));
if (projectType !== 'application') {
context.logger.error(`Tried to extract from ${projectName} with 'projectType' ${projectType}, which is not supported.` +
` The 'extract-i18n' builder can only extract from applications.`);
return { success: false };
}
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(context.workspaceRoot);
// Load the Angular localize package.
// The package is a peer dependency and might not be present
let localizeToolsModule;
try {
localizeToolsModule =
await (0, load_esm_1.loadEsmModule)('@angular/localize/tools');
}
catch {
return {
success: false,
error: `i18n extraction requires the '@angular/localize' package.` +
` You can add it by using 'ng add @angular/localize'.`,
};
}
// Normalize options
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
// Extract messages based on configured builder
const { extractMessages } = await Promise.resolve().then(() => __importStar(require('./application-extraction')));
const extractionResult = await extractMessages(normalizedOptions, builderName, context, localizeToolsModule.MessageExtractor, extensions);
if (!extractionResult.success) {
return { success: false };
}
// Perform duplicate message checks
const { checkDuplicateMessages } = localizeToolsModule;
// The filesystem is used to create a relative path for each file
// from the basePath. This relative path is then used in the error message.
const checkFileSystem = {
relative(from, to) {
return node_path_1.default.relative(from, to);
},
};
const duplicateTranslationBehavior = normalizedOptions.i18nOptions.duplicateTranslationBehavior;
const diagnostics = checkDuplicateMessages(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
checkFileSystem, extractionResult.messages, duplicateTranslationBehavior,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extractionResult.basePath);
if (diagnostics.messages.length > 0 && duplicateTranslationBehavior !== 'ignore') {
if (duplicateTranslationBehavior === 'error') {
context.logger.error(`Extraction Failed: ${diagnostics.formatDiagnostics('')}`);
return { success: false };
}
else {
context.logger.warn(diagnostics.formatDiagnostics(''));
}
}
// Serialize all extracted messages
const serializer = await createSerializer(localizeToolsModule, normalizedOptions.format, normalizedOptions.i18nOptions.sourceLocale, extractionResult.basePath, extractionResult.useLegacyIds, diagnostics);
const content = serializer.serialize(extractionResult.messages);
// Ensure directory exists
const outputPath = node_path_1.default.dirname(normalizedOptions.outFile);
if (!node_fs_1.default.existsSync(outputPath)) {
node_fs_1.default.mkdirSync(outputPath, { recursive: true });
}
// Write translation file
node_fs_1.default.writeFileSync(normalizedOptions.outFile, content);
if (normalizedOptions.progress) {
context.logger.info(`Extraction Complete. (Messages: ${extractionResult.messages.length})`);
}
return { success: true, outputPath: normalizedOptions.outFile };
}
async function createSerializer(localizeToolsModule, format, sourceLocale, basePath, useLegacyIds, diagnostics) {
const { XmbTranslationSerializer, LegacyMessageIdMigrationSerializer, ArbTranslationSerializer, Xliff1TranslationSerializer, Xliff2TranslationSerializer, SimpleJsonTranslationSerializer, } = localizeToolsModule;
switch (format) {
case schema_1.Format.Xmb:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new XmbTranslationSerializer(basePath, useLegacyIds);
case schema_1.Format.Xlf:
case schema_1.Format.Xlif:
case schema_1.Format.Xliff:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Xliff1TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
case schema_1.Format.Xlf2:
case schema_1.Format.Xliff2:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Xliff2TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
case schema_1.Format.Json:
return new SimpleJsonTranslationSerializer(sourceLocale);
case schema_1.Format.LegacyMigrate:
return new LegacyMessageIdMigrationSerializer(diagnostics);
case schema_1.Format.Arb:
return new ArbTranslationSerializer(sourceLocale,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath, {
relative(from, to) {
return node_path_1.default.relative(from, to);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
});
}
}

View File

@@ -0,0 +1,13 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { Builder } from '@angular-devkit/architect';
import { execute } from './builder';
import type { Schema as ExtractI18nBuilderOptions } from './schema';
export { ExtractI18nBuilderOptions, execute };
declare const builder: Builder<ExtractI18nBuilderOptions>;
export default builder;

View File

@@ -0,0 +1,15 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const builder_1 = require("./builder");
Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return builder_1.execute; } });
const builder = (0, architect_1.createBuilder)(builder_1.execute);
exports.default = builder;

View File

@@ -0,0 +1,33 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { type DiagnosticHandlingStrategy } from '@angular/localize/tools';
import { BuilderContext } from '@angular-devkit/architect';
import { type I18nOptions } from '../../utils/i18n-options';
import { Schema as ExtractI18nOptions, Format } from './schema';
export type NormalizedExtractI18nOptions = Awaited<ReturnType<typeof normalizeOptions>>;
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: ExtractI18nOptions): Promise<{
workspaceRoot: string;
projectRoot: string;
buildTarget: import("@angular-devkit/architect").Target;
i18nOptions: I18nOptions & {
duplicateTranslationBehavior: DiagnosticHandlingStrategy;
};
format: Format.Arb | Format.Json | Format.LegacyMigrate | Format.Xliff | Format.Xliff2 | Format.Xmb;
outFile: string;
progress: boolean;
}>;

View File

@@ -0,0 +1,85 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = normalizeOptions;
const architect_1 = require("@angular-devkit/architect");
const node_assert_1 = require("node:assert");
const node_path_1 = __importDefault(require("node:path"));
const i18n_options_1 = require("../../utils/i18n-options");
const schema_1 = require("./schema");
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
async function normalizeOptions(context, projectName, options) {
const workspaceRoot = context.workspaceRoot;
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
// Target specifier defaults to the current project's build target with no specified configuration
const buildTargetSpecifier = options.buildTarget ?? ':';
const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
const i18nOptions = {
...(0, i18n_options_1.createI18nOptions)(projectMetadata, /** inline */ false, context.logger),
duplicateTranslationBehavior: options.i18nDuplicateTranslation || 'warning',
};
// Normalize xliff format extensions
let format = options.format;
switch (format) {
// Default format is xliff1
case undefined:
case schema_1.Format.Xlf:
case schema_1.Format.Xlif:
case schema_1.Format.Xliff:
format = schema_1.Format.Xliff;
break;
case schema_1.Format.Xlf2:
case schema_1.Format.Xliff2:
format = schema_1.Format.Xliff2;
break;
}
let outFile = options.outFile || getDefaultOutFile(format);
if (options.outputPath) {
outFile = node_path_1.default.join(options.outputPath, outFile);
}
outFile = node_path_1.default.resolve(context.workspaceRoot, outFile);
return {
workspaceRoot,
projectRoot,
buildTarget,
i18nOptions,
format,
outFile,
progress: options.progress ?? true,
};
}
function getDefaultOutFile(format) {
switch (format) {
case schema_1.Format.Xmb:
return 'messages.xmb';
case schema_1.Format.Xliff:
case schema_1.Format.Xliff2:
return 'messages.xlf';
case schema_1.Format.Json:
case schema_1.Format.LegacyMigrate:
return 'messages.json';
case schema_1.Format.Arb:
return 'messages.arb';
default:
(0, node_assert_1.fail)(`Invalid Format enum value: ${format}`);
}
}

View File

@@ -0,0 +1,53 @@
/**
* Extract i18n target options for Build Facade.
*/
export type Schema = {
/**
* A builder target to extract i18n messages in the format of
* `project:target[:configuration]`. You can also pass in more than one configuration name
* as a comma-separated list. Example: `project:target:production,staging`.
*/
buildTarget?: string;
/**
* Output format for the generated file.
*/
format?: Format;
/**
* How to handle duplicate translations.
*/
i18nDuplicateTranslation?: I18NDuplicateTranslation;
/**
* Name of the file to output.
*/
outFile?: string;
/**
* Path where output will be placed.
*/
outputPath?: string;
/**
* Log progress to the console.
*/
progress?: boolean;
};
/**
* Output format for the generated file.
*/
export declare enum Format {
Arb = "arb",
Json = "json",
LegacyMigrate = "legacy-migrate",
Xlf = "xlf",
Xlf2 = "xlf2",
Xlif = "xlif",
Xliff = "xliff",
Xliff2 = "xliff2",
Xmb = "xmb"
}
/**
* How to handle duplicate translations.
*/
export declare enum I18NDuplicateTranslation {
Error = "error",
Ignore = "ignore",
Warning = "warning"
}

View File

@@ -0,0 +1,29 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.I18NDuplicateTranslation = exports.Format = void 0;
/**
* Output format for the generated file.
*/
var Format;
(function (Format) {
Format["Arb"] = "arb";
Format["Json"] = "json";
Format["LegacyMigrate"] = "legacy-migrate";
Format["Xlf"] = "xlf";
Format["Xlf2"] = "xlf2";
Format["Xlif"] = "xlif";
Format["Xliff"] = "xliff";
Format["Xliff2"] = "xliff2";
Format["Xmb"] = "xmb";
})(Format || (exports.Format = Format = {}));
/**
* How to handle duplicate translations.
*/
var I18NDuplicateTranslation;
(function (I18NDuplicateTranslation) {
I18NDuplicateTranslation["Error"] = "error";
I18NDuplicateTranslation["Ignore"] = "ignore";
I18NDuplicateTranslation["Warning"] = "warning";
})(I18NDuplicateTranslation || (exports.I18NDuplicateTranslation = I18NDuplicateTranslation = {}));

View File

@@ -0,0 +1,38 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Extract i18n Target",
"description": "Extract i18n target options for Build Facade.",
"type": "object",
"properties": {
"buildTarget": {
"type": "string",
"description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
},
"format": {
"type": "string",
"description": "Output format for the generated file.",
"default": "xlf",
"enum": ["xmb", "xlf", "xlif", "xliff", "xlf2", "xliff2", "json", "arb", "legacy-migrate"]
},
"progress": {
"type": "boolean",
"description": "Log progress to the console.",
"default": true
},
"outputPath": {
"type": "string",
"description": "Path where output will be placed."
},
"outFile": {
"type": "string",
"description": "Name of the file to output."
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations.",
"enum": ["error", "warning", "ignore"]
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,13 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import { ResultFile } from '../application/results';
import { Schema as KarmaBuilderOptions } from './schema';
import type { KarmaBuilderTransformsOptions } from './index';
export declare function execute(options: KarmaBuilderOptions, context: BuilderContext, transforms?: KarmaBuilderTransformsOptions): AsyncIterable<BuilderOutput>;
export declare function writeTestFiles(files: Record<string, ResultFile>, testDir: string): Promise<void>;

View File

@@ -0,0 +1,632 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = execute;
exports.writeTestFiles = writeTestFiles;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = require("node:fs");
const fs = __importStar(require("node:fs/promises"));
const node_module_1 = require("node:module");
const node_path_1 = __importDefault(require("node:path"));
const tinyglobby_1 = require("tinyglobby");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const utils_1 = require("../../tools/esbuild/utils");
const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin");
const project_metadata_1 = require("../../utils/project-metadata");
const index_1 = require("../application/index");
const results_1 = require("../application/results");
const schema_1 = require("../application/schema");
const find_tests_1 = require("./find-tests");
const options_1 = require("./options");
const localResolve = (0, node_module_1.createRequire)(__filename).resolve;
const isWindows = process.platform === 'win32';
class ApplicationBuildError extends Error {
constructor(message) {
super(message);
this.name = 'ApplicationBuildError';
}
}
const LATEST_BUILD_FILES_TOKEN = 'angularLatestBuildFiles';
class AngularAssetsMiddleware {
serveFile;
latestBuildFiles;
static $inject = ['serveFile', LATEST_BUILD_FILES_TOKEN];
static NAME = 'angular-test-assets';
constructor(serveFile, latestBuildFiles) {
this.serveFile = serveFile;
this.latestBuildFiles = latestBuildFiles;
}
handle(req, res, next) {
const url = new URL(`http://${req.headers['host']}${req.url}`);
// Remove the leading slash from the URL path and convert to platform specific.
// The latest build files will use the platform path separator.
let pathname = url.pathname.slice(1);
if (isWindows) {
pathname = pathname.replaceAll(node_path_1.default.posix.sep, node_path_1.default.win32.sep);
}
const file = this.latestBuildFiles.files[pathname];
if (!file) {
next();
return;
}
// Implementation of serverFile can be found here:
// https://github.com/karma-runner/karma/blob/84f85e7016efc2266fa6b3465f494a3fa151c85c/lib/middleware/common.js#L10
switch (file.origin) {
case 'disk':
this.serveFile(file.inputPath, undefined, res, undefined, undefined, /* doNotCache */ true);
break;
case 'memory':
// Include pathname to help with Content-Type headers.
this.serveFile(`/unused/${url.pathname}`, undefined, res, undefined, file.contents,
/* doNotCache */ false);
break;
}
}
static createPlugin(initialFiles) {
return {
[LATEST_BUILD_FILES_TOKEN]: ['value', { files: { ...initialFiles.files } }],
[`middleware:${AngularAssetsMiddleware.NAME}`]: [
'factory',
Object.assign((...args) => {
const inst = new AngularAssetsMiddleware(...args);
return inst.handle.bind(inst);
}, AngularAssetsMiddleware),
],
};
}
}
class AngularPolyfillsPlugin {
static $inject = ['config.files'];
static NAME = 'angular-polyfills';
static createPlugin(polyfillsFile, jasmineCleanupFiles, scriptsFiles) {
return {
// This has to be a "reporter" because reporters run _after_ frameworks
// and karma-jasmine-html-reporter injects additional scripts that may
// depend on Jasmine but aren't modules - which means that they would run
// _before_ all module code (including jasmine).
[`reporter:${AngularPolyfillsPlugin.NAME}`]: [
'factory',
Object.assign((files) => {
// The correct order is zone.js -> jasmine -> zone.js/testing.
// Jasmine has to see the patched version of the global `setTimeout`
// function so it doesn't cache the unpatched version. And /testing
// needs to see the global `jasmine` object so it can patch it.
const polyfillsIndex = 0;
files.splice(polyfillsIndex, 0, polyfillsFile);
// Insert just before test_main.js.
const zoneTestingIndex = files.findIndex((f) => {
if (typeof f === 'string') {
return false;
}
return f.pattern.endsWith('/test_main.js');
});
if (zoneTestingIndex === -1) {
throw new Error('Could not find test entrypoint file.');
}
files.splice(zoneTestingIndex, 0, jasmineCleanupFiles);
// We need to ensure that all files are served as modules, otherwise
// the order in the files list gets really confusing: Karma doesn't
// set defer on scripts, so all scripts with type=js will run first,
// even if type=module files appeared earlier in `files`.
for (const f of files) {
if (typeof f === 'string') {
throw new Error(`Unexpected string-based file: "${f}"`);
}
if (f.included === false) {
// Don't worry about files that aren't included on the initial
// page load. `type` won't affect them.
continue;
}
if (f.pattern.endsWith('.js') && 'js' === (f.type ?? 'js')) {
f.type = 'module';
}
}
// Add "scripts" option files as classic scripts
files.unshift(...scriptsFiles);
// Add browser sourcemap support as a classic script
files.unshift({
pattern: localResolve('source-map-support/browser-source-map-support.js'),
included: true,
watched: false,
});
// Karma needs a return value for a factory and Karma's multi-reporter expects an `adapters` array
return { adapters: [] };
}, AngularPolyfillsPlugin),
],
};
}
}
function injectKarmaReporter(buildOptions, buildIterator, karmaConfig, controller) {
const reporterName = 'angular-progress-notifier';
class ProgressNotifierReporter {
emitter;
latestBuildFiles;
static $inject = ['emitter', LATEST_BUILD_FILES_TOKEN];
// Needed for the karma reporter interface, see https://github.com/angular/angular-cli/issues/31629
adapters = [];
constructor(emitter, latestBuildFiles) {
this.emitter = emitter;
this.latestBuildFiles = latestBuildFiles;
this.startWatchingBuild();
}
startWatchingBuild() {
void (async () => {
// This is effectively "for await of but skip what's already consumed".
let isDone = false; // to mark the loop condition as "not constant".
while (!isDone) {
const { done, value: buildOutput } = await buildIterator.next();
if (done) {
isDone = true;
break;
}
if (buildOutput.kind === results_1.ResultKind.Failure) {
controller.enqueue({ success: false, message: 'Build failed' });
}
else if (buildOutput.kind === results_1.ResultKind.Incremental ||
buildOutput.kind === results_1.ResultKind.Full) {
if (buildOutput.kind === results_1.ResultKind.Full) {
this.latestBuildFiles.files = buildOutput.files;
}
else {
this.latestBuildFiles.files = {
...this.latestBuildFiles.files,
...buildOutput.files,
};
}
await writeTestFiles(buildOutput.files, buildOptions.outputPath);
this.emitter.refreshFiles();
}
}
})();
}
onRunComplete = function (_browsers, results) {
if (results.exitCode === 0) {
controller.enqueue({ success: true });
}
else {
controller.enqueue({ success: false });
}
};
}
karmaConfig.reporters ??= [];
karmaConfig.reporters.push(reporterName);
karmaConfig.plugins ??= [];
karmaConfig.plugins.push({
[`reporter:${reporterName}`]: [
'factory',
Object.assign((...args) => new ProgressNotifierReporter(...args), ProgressNotifierReporter),
],
});
}
function execute(options, context, transforms) {
const normalizedOptions = (0, options_1.normalizeOptions)(context, options);
const karmaOptions = getBaseKarmaOptions(normalizedOptions, context);
let karmaServer;
return new ReadableStream({
async start(controller) {
let init;
try {
init = await initializeApplication(normalizedOptions, context, karmaOptions, transforms);
}
catch (err) {
if (err instanceof ApplicationBuildError) {
controller.enqueue({ success: false, message: err.message });
controller.close();
return;
}
throw err;
}
const [karma, karmaConfig, buildOptions, buildIterator] = init;
// If `--watch` is explicitly enabled or if we are keeping the Karma
// process running, we should hook Karma into the build.
if (buildIterator) {
injectKarmaReporter(buildOptions, buildIterator, karmaConfig, controller);
}
// Close the stream once the Karma server returns.
karmaServer = new karma.Server(karmaConfig, (exitCode) => {
controller.enqueue({ success: exitCode === 0 });
controller.close();
});
await karmaServer.start();
},
async cancel() {
await karmaServer?.stop();
},
});
}
async function getProjectSourceRoot(context) {
// We have already validated that the project name is set before calling this function.
const projectName = context.target?.project;
if (!projectName) {
return context.workspaceRoot;
}
const projectMetadata = await context.getProjectMetadata(projectName);
const { projectSourceRoot } = (0, project_metadata_1.getProjectRootPaths)(context.workspaceRoot, projectMetadata);
return projectSourceRoot;
}
function normalizePolyfills(polyfills = []) {
const jasmineGlobalEntryPoint = localResolve('./polyfills/jasmine_global.js');
const jasmineGlobalCleanupEntrypoint = localResolve('./polyfills/jasmine_global_cleanup.js');
const sourcemapEntrypoint = localResolve('./polyfills/init_sourcemaps.js');
const zoneTestingEntryPoint = 'zone.js/testing';
const polyfillsExludingZoneTesting = polyfills.filter((p) => p !== zoneTestingEntryPoint);
return [
polyfillsExludingZoneTesting.concat([jasmineGlobalEntryPoint, sourcemapEntrypoint]),
polyfillsExludingZoneTesting.length === polyfills.length
? [jasmineGlobalCleanupEntrypoint]
: [jasmineGlobalCleanupEntrypoint, zoneTestingEntryPoint],
];
}
async function collectEntrypoints(options, context, projectSourceRoot) {
// Glob for files to test.
const testFiles = await (0, find_tests_1.findTests)(options.include, options.exclude, context.workspaceRoot, projectSourceRoot);
return (0, find_tests_1.getTestEntrypoints)(testFiles, { projectSourceRoot, workspaceRoot: context.workspaceRoot });
}
// eslint-disable-next-line max-lines-per-function
async function initializeApplication(options, context, karmaOptions, transforms) {
const outputPath = node_path_1.default.join(context.workspaceRoot, 'dist/test-out', (0, node_crypto_1.randomUUID)());
const projectSourceRoot = await getProjectSourceRoot(context);
// Setup exit cleanup for temporary directory
const handleProcessExit = () => (0, node_fs_1.rmSync)(outputPath, { recursive: true, force: true });
process.once('exit', handleProcessExit);
process.once('SIGINT', handleProcessExit);
process.once('uncaughtException', handleProcessExit);
const [karma, entryPoints] = await Promise.all([
Promise.resolve().then(() => __importStar(require('karma'))),
collectEntrypoints(options, context, projectSourceRoot),
fs.rm(outputPath, { recursive: true, force: true }),
]);
const mainName = 'test_main';
if (options.main) {
entryPoints.set(mainName, options.main);
}
else {
entryPoints.set(mainName, 'angular:test-bed-init');
}
const instrumentForCoverage = options.codeCoverage
? createInstrumentationFilter(projectSourceRoot, getInstrumentationExcludedPaths(context.workspaceRoot, options.codeCoverageExclude ?? []))
: undefined;
const [polyfills, jasmineCleanup] = normalizePolyfills(options.polyfills);
for (let idx = 0; idx < jasmineCleanup.length; ++idx) {
entryPoints.set(`jasmine-cleanup-${idx}`, jasmineCleanup[idx]);
}
const buildOptions = {
assets: options.assets,
entryPoints,
tsConfig: options.tsConfig,
outputPath,
preserveSymlinks: options.preserveSymlinks,
aot: options.aot,
index: false,
outputHashing: schema_1.OutputHashing.None,
optimization: false,
sourceMap: options.sourceMap,
instrumentForCoverage,
styles: options.styles,
scripts: options.scripts,
polyfills,
webWorkerTsConfig: options.webWorkerTsConfig,
watch: options.watch,
stylePreprocessorOptions: options.stylePreprocessorOptions,
inlineStyleLanguage: options.inlineStyleLanguage,
fileReplacements: options.fileReplacements,
define: options.define,
loader: options.loader,
externalDependencies: options.externalDependencies,
};
const virtualTestBedInit = (0, virtual_module_plugin_1.createVirtualModulePlugin)({
namespace: 'angular:test-bed-init',
loadContent: async () => {
const contents = [
// Initialize the Angular testing environment
`import { getTestBed } from '@angular/core/testing';`,
`import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
`getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {`,
` errorOnUnknownElements: true,`,
` errorOnUnknownProperties: true,`,
'});',
];
return {
contents: contents.join('\n'),
loader: 'js',
resolveDir: projectSourceRoot,
};
},
});
// Build tests with `application` builder, using test files as entry points.
const [buildOutput, buildIterator] = await first((0, index_1.buildApplicationInternal)(buildOptions, context, { codePlugins: [virtualTestBedInit] }), { cancel: !buildOptions.watch });
if (buildOutput.kind === results_1.ResultKind.Failure) {
throw new ApplicationBuildError('Build failed');
}
else if (buildOutput.kind !== results_1.ResultKind.Full) {
throw new ApplicationBuildError('A full build result is required from the application builder.');
}
// Write test files
await writeTestFiles(buildOutput.files, buildOptions.outputPath);
// We need to add this to the beginning *after* the testing framework has
// prepended its files. The output path is required for each since they are
// added later in the test process via a plugin.
const polyfillsFile = {
pattern: `${outputPath}/polyfills.js`,
included: true,
served: true,
type: 'module',
watched: false,
};
const jasmineCleanupFiles = {
pattern: `${outputPath}/jasmine-cleanup-*.js`,
included: true,
served: true,
type: 'module',
watched: false,
};
karmaOptions.basePath = outputPath;
const scriptsFiles = [];
if (options.scripts?.length) {
const outputScripts = new Set();
for (const scriptEntry of options.scripts) {
const outputName = typeof scriptEntry === 'string'
? 'scripts.js'
: `${scriptEntry.bundleName ?? 'scripts'}.js`;
if (outputScripts.has(outputName)) {
continue;
}
outputScripts.add(outputName);
scriptsFiles.push({
pattern: `${outputPath}/${outputName}`,
watched: false,
included: typeof scriptEntry === 'string' ? true : scriptEntry.inject !== false,
type: 'js',
});
}
}
karmaOptions.files ??= [];
karmaOptions.files.push(
// Serve global setup script.
{ pattern: `${mainName}.js`, type: 'module', watched: false },
// Serve all source maps.
{ pattern: `*.map`, included: false, watched: false },
// These are the test entrypoints.
{ pattern: `spec-*.js`, type: 'module', watched: false });
if (hasChunkOrWorkerFiles(buildOutput.files)) {
karmaOptions.files.push(
// Allow loading of chunk-* files but don't include them all on load.
{
pattern: `{chunk,worker}-*.js`,
type: 'module',
included: false,
watched: false,
});
}
if (options.styles?.length) {
// Serve CSS outputs on page load, these are the global styles.
karmaOptions.files.push({ pattern: `*.css`, type: 'css', watched: false });
}
const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig, transforms?.karmaOptions ? await transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
// Check for jsdom which does not support executing ESM scripts.
// If present, remove jsdom and issue a warning.
const updatedBrowsers = parsedKarmaConfig.browsers?.filter((browser) => browser !== 'jsdom');
if (parsedKarmaConfig.browsers?.length !== updatedBrowsers?.length) {
parsedKarmaConfig.browsers = updatedBrowsers;
context.logger.warn(`'jsdom' does not support ESM code execution and cannot be used for karma testing.` +
` The 'jsdom' entry has been removed from the 'browsers' option.`);
}
// Remove the webpack plugin/framework:
// Alternative would be to make the Karma plugin "smart" but that's a tall order
// with managing unneeded imports etc..
parsedKarmaConfig.plugins ??= [];
const pluginLengthBefore = parsedKarmaConfig.plugins.length;
parsedKarmaConfig.plugins = parsedKarmaConfig.plugins.filter((plugin) => {
if (typeof plugin === 'string') {
return plugin !== 'framework:@angular-devkit/build-angular';
}
return !plugin['framework:@angular-devkit/build-angular'];
});
parsedKarmaConfig.frameworks ??= [];
parsedKarmaConfig.frameworks = parsedKarmaConfig.frameworks.filter((framework) => framework !== '@angular-devkit/build-angular');
const pluginLengthAfter = parsedKarmaConfig.plugins.length;
if (pluginLengthBefore !== pluginLengthAfter) {
context.logger.warn(`Ignoring framework "@angular-devkit/build-angular" from karma config file because it's not compatible with the application builder.`);
}
parsedKarmaConfig.plugins.push(AngularAssetsMiddleware.createPlugin(buildOutput));
parsedKarmaConfig.middleware ??= [];
parsedKarmaConfig.middleware.push(AngularAssetsMiddleware.NAME);
parsedKarmaConfig.plugins.push(AngularPolyfillsPlugin.createPlugin(polyfillsFile, jasmineCleanupFiles, scriptsFiles));
parsedKarmaConfig.reporters ??= [];
parsedKarmaConfig.reporters.push(AngularPolyfillsPlugin.NAME);
// Adjust karma junit reporter outDir location to maintain previous (devkit) behavior
// The base path for the reporter was previously the workspace root.
// To keep the files in the same location, the reporter's output directory is adjusted
// to be relative to the workspace root when using junit.
if (parsedKarmaConfig.reporters?.some((reporter) => reporter === 'junit')) {
if ('junitReporter' in parsedKarmaConfig) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const junitReporterOptions = parsedKarmaConfig['junitReporter'];
if (junitReporterOptions.outputDir == undefined) {
junitReporterOptions.outputDir = context.workspaceRoot;
}
else if (typeof junitReporterOptions.outputDir === 'string' &&
!node_path_1.default.isAbsolute(junitReporterOptions.outputDir)) {
junitReporterOptions.outputDir = node_path_1.default.join(context.workspaceRoot, junitReporterOptions.outputDir);
}
}
else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parsedKarmaConfig['junitReporter'] = {
outputDir: context.workspaceRoot,
};
}
}
// When using code-coverage, auto-add karma-coverage.
// This was done as part of the karma plugin for webpack.
if (options.codeCoverage &&
!parsedKarmaConfig.reporters?.some((r) => r === 'coverage' || r === 'coverage-istanbul')) {
parsedKarmaConfig.reporters = (parsedKarmaConfig.reporters ?? []).concat(['coverage']);
}
return [karma, parsedKarmaConfig, buildOptions, buildIterator];
}
function hasChunkOrWorkerFiles(files) {
return Object.keys(files).some((filename) => {
return /(?:^|\/)(?:worker|chunk)[^/]+\.js$/.test(filename);
});
}
async function writeTestFiles(files, testDir) {
const directoryExists = new Set();
// Writes the test related output files to disk and ensures the containing directories are present
await (0, utils_1.emitFilesToDisk)(Object.entries(files), async ([filePath, file]) => {
if (file.type !== bundler_context_1.BuildOutputFileType.Browser && file.type !== bundler_context_1.BuildOutputFileType.Media) {
return;
}
const fullFilePath = node_path_1.default.join(testDir, filePath);
// Ensure output subdirectories exist
const fileBasePath = node_path_1.default.dirname(fullFilePath);
if (fileBasePath && !directoryExists.has(fileBasePath)) {
await fs.mkdir(fileBasePath, { recursive: true });
directoryExists.add(fileBasePath);
}
if (file.origin === 'memory') {
// Write file contents
await fs.writeFile(fullFilePath, file.contents);
}
else {
// Copy file contents
await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE);
}
});
}
/** Returns the first item yielded by the given generator and cancels the execution. */
async function first(generator, { cancel }) {
if (!cancel) {
const iterator = generator[Symbol.asyncIterator]();
const firstValue = await iterator.next();
if (firstValue.done) {
throw new Error('Expected generator to emit at least once.');
}
return [firstValue.value, iterator];
}
for await (const value of generator) {
return [value, null];
}
throw new Error('Expected generator to emit at least once.');
}
function createInstrumentationFilter(includedBasePath, excludedPaths) {
return (request) => {
return (!excludedPaths.has(request) &&
!/\.(e2e|spec)\.tsx?$|[\\/]node_modules[\\/]/.test(request) &&
request.startsWith(includedBasePath));
};
}
function getInstrumentationExcludedPaths(root, excludedPaths) {
const excluded = new Set();
for (const excludeGlob of excludedPaths) {
const excludePath = excludeGlob[0] === '/' ? excludeGlob.slice(1) : excludeGlob;
(0, tinyglobby_1.globSync)(excludePath, { cwd: root }).forEach((p) => excluded.add(node_path_1.default.join(root, p)));
}
return excluded;
}
function getBaseKarmaOptions(options, context) {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
throw new Error(`The 'karma' builder requires a target to be specified.`);
}
const karmaOptions = options.karmaConfig
? {}
: getBuiltInKarmaConfig(context.workspaceRoot, projectName);
const singleRun = !options.watch;
karmaOptions.singleRun = singleRun;
// Workaround https://github.com/angular/angular-cli/issues/28271, by clearing context by default
// for single run executions. Not clearing context for multi-run (watched) builds allows the
// Jasmine Spec Runner to be visible in the browser after test execution.
karmaOptions.client ??= {};
karmaOptions.client.clearContext ??= singleRun;
// Convert browsers from a string to an array
if (options.browsers) {
karmaOptions.browsers = options.browsers;
}
if (options.reporters) {
karmaOptions.reporters = options.reporters;
}
return karmaOptions;
}
function getBuiltInKarmaConfig(workspaceRoot, projectName) {
let coverageFolderName = projectName.charAt(0) === '@' ? projectName.slice(1) : projectName;
coverageFolderName = coverageFolderName.toLowerCase();
const workspaceRootRequire = (0, node_module_1.createRequire)(workspaceRoot + '/');
// Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template
return {
basePath: '',
frameworks: ['jasmine'],
plugins: [
'karma-jasmine',
'karma-chrome-launcher',
'karma-jasmine-html-reporter',
'karma-coverage',
].map((p) => workspaceRootRequire(p)),
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: node_path_1.default.join(workspaceRoot, 'coverage', coverageFolderName),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
browsers: ['Chrome'],
customLaunchers: {
// Chrome configured to run in a bazel sandbox.
// Disable the use of the gpu and `/dev/shm` because it causes Chrome to
// crash on some environments.
// See:
// https://github.com/puppeteer/puppeteer/blob/v1.0.0/docs/troubleshooting.md#tips
// https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage'],
},
},
restartOnFileChange: true,
};
}

View File

@@ -0,0 +1,15 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export declare function findTests(include: string[], exclude: string[], workspaceRoot: string, projectSourceRoot: string): Promise<string[]>;
interface TestEntrypointsOptions {
projectSourceRoot: string;
workspaceRoot: string;
}
/** Generate unique bundle names for a set of test files. */
export declare function getTestEntrypoints(testFiles: string[], { projectSourceRoot, workspaceRoot }: TestEntrypointsOptions): Map<string, string>;
export {};

111
node_modules/@angular/build/src/builders/karma/find-tests.js generated vendored Executable file
View File

@@ -0,0 +1,111 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.findTests = findTests;
exports.getTestEntrypoints = getTestEntrypoints;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const tinyglobby_1 = require("tinyglobby");
const path_1 = require("../../utils/path");
/* Go through all patterns and find unique list of files */
async function findTests(include, exclude, workspaceRoot, projectSourceRoot) {
const matchingTestsPromises = include.map((pattern) => findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot));
const files = await Promise.all(matchingTestsPromises);
// Unique file names
return [...new Set(files.flat())];
}
/** Generate unique bundle names for a set of test files. */
function getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot }) {
const seen = new Set();
return new Map(Array.from(testFiles, (testFile) => {
const relativePath = removeRoots(testFile, [projectSourceRoot, workspaceRoot])
// Strip leading dots and path separators.
.replace(/^[./\\]+/, '')
// Replace any path separators with dashes.
.replace(/[/\\]/g, '-');
const baseName = `spec-${(0, node_path_1.basename)(relativePath, (0, node_path_1.extname)(relativePath))}`;
let uniqueName = baseName;
let suffix = 2;
while (seen.has(uniqueName)) {
uniqueName = `${baseName}-${suffix}`.replace(/([^\w](?:spec|test))-([\d]+)$/, '-$2$1');
++suffix;
}
seen.add(uniqueName);
return [uniqueName, testFile];
}));
}
const removeLeadingSlash = (pattern) => {
if (pattern.charAt(0) === '/') {
return pattern.substring(1);
}
return pattern;
};
const removeRelativeRoot = (path, root) => {
if (path.startsWith(root)) {
return path.substring(root.length);
}
return path;
};
function removeRoots(path, roots) {
for (const root of roots) {
if (path.startsWith(root)) {
return path.substring(root.length);
}
}
return (0, node_path_1.basename)(path);
}
async function findMatchingTests(pattern, ignore, workspaceRoot, projectSourceRoot) {
// normalize pattern, glob lib only accepts forward slashes
let normalizedPattern = (0, path_1.toPosixPath)(pattern);
normalizedPattern = removeLeadingSlash(normalizedPattern);
const relativeProjectRoot = (0, path_1.toPosixPath)((0, node_path_1.relative)(workspaceRoot, projectSourceRoot) + '/');
// remove relativeProjectRoot to support relative paths from root
// such paths are easy to get when running scripts via IDEs
normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot);
// special logic when pattern does not look like a glob
if (!(0, tinyglobby_1.isDynamicPattern)(normalizedPattern)) {
if (await isDirectory((0, node_path_1.join)(projectSourceRoot, normalizedPattern))) {
normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
}
else {
// see if matching spec file exists
const fileExt = (0, node_path_1.extname)(normalizedPattern);
// Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts`
const potentialSpec = (0, node_path_1.join)(projectSourceRoot, (0, node_path_1.dirname)(normalizedPattern), `${(0, node_path_1.basename)(normalizedPattern, fileExt)}.spec${fileExt}`);
if (await exists(potentialSpec)) {
return [potentialSpec];
}
}
}
// normalize the patterns in the ignore list
const normalizedIgnorePatternList = ignore.map((pattern) => removeRelativeRoot(removeLeadingSlash((0, path_1.toPosixPath)(pattern)), relativeProjectRoot));
return (0, tinyglobby_1.glob)(normalizedPattern, {
cwd: projectSourceRoot,
absolute: true,
ignore: ['**/node_modules/**', ...normalizedIgnorePatternList],
});
}
async function isDirectory(path) {
try {
const stats = await node_fs_1.promises.stat(path);
return stats.isDirectory();
}
catch {
return false;
}
}
async function exists(path) {
try {
await node_fs_1.promises.access(path, node_fs_1.constants.F_OK);
return true;
}
catch {
return false;
}
}

20
node_modules/@angular/build/src/builders/karma/index.d.ts generated vendored Executable file
View File

@@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { type Builder, type BuilderContext, type BuilderOutput } from '@angular-devkit/architect';
import type { ConfigOptions } from 'karma';
import type { Schema as KarmaBuilderOptions } from './schema';
export interface KarmaBuilderTransformsOptions {
karmaOptions?: (options: ConfigOptions) => ConfigOptions | Promise<ConfigOptions>;
}
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: KarmaBuilderOptions, context: BuilderContext, transforms?: KarmaBuilderTransformsOptions): AsyncIterable<BuilderOutput>;
export type { KarmaBuilderOptions };
declare const builder: Builder<KarmaBuilderOptions>;
export default builder;

53
node_modules/@angular/build/src/builders/karma/index.js generated vendored Executable file
View File

@@ -0,0 +1,53 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = execute;
const architect_1 = require("@angular-devkit/architect");
/**
* @experimental Direct usage of this function is considered experimental.
*/
async function* execute(options, context, transforms) {
const { execute } = await Promise.resolve().then(() => __importStar(require('./application_builder')));
yield* execute(options, context, transforms);
}
const builder = (0, architect_1.createBuilder)(execute);
exports.default = builder;

42
node_modules/@angular/build/src/builders/karma/options.d.ts generated vendored Executable file
View File

@@ -0,0 +1,42 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext } from '@angular-devkit/architect';
import { Schema as KarmaBuilderOptions } from './schema';
export type NormalizedKarmaBuilderOptions = ReturnType<typeof normalizeOptions>;
export declare function normalizeOptions(context: BuilderContext, options: KarmaBuilderOptions): {
sourceMap: import("./schema").SourceMapUnion | undefined;
karmaConfig: string | undefined;
reporters: string[] | undefined;
browsers: string[] | undefined;
watch: boolean;
include: string[];
exclude: string[];
aot?: boolean;
assets?: import("./schema").AssetPattern[];
codeCoverage?: boolean;
codeCoverageExclude?: string[];
define?: {
[key: string]: string;
};
externalDependencies?: string[];
fileReplacements?: import("./schema").FileReplacement[];
inlineStyleLanguage?: import("./schema").InlineStyleLanguage;
loader?: {
[key: string]: any;
};
main?: string;
poll?: number;
polyfills?: string[];
preserveSymlinks?: boolean;
progress?: boolean;
scripts?: import("./schema").ScriptElement[];
stylePreprocessorOptions?: import("./schema").StylePreprocessorOptions;
styles?: import("./schema").StyleElement[];
tsConfig: string;
webWorkerTsConfig?: string;
};

43
node_modules/@angular/build/src/builders/karma/options.js generated vendored Executable file
View File

@@ -0,0 +1,43 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = normalizeOptions;
const node_path_1 = require("node:path");
function normalizeOptions(context, options) {
const { sourceMap, karmaConfig, browsers, watch = true, include = [], exclude = [], reporters = [], ...rest } = options;
let normalizedBrowsers;
if (typeof options.browsers === 'string' && options.browsers) {
normalizedBrowsers = options.browsers.split(',').map((browser) => browser.trim());
}
else if (options.browsers === false) {
normalizedBrowsers = [];
}
// Split along commas to make it more natural, and remove empty strings.
const normalizedReporters = reporters
.reduce((acc, curr) => acc.concat(curr.split(',')), [])
.filter((x) => !!x);
// Sourcemaps are always needed when code coverage is enabled.
const normalizedSourceMap = options.codeCoverage
? {
scripts: true,
styles: true,
vendor: true,
}
: sourceMap;
return {
...rest,
sourceMap: normalizedSourceMap,
karmaConfig: karmaConfig ? (0, node_path_1.resolve)(context.workspaceRoot, karmaConfig) : undefined,
reporters: normalizedReporters.length ? normalizedReporters : undefined,
browsers: normalizedBrowsers,
watch,
include,
exclude,
};
}

View File

@@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
// eslint-disable-next-line no-undef
globalThis.sourceMapSupport?.install();

View File

@@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
// See: https://github.com/jasmine/jasmine/issues/2015
(function () {
'use strict';
// jasmine will ignore `window` unless it returns this specific (but uncommon)
// value from toString().
window.toString = function () {
return '[object GjsGlobal]';
};
})();

View File

@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
// See: https://github.com/jasmine/jasmine/issues/2015
(function () {
'use strict';
delete window.toString;
})();

250
node_modules/@angular/build/src/builders/karma/schema.d.ts generated vendored Executable file
View File

@@ -0,0 +1,250 @@
/**
* Karma target options for Build Facade.
*/
export type Schema = {
/**
* Run tests using Ahead of Time compilation.
*/
aot?: boolean;
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Override which browsers tests are run against. Set to `false` to not use any browser.
*/
browsers?: Browsers;
/**
* Output a code coverage report.
*/
codeCoverage?: boolean;
/**
* Globs to exclude from code coverage.
*/
codeCoverageExclude?: string[];
/**
* Defines global identifiers that will be replaced with a specified constant value when
* found in any JavaScript or TypeScript code including libraries. The value will be used
* directly. String values must be put in quotes. Identifiers within Angular metadata such
* as Component Decorators will not be replaced.
*/
define?: {
[key: string]: string;
};
/**
* Globs of files to exclude, relative to the project root.
*/
exclude?: string[];
/**
* Exclude the listed external dependencies from being bundled into the bundle. Instead, the
* created bundle relies on these dependencies to be available during runtime.
*/
externalDependencies?: string[];
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* Globs of files to include, relative to project root.
* There are 2 special cases:
* - when a path to directory is provided, all spec files ending ".spec.@(ts|tsx)" will be
* included
* - when a path to a file is provided, and a matching spec file exists it will be included
* instead.
*/
include?: string[];
/**
* The stylesheet language to use for the application's inline component styles.
*/
inlineStyleLanguage?: InlineStyleLanguage;
/**
* The name of the Karma configuration file.
*/
karmaConfig?: string;
/**
* Defines the type of loader to use with a specified file extension when used with a
* JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content
* as a Uint8Array; `file` emits the file and provides the runtime location of the file;
* `dataurl` inlines the content as a data URL with best guess of MIME type; `base64`
* inlines the content as a Base64-encoded string; `empty` considers the content to be empty
* and not include it in bundles.
*/
loader?: {
[key: string]: any;
};
/**
* The name of the main entry-point file.
*/
main?: string;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* A list of polyfills to include in the build. Can be a full path for a file, relative to
* the current workspace or module specifier. Example: 'zone.js'.
*/
polyfills?: string[];
/**
* Do not use the real path when resolving modules. If unset then will default to `true` if
* NodeJS option --preserve-symlinks is set.
*/
preserveSymlinks?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* Karma reporters to use. Directly passed to the karma runner.
*/
reporters?: string[];
/**
* Global scripts to be included in the build.
*/
scripts?: ScriptElement[];
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.dev/reference/configs/workspace-config#source-map-configuration.
*/
sourceMap?: SourceMapUnion;
/**
* Options to pass to style preprocessors.
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: StyleElement[];
/**
* The name of the TypeScript configuration file.
*/
tsConfig: string;
/**
* Re-run tests when source files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
};
export type AssetPattern = AssetPatternClass | string;
export type AssetPatternClass = {
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output?: string;
};
/**
* Override which browsers tests are run against. Set to `false` to not use any browser.
*/
export type Browsers = boolean | string;
export type FileReplacement = {
replace: string;
with: string;
};
/**
* The stylesheet language to use for the application's inline component styles.
*/
export declare enum InlineStyleLanguage {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
export type ScriptElement = ScriptClass | string;
export type ScriptClass = {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
};
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.dev/reference/configs/workspace-config#source-map-configuration.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export type SourceMapClass = {
/**
* Output source maps for all scripts.
*/
scripts?: boolean;
/**
* Output source maps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages source maps.
*/
vendor?: boolean;
};
/**
* Options to pass to style preprocessors.
*/
export type StylePreprocessorOptions = {
/**
* Paths to include. Paths will be resolved to workspace root.
*/
includePaths?: string[];
/**
* Options to pass to the sass preprocessor.
*/
sass?: Sass;
};
/**
* Options to pass to the sass preprocessor.
*/
export type Sass = {
/**
* A set of deprecations to treat as fatal. If a deprecation warning of any provided type is
* encountered during compilation, the compiler will error instead. If a Version is
* provided, then all deprecations that were active in that compiler version will be treated
* as fatal.
*/
fatalDeprecations?: string[];
/**
* A set of future deprecations to opt into early. Future deprecations passed here will be
* treated as active by the compiler, emitting warnings as necessary.
*/
futureDeprecations?: string[];
/**
* A set of active deprecations to ignore. If a deprecation warning of any provided type is
* encountered during compilation, the compiler will ignore it instead.
*/
silenceDeprecations?: string[];
};
export type StyleElement = StyleClass | string;
export type StyleClass = {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
};

15
node_modules/@angular/build/src/builders/karma/schema.js generated vendored Executable file
View File

@@ -0,0 +1,15 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.InlineStyleLanguage = void 0;
/**
* The stylesheet language to use for the application's inline component styles.
*/
var InlineStyleLanguage;
(function (InlineStyleLanguage) {
InlineStyleLanguage["Css"] = "css";
InlineStyleLanguage["Less"] = "less";
InlineStyleLanguage["Sass"] = "sass";
InlineStyleLanguage["Scss"] = "scss";
})(InlineStyleLanguage || (exports.InlineStyleLanguage = InlineStyleLanguage = {}));

348
node_modules/@angular/build/src/builders/karma/schema.json generated vendored Executable file
View File

@@ -0,0 +1,348 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Karma Target",
"description": "Karma target options for Build Facade.",
"type": "object",
"properties": {
"main": {
"type": "string",
"description": "The name of the main entry-point file."
},
"tsConfig": {
"type": "string",
"description": "The name of the TypeScript configuration file."
},
"karmaConfig": {
"type": "string",
"description": "The name of the Karma configuration file."
},
"polyfills": {
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"type": "array",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
},
"assets": {
"type": "array",
"description": "List of static application assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
}
]
}
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": ["css", "less", "sass", "scss"]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"sass": {
"description": "Options to pass to the sass preprocessor.",
"type": "object",
"properties": {
"fatalDeprecations": {
"description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.",
"type": "array",
"items": {
"type": "string"
}
},
"silenceDeprecations": {
"description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.",
"type": "array",
"items": {
"type": "string"
}
},
"futureDeprecations": {
"description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.",
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"externalDependencies": {
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"loader": {
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `dataurl` inlines the content as a data URL with best guess of MIME type; `base64` inlines the content as a Base64-encoded string; `empty` considers the content to be empty and not include it in bundles.",
"type": "object",
"patternProperties": {
"^\\.\\S+$": { "enum": ["text", "binary", "file", "dataurl", "base64", "empty"] }
}
},
"define": {
"description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"default": ["**/*.spec.ts"],
"description": "Globs of files to include, relative to project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead."
},
"exclude": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Globs of files to exclude, relative to the project root."
},
"sourceMap": {
"description": "Output source maps for scripts and styles. For more information, see https://angular.dev/reference/configs/workspace-config#source-map-configuration.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Output source maps for all scripts.",
"default": true
},
"styles": {
"type": "boolean",
"description": "Output source maps for all styles.",
"default": true
},
"vendor": {
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"progress": {
"type": "boolean",
"description": "Log progress to the console while building.",
"default": true
},
"watch": {
"type": "boolean",
"description": "Re-run tests when source files change.",
"default": true
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"preserveSymlinks": {
"type": "boolean",
"description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set."
},
"browsers": {
"description": "Override which browsers tests are run against. Set to `false` to not use any browser.",
"oneOf": [
{
"type": "string",
"description": "A comma seperate list of browsers to run tests against."
},
{
"const": false,
"type": "boolean",
"description": "Does use run tests against a browser."
}
]
},
"codeCoverage": {
"type": "boolean",
"description": "Output a code coverage report.",
"default": false
},
"codeCoverageExclude": {
"type": "array",
"description": "Globs to exclude from code coverage.",
"items": {
"type": "string"
},
"default": []
},
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
"items": {
"$ref": "#/definitions/fileReplacement"
},
"default": []
},
"reporters": {
"type": "array",
"description": "Karma reporters to use. Directly passed to the karma runner.",
"items": {
"type": "string"
}
},
"webWorkerTsConfig": {
"type": "string",
"description": "TypeScript configuration for Web Worker modules."
},
"aot": {
"type": "boolean",
"description": "Run tests using Ahead of Time compilation.",
"default": false
}
},
"additionalProperties": false,
"required": ["tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"output": {
"type": "string",
"default": "",
"description": "Absolute path within the output."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": ["glob", "input"]
},
{
"type": "string"
}
]
},
"fileReplacement": {
"type": "object",
"properties": {
"replace": {
"type": "string",
"pattern": "\\.(([cm]?[jt])sx?|json)$"
},
"with": {
"type": "string",
"pattern": "\\.(([cm]?[jt])sx?|json)$"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
}
}
}

View File

@@ -0,0 +1,19 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { Schema as NgPackagrBuilderOptions } from './schema';
/**
* A Builder that executes the `ng-packagr` tool to build an Angular library.
*
* @param options The builder options as defined by the JSON schema.
* @param context A BuilderContext instance.
* @returns A BuilderOutput object.
*
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: NgPackagrBuilderOptions, context: BuilderContext): AsyncIterableIterator<BuilderOutput>;

View File

@@ -0,0 +1,106 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = execute;
const node_path_1 = require("node:path");
const error_1 = require("../../utils/error");
const normalize_cache_1 = require("../../utils/normalize-cache");
const purge_cache_1 = require("../../utils/purge-cache");
/**
* A Builder that executes the `ng-packagr` tool to build an Angular library.
*
* @param options The builder options as defined by the JSON schema.
* @param context A BuilderContext instance.
* @returns A BuilderOutput object.
*
* @experimental Direct usage of this function is considered experimental.
*/
async function* execute(options, context) {
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const workspaceRoot = context.workspaceRoot;
let packager;
try {
packager = (await Promise.resolve().then(() => __importStar(require('ng-packagr')))).ngPackagr();
}
catch (error) {
(0, error_1.assertIsError)(error);
if (error.code === 'MODULE_NOT_FOUND') {
return {
success: false,
error: 'The "ng-packagr" package was not found. To correct this error, ensure this package is installed in the project.',
};
}
throw error;
}
const projectName = context.target?.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const metadata = await context.getProjectMetadata(projectName);
const ngPackagrConfig = options.project
? (0, node_path_1.join)(workspaceRoot, options.project)
: (0, node_path_1.join)(workspaceRoot, metadata.root ?? '', 'ng-package.json');
packager.forProject(ngPackagrConfig);
if (options.tsConfig) {
packager.withTsConfig((0, node_path_1.resolve)(workspaceRoot, options.tsConfig));
}
const { enabled: cacheEnabled, path: cacheDirectory } = (0, normalize_cache_1.normalizeCacheOptions)(metadata, context.workspaceRoot);
const ngPackagrOptions = {
cacheEnabled,
poll: options.poll,
cacheDirectory: (0, node_path_1.join)(cacheDirectory, 'ng-packagr'),
};
try {
if (options.watch) {
await packager.watch(ngPackagrOptions).toPromise();
}
else {
await packager.build(ngPackagrOptions);
}
yield { success: true };
}
catch (error) {
(0, error_1.assertIsError)(error);
yield { success: false, error: error.message };
}
}

View File

@@ -0,0 +1,13 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { Builder } from '@angular-devkit/architect';
import { execute } from './builder';
import type { Schema as NgPackagrBuilderOptions } from './schema';
export { type NgPackagrBuilderOptions, execute };
declare const builder: Builder<NgPackagrBuilderOptions>;
export default builder;

15
node_modules/@angular/build/src/builders/ng-packagr/index.js generated vendored Executable file
View File

@@ -0,0 +1,15 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const builder_1 = require("./builder");
Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return builder_1.execute; } });
const builder = (0, architect_1.createBuilder)(builder_1.execute);
exports.default = builder;

View File

@@ -0,0 +1,21 @@
/**
* ng-packagr target options for Build Architect. Use to build library projects.
*/
export type Schema = {
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* The file path for the ng-packagr configuration file, relative to the current workspace.
*/
project?: string;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig?: string;
/**
* Run build when files change.
*/
watch?: boolean;
};

View File

@@ -0,0 +1,4 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "ng-packagr Target",
"description": "ng-packagr target options for Build Architect. Use to build library projects.",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The file path for the ng-packagr configuration file, relative to the current workspace."
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,15 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { ApplicationBuilderExtensions } from '../application/options';
import type { Schema as UnitTestBuilderOptions } from './schema';
export type { UnitTestBuilderOptions };
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: UnitTestBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable<BuilderOutput>;

335
node_modules/@angular/build/src/builders/unit-test/builder.js generated vendored Executable file
View File

@@ -0,0 +1,335 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = execute;
const node_assert_1 = __importDefault(require("node:assert"));
const node_crypto_1 = require("node:crypto");
const node_module_1 = require("node:module");
const node_path_1 = __importDefault(require("node:path"));
const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin");
const error_1 = require("../../utils/error");
const load_esm_1 = require("../../utils/load-esm");
const path_1 = require("../../utils/path");
const application_1 = require("../application");
const results_1 = require("../application/results");
const schema_1 = require("../application/schema");
const application_builder_1 = require("../karma/application_builder");
const find_tests_1 = require("../karma/find-tests");
const karma_bridge_1 = require("./karma-bridge");
const options_1 = require("./options");
function adjustOutputHashing(hashing) {
switch (hashing) {
case schema_1.OutputHashing.All:
case schema_1.OutputHashing.Media:
// Ensure media is continued to be hashed to avoid overwriting of output media files
return schema_1.OutputHashing.Media;
default:
return schema_1.OutputHashing.None;
}
}
/**
* @experimental Direct usage of this function is considered experimental.
*/
// eslint-disable-next-line max-lines-per-function
async function* execute(options, context, extensions = {}) {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
context.logger.error(`The "${context.builder.builderName}" builder requires a target to be specified.`);
return;
}
context.logger.warn(`NOTE: The "${context.builder.builderName}" builder is currently EXPERIMENTAL and not ready for production use.`);
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options);
const { projectSourceRoot, workspaceRoot, runnerName } = normalizedOptions;
// Translate options and use karma builder directly if specified
if (runnerName === 'karma') {
const karmaBridge = await (0, karma_bridge_1.useKarmaBuilder)(context, normalizedOptions);
yield* karmaBridge;
return;
}
if (runnerName !== 'vitest') {
context.logger.error('Unknown test runner: ' + runnerName);
return;
}
// Find test files
const testFiles = await (0, find_tests_1.findTests)(normalizedOptions.include, normalizedOptions.exclude, workspaceRoot, projectSourceRoot);
if (testFiles.length === 0) {
context.logger.error('No tests found.');
return { success: false };
}
const entryPoints = (0, find_tests_1.getTestEntrypoints)(testFiles, { projectSourceRoot, workspaceRoot });
entryPoints.set('init-testbed', 'angular:test-bed-init');
let vitestNodeModule;
try {
vitestNodeModule = await (0, load_esm_1.loadEsmModule)('vitest/node');
}
catch (error) {
(0, error_1.assertIsError)(error);
if (error.code !== 'ERR_MODULE_NOT_FOUND') {
throw error;
}
context.logger.error('The `vitest` package was not found. Please install the package and rerun the test command.');
return;
}
const { startVitest } = vitestNodeModule;
// Setup test file build options based on application build target options
const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), await context.getBuilderNameForTarget(normalizedOptions.buildTarget)));
buildTargetOptions.polyfills = (0, options_1.injectTestingPolyfills)(buildTargetOptions.polyfills);
const outputPath = (0, path_1.toPosixPath)(node_path_1.default.join(context.workspaceRoot, generateOutputPath()));
const buildOptions = {
...buildTargetOptions,
watch: normalizedOptions.watch,
incrementalResults: normalizedOptions.watch,
outputPath,
index: false,
browser: undefined,
server: undefined,
outputMode: undefined,
localize: false,
budgets: [],
serviceWorker: false,
appShell: false,
ssr: false,
prerender: false,
sourceMap: { scripts: true, vendor: false, styles: false },
outputHashing: adjustOutputHashing(buildTargetOptions.outputHashing),
optimization: false,
tsConfig: normalizedOptions.tsConfig,
entryPoints,
externalDependencies: [
'vitest',
'@vitest/browser/context',
...(buildTargetOptions.externalDependencies ?? []),
],
};
extensions ??= {};
extensions.codePlugins ??= [];
const virtualTestBedInit = (0, virtual_module_plugin_1.createVirtualModulePlugin)({
namespace: 'angular:test-bed-init',
loadContent: async () => {
const contents = [
// Initialize the Angular testing environment
`import { NgModule } from '@angular/core';`,
`import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing';`,
`import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
'',
normalizedOptions.providersFile
? `import providers from './${(0, path_1.toPosixPath)(node_path_1.default
.relative(projectSourceRoot, normalizedOptions.providersFile)
.replace(/.[mc]?ts$/, ''))}'`
: 'const providers = [];',
'',
// Same as https://github.com/angular/angular/blob/05a03d3f975771bb59c7eefd37c01fa127ee2229/packages/core/testing/src/test_hooks.ts#L21-L29
`beforeEach(getCleanupHook(false));`,
`afterEach(getCleanupHook(true));`,
'',
`@NgModule({`,
` providers,`,
`})`,
`export class TestModule {}`,
'',
`getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {`,
` errorOnUnknownElements: true,`,
` errorOnUnknownProperties: true,`,
'});',
];
return {
contents: contents.join('\n'),
loader: 'js',
resolveDir: projectSourceRoot,
};
},
});
extensions.codePlugins.unshift(virtualTestBedInit);
let instance;
// Setup vitest browser options if configured
const { browser, errors } = setupBrowserConfiguration(normalizedOptions.browsers, normalizedOptions.debug, projectSourceRoot);
if (errors?.length) {
errors.forEach((error) => context.logger.error(error));
return { success: false };
}
// Add setup file entries for TestBed initialization and project polyfills
const setupFiles = ['init-testbed.js', ...normalizedOptions.setupFiles];
if (buildTargetOptions?.polyfills?.length) {
// Placed first as polyfills may be required by the Testbed initialization
// or other project provided setup files (e.g., zone.js, ECMAScript polyfills).
setupFiles.unshift('polyfills.js');
}
const debugOptions = normalizedOptions.debug
? {
inspectBrk: true,
isolate: false,
fileParallelism: false,
}
: {};
try {
for await (const result of (0, application_1.buildApplicationInternal)(buildOptions, context, extensions)) {
if (result.kind === results_1.ResultKind.Failure) {
continue;
}
else if (result.kind !== results_1.ResultKind.Full && result.kind !== results_1.ResultKind.Incremental) {
node_assert_1.default.fail('A full and/or incremental build result is required from the application builder.');
}
(0, node_assert_1.default)(result.files, 'Builder did not provide result files.');
await (0, application_builder_1.writeTestFiles)(result.files, outputPath);
instance ??= await startVitest('test', undefined /* cliFilters */, {
// Disable configuration file resolution/loading
config: false,
root: workspaceRoot,
project: ['base', projectName],
name: 'base',
include: [],
reporters: normalizedOptions.reporters ?? ['default'],
watch: normalizedOptions.watch,
coverage: generateCoverageOption(normalizedOptions.codeCoverage, workspaceRoot, outputPath),
...debugOptions,
}, {
plugins: [
{
name: 'angular:project-init',
async configureVitest(context) {
// Create a subproject that can be configured with plugins for browser mode.
// Plugins defined directly in the vite overrides will not be present in the
// browser specific Vite instance.
const [project] = await context.injectTestProjects({
test: {
name: projectName,
root: outputPath,
globals: true,
setupFiles,
// Use `jsdom` if no browsers are explicitly configured.
// `node` is effectively no "environment" and the default.
environment: browser ? 'node' : 'jsdom',
browser,
},
plugins: [
{
name: 'angular:html-index',
transformIndexHtml() {
// Add all global stylesheets
return (Object.entries(result.files)
// TODO: Expand this to all configured global stylesheets
.filter(([file]) => file === 'styles.css')
.map(([styleUrl]) => ({
tag: 'link',
attrs: {
'href': styleUrl,
'rel': 'stylesheet',
},
injectTo: 'head',
})));
},
},
],
});
// Adjust coverage excludes to not include the otherwise automatically inserted included unit tests.
// Vite does this as a convenience but is problematic for the bundling strategy employed by the
// builder's test setup. To workaround this, the excludes are adjusted here to only automaticallyAdd commentMore actions
// exclude the TypeScript source test files.
project.config.coverage.exclude = [
...(normalizedOptions.codeCoverage?.exclude ?? []),
'**/*.{test,spec}.?(c|m)ts',
];
},
},
],
});
// Check if all the tests pass to calculate the result
const testModules = instance.state.getTestModules();
yield { success: testModules.every((testModule) => testModule.ok()) };
}
}
finally {
if (normalizedOptions.watch) {
// Vitest will automatically close if not using watch mode
await instance?.close();
}
}
}
function findBrowserProvider(projectResolver) {
// One of these must be installed in the project to use browser testing
const vitestBuiltinProviders = ['playwright', 'webdriverio'];
for (const providerName of vitestBuiltinProviders) {
try {
projectResolver(providerName);
return providerName;
}
catch { }
}
}
function normalizeBrowserName(browserName) {
// Normalize browser names to match Vitest's expectations for headless but also supports karma's names
// e.g., 'ChromeHeadless' -> 'chrome', 'FirefoxHeadless'
// and 'Chrome' -> 'chrome', 'Firefox' -> 'firefox'.
const normalized = browserName.toLowerCase();
return normalized.replace(/headless$/, '');
}
function setupBrowserConfiguration(browsers, debug, projectSourceRoot) {
if (browsers === undefined) {
return {};
}
const projectResolver = (0, node_module_1.createRequire)(projectSourceRoot + '/').resolve;
let errors;
try {
projectResolver('@vitest/browser');
}
catch {
errors ??= [];
errors.push('The "browsers" option requires the "@vitest/browser" package to be installed within the project.' +
' Please install this package and rerun the test command.');
}
const provider = findBrowserProvider(projectResolver);
if (!provider) {
errors ??= [];
errors.push('The "browsers" option requires either "playwright" or "webdriverio" to be installed within the project.' +
' Please install one of these packages and rerun the test command.');
}
// Vitest current requires the playwright browser provider to use the inspect-brk option used by "debug"
if (debug && provider !== 'playwright') {
errors ??= [];
errors.push('Debugging browser mode tests currently requires the use of "playwright".' +
' Please install this package and rerun the test command.');
}
if (errors) {
return { errors };
}
const browser = {
enabled: true,
provider,
headless: browsers.some((name) => name.toLowerCase().includes('headless')),
instances: browsers.map((browserName) => ({
browser: normalizeBrowserName(browserName),
})),
};
return { browser };
}
function generateOutputPath() {
const datePrefix = new Date().toISOString().replaceAll(/[-:.]/g, '');
const uuidSuffix = (0, node_crypto_1.randomUUID)().slice(0, 8);
return node_path_1.default.join('dist', 'test-out', `${datePrefix}-${uuidSuffix}`);
}
function generateCoverageOption(codeCoverage, workspaceRoot, outputPath) {
if (!codeCoverage) {
return {
enabled: false,
};
}
return {
enabled: true,
excludeAfterRemap: true,
include: [`${(0, path_1.toPosixPath)(node_path_1.default.relative(workspaceRoot, outputPath))}/**`],
// Special handling for `reporter` due to an undefined value causing upstream failures
...(codeCoverage.reporters
? { reporter: codeCoverage.reporters }
: {}),
};
}

View File

@@ -0,0 +1,12 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { type Builder } from '@angular-devkit/architect';
import { type UnitTestBuilderOptions, execute } from './builder';
export { type UnitTestBuilderOptions, execute };
declare const builder: Builder<UnitTestBuilderOptions>;
export default builder;

15
node_modules/@angular/build/src/builders/unit-test/index.js generated vendored Executable file
View File

@@ -0,0 +1,15 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const builder_1 = require("./builder");
Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return builder_1.execute; } });
const builder = (0, architect_1.createBuilder)(builder_1.execute);
exports.default = builder;

View File

@@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import { type NormalizedUnitTestBuilderOptions } from './options';
export declare function useKarmaBuilder(context: BuilderContext, unitTestOptions: NormalizedUnitTestBuilderOptions): Promise<AsyncIterable<BuilderOutput>>;

View File

@@ -0,0 +1,82 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.useKarmaBuilder = useKarmaBuilder;
const options_1 = require("./options");
async function useKarmaBuilder(context, unitTestOptions) {
if (unitTestOptions.debug) {
context.logger.warn('The "karma" test runner does not support the "debug" option. The option will be ignored.');
}
if (unitTestOptions.setupFiles.length) {
context.logger.warn('The "karma" test runner does not support the "setupFiles" option. The option will be ignored.');
}
const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(unitTestOptions.buildTarget), await context.getBuilderNameForTarget(unitTestOptions.buildTarget)));
buildTargetOptions.polyfills = (0, options_1.injectTestingPolyfills)(buildTargetOptions.polyfills);
const options = {
tsConfig: unitTestOptions.tsConfig,
polyfills: buildTargetOptions.polyfills,
assets: buildTargetOptions.assets,
scripts: buildTargetOptions.scripts,
styles: buildTargetOptions.styles,
inlineStyleLanguage: buildTargetOptions.inlineStyleLanguage,
stylePreprocessorOptions: buildTargetOptions.stylePreprocessorOptions,
externalDependencies: buildTargetOptions.externalDependencies,
loader: buildTargetOptions.loader,
define: buildTargetOptions.define,
include: unitTestOptions.include,
exclude: unitTestOptions.exclude,
sourceMap: buildTargetOptions.sourceMap,
progress: buildTargetOptions.progress,
watch: unitTestOptions.watch,
poll: buildTargetOptions.poll,
preserveSymlinks: buildTargetOptions.preserveSymlinks,
browsers: unitTestOptions.browsers?.join(','),
codeCoverage: !!unitTestOptions.codeCoverage,
codeCoverageExclude: unitTestOptions.codeCoverage?.exclude,
fileReplacements: buildTargetOptions.fileReplacements,
reporters: unitTestOptions.reporters,
webWorkerTsConfig: buildTargetOptions.webWorkerTsConfig,
aot: buildTargetOptions.aot,
};
const { execute } = await Promise.resolve().then(() => __importStar(require('../karma')));
return execute(options, context);
}

View File

@@ -0,0 +1,32 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { type BuilderContext } from '@angular-devkit/architect';
import type { Schema as UnitTestBuilderOptions } from './schema';
export type NormalizedUnitTestBuilderOptions = Awaited<ReturnType<typeof normalizeOptions>>;
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: UnitTestBuilderOptions): Promise<{
workspaceRoot: string;
projectRoot: string;
projectSourceRoot: string;
cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
buildTarget: import("@angular-devkit/architect").Target;
include: string[];
exclude: string[];
runnerName: import("./schema").Runner;
codeCoverage: {
exclude: string[] | undefined;
reporters: [string, Record<string, unknown>][] | undefined;
} | undefined;
tsConfig: string;
reporters: string[] | undefined;
browsers: string[] | undefined;
watch: boolean;
debug: boolean;
providersFile: string | undefined;
setupFiles: string[];
}>;
export declare function injectTestingPolyfills(polyfills?: string[]): string[];

View File

@@ -0,0 +1,64 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = normalizeOptions;
exports.injectTestingPolyfills = injectTestingPolyfills;
const architect_1 = require("@angular-devkit/architect");
const node_path_1 = __importDefault(require("node:path"));
const normalize_cache_1 = require("../../utils/normalize-cache");
const project_metadata_1 = require("../../utils/project-metadata");
const tty_1 = require("../../utils/tty");
async function normalizeOptions(context, projectName, options) {
// Setup base paths based on workspace root and project information
const workspaceRoot = context.workspaceRoot;
const projectMetadata = await context.getProjectMetadata(projectName);
const { projectRoot, projectSourceRoot } = (0, project_metadata_1.getProjectRootPaths)(workspaceRoot, projectMetadata);
// Gather persistent caching option and provide a project specific cache location
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
cacheOptions.path = node_path_1.default.join(cacheOptions.path, projectName);
// Target specifier defaults to the current project's build target using a development configuration
const buildTargetSpecifier = options.buildTarget ?? `::development`;
const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
const { tsConfig, runner, reporters, browsers } = options;
return {
// Project/workspace information
workspaceRoot,
projectRoot,
projectSourceRoot,
cacheOptions,
// Target/configuration specified options
buildTarget,
include: options.include ?? ['**/*.spec.ts'],
exclude: options.exclude ?? [],
runnerName: runner,
codeCoverage: options.codeCoverage
? {
exclude: options.codeCoverageExclude,
reporters: options.codeCoverageReporters?.map((entry) => typeof entry === 'string'
? [entry, {}]
: entry),
}
: undefined,
tsConfig,
reporters,
browsers,
watch: options.watch ?? (0, tty_1.isTTY)(),
debug: options.debug ?? false,
providersFile: options.providersFile && node_path_1.default.join(workspaceRoot, options.providersFile),
setupFiles: options.setupFiles
? options.setupFiles.map((setupFile) => node_path_1.default.join(workspaceRoot, setupFile))
: [],
};
}
function injectTestingPolyfills(polyfills = []) {
return polyfills.includes('zone.js') ? [...polyfills, 'zone.js/testing'] : polyfills;
}

View File

@@ -0,0 +1,95 @@
/**
* Unit testing options for Angular applications.
*/
export type Schema = {
/**
* A list of browsers to use for test execution. If undefined, jsdom on Node.js will be used
* instead of a browser. For Vitest and Karma, browser names ending with 'Headless' (e.g.,
* 'ChromeHeadless') will enable headless mode for that browser.
*/
browsers?: string[];
/**
* A build builder target to serve in the format of `project:target[:configuration]`. You
* can also pass in more than one configuration name as a comma-separated list. Example:
* `project:target:production,staging`.
*/
buildTarget: string;
/**
* Output a code coverage report.
*/
codeCoverage?: boolean;
/**
* Globs to exclude from code coverage.
*/
codeCoverageExclude?: string[];
/**
* Reporters to use for code coverage results.
*/
codeCoverageReporters?: SchemaCodeCoverageReporter[];
/**
* Initialize the test runner to support using the Node Inspector for test debugging.
*/
debug?: boolean;
/**
* Globs of files to exclude, relative to the project root.
*/
exclude?: string[];
/**
* Globs of files to include, relative to project root.
* There are 2 special cases:
* - when a path to directory is provided, all spec files ending ".spec.@(ts|tsx)" will be
* included
* - when a path to a file is provided, and a matching spec file exists it will be included
* instead.
*/
include?: string[];
/**
* TypeScript file that exports an array of Angular providers to use during test execution.
* The array must be a default export.
*/
providersFile?: string;
/**
* Test runner reporters to use. Directly passed to the test runner.
*/
reporters?: string[];
/**
* The name of the test runner to use for test execution.
*/
runner: Runner;
/**
* A list of global setup and configuration files that are included before the test files.
* The application's polyfills are always included before these files. The Angular Testbed
* is also initialized prior to the execution of these files.
*/
setupFiles?: string[];
/**
* The name of the TypeScript configuration file.
*/
tsConfig: string;
/**
* Re-run tests when source files change. Defaults to `true` in TTY environments and `false`
* otherwise.
*/
watch?: boolean;
};
export type SchemaCodeCoverageReporter = CodeCoverageReporterCodeCoverageReporter[] | CoverageReporters;
export type CodeCoverageReporterCodeCoverageReporter = CoverageReporters | {
[key: string]: any;
};
export declare enum CoverageReporters {
Cobertura = "cobertura",
Html = "html",
Json = "json",
JsonSummary = "json-summary",
Lcov = "lcov",
Lcovonly = "lcovonly",
Text = "text",
TextSummary = "text-summary"
}
/**
* The name of the test runner to use for test execution.
*/
export declare enum Runner {
Karma = "karma",
Vitest = "vitest"
}

24
node_modules/@angular/build/src/builders/unit-test/schema.js generated vendored Executable file
View File

@@ -0,0 +1,24 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runner = exports.CoverageReporters = void 0;
var CoverageReporters;
(function (CoverageReporters) {
CoverageReporters["Cobertura"] = "cobertura";
CoverageReporters["Html"] = "html";
CoverageReporters["Json"] = "json";
CoverageReporters["JsonSummary"] = "json-summary";
CoverageReporters["Lcov"] = "lcov";
CoverageReporters["Lcovonly"] = "lcovonly";
CoverageReporters["Text"] = "text";
CoverageReporters["TextSummary"] = "text-summary";
})(CoverageReporters || (exports.CoverageReporters = CoverageReporters = {}));
/**
* The name of the test runner to use for test execution.
*/
var Runner;
(function (Runner) {
Runner["Karma"] = "karma";
Runner["Vitest"] = "vitest";
})(Runner || (exports.Runner = Runner = {}));

View File

@@ -0,0 +1,127 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Unit testing",
"description": "Unit testing options for Angular applications.",
"type": "object",
"properties": {
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
},
"tsConfig": {
"type": "string",
"description": "The name of the TypeScript configuration file."
},
"runner": {
"type": "string",
"description": "The name of the test runner to use for test execution.",
"enum": ["karma", "vitest"]
},
"browsers": {
"description": "A list of browsers to use for test execution. If undefined, jsdom on Node.js will be used instead of a browser. For Vitest and Karma, browser names ending with 'Headless' (e.g., 'ChromeHeadless') will enable headless mode for that browser.",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"default": ["**/*.spec.ts"],
"description": "Globs of files to include, relative to project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead."
},
"exclude": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Globs of files to exclude, relative to the project root."
},
"watch": {
"type": "boolean",
"description": "Re-run tests when source files change. Defaults to `true` in TTY environments and `false` otherwise."
},
"debug": {
"type": "boolean",
"description": "Initialize the test runner to support using the Node Inspector for test debugging.",
"default": false
},
"codeCoverage": {
"type": "boolean",
"description": "Output a code coverage report.",
"default": false
},
"codeCoverageExclude": {
"type": "array",
"description": "Globs to exclude from code coverage.",
"items": {
"type": "string"
},
"default": []
},
"codeCoverageReporters": {
"type": "array",
"description": "Reporters to use for code coverage results.",
"items": {
"oneOf": [
{
"$ref": "#/definitions/coverage-reporters"
},
{
"type": "array",
"minItems": 1,
"maxItems": 2,
"items": [
{
"$ref": "#/definitions/coverage-reporters"
},
{
"type": "object"
}
]
}
]
}
},
"reporters": {
"type": "array",
"description": "Test runner reporters to use. Directly passed to the test runner.",
"items": {
"type": "string"
}
},
"providersFile": {
"type": "string",
"description": "TypeScript file that exports an array of Angular providers to use during test execution. The array must be a default export.",
"minLength": 1
},
"setupFiles": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of global setup and configuration files that are included before the test files. The application's polyfills are always included before these files. The Angular Testbed is also initialized prior to the execution of these files."
}
},
"additionalProperties": false,
"required": ["buildTarget", "tsConfig", "runner"],
"definitions": {
"coverage-reporters": {
"enum": [
"html",
"lcov",
"lcovonly",
"text",
"text-summary",
"cobertura",
"json",
"json-summary"
]
}
}
}

16
node_modules/@angular/build/src/index.d.ts generated vendored Executable file
View File

@@ -0,0 +1,16 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export { buildApplication, type ApplicationBuilderOptions } from './builders/application';
export type { ApplicationBuilderExtensions } from './builders/application/options';
export { type BuildOutputFile, BuildOutputFileType } from './tools/esbuild/bundler-context';
export type { BuildOutputAsset } from './tools/esbuild/bundler-execution-result';
export { executeDevServerBuilder, type DevServerBuilderOptions, type DevServerBuilderOutput, } from './builders/dev-server';
export { execute as executeExtractI18nBuilder, type ExtractI18nBuilderOptions, } from './builders/extract-i18n';
export { execute as executeNgPackagrBuilder, type NgPackagrBuilderOptions, } from './builders/ng-packagr';
export { execute as executeUnitTestBuilder, type UnitTestBuilderOptions, } from './builders/unit-test';
export { execute as executeKarmaBuilder, type KarmaBuilderOptions } from './builders/karma';

24
node_modules/@angular/build/src/index.js generated vendored Executable file
View File

@@ -0,0 +1,24 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeKarmaBuilder = exports.executeUnitTestBuilder = exports.executeNgPackagrBuilder = exports.executeExtractI18nBuilder = exports.executeDevServerBuilder = exports.BuildOutputFileType = exports.buildApplication = void 0;
var application_1 = require("./builders/application");
Object.defineProperty(exports, "buildApplication", { enumerable: true, get: function () { return application_1.buildApplication; } });
var bundler_context_1 = require("./tools/esbuild/bundler-context");
Object.defineProperty(exports, "BuildOutputFileType", { enumerable: true, get: function () { return bundler_context_1.BuildOutputFileType; } });
var dev_server_1 = require("./builders/dev-server");
Object.defineProperty(exports, "executeDevServerBuilder", { enumerable: true, get: function () { return dev_server_1.executeDevServerBuilder; } });
var extract_i18n_1 = require("./builders/extract-i18n");
Object.defineProperty(exports, "executeExtractI18nBuilder", { enumerable: true, get: function () { return extract_i18n_1.execute; } });
var ng_packagr_1 = require("./builders/ng-packagr");
Object.defineProperty(exports, "executeNgPackagrBuilder", { enumerable: true, get: function () { return ng_packagr_1.execute; } });
var unit_test_1 = require("./builders/unit-test");
Object.defineProperty(exports, "executeUnitTestBuilder", { enumerable: true, get: function () { return unit_test_1.execute; } });
var karma_1 = require("./builders/karma");
Object.defineProperty(exports, "executeKarmaBuilder", { enumerable: true, get: function () { return karma_1.execute; } });

53
node_modules/@angular/build/src/private.d.ts generated vendored Executable file
View File

@@ -0,0 +1,53 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/**
* @fileoverview
* Private exports intended only for use with the @angular-devkit/build-angular package.
* All exports are not supported for external use, do not provide SemVer guarantees, and
* their existence may change in any future version.
*/
import { createAngularCompilation } from './tools/angular/compilation';
import { CompilerPluginOptions } from './tools/esbuild/angular/compiler-plugin';
import { BundleStylesheetOptions } from './tools/esbuild/stylesheets/bundle-options';
export { buildApplicationInternal } from './builders/application';
export type { ApplicationBuilderInternalOptions } from './builders/application/options';
export { type Result, type ResultFile, ResultKind } from './builders/application/results';
export { serveWithVite } from './builders/dev-server/vite-server';
export * from './tools/babel/plugins';
export type { ExternalResultMetadata } from './tools/esbuild/bundler-execution-result';
export { emitFilesToDisk } from './tools/esbuild/utils';
export { transformSupportedBrowsersToTargets } from './tools/esbuild/utils';
export { SassWorkerImplementation } from './tools/sass/sass-service';
export { SourceFileCache } from './tools/esbuild/angular/source-file-cache';
export { createJitResourceTransformer } from './tools/angular/transformers/jit-resource-transformer';
export { JavaScriptTransformer } from './tools/esbuild/javascript-transformer';
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions & {
browserOnlyBuild?: boolean;
noopTypeScriptCompilation?: boolean;
}, styleOptions: BundleStylesheetOptions & {
inlineStyleLanguage: string;
}): import('esbuild').Plugin;
export type { AngularCompilation } from './tools/angular/compilation';
export { createAngularCompilation };
export { ComponentStylesheetBundler } from './tools/esbuild/angular/component-stylesheets';
export * from './utils/bundle-calculator';
export { checkPort } from './utils/check-port';
export { deleteOutputDir } from './utils/delete-output-dir';
export { type I18nOptions, createI18nOptions, loadTranslations } from './utils/i18n-options';
export { IndexHtmlGenerator, type IndexHtmlGeneratorOptions, type IndexHtmlGeneratorProcessOptions, type IndexHtmlTransform, } from './utils/index-file/index-html-generator';
export type { FileInfo } from './utils/index-file/augment-index-html';
export { type InlineCriticalCssProcessOptions, InlineCriticalCssProcessor, type InlineCriticalCssProcessorOptions, } from './utils/index-file/inline-critical-css';
export { loadProxyConfiguration } from './utils/load-proxy-config';
export { type TranslationLoader, createTranslationLoader } from './utils/load-translations';
export { purgeStaleBuildCache } from './utils/purge-cache';
export { augmentAppWithServiceWorker } from './utils/service-worker';
export { type BundleStats, generateBuildStatsTable } from './utils/stats-table';
export { getSupportedBrowsers } from './utils/supported-browsers';
export { assertCompatibleAngularVersion } from './utils/version';
export { findTests, getTestEntrypoints } from './builders/karma/find-tests';
export { findTailwindConfiguration, generateSearchDirectories, loadPostcssConfiguration, } from './utils/postcss-configuration';

97
node_modules/@angular/build/src/private.js generated vendored Executable file
View File

@@ -0,0 +1,97 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadPostcssConfiguration = exports.generateSearchDirectories = exports.findTailwindConfiguration = exports.getTestEntrypoints = exports.findTests = exports.assertCompatibleAngularVersion = exports.getSupportedBrowsers = exports.generateBuildStatsTable = exports.augmentAppWithServiceWorker = exports.purgeStaleBuildCache = exports.createTranslationLoader = exports.loadProxyConfiguration = exports.InlineCriticalCssProcessor = exports.IndexHtmlGenerator = exports.loadTranslations = exports.createI18nOptions = exports.deleteOutputDir = exports.checkPort = exports.ComponentStylesheetBundler = exports.createAngularCompilation = exports.JavaScriptTransformer = exports.createJitResourceTransformer = exports.SourceFileCache = exports.SassWorkerImplementation = exports.transformSupportedBrowsersToTargets = exports.emitFilesToDisk = exports.serveWithVite = exports.ResultKind = exports.buildApplicationInternal = void 0;
exports.createCompilerPlugin = createCompilerPlugin;
/**
* @fileoverview
* Private exports intended only for use with the @angular-devkit/build-angular package.
* All exports are not supported for external use, do not provide SemVer guarantees, and
* their existence may change in any future version.
*/
const compilation_1 = require("./tools/angular/compilation");
Object.defineProperty(exports, "createAngularCompilation", { enumerable: true, get: function () { return compilation_1.createAngularCompilation; } });
const compiler_plugin_1 = require("./tools/esbuild/angular/compiler-plugin");
const component_stylesheets_1 = require("./tools/esbuild/angular/component-stylesheets");
// Builders
var application_1 = require("./builders/application");
Object.defineProperty(exports, "buildApplicationInternal", { enumerable: true, get: function () { return application_1.buildApplicationInternal; } });
var results_1 = require("./builders/application/results");
Object.defineProperty(exports, "ResultKind", { enumerable: true, get: function () { return results_1.ResultKind; } });
var vite_server_1 = require("./builders/dev-server/vite-server");
Object.defineProperty(exports, "serveWithVite", { enumerable: true, get: function () { return vite_server_1.serveWithVite; } });
// Tools
__exportStar(require("./tools/babel/plugins"), exports);
var utils_1 = require("./tools/esbuild/utils");
Object.defineProperty(exports, "emitFilesToDisk", { enumerable: true, get: function () { return utils_1.emitFilesToDisk; } });
var utils_2 = require("./tools/esbuild/utils");
Object.defineProperty(exports, "transformSupportedBrowsersToTargets", { enumerable: true, get: function () { return utils_2.transformSupportedBrowsersToTargets; } });
var sass_service_1 = require("./tools/sass/sass-service");
Object.defineProperty(exports, "SassWorkerImplementation", { enumerable: true, get: function () { return sass_service_1.SassWorkerImplementation; } });
var source_file_cache_1 = require("./tools/esbuild/angular/source-file-cache");
Object.defineProperty(exports, "SourceFileCache", { enumerable: true, get: function () { return source_file_cache_1.SourceFileCache; } });
var jit_resource_transformer_1 = require("./tools/angular/transformers/jit-resource-transformer");
Object.defineProperty(exports, "createJitResourceTransformer", { enumerable: true, get: function () { return jit_resource_transformer_1.createJitResourceTransformer; } });
var javascript_transformer_1 = require("./tools/esbuild/javascript-transformer");
Object.defineProperty(exports, "JavaScriptTransformer", { enumerable: true, get: function () { return javascript_transformer_1.JavaScriptTransformer; } });
function createCompilerPlugin(pluginOptions, styleOptions) {
return (0, compiler_plugin_1.createCompilerPlugin)(pluginOptions, pluginOptions.noopTypeScriptCompilation
? new compilation_1.NoopCompilation()
: () => (0, compilation_1.createAngularCompilation)(!!pluginOptions.jit, !!pluginOptions.browserOnlyBuild), new component_stylesheets_1.ComponentStylesheetBundler(styleOptions, styleOptions.inlineStyleLanguage, pluginOptions.incremental));
}
var component_stylesheets_2 = require("./tools/esbuild/angular/component-stylesheets");
Object.defineProperty(exports, "ComponentStylesheetBundler", { enumerable: true, get: function () { return component_stylesheets_2.ComponentStylesheetBundler; } });
// Utilities
__exportStar(require("./utils/bundle-calculator"), exports);
var check_port_1 = require("./utils/check-port");
Object.defineProperty(exports, "checkPort", { enumerable: true, get: function () { return check_port_1.checkPort; } });
var delete_output_dir_1 = require("./utils/delete-output-dir");
Object.defineProperty(exports, "deleteOutputDir", { enumerable: true, get: function () { return delete_output_dir_1.deleteOutputDir; } });
var i18n_options_1 = require("./utils/i18n-options");
Object.defineProperty(exports, "createI18nOptions", { enumerable: true, get: function () { return i18n_options_1.createI18nOptions; } });
Object.defineProperty(exports, "loadTranslations", { enumerable: true, get: function () { return i18n_options_1.loadTranslations; } });
var index_html_generator_1 = require("./utils/index-file/index-html-generator");
Object.defineProperty(exports, "IndexHtmlGenerator", { enumerable: true, get: function () { return index_html_generator_1.IndexHtmlGenerator; } });
var inline_critical_css_1 = require("./utils/index-file/inline-critical-css");
Object.defineProperty(exports, "InlineCriticalCssProcessor", { enumerable: true, get: function () { return inline_critical_css_1.InlineCriticalCssProcessor; } });
var load_proxy_config_1 = require("./utils/load-proxy-config");
Object.defineProperty(exports, "loadProxyConfiguration", { enumerable: true, get: function () { return load_proxy_config_1.loadProxyConfiguration; } });
var load_translations_1 = require("./utils/load-translations");
Object.defineProperty(exports, "createTranslationLoader", { enumerable: true, get: function () { return load_translations_1.createTranslationLoader; } });
var purge_cache_1 = require("./utils/purge-cache");
Object.defineProperty(exports, "purgeStaleBuildCache", { enumerable: true, get: function () { return purge_cache_1.purgeStaleBuildCache; } });
var service_worker_1 = require("./utils/service-worker");
Object.defineProperty(exports, "augmentAppWithServiceWorker", { enumerable: true, get: function () { return service_worker_1.augmentAppWithServiceWorker; } });
var stats_table_1 = require("./utils/stats-table");
Object.defineProperty(exports, "generateBuildStatsTable", { enumerable: true, get: function () { return stats_table_1.generateBuildStatsTable; } });
var supported_browsers_1 = require("./utils/supported-browsers");
Object.defineProperty(exports, "getSupportedBrowsers", { enumerable: true, get: function () { return supported_browsers_1.getSupportedBrowsers; } });
var version_1 = require("./utils/version");
Object.defineProperty(exports, "assertCompatibleAngularVersion", { enumerable: true, get: function () { return version_1.assertCompatibleAngularVersion; } });
var find_tests_1 = require("./builders/karma/find-tests");
Object.defineProperty(exports, "findTests", { enumerable: true, get: function () { return find_tests_1.findTests; } });
Object.defineProperty(exports, "getTestEntrypoints", { enumerable: true, get: function () { return find_tests_1.getTestEntrypoints; } });
var postcss_configuration_1 = require("./utils/postcss-configuration");
Object.defineProperty(exports, "findTailwindConfiguration", { enumerable: true, get: function () { return postcss_configuration_1.findTailwindConfiguration; } });
Object.defineProperty(exports, "generateSearchDirectories", { enumerable: true, get: function () { return postcss_configuration_1.generateSearchDirectories; } });
Object.defineProperty(exports, "loadPostcssConfiguration", { enumerable: true, get: function () { return postcss_configuration_1.loadPostcssConfiguration; } });

View File

@@ -0,0 +1,27 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type ng from '@angular/compiler-cli';
import type ts from 'typescript';
export type AngularCompilerOptions = ng.CompilerOptions;
export type AngularCompilerHost = ng.CompilerHost;
export interface AngularHostOptions {
fileReplacements?: Record<string, string>;
sourceFileCache?: Map<string, ts.SourceFile>;
modifiedFiles?: Set<string>;
externalStylesheets?: Map<string, string>;
transformStylesheet(data: string, containingFile: string, stylesheetFile?: string, order?: number, className?: string): Promise<string | null>;
processWebWorker(workerFile: string, containingFile: string): string;
}
/**
* Patches in-place the `getSourceFiles` function on an instance of a TypeScript
* `Program` to ensure that all returned SourceFile instances have a `version`
* field. The `version` field is required when used with a TypeScript BuilderProgram.
* @param program The TypeScript Program instance to patch.
*/
export declare function ensureSourceFileVersions(program: ts.Program): void;
export declare function createAngularCompilerHost(typescript: typeof ts, compilerOptions: AngularCompilerOptions, hostOptions: AngularHostOptions, packageJsonCache: ts.PackageJsonInfoCache | undefined): AngularCompilerHost;

163
node_modules/@angular/build/src/tools/angular/angular-host.js generated vendored Executable file
View File

@@ -0,0 +1,163 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureSourceFileVersions = ensureSourceFileVersions;
exports.createAngularCompilerHost = createAngularCompilerHost;
const node_assert_1 = __importDefault(require("node:assert"));
const node_crypto_1 = require("node:crypto");
const node_path_1 = __importDefault(require("node:path"));
/**
* Patches in-place the `getSourceFiles` function on an instance of a TypeScript
* `Program` to ensure that all returned SourceFile instances have a `version`
* field. The `version` field is required when used with a TypeScript BuilderProgram.
* @param program The TypeScript Program instance to patch.
*/
function ensureSourceFileVersions(program) {
const baseGetSourceFiles = program.getSourceFiles;
// TODO: Update Angular compiler to add versions to all internal files and remove this
program.getSourceFiles = function (...parameters) {
const files = baseGetSourceFiles(...parameters);
for (const file of files) {
if (file.version === undefined) {
file.version = (0, node_crypto_1.createHash)('sha256').update(file.text).digest('hex');
}
}
return files;
};
}
function augmentHostWithCaching(host, cache) {
const baseGetSourceFile = host.getSourceFile;
host.getSourceFile = function (fileName, languageVersion, onError, shouldCreateNewSourceFile, ...parameters) {
if (!shouldCreateNewSourceFile && cache.has(fileName)) {
return cache.get(fileName);
}
const file = baseGetSourceFile.call(host, fileName, languageVersion, onError, true, ...parameters);
if (file) {
cache.set(fileName, file);
}
return file;
};
}
function augmentResolveModuleNames(typescript, host, resolvedModuleModifier, moduleResolutionCache) {
if (host.resolveModuleNames) {
const baseResolveModuleNames = host.resolveModuleNames;
host.resolveModuleNames = function (moduleNames, ...parameters) {
return moduleNames.map((name) => {
const result = baseResolveModuleNames.call(host, [name], ...parameters);
return resolvedModuleModifier(result[0], name);
});
};
}
else {
host.resolveModuleNames = function (moduleNames, containingFile, _reusedNames, redirectedReference, options) {
return moduleNames.map((name) => {
const result = typescript.resolveModuleName(name, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule;
return resolvedModuleModifier(result, name);
});
};
}
}
function normalizePath(path) {
return node_path_1.default.win32.normalize(path).replace(/\\/g, node_path_1.default.posix.sep);
}
function augmentHostWithReplacements(typescript, host, replacements, moduleResolutionCache) {
if (Object.keys(replacements).length === 0) {
return;
}
const normalizedReplacements = {};
for (const [key, value] of Object.entries(replacements)) {
normalizedReplacements[normalizePath(key)] = normalizePath(value);
}
const tryReplace = (resolvedModule) => {
const replacement = resolvedModule && normalizedReplacements[resolvedModule.resolvedFileName];
if (replacement) {
return {
resolvedFileName: replacement,
isExternalLibraryImport: /[/\\]node_modules[/\\]/.test(replacement),
};
}
else {
return resolvedModule;
}
};
augmentResolveModuleNames(typescript, host, tryReplace, moduleResolutionCache);
}
function createAngularCompilerHost(typescript, compilerOptions, hostOptions, packageJsonCache) {
// Create TypeScript compiler host
const host = typescript.createIncrementalCompilerHost(compilerOptions);
// Set the parsing mode to the same as TS 5.3+ default for tsc. This provides a parse
// performance improvement by skipping non-type related JSDoc parsing.
host.jsDocParsingMode = typescript.JSDocParsingMode.ParseForTypeErrors;
// The AOT compiler currently requires this hook to allow for a transformResource hook.
// Once the AOT compiler allows only a transformResource hook, this can be reevaluated.
host.readResource = async function (filename) {
return this.readFile(filename) ?? '';
};
// Add an AOT compiler resource transform hook
host.transformResource = async function (data, context) {
// Only style resources are transformed currently
if (context.type !== 'style') {
return null;
}
(0, node_assert_1.default)(!context.resourceFile || !hostOptions.externalStylesheets?.has(context.resourceFile), 'External runtime stylesheets should not be transformed: ' + context.resourceFile);
// No transformation required if the resource is empty
if (data.trim().length === 0) {
return { content: '' };
}
const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined, context.order, context.className);
return typeof result === 'string' ? { content: result } : null;
};
host.resourceNameToFileName = function (resourceName, containingFile) {
const resolvedPath = node_path_1.default.join(node_path_1.default.dirname(containingFile), resourceName);
if (!this.fileExists(resolvedPath)) {
return null;
}
// All resource names that have template file extensions are assumed to be templates
// TODO: Update compiler to provide the resource type to avoid extension matching here.
if (!hostOptions.externalStylesheets || hasTemplateExtension(resolvedPath)) {
return resolvedPath;
}
// For external stylesheets, create a unique identifier and store the mapping
let externalId = hostOptions.externalStylesheets.get(resolvedPath);
if (externalId === undefined) {
externalId = (0, node_crypto_1.createHash)('sha256').update(resolvedPath).digest('hex');
hostOptions.externalStylesheets.set(resolvedPath, externalId);
}
return externalId + '.css';
};
// Allow the AOT compiler to request the set of changed templates and styles
host.getModifiedResourceFiles = function () {
return hostOptions.modifiedFiles;
};
// Provide a resolution cache to ensure package.json lookups are cached
const resolutionCache = typescript.createModuleResolutionCache(host.getCurrentDirectory(), host.getCanonicalFileName.bind(host), compilerOptions, packageJsonCache);
host.getModuleResolutionCache = () => resolutionCache;
// Augment TypeScript Host for file replacements option
if (hostOptions.fileReplacements) {
augmentHostWithReplacements(typescript, host, hostOptions.fileReplacements, resolutionCache);
}
// Augment TypeScript Host with source file caching if provided
if (hostOptions.sourceFileCache) {
augmentHostWithCaching(host, hostOptions.sourceFileCache);
}
return host;
}
function hasTemplateExtension(file) {
const extension = node_path_1.default.extname(file).toLowerCase();
switch (extension) {
case '.htm':
case '.html':
case '.svg':
return true;
}
return false;
}

View File

@@ -0,0 +1,44 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type ng from '@angular/compiler-cli';
import type { PartialMessage } from 'esbuild';
import type ts from 'typescript';
import type { AngularHostOptions } from '../angular-host';
export interface EmitFileResult {
filename: string;
contents: string;
dependencies?: readonly string[];
}
export declare enum DiagnosticModes {
None = 0,
Option = 1,
Syntactic = 2,
Semantic = 4,
All = 7
}
export declare abstract class AngularCompilation {
#private;
static loadCompilerCli(): Promise<typeof ng>;
static loadTypescript(): Promise<typeof ts>;
protected loadConfiguration(tsconfig: string): Promise<ng.CompilerOptions>;
abstract initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions): Promise<{
affectedFiles: ReadonlySet<ts.SourceFile>;
compilerOptions: ng.CompilerOptions;
referencedFiles: readonly string[];
externalStylesheets?: ReadonlyMap<string, string>;
templateUpdates?: ReadonlyMap<string, string>;
}>;
abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;
protected abstract collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
diagnoseFiles(modes?: DiagnosticModes): Promise<{
errors?: PartialMessage[];
warnings?: PartialMessage[];
}>;
update?(files: Set<string>): Promise<void>;
close?(): Promise<void>;
}

View File

@@ -0,0 +1,107 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.AngularCompilation = exports.DiagnosticModes = void 0;
const load_esm_1 = require("../../../utils/load-esm");
const diagnostics_1 = require("../../esbuild/angular/diagnostics");
const profiling_1 = require("../../esbuild/profiling");
var DiagnosticModes;
(function (DiagnosticModes) {
DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
DiagnosticModes[DiagnosticModes["Option"] = 1] = "Option";
DiagnosticModes[DiagnosticModes["Syntactic"] = 2] = "Syntactic";
DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
})(DiagnosticModes || (exports.DiagnosticModes = DiagnosticModes = {}));
class AngularCompilation {
static #angularCompilerCliModule;
static #typescriptModule;
static async loadCompilerCli() {
// This uses a wrapped dynamic import to load `@angular/compiler-cli` which is ESM.
// Once TypeScript provides support for retaining dynamic imports this workaround can be dropped.
AngularCompilation.#angularCompilerCliModule ??=
await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli');
return AngularCompilation.#angularCompilerCliModule;
}
static async loadTypescript() {
AngularCompilation.#typescriptModule ??= await Promise.resolve().then(() => __importStar(require('typescript')));
return AngularCompilation.#typescriptModule;
}
async loadConfiguration(tsconfig) {
const { readConfiguration } = await AngularCompilation.loadCompilerCli();
return (0, profiling_1.profileSync)('NG_READ_CONFIG', () => readConfiguration(tsconfig, {
// Angular specific configuration defaults and overrides to ensure a functioning compilation.
suppressOutputPathCheck: true,
outDir: undefined,
sourceMap: false,
declaration: false,
declarationMap: false,
allowEmptyCodegenFiles: false,
annotationsAs: 'decorators',
enableResourceInlining: false,
supportTestBed: false,
supportJitMode: false,
// Disable removing of comments as TS is quite aggressive with these and can
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
removeComments: false,
}));
}
async diagnoseFiles(modes = DiagnosticModes.All) {
const result = {};
// Avoid loading typescript until actually needed.
// This allows for avoiding the load of typescript in the main thread when using the parallel compilation.
const typescript = await AngularCompilation.loadTypescript();
await (0, profiling_1.profileAsync)('NG_DIAGNOSTICS_TOTAL', async () => {
for (const diagnostic of await this.collectDiagnostics(modes)) {
const message = (0, diagnostics_1.convertTypeScriptDiagnostic)(typescript, diagnostic);
if (diagnostic.category === typescript.DiagnosticCategory.Error) {
(result.errors ??= []).push(message);
}
else {
(result.warnings ??= []).push(message);
}
}
});
return result;
}
}
exports.AngularCompilation = AngularCompilation;

View File

@@ -0,0 +1,25 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type ng from '@angular/compiler-cli';
import ts from 'typescript';
import { AngularHostOptions } from '../angular-host';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
export declare class AotCompilation extends AngularCompilation {
#private;
private readonly browserOnlyBuild;
constructor(browserOnlyBuild: boolean);
initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions): Promise<{
affectedFiles: ReadonlySet<ts.SourceFile>;
compilerOptions: ng.CompilerOptions;
referencedFiles: readonly string[];
externalStylesheets?: ReadonlyMap<string, string>;
templateUpdates?: ReadonlyMap<string, string>;
}>;
collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
emitAffectedFiles(): Iterable<EmitFileResult>;
}

View File

@@ -0,0 +1,337 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AotCompilation = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const node_path_1 = require("node:path");
const typescript_1 = __importDefault(require("typescript"));
const profiling_1 = require("../../esbuild/profiling");
const angular_host_1 = require("../angular-host");
const jit_bootstrap_transformer_1 = require("../transformers/jit-bootstrap-transformer");
const lazy_routes_transformer_1 = require("../transformers/lazy-routes-transformer");
const web_worker_transformer_1 = require("../transformers/web-worker-transformer");
const angular_compilation_1 = require("./angular-compilation");
const hmr_candidates_1 = require("./hmr-candidates");
/**
* The modified files count limit for performing component HMR analysis.
* Performing content analysis for a large amount of files can result in longer rebuild times
* than a full rebuild would entail.
*/
const HMR_MODIFIED_FILE_LIMIT = 32;
class AngularCompilationState {
angularProgram;
compilerHost;
typeScriptProgram;
affectedFiles;
templateDiagnosticsOptimization;
webWorkerTransform;
diagnosticCache;
constructor(angularProgram, compilerHost, typeScriptProgram, affectedFiles, templateDiagnosticsOptimization, webWorkerTransform, diagnosticCache = new WeakMap()) {
this.angularProgram = angularProgram;
this.compilerHost = compilerHost;
this.typeScriptProgram = typeScriptProgram;
this.affectedFiles = affectedFiles;
this.templateDiagnosticsOptimization = templateDiagnosticsOptimization;
this.webWorkerTransform = webWorkerTransform;
this.diagnosticCache = diagnosticCache;
}
get angularCompiler() {
return this.angularProgram.compiler;
}
}
class AotCompilation extends angular_compilation_1.AngularCompilation {
browserOnlyBuild;
#state;
constructor(browserOnlyBuild) {
super();
this.browserOnlyBuild = browserOnlyBuild;
}
async initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
// Dynamically load the Angular compiler CLI package
const { NgtscProgram, OptimizeFor } = await angular_compilation_1.AngularCompilation.loadCompilerCli();
// Load the compiler configuration and transform as needed
const { options: originalCompilerOptions, rootNames, errors: configurationDiagnostics, } = await this.loadConfiguration(tsconfig);
const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
if (compilerOptions.externalRuntimeStyles) {
hostOptions.externalStylesheets ??= new Map();
}
// Reuse the package.json cache from the previous compilation
const packageJsonCache = this.#state?.compilerHost
.getModuleResolutionCache?.()
?.getPackageJsonInfoCache();
const useHmr = compilerOptions['_enableHmr'] &&
hostOptions.modifiedFiles &&
hostOptions.modifiedFiles.size <= HMR_MODIFIED_FILE_LIMIT;
let staleSourceFiles;
let clearPackageJsonCache = false;
if (hostOptions.modifiedFiles && this.#state) {
for (const modifiedFile of hostOptions.modifiedFiles) {
// Clear package.json cache if a node modules file was modified
if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) {
clearPackageJsonCache = true;
packageJsonCache?.clear();
}
// Collect stale source files for HMR analysis of inline component resources
if (useHmr) {
const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile);
if (sourceFile) {
staleSourceFiles ??= new Map();
staleSourceFiles.set(modifiedFile, sourceFile);
}
}
}
}
// Create Angular compiler host
const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions, packageJsonCache);
// Create the Angular specific program that contains the Angular compiler
const angularProgram = (0, profiling_1.profileSync)('NG_CREATE_PROGRAM', () => new NgtscProgram(rootNames, compilerOptions, host, this.#state?.angularProgram));
const angularCompiler = angularProgram.compiler;
const angularTypeScriptProgram = angularProgram.getTsProgram();
(0, angular_host_1.ensureSourceFileVersions)(angularTypeScriptProgram);
let oldProgram = this.#state?.typeScriptProgram;
let usingBuildInfo = false;
if (!oldProgram) {
oldProgram = typescript_1.default.readBuilderProgram(compilerOptions, host);
usingBuildInfo = !!oldProgram;
}
const typeScriptProgram = typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(angularTypeScriptProgram, host, oldProgram, configurationDiagnostics);
await (0, profiling_1.profileAsync)('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync());
let templateUpdates;
if (useHmr && hostOptions.modifiedFiles && this.#state) {
const componentNodes = (0, hmr_candidates_1.collectHmrCandidates)(hostOptions.modifiedFiles, angularProgram, staleSourceFiles);
for (const node of componentNodes) {
if (!typescript_1.default.isClassDeclaration(node)) {
continue;
}
const componentFilename = node.getSourceFile().fileName;
let relativePath = (0, node_path_1.relative)(host.getCurrentDirectory(), componentFilename);
if (relativePath.startsWith('..')) {
relativePath = componentFilename;
}
relativePath = relativePath.replaceAll('\\', '/');
const updateId = encodeURIComponent(`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`);
const updateText = angularCompiler.emitHmrUpdateModule(node);
// If compiler cannot generate an update for the component, prevent template updates.
if (updateText === null) {
// Build is needed if a template cannot be updated
templateUpdates = undefined;
break;
}
templateUpdates ??= new Map();
templateUpdates.set(updateId, updateText);
}
}
const affectedFiles = (0, profiling_1.profileSync)('NG_FIND_AFFECTED', () => findAffectedFiles(typeScriptProgram, angularCompiler, usingBuildInfo));
// Get all files referenced in the TypeScript/Angular program including component resources
const referencedFiles = typeScriptProgram
.getSourceFiles()
.filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile))
.flatMap((sourceFile) => {
const resourceDependencies = angularCompiler.getResourceDependencies(sourceFile);
// Also invalidate Angular diagnostics for a source file if component resources are modified
if (this.#state && hostOptions.modifiedFiles?.size) {
for (const resourceDependency of resourceDependencies) {
if (hostOptions.modifiedFiles.has(resourceDependency)) {
this.#state.diagnosticCache.delete(sourceFile);
// Also mark as affected in case changed template affects diagnostics
affectedFiles.add(sourceFile);
}
}
}
return [sourceFile.fileName, ...resourceDependencies];
});
this.#state = new AngularCompilationState(angularProgram, host, typeScriptProgram, affectedFiles, affectedFiles.size === 1 ? OptimizeFor.SingleFile : OptimizeFor.WholeProgram, (0, web_worker_transformer_1.createWorkerTransformer)(hostOptions.processWebWorker.bind(hostOptions)), this.#state?.diagnosticCache);
return {
affectedFiles,
compilerOptions,
referencedFiles,
externalStylesheets: hostOptions.externalStylesheets,
templateUpdates,
};
}
*collectDiagnostics(modes) {
(0, node_assert_1.default)(this.#state, 'Angular compilation must be initialized prior to collecting diagnostics.');
const { affectedFiles, angularCompiler, diagnosticCache, templateDiagnosticsOptimization, typeScriptProgram, } = this.#state;
const syntactic = modes & angular_compilation_1.DiagnosticModes.Syntactic;
const semantic = modes & angular_compilation_1.DiagnosticModes.Semantic;
// Collect program level diagnostics
if (modes & angular_compilation_1.DiagnosticModes.Option) {
yield* typeScriptProgram.getConfigFileParsingDiagnostics();
yield* angularCompiler.getOptionDiagnostics();
yield* typeScriptProgram.getOptionsDiagnostics();
}
if (syntactic) {
yield* typeScriptProgram.getGlobalDiagnostics();
}
// Collect source file specific diagnostics
for (const sourceFile of typeScriptProgram.getSourceFiles()) {
if (angularCompiler.ignoreForDiagnostics.has(sourceFile)) {
continue;
}
if (syntactic) {
// TypeScript will use cached diagnostics for files that have not been
// changed or affected for this build when using incremental building.
yield* (0, profiling_1.profileSync)('NG_DIAGNOSTICS_SYNTACTIC', () => typeScriptProgram.getSyntacticDiagnostics(sourceFile), true);
}
if (!semantic) {
continue;
}
yield* (0, profiling_1.profileSync)('NG_DIAGNOSTICS_SEMANTIC', () => typeScriptProgram.getSemanticDiagnostics(sourceFile), true);
// Declaration files cannot have template diagnostics
if (sourceFile.isDeclarationFile) {
continue;
}
// Only request Angular template diagnostics for affected files to avoid
// overhead of template diagnostics for unchanged files.
if (affectedFiles.has(sourceFile)) {
const angularDiagnostics = (0, profiling_1.profileSync)('NG_DIAGNOSTICS_TEMPLATE', () => angularCompiler.getDiagnosticsForFile(sourceFile, templateDiagnosticsOptimization), true);
diagnosticCache.set(sourceFile, angularDiagnostics);
yield* angularDiagnostics;
}
else {
const angularDiagnostics = diagnosticCache.get(sourceFile);
if (angularDiagnostics) {
yield* angularDiagnostics;
}
}
}
}
emitAffectedFiles() {
(0, node_assert_1.default)(this.#state, 'Angular compilation must be initialized prior to emitting files.');
const { affectedFiles, angularCompiler, compilerHost, typeScriptProgram, webWorkerTransform } = this.#state;
const compilerOptions = typeScriptProgram.getCompilerOptions();
const buildInfoFilename = compilerOptions.tsBuildInfoFile ?? '.tsbuildinfo';
const useTypeScriptTranspilation = !compilerOptions.isolatedModules ||
!!compilerOptions.sourceMap ||
!!compilerOptions.inlineSourceMap;
const emittedFiles = new Map();
const writeFileCallback = (filename, contents, _a, _b, sourceFiles) => {
if (!sourceFiles?.length && filename.endsWith(buildInfoFilename)) {
// Save builder info contents to specified location
compilerHost.writeFile(filename, contents, false);
return;
}
(0, node_assert_1.default)(sourceFiles?.length === 1, 'Invalid TypeScript program emit for ' + filename);
const sourceFile = typescript_1.default.getOriginalNode(sourceFiles[0], typescript_1.default.isSourceFile);
if (angularCompiler.ignoreForEmit.has(sourceFile)) {
return;
}
angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents });
};
const transformers = angularCompiler.prepareEmit().transformers;
transformers.before ??= [];
transformers.before.push((0, jit_bootstrap_transformer_1.replaceBootstrap)(() => typeScriptProgram.getProgram().getTypeChecker()), webWorkerTransform);
if (!this.browserOnlyBuild) {
transformers.before.push((0, lazy_routes_transformer_1.lazyRoutesTransformer)(compilerOptions, compilerHost));
}
// Emit is handled in write file callback when using TypeScript
if (useTypeScriptTranspilation) {
// TypeScript will loop until there are no more affected files in the program
while (typeScriptProgram.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
/* empty */
}
}
else if (compilerOptions.tsBuildInfoFile) {
// Manually get the builder state for the persistent cache
// The TypeScript API currently embeds this behavior inside the program emit
// via emitNextAffectedFile but that also applies all internal transforms.
const programWithGetState = typeScriptProgram.getProgram();
(0, node_assert_1.default)(typeof programWithGetState.emitBuildInfo === 'function', 'TypeScript program emitBuildInfo is missing.');
programWithGetState.emitBuildInfo();
}
// Angular may have files that must be emitted but TypeScript does not consider affected
for (const sourceFile of typeScriptProgram.getSourceFiles()) {
if (emittedFiles.has(sourceFile) || angularCompiler.ignoreForEmit.has(sourceFile)) {
continue;
}
if (sourceFile.isDeclarationFile) {
continue;
}
if (angularCompiler.incrementalCompilation.safeToSkipEmit(sourceFile) &&
!affectedFiles.has(sourceFile)) {
continue;
}
if (useTypeScriptTranspilation) {
typeScriptProgram.emit(sourceFile, writeFileCallback, undefined, undefined, transformers);
continue;
}
// When not using TypeScript transpilation, directly apply only Angular specific transformations
const transformResult = typescript_1.default.transform(sourceFile, [
...(transformers.before ?? []),
...(transformers.after ?? []),
], compilerOptions);
(0, node_assert_1.default)(transformResult.transformed.length === 1, 'TypeScript transforms should not produce multiple outputs for ' + sourceFile.fileName);
let contents;
if (sourceFile === transformResult.transformed[0]) {
// Use original content if no changes were made
contents = sourceFile.text;
}
else {
// Otherwise, print the transformed source file
const printer = typescript_1.default.createPrinter(compilerOptions, transformResult);
contents = printer.printFile(transformResult.transformed[0]);
}
angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents });
}
return emittedFiles.values();
}
}
exports.AotCompilation = AotCompilation;
function findAffectedFiles(builder, { ignoreForDiagnostics }, includeTTC) {
const affectedFiles = new Set();
// eslint-disable-next-line no-constant-condition
while (true) {
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
// If the affected file is a TTC shim, add the shim's original source file.
// This ensures that changes that affect TTC are typechecked even when the changes
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
// For example, changing @Input property types of a directive used in another component's
// template.
// A TTC shim is a file that has been ignored for diagnostics and has a filename ending in `.ngtypecheck.ts`.
if (ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith('.ngtypecheck.ts')) {
// This file name conversion relies on internal compiler logic and should be converted
// to an official method when available. 15 is length of `.ngtypecheck.ts`
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
const originalSourceFile = builder.getSourceFile(originalFilename);
if (originalSourceFile) {
affectedFiles.add(originalSourceFile);
}
return true;
}
return false;
});
if (!result) {
break;
}
affectedFiles.add(result.affected);
}
// Add all files with associated template type checking files.
// Stored TS build info does not have knowledge of the AOT compiler or the typechecking state of the templates.
// To ensure that errors are reported correctly, all AOT component diagnostics need to be analyzed even if build
// info is present.
if (includeTTC) {
for (const sourceFile of builder.getSourceFiles()) {
if (ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith('.ngtypecheck.ts')) {
// This file name conversion relies on internal compiler logic and should be converted
// to an official method when available. 15 is length of `.ngtypecheck.ts`
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
const originalSourceFile = builder.getSourceFile(originalFilename);
if (originalSourceFile) {
affectedFiles.add(originalSourceFile);
}
}
}
}
return affectedFiles;
}

View File

@@ -0,0 +1,17 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { AngularCompilation } from './angular-compilation';
/**
* Creates an Angular compilation object that can be used to perform Angular application
* compilation either for AOT or JIT mode. By default a parallel compilation is created
* that uses a Node.js worker thread.
* @param jit True, for Angular JIT compilation; False, for Angular AOT compilation.
* @param browserOnlyBuild True, for browser only builds; False, for browser and server builds.
* @returns An instance of an Angular compilation object.
*/
export declare function createAngularCompilation(jit: boolean, browserOnlyBuild: boolean, parallel?: boolean): Promise<AngularCompilation>;

View File

@@ -0,0 +1,66 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAngularCompilation = createAngularCompilation;
const environment_options_1 = require("../../../utils/environment-options");
/**
* Creates an Angular compilation object that can be used to perform Angular application
* compilation either for AOT or JIT mode. By default a parallel compilation is created
* that uses a Node.js worker thread.
* @param jit True, for Angular JIT compilation; False, for Angular AOT compilation.
* @param browserOnlyBuild True, for browser only builds; False, for browser and server builds.
* @returns An instance of an Angular compilation object.
*/
async function createAngularCompilation(jit, browserOnlyBuild, parallel = environment_options_1.useParallelTs) {
if (parallel) {
const { ParallelCompilation } = await Promise.resolve().then(() => __importStar(require('./parallel-compilation')));
return new ParallelCompilation(jit, browserOnlyBuild);
}
if (jit) {
const { JitCompilation } = await Promise.resolve().then(() => __importStar(require('./jit-compilation')));
return new JitCompilation(browserOnlyBuild);
}
else {
const { AotCompilation } = await Promise.resolve().then(() => __importStar(require('./aot-compilation')));
return new AotCompilation(browserOnlyBuild);
}
}

View File

@@ -0,0 +1,22 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type ng from '@angular/compiler-cli';
import ts from 'typescript';
/**
* Analyzes one or more modified files for changes to determine if any
* class declarations for Angular components are candidates for hot
* module replacement (HMR). If any source files are also modified but
* are not candidates then all candidates become invalid. This invalidation
* ensures that a full rebuild occurs and the running application stays
* synchronized with the code.
* @param modifiedFiles A set of modified files to analyze.
* @param param1 An Angular compiler instance
* @param staleSourceFiles A map of paths to previous source file instances.
* @returns A set of HMR candidate component class declarations.
*/
export declare function collectHmrCandidates(modifiedFiles: Set<string>, { compiler }: ng.NgtscProgram, staleSourceFiles: Map<string, ts.SourceFile> | undefined): Set<ts.ClassDeclaration>;

View File

@@ -0,0 +1,269 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectHmrCandidates = collectHmrCandidates;
const node_assert_1 = __importDefault(require("node:assert"));
const typescript_1 = __importDefault(require("typescript"));
/**
* Analyzes one or more modified files for changes to determine if any
* class declarations for Angular components are candidates for hot
* module replacement (HMR). If any source files are also modified but
* are not candidates then all candidates become invalid. This invalidation
* ensures that a full rebuild occurs and the running application stays
* synchronized with the code.
* @param modifiedFiles A set of modified files to analyze.
* @param param1 An Angular compiler instance
* @param staleSourceFiles A map of paths to previous source file instances.
* @returns A set of HMR candidate component class declarations.
*/
function collectHmrCandidates(modifiedFiles, { compiler }, staleSourceFiles) {
const candidates = new Set();
for (const file of modifiedFiles) {
// If the file is a template for component(s), add component classes as candidates
const templateFileNodes = compiler.getComponentsWithTemplateFile(file);
if (templateFileNodes.size) {
templateFileNodes.forEach((node) => candidates.add(node));
continue;
}
// If the file is a style for component(s), add component classes as candidates
const styleFileNodes = compiler.getComponentsWithStyleFile(file);
if (styleFileNodes.size) {
styleFileNodes.forEach((node) => candidates.add(node));
continue;
}
const staleSource = staleSourceFiles?.get(file);
if (staleSource === undefined) {
// Unknown file requires a rebuild so clear out the candidates and stop collecting
candidates.clear();
break;
}
const updatedSource = compiler.getCurrentProgram().getSourceFile(file);
if (updatedSource === undefined) {
// No longer existing program file requires a rebuild so clear out the candidates and stop collecting
candidates.clear();
break;
}
// Analyze the stale and updated file for changes
const fileCandidates = analyzeFileUpdates(staleSource, updatedSource, compiler);
if (fileCandidates) {
fileCandidates.forEach((node) => candidates.add(node));
}
else {
// Unsupported HMR changes present
// Only template and style literal changes are allowed.
candidates.clear();
break;
}
}
return candidates;
}
/**
* Analyzes the updates of a source file for potential HMR component class candidates.
* A source file can contain candidates if only the Angular component metadata of a class
* has been changed and the metadata changes are only of supported fields.
* @param stale The stale (previous) source file instance.
* @param updated The updated source file instance.
* @param compiler An Angular compiler instance.
* @returns An array of candidate class declarations; or `null` if unsupported changes are present.
*/
function analyzeFileUpdates(stale, updated, compiler) {
if (stale.statements.length !== updated.statements.length) {
return null;
}
const candidates = [];
for (let i = 0; i < updated.statements.length; ++i) {
const updatedNode = updated.statements[i];
const staleNode = stale.statements[i];
if (typescript_1.default.isClassDeclaration(updatedNode)) {
if (!typescript_1.default.isClassDeclaration(staleNode)) {
return null;
}
// Check class declaration differences (name/heritage/modifiers)
if (updatedNode.name?.text !== staleNode.name?.text) {
return null;
}
if (!equalRangeText(updatedNode.heritageClauses, updated, staleNode.heritageClauses, stale)) {
return null;
}
const updatedModifiers = typescript_1.default.getModifiers(updatedNode);
const staleModifiers = typescript_1.default.getModifiers(staleNode);
if (updatedModifiers?.length !== staleModifiers?.length ||
!updatedModifiers?.every((updatedModifier) => staleModifiers?.some((staleModifier) => updatedModifier.kind === staleModifier.kind))) {
return null;
}
// Check for component class nodes
const meta = compiler.getMeta(updatedNode);
if (meta?.decorator && meta.isComponent === true) {
const updatedDecorators = typescript_1.default.getDecorators(updatedNode);
const staleDecorators = typescript_1.default.getDecorators(staleNode);
if (!staleDecorators || staleDecorators.length !== updatedDecorators?.length) {
return null;
}
// TODO: Check other decorators instead of assuming all multi-decorator components are unsupported
if (staleDecorators.length > 1) {
return null;
}
// Find index of component metadata decorator
const metaDecoratorIndex = updatedDecorators?.indexOf(meta.decorator);
(0, node_assert_1.default)(metaDecoratorIndex !== undefined, 'Component metadata decorator should always be present on component class.');
const updatedDecoratorExpression = meta.decorator.expression;
(0, node_assert_1.default)(typescript_1.default.isCallExpression(updatedDecoratorExpression) &&
updatedDecoratorExpression.arguments.length === 1, 'Component metadata decorator should contain a call expression with a single argument.');
// Check the matching stale index for the component decorator
const staleDecoratorExpression = staleDecorators[metaDecoratorIndex]?.expression;
if (!staleDecoratorExpression ||
!typescript_1.default.isCallExpression(staleDecoratorExpression) ||
staleDecoratorExpression.arguments.length !== 1) {
return null;
}
// Check decorator name/expression
// NOTE: This would typically be `Component` but can also be a property expression or some other alias.
// To avoid complex checks, this ensures the textual representation does not change. This has a low chance
// of a false positive if the expression is changed to still reference the `Component` type but has different
// text. However, it is rare for `Component` to not be used directly and additionally unlikely that it would
// be changed between edits. A false positive would also only lead to a difference of a full page reload versus
// an HMR update.
if (!equalRangeText(updatedDecoratorExpression.expression, updated, staleDecoratorExpression.expression, stale)) {
return null;
}
// Compare component meta decorator object literals
const analysis = analyzeMetaUpdates(staleDecoratorExpression, stale, updatedDecoratorExpression, updated);
if (analysis === MetaUpdateAnalysis.Unsupported) {
return null;
}
// Compare text of the member nodes to determine if any changes have occurred
if (!equalRangeText(updatedNode.members, updated, staleNode.members, stale)) {
// A change to a member outside a component's metadata is unsupported
return null;
}
// If all previous class checks passed, this class is supported for HMR updates
if (analysis === MetaUpdateAnalysis.Supported) {
candidates.push(updatedNode);
}
continue;
}
}
// Compare text of the statement nodes to determine if any changes have occurred
// TODO: Consider expanding this to check semantic updates for each node kind
if (!equalRangeText(updatedNode, updated, staleNode, stale)) {
// A change to a statement outside a component's metadata is unsupported
return null;
}
}
return candidates;
}
/**
* The set of Angular component metadata fields that are supported by HMR updates.
*/
const SUPPORTED_FIELD_NAMES = new Set([
'template',
'templateUrl',
'styles',
'styleUrl',
'stylesUrl',
]);
var MetaUpdateAnalysis;
(function (MetaUpdateAnalysis) {
MetaUpdateAnalysis[MetaUpdateAnalysis["Supported"] = 0] = "Supported";
MetaUpdateAnalysis[MetaUpdateAnalysis["Unsupported"] = 1] = "Unsupported";
MetaUpdateAnalysis[MetaUpdateAnalysis["None"] = 2] = "None";
})(MetaUpdateAnalysis || (MetaUpdateAnalysis = {}));
/**
* Analyzes the metadata fields of a decorator call expression for unsupported HMR updates.
* Only updates to supported fields can be present for HMR to be viable.
* @param staleCall A call expression instance.
* @param staleSource The source file instance containing the stale call instance.
* @param updatedCall A call expression instance.
* @param updatedSource The source file instance containing the updated call instance.
* @returns A MetaUpdateAnalysis enum value.
*/
function analyzeMetaUpdates(staleCall, staleSource, updatedCall, updatedSource) {
const staleObject = staleCall.arguments[0];
const updatedObject = updatedCall.arguments[0];
let hasSupportedUpdate = false;
if (!typescript_1.default.isObjectLiteralExpression(staleObject) || !typescript_1.default.isObjectLiteralExpression(updatedObject)) {
return MetaUpdateAnalysis.Unsupported;
}
const supportedFields = new Map();
const unsupportedFields = [];
for (const property of staleObject.properties) {
if (!typescript_1.default.isPropertyAssignment(property) || typescript_1.default.isComputedPropertyName(property.name)) {
// Unsupported object literal property
return MetaUpdateAnalysis.Unsupported;
}
const name = property.name.text;
if (SUPPORTED_FIELD_NAMES.has(name)) {
supportedFields.set(name, property.initializer);
continue;
}
unsupportedFields.push(property.initializer);
}
let i = 0;
for (const property of updatedObject.properties) {
if (!typescript_1.default.isPropertyAssignment(property) || typescript_1.default.isComputedPropertyName(property.name)) {
// Unsupported object literal property
return MetaUpdateAnalysis.Unsupported;
}
const name = property.name.text;
if (SUPPORTED_FIELD_NAMES.has(name)) {
const staleInitializer = supportedFields.get(name);
// If the supported field was added or has its content changed, there has been a supported update
if (!staleInitializer ||
!equalRangeText(property.initializer, updatedSource, staleInitializer, staleSource)) {
hasSupportedUpdate = true;
}
// Remove the field entry to allow tracking removed fields
supportedFields.delete(name);
continue;
}
// Compare in order
if (!equalRangeText(property.initializer, updatedSource, unsupportedFields[i++], staleSource)) {
return MetaUpdateAnalysis.Unsupported;
}
}
if (i !== unsupportedFields.length) {
return MetaUpdateAnalysis.Unsupported;
}
// Any remaining supported field indicates a field removal. This is also considered a supported update.
hasSupportedUpdate ||= supportedFields.size > 0;
return hasSupportedUpdate ? MetaUpdateAnalysis.Supported : MetaUpdateAnalysis.None;
}
/**
* Compares the text from a provided range in a source file to the text of a range in a second source file.
* The comparison avoids making any intermediate string copies.
* @param firstRange A text range within the first source file.
* @param firstSource A source file instance.
* @param secondRange A text range within the second source file.
* @param secondSource A source file instance.
* @returns true, if the text from both ranges is equal; false, otherwise.
*/
function equalRangeText(firstRange, firstSource, secondRange, secondSource) {
// Check matching undefined values
if (!firstRange || !secondRange) {
return firstRange === secondRange;
}
// Ensure lengths are equal
const firstLength = firstRange.end - firstRange.pos;
const secondLength = secondRange.end - secondRange.pos;
if (firstLength !== secondLength) {
return false;
}
// Check each character
for (let i = 0; i < firstLength; ++i) {
const firstChar = firstSource.text.charCodeAt(i + firstRange.pos);
const secondChar = secondSource.text.charCodeAt(i + secondRange.pos);
if (firstChar !== secondChar) {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export { AngularCompilation, DiagnosticModes } from './angular-compilation';
export { createAngularCompilation } from './factory';
export { NoopCompilation } from './noop-compilation';

View File

@@ -0,0 +1,17 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NoopCompilation = exports.createAngularCompilation = exports.DiagnosticModes = exports.AngularCompilation = void 0;
var angular_compilation_1 = require("./angular-compilation");
Object.defineProperty(exports, "AngularCompilation", { enumerable: true, get: function () { return angular_compilation_1.AngularCompilation; } });
Object.defineProperty(exports, "DiagnosticModes", { enumerable: true, get: function () { return angular_compilation_1.DiagnosticModes; } });
var factory_1 = require("./factory");
Object.defineProperty(exports, "createAngularCompilation", { enumerable: true, get: function () { return factory_1.createAngularCompilation; } });
var noop_compilation_1 = require("./noop-compilation");
Object.defineProperty(exports, "NoopCompilation", { enumerable: true, get: function () { return noop_compilation_1.NoopCompilation; } });

View File

@@ -0,0 +1,23 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type ng from '@angular/compiler-cli';
import ts from 'typescript';
import { AngularHostOptions } from '../angular-host';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
export declare class JitCompilation extends AngularCompilation {
#private;
private readonly browserOnlyBuild;
constructor(browserOnlyBuild: boolean);
initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions): Promise<{
affectedFiles: ReadonlySet<ts.SourceFile>;
compilerOptions: ng.CompilerOptions;
referencedFiles: readonly string[];
}>;
collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
emitAffectedFiles(): Iterable<EmitFileResult>;
}

View File

@@ -0,0 +1,117 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JitCompilation = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const typescript_1 = __importDefault(require("typescript"));
const load_esm_1 = require("../../../utils/load-esm");
const profiling_1 = require("../../esbuild/profiling");
const angular_host_1 = require("../angular-host");
const jit_resource_transformer_1 = require("../transformers/jit-resource-transformer");
const lazy_routes_transformer_1 = require("../transformers/lazy-routes-transformer");
const web_worker_transformer_1 = require("../transformers/web-worker-transformer");
const angular_compilation_1 = require("./angular-compilation");
class JitCompilationState {
compilerHost;
typeScriptProgram;
constructorParametersDownlevelTransform;
replaceResourcesTransform;
webWorkerTransform;
constructor(compilerHost, typeScriptProgram, constructorParametersDownlevelTransform, replaceResourcesTransform, webWorkerTransform) {
this.compilerHost = compilerHost;
this.typeScriptProgram = typeScriptProgram;
this.constructorParametersDownlevelTransform = constructorParametersDownlevelTransform;
this.replaceResourcesTransform = replaceResourcesTransform;
this.webWorkerTransform = webWorkerTransform;
}
}
class JitCompilation extends angular_compilation_1.AngularCompilation {
browserOnlyBuild;
#state;
constructor(browserOnlyBuild) {
super();
this.browserOnlyBuild = browserOnlyBuild;
}
async initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
// Dynamically load the Angular compiler CLI package
const { constructorParametersDownlevelTransform } = await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli/private/tooling');
// Load the compiler configuration and transform as needed
const { options: originalCompilerOptions, rootNames, errors: configurationDiagnostics, } = await this.loadConfiguration(tsconfig);
const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
// Create Angular compiler host
const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions, undefined);
// Create the TypeScript Program
const typeScriptProgram = (0, profiling_1.profileSync)('TS_CREATE_PROGRAM', () => typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, this.#state?.typeScriptProgram ?? typescript_1.default.readBuilderProgram(compilerOptions, host), configurationDiagnostics));
const affectedFiles = (0, profiling_1.profileSync)('TS_FIND_AFFECTED', () => findAffectedFiles(typeScriptProgram));
this.#state = new JitCompilationState(host, typeScriptProgram, constructorParametersDownlevelTransform(typeScriptProgram.getProgram()), (0, jit_resource_transformer_1.createJitResourceTransformer)(() => typeScriptProgram.getProgram().getTypeChecker()), (0, web_worker_transformer_1.createWorkerTransformer)(hostOptions.processWebWorker.bind(hostOptions)));
const referencedFiles = typeScriptProgram
.getSourceFiles()
.map((sourceFile) => sourceFile.fileName);
return { affectedFiles, compilerOptions, referencedFiles };
}
*collectDiagnostics(modes) {
(0, node_assert_1.default)(this.#state, 'Compilation must be initialized prior to collecting diagnostics.');
const { typeScriptProgram } = this.#state;
// Collect program level diagnostics
if (modes & angular_compilation_1.DiagnosticModes.Option) {
yield* typeScriptProgram.getConfigFileParsingDiagnostics();
yield* typeScriptProgram.getOptionsDiagnostics();
}
if (modes & angular_compilation_1.DiagnosticModes.Syntactic) {
yield* typeScriptProgram.getGlobalDiagnostics();
yield* (0, profiling_1.profileSync)('NG_DIAGNOSTICS_SYNTACTIC', () => typeScriptProgram.getSyntacticDiagnostics());
}
if (modes & angular_compilation_1.DiagnosticModes.Semantic) {
yield* (0, profiling_1.profileSync)('NG_DIAGNOSTICS_SEMANTIC', () => typeScriptProgram.getSemanticDiagnostics());
}
}
emitAffectedFiles() {
(0, node_assert_1.default)(this.#state, 'Compilation must be initialized prior to emitting files.');
const { compilerHost, typeScriptProgram, constructorParametersDownlevelTransform, replaceResourcesTransform, webWorkerTransform, } = this.#state;
const compilerOptions = typeScriptProgram.getCompilerOptions();
const buildInfoFilename = compilerOptions.tsBuildInfoFile ?? '.tsbuildinfo';
const emittedFiles = [];
const writeFileCallback = (filename, contents, _a, _b, sourceFiles) => {
if (!sourceFiles?.length && filename.endsWith(buildInfoFilename)) {
// Save builder info contents to specified location
compilerHost.writeFile(filename, contents, false);
return;
}
(0, node_assert_1.default)(sourceFiles?.length === 1, 'Invalid TypeScript program emit for ' + filename);
emittedFiles.push({ filename: sourceFiles[0].fileName, contents });
};
const transformers = {
before: [
replaceResourcesTransform,
constructorParametersDownlevelTransform,
webWorkerTransform,
],
};
if (!this.browserOnlyBuild) {
transformers.before.push((0, lazy_routes_transformer_1.lazyRoutesTransformer)(compilerOptions, compilerHost));
}
// TypeScript will loop until there are no more affected files in the program
while (typeScriptProgram.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
/* empty */
}
return emittedFiles;
}
}
exports.JitCompilation = JitCompilation;
function findAffectedFiles(builder) {
const affectedFiles = new Set();
let result;
while ((result = builder.getSemanticDiagnosticsOfNextAffectedFile())) {
affectedFiles.add(result.affected);
}
return affectedFiles;
}

View File

@@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type ng from '@angular/compiler-cli';
import type ts from 'typescript';
import { AngularHostOptions } from '../angular-host';
import { AngularCompilation } from './angular-compilation';
export declare class NoopCompilation extends AngularCompilation {
initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions): Promise<{
affectedFiles: ReadonlySet<ts.SourceFile>;
compilerOptions: ng.CompilerOptions;
referencedFiles: readonly string[];
}>;
collectDiagnostics(): never;
emitAffectedFiles(): never;
}

View File

@@ -0,0 +1,26 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NoopCompilation = void 0;
const angular_compilation_1 = require("./angular-compilation");
class NoopCompilation extends angular_compilation_1.AngularCompilation {
async initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
// Load the compiler configuration and transform as needed
const { options: originalCompilerOptions } = await this.loadConfiguration(tsconfig);
const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
return { affectedFiles: new Set(), compilerOptions, referencedFiles: [] };
}
collectDiagnostics() {
throw new Error('Not available when using noop compilation.');
}
emitAffectedFiles() {
throw new Error('Not available when using noop compilation.');
}
}
exports.NoopCompilation = NoopCompilation;

View File

@@ -0,0 +1,44 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { CompilerOptions } from '@angular/compiler-cli';
import type { PartialMessage } from 'esbuild';
import type { SourceFile } from 'typescript';
import type { AngularHostOptions } from '../angular-host';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
/**
* An Angular compilation which uses a Node.js Worker thread to load and execute
* the TypeScript and Angular compilers. This allows for longer synchronous actions
* such as semantic and template diagnostics to be calculated in parallel to the
* other aspects of the application bundling process. The worker thread also has
* a separate memory pool which significantly reduces the need for adjusting the
* main Node.js CLI process memory settings with large application code sizes.
*/
export declare class ParallelCompilation extends AngularCompilation {
#private;
private readonly jit;
private readonly browserOnlyBuild;
constructor(jit: boolean, browserOnlyBuild: boolean);
initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: CompilerOptions) => CompilerOptions): Promise<{
affectedFiles: ReadonlySet<SourceFile>;
compilerOptions: CompilerOptions;
referencedFiles: readonly string[];
externalStylesheets?: ReadonlyMap<string, string>;
}>;
/**
* This is not needed with this compilation type since the worker will already send a response
* with the serializable esbuild compatible diagnostics.
*/
protected collectDiagnostics(): never;
diagnoseFiles(modes?: DiagnosticModes): Promise<{
errors?: PartialMessage[];
warnings?: PartialMessage[];
}>;
emitAffectedFiles(): Promise<Iterable<EmitFileResult>>;
update(files: Set<string>): Promise<void>;
close(): Promise<void>;
}

Some files were not shown because too many files have changed in this diff Show More