Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ function* emitOutputResults(
incrementalResult.files.push({
path: destination,
type: BuildOutputFileType.Browser,
inputPath: source,
source,
origin: 'disk',
});
}
Expand Down Expand Up @@ -406,7 +406,7 @@ function createFullResult(
...assetFiles.map(({ source, destination }) => ({
path: destination,
type: BuildOutputFileType.Browser,
inputPath: source,
source,
origin: 'disk' as const,
})),
...outputFiles.map((file) => ({
Expand Down
40 changes: 9 additions & 31 deletions packages/angular/build/src/builders/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import assert from 'node:assert';
import fs from 'node:fs/promises';
import path from 'node:path';
import { BuildOutputFileType } from '../../tools/esbuild/bundler-files';
import { createJsonBuildManifest, emitFilesToDisk } from '../../tools/esbuild/utils';
import { createJsonBuildManifest } from '../../tools/esbuild/utils';
import { colors as ansiColors } from '../../utils/color';
import { deleteOutputDir } from '../../utils/delete-output-dir';
import { emitFilesToDisk } from '../../utils/emit-files-to-disk';
import { bazelEsbuildPluginPath, useJSONBuildLogs } from '../../utils/environment-options';
import { isSubDirectory } from '../../utils/path';
import { purgeStaleBuildCache } from '../../utils/purge-cache';
Expand Down Expand Up @@ -197,37 +198,14 @@ export async function* buildApplication(
}

// Writes the output files to disk and ensures the containing directories are present
const directoryExists = new Set<string>();
try {
await emitFilesToDisk(result.files, async (file) => {
if (
outputOptions.ignoreServer &&
(file.type === BuildOutputFileType.ServerApplication ||
file.type === BuildOutputFileType.ServerRoot)
) {
return;
}

const fullFilePath = generateFullPath(file.path, file.type, outputOptions);

// Ensure output subdirectories exist
const fileBasePath = path.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.cp(file.inputPath, fullFilePath, {
mode: fs.constants.COPYFILE_FICLONE,
preserveTimestamps: true,
});
}
});
await emitFilesToDisk(result.files, (file) =>
outputOptions.ignoreServer &&
(file.type === BuildOutputFileType.ServerApplication ||
file.type === BuildOutputFileType.ServerRoot)
? undefined
: generateFullPath(file.path, file.type, outputOptions),
);
} catch (error) {
context.logger.error(error instanceof Error ? error.message : String(error));
yield { success: false };
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/build/src/builders/application/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export interface BaseResultFile {

export interface DiskFile extends BaseResultFile {
origin: 'disk';
inputPath: string;
source: string;
}

export interface MemoryFile extends BaseResultFile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function updateResultRecord(
): void {
if (file.origin === 'disk') {
assetFiles.set('/' + normalizePath(file.path), {
source: normalizePath(file.inputPath),
source: normalizePath(file.source),
updated: !initial,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function setupLocalizeExtractor(
if (file?.origin === 'memory') {
content = textDecoder.decode(file.contents);
} else if (file?.origin === 'disk') {
content = readFileSync(file.inputPath, 'utf-8');
content = readFileSync(file.source, 'utf-8');
}
if (content === undefined) {
throw new Error('Unknown file requested: ' + requestedPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class AngularAssetsMiddleware {
// 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);
this.serveFile(file.source, undefined, res, undefined, undefined, /* doNotCache */ true);
break;
case 'memory':
// Include pathname to help with Content-Type headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
*/

import type { BuilderContext } from '@angular-devkit/architect';
import { constants, copyFile, mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
import { emitFilesToDisk } from '../../../tools/esbuild/utils';
import { emitFilesToDisk } from '../../../utils/emit-files-to-disk';
import { toPosixPath } from '../../../utils/path';
import type { NormalizedLibraryOptions, SingleBuildState } from '../types';
import { collectAssetsToEmit } from './assets';
Expand All @@ -27,7 +26,6 @@ export function createSingleBuildState(): SingleBuildState {
previousBundleResults: new Map(),
pendingChangedEsmFiles: new Set(),
pendingChangedDtsFiles: new Set(),
directoryExists: new Set(),
};
}

Expand Down Expand Up @@ -90,7 +88,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
} = actionContext;

const posixPackageJsonPath = toPosixPath(options.packageJsonPath);
const { pendingChangedEsmFiles, pendingChangedDtsFiles, directoryExists } = buildState;
const { pendingChangedEsmFiles, pendingChangedDtsFiles } = buildState;

if (!modifiedFiles || modifiedFiles.has(posixPackageJsonPath)) {
buildState.hasEmittedManifests = false;
Expand Down Expand Up @@ -231,20 +229,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
));
filesToEmit.push(...resolvedAssetsToEmit);

await emitFilesToDisk<OutputFile>(filesToEmit, async (file) => {
const fullFilePath = path.join(options.outputPath, file.path);
const fileBasePath = path.dirname(fullFilePath);
if (fileBasePath && !directoryExists.has(fileBasePath)) {
await mkdir(fileBasePath, { recursive: true });
directoryExists.add(fileBasePath);
}

if (file.type === 'memory') {
await writeFile(fullFilePath, file.contents);
} else {
await copyFile(file.source, fullFilePath, constants.COPYFILE_FICLONE);
}
});
await emitFilesToDisk(filesToEmit, (file) => path.join(options.outputPath, file.path));

buildState.hasEmittedManifests = true;
buildState.hasEmittedTslib = hasTslibImport;
Expand Down
8 changes: 4 additions & 4 deletions packages/angular/build/src/builders/library/pipeline/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const TYPES_OUTPUT_DIR = 'types';
* Represents an in-memory file to be emitted to disk.
*/
export interface MemoryOutputFile {
type: 'memory';
origin: 'memory';

/** The destination path where the file should be written. */
path: string;
Expand All @@ -39,7 +39,7 @@ export interface MemoryOutputFile {
* Represents an existing file on disk to be copied to a destination path.
*/
export interface DiskOutputFile {
type: 'disk';
origin: 'disk';

/** The path to the source file on disk. */
source: string;
Expand All @@ -62,7 +62,7 @@ export type OutputFile = MemoryOutputFile | DiskOutputFile;
*/
export function createDiskOutputFile(source: string, path: string): DiskOutputFile {
return {
type: 'disk',
origin: 'disk',
source,
path,
};
Expand All @@ -80,7 +80,7 @@ export function createMemoryOutputFile(
contents: string | Uint8Array | Record<string, unknown>,
): MemoryOutputFile {
return {
type: 'memory',
origin: 'memory',
path,
contents:
typeof contents === 'string' || contents instanceof Uint8Array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { rm } from 'node:fs/promises';
import { executeLibraryBuilder } from '../../builder';
import { BASE_OPTIONS, LIBRARY_BUILDER_INFO, describeLibraryBuilder } from '../setup';

Expand Down Expand Up @@ -563,5 +564,42 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
},
]);
});

it('should recreate deleted output directories on subsequent watch rebuilds', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
watch: true,
});

await harness.executeWithCases([
async ({ result }) => {
expect(result?.success).toBeTrue();
expect(harness.hasFile('dist/lib/fesm2022/lib.mjs')).toBeTrue();

// Remove output subdirectory externally between rebuilds
await rm(harness.resolvePath('dist/lib/fesm2022'), { recursive: true, force: true });
expect(harness.hasFile('dist/lib/fesm2022/lib.mjs')).toBeFalse();

// Trigger a rebuild
await harness.writeFile(
'projects/lib/src/lib/lib.component.ts',
`
import { Component } from '@angular/core';

@Component({
selector: 'lib-recreated',
template: '<span>Recreated</span>',
})
export class LibComponent {}
`,
);
},
({ result }) => {
expect(result?.success).toBeTrue();
const content = harness.readFile('dist/lib/fesm2022/lib.mjs');
expect(content).toContain('lib-recreated');
},
]);
});
});
});
3 changes: 0 additions & 3 deletions packages/angular/build/src/builders/library/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,4 @@ export interface SingleBuildState {

/** Whether entry points in package.json changed since the last compilation. */
hasEntryPointsChanges?: boolean;

/** Cache of output directory paths known to exist on disk. */
directoryExists: Set<string>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ async function loadResultFile(file: ResultFile): Promise<string> {
return textDecoder.decode(file.contents);
}

return readFile(file.inputPath, 'utf-8');
return readFile(file.source, 'utf-8');
}

export function createVitestPlugins(pluginOptions: PluginOptions): Vite.Plugin[] {
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/build/src/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export {

// Tools
export type { ExternalResultMetadata } from './tools/esbuild/bundler-execution-result';
export { emitFilesToDisk } from './tools/esbuild/utils';
export { transformSupportedBrowsersToTargets } from './tools/esbuild/target';
export { SassCompiler } from './tools/sass/sass-service';

Expand Down Expand Up @@ -73,6 +72,7 @@ export { ComponentStylesheetBundler } from './tools/esbuild/angular/component-st
export * from './utils/bundle-calculator';
export { checkPort } from './utils/check-port';
export { deleteOutputDir } from './utils/delete-output-dir';
export { emitFilesToDisk } from './utils/emit-files-to-disk';
export { type I18nOptions, createI18nOptions, loadTranslations } from './utils/i18n-options';
export {
IndexHtmlGenerator,
Expand Down
24 changes: 1 addition & 23 deletions packages/angular/build/src/tools/esbuild/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import { OutputMode } from '../../builders/application/schema';
import { BudgetCalculatorResult } from '../../utils/bundle-calculator';

import { BundleStats, generateEsbuildBuildStatsTable } from '../../utils/stats-table';
import {
BuildOutputAsset,
ExecutionResult,
PrerenderedRoutesRecord,
} from './bundler-execution-result';
import { ExecutionResult, PrerenderedRoutesRecord } from './bundler-execution-result';
import { type BuildOutputFile, BuildOutputFileType, type InitialFileRecord } from './bundler-files';

export function logBuildStats(
Expand Down Expand Up @@ -212,24 +208,6 @@ export function getFeatureSupport(nativeAsyncAwait: boolean): BuildOptions['supp
};
}

const MAX_CONCURRENT_WRITES = 64;
export async function emitFilesToDisk<T = BuildOutputAsset | BuildOutputFile>(
files: readonly T[],
writeFileCallback: (file: T) => Promise<void>,
): Promise<void> {
// Write files in groups of MAX_CONCURRENT_WRITES to avoid too many open files
for (let fileIndex = 0; fileIndex < files.length;) {
const groupMax = Math.min(fileIndex + MAX_CONCURRENT_WRITES, files.length);

const actions = [];
while (fileIndex < groupMax) {
actions.push(writeFileCallback(files[fileIndex++]));
}

await Promise.all(actions);
}
}

interface BuildManifest {
errors: string[];
warnings: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export function createBuildAssetsMiddleware(
const outputFile = buildResultFiles.get(pathname.slice(1));
if (outputFile) {
const contents =
outputFile.origin === 'memory' ? outputFile.contents : readHandler(outputFile.inputPath);
outputFile.origin === 'memory' ? outputFile.contents : readHandler(outputFile.source);

const etag = `W/${calculateHash(contents)}`;
if (checkAndHandleEtag(req, res, etag)) {
Expand Down
Loading
Loading