Skip to content

Commit 8c915d4

Browse files
committed
perf(@angular-devkit/build-angular): avoid extra babel file reads in esbuild builder rebuilds
To further improve incremental rebuild performance of the experimental esbuild-based browser application builder, the output of JS file babel transformations are now cached in memory by the input file name and invalidated via the file watching events. This allows an additional file read per JS input file to be avoided if the file has not changed. Previously the content of the JS input file was used as the basis of the cache key for the transformation output which necessitated reading the file. With the file change information available, the content based method is no longer necessary.
1 parent 6197a2d commit 8c915d4

File tree

1 file changed

+27
-43
lines changed

1 file changed

+27
-43
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/compiler-plugin.ts

+27-43
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,13 @@ const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g');
126126

127127
export class SourceFileCache extends Map<string, ts.SourceFile> {
128128
readonly modifiedFiles = new Set<string>();
129+
readonly babelFileCache = new Map<string, string>();
129130

130131
invalidate(files: Iterable<string>): void {
131132
this.modifiedFiles.clear();
132133
for (let file of files) {
134+
this.babelFileCache.delete(file);
135+
133136
// Normalize separators to allow matching TypeScript Host paths
134137
if (USING_WINDOWS) {
135138
file = file.replace(WINDOWS_SEP_REGEXP, path.posix.sep);
@@ -223,7 +226,7 @@ export function createCompilerPlugin(
223226

224227
let previousBuilder: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
225228
let previousAngularProgram: NgtscProgram | undefined;
226-
const babelMemoryCache = new Map<string, string>();
229+
const babelDataCache = new Map<string, string>();
227230

228231
build.onStart(async () => {
229232
const result: OnStartResult = {};
@@ -401,28 +404,38 @@ export function createCompilerPlugin(
401404
};
402405
}
403406

407+
const data = typescriptResult.content ?? '';
408+
// The pre-transformed data is used as a cache key. Since the cache is memory only,
409+
// the options cannot change and do not need to be represented in the key. If the
410+
// cache is later stored to disk, then the options that affect transform output
411+
// would need to be added to the key as well.
412+
let contents = babelDataCache.get(data);
413+
if (contents === undefined) {
414+
contents = await transformWithBabel(args.path, data, pluginOptions);
415+
babelDataCache.set(data, contents);
416+
}
417+
404418
return {
405-
contents: await transformWithBabelCached(
406-
args.path,
407-
typescriptResult.content ?? '',
408-
pluginOptions,
409-
babelMemoryCache,
410-
),
419+
contents,
411420
loader: 'js',
412421
};
413422
},
414423
);
415424

416425
build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
417-
const data = await fs.readFile(args.path, 'utf-8');
426+
// The filename is currently used as a cache key. Since the cache is memory only,
427+
// the options cannot change and do not need to be represented in the key. If the
428+
// cache is later stored to disk, then the options that affect transform output
429+
// would need to be added to the key as well as a check for any change of content.
430+
let contents = pluginOptions.sourceFileCache?.babelFileCache.get(args.path);
431+
if (contents === undefined) {
432+
const data = await fs.readFile(args.path, 'utf-8');
433+
contents = await transformWithBabel(args.path, data, pluginOptions);
434+
pluginOptions.sourceFileCache?.babelFileCache.set(args.path, contents);
435+
}
418436

419437
return {
420-
contents: await transformWithBabelCached(
421-
args.path,
422-
data,
423-
pluginOptions,
424-
babelMemoryCache,
425-
),
438+
contents,
426439
loader: 'js',
427440
};
428441
});
@@ -524,32 +537,3 @@ async function transformWithBabel(
524537

525538
return result?.code ?? data;
526539
}
527-
528-
/**
529-
* Transforms JavaScript file data using the babel transforms setup in transformWithBabel. The
530-
* supplied cache will be used to avoid repeating the transforms for data that has previously
531-
* been transformed such as in a previous rebuild cycle.
532-
* @param filename The file path of the data to be transformed.
533-
* @param data The file data that will be transformed.
534-
* @param pluginOptions Compiler plugin options that will be used to control the transformation.
535-
* @param cache A cache of previously transformed data that will be used to avoid repeat transforms.
536-
* @returns A promise containing the transformed data.
537-
*/
538-
async function transformWithBabelCached(
539-
filename: string,
540-
data: string,
541-
pluginOptions: CompilerPluginOptions,
542-
cache: Map<string, string>,
543-
): Promise<string> {
544-
// The pre-transformed data is used as a cache key. Since the cache is memory only,
545-
// the options cannot change and do not need to be represented in the key. If the
546-
// cache is later stored to disk, then the options that affect transform output
547-
// would need to be added to the key as well.
548-
let result = cache.get(data);
549-
if (result === undefined) {
550-
result = await transformWithBabel(filename, data, pluginOptions);
551-
cache.set(data, result);
552-
}
553-
554-
return result;
555-
}

0 commit comments

Comments
 (0)