Skip to content

Commit

Permalink
perf(@angular-devkit/build-angular): reuse esbuild generated output f…
Browse files Browse the repository at this point in the history
…ile hashes

The development server used with the esbuild-based builders (`application`/`browser-esbuild`) will
now use the recently introduced hash values provided by esbuild in its output files. This removes
the need for each file to be hashed and analyzed on each rebuild during the development server
update analysis.

(cherry picked from commit 8321463)
  • Loading branch information
clydin authored and dgp1130 committed Oct 20, 2023
1 parent 6beef14 commit c28475d
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 25 deletions.
Expand Up @@ -35,16 +35,11 @@ import type { DevServerBuilderOutput } from './webpack-server';
interface OutputFileRecord {
contents: Uint8Array;
size: number;
hash?: Buffer;
hash?: string;
updated: boolean;
servable: boolean;
}

function hashContent(contents: BinaryLike): Buffer {
// TODO: Consider xxhash
return createHash('sha256').update(contents).digest();
}

export async function* serveWithVite(
serverOptions: NormalizedDevServerOptions,
builderName: string,
Expand Down Expand Up @@ -306,27 +301,22 @@ function analyzeResultFiles(
continue;
}

let fileHash: Buffer | undefined;
const existingRecord = generatedFiles.get(filePath);
if (existingRecord && existingRecord.size === file.contents.byteLength) {
// Only hash existing file when needed
if (existingRecord.hash === undefined) {
existingRecord.hash = hashContent(existingRecord.contents);
}

// Compare against latest result output
fileHash = hashContent(file.contents);
if (fileHash.equals(existingRecord.hash)) {
// Same file
existingRecord.updated = false;
continue;
}
if (
existingRecord &&
existingRecord.size === file.contents.byteLength &&
existingRecord.hash === file.hash
) {
// Same file
existingRecord.updated = false;
continue;
}

// New or updated file
generatedFiles.set(filePath, {
contents: file.contents,
size: file.contents.byteLength,
hash: fileHash,
hash: file.hash,
updated: true,
servable:
file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media,
Expand Down
Expand Up @@ -17,7 +17,7 @@ import {
context,
} from 'esbuild';
import { basename, dirname, extname, join, relative } from 'node:path';
import { createOutputFileFromData, createOutputFileFromText } from './utils';
import { convertOutputFile } from './utils';

export type BundleContextResult =
| { errors: Message[]; warnings: Message[] }
Expand Down Expand Up @@ -231,9 +231,9 @@ export class BundlerContext {
}
}

const outputFiles = result.outputFiles.map(({ contents, path }) => {
const outputFiles = result.outputFiles.map((file) => {
let fileType: BuildOutputFileType;
if (dirname(path) === 'media') {
if (dirname(file.path) === 'media') {
fileType = BuildOutputFileType.Media;
} else {
fileType =
Expand All @@ -242,7 +242,7 @@ export class BundlerContext {
: BuildOutputFileType.Browser;
}

return createOutputFileFromData(path, contents, fileType);
return convertOutputFile(file, fileType);
});

// Return the successful build results
Expand Down
24 changes: 24 additions & 0 deletions packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts
Expand Up @@ -281,6 +281,30 @@ export function createOutputFileFromData(
};
}

export function convertOutputFile(file: OutputFile, type: BuildOutputFileType): BuildOutputFile {
const { path, contents, hash } = file;

return {
contents,
hash,
path,
type,
get text() {
return Buffer.from(
this.contents.buffer,
this.contents.byteOffset,
this.contents.byteLength,
).toString('utf-8');
},
get fullOutputPath(): string {
return getFullOutputPath(this);
},
clone(): BuildOutputFile {
return convertOutputFile(this, this.type);
},
};
}

export function getFullOutputPath(file: BuildOutputFile): string {
switch (file.type) {
case BuildOutputFileType.Browser:
Expand Down

0 comments on commit c28475d

Please sign in to comment.