8
8
9
9
import type { CompilerHost , NgtscProgram } from '@angular/compiler-cli' ;
10
10
import { transformAsync } from '@babel/core' ;
11
- import * as assert from 'assert' ;
12
11
import type {
13
12
OnStartResult ,
14
13
OutputFile ,
@@ -17,9 +16,11 @@ import type {
17
16
Plugin ,
18
17
PluginBuild ,
19
18
} from 'esbuild' ;
20
- import { promises as fs } from 'fs' ;
21
- import { platform } from 'os' ;
22
- import * as path from 'path' ;
19
+ import * as assert from 'node:assert' ;
20
+ import * as fs from 'node:fs/promises' ;
21
+ import { platform } from 'node:os' ;
22
+ import * as path from 'node:path' ;
23
+ import { pathToFileURL } from 'node:url' ;
23
24
import ts from 'typescript' ;
24
25
import angularApplicationPreset from '../../babel/presets/application' ;
25
26
import { requiresLinking } from '../../babel/webpack-loader' ;
@@ -133,11 +134,13 @@ const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g');
133
134
export class SourceFileCache extends Map < string , ts . SourceFile > {
134
135
readonly modifiedFiles = new Set < string > ( ) ;
135
136
readonly babelFileCache = new Map < string , Uint8Array > ( ) ;
137
+ readonly typeScriptFileCache = new Map < string , Uint8Array > ( ) ;
136
138
137
139
invalidate ( files : Iterable < string > ) : void {
138
140
this . modifiedFiles . clear ( ) ;
139
141
for ( let file of files ) {
140
142
this . babelFileCache . delete ( file ) ;
143
+ this . typeScriptFileCache . delete ( pathToFileURL ( file ) . href ) ;
141
144
142
145
// Normalize separators to allow matching TypeScript Host paths
143
146
if ( USING_WINDOWS ) {
@@ -355,6 +358,17 @@ export function createCompilerPlugin(
355
358
previousBuilder = builder ;
356
359
357
360
await profileAsync ( 'NG_ANALYZE_PROGRAM' , ( ) => angularCompiler . analyzeAsync ( ) ) ;
361
+ const affectedFiles = profileSync ( 'NG_FIND_AFFECTED' , ( ) =>
362
+ findAffectedFiles ( builder , angularCompiler ) ,
363
+ ) ;
364
+
365
+ if ( pluginOptions . sourceFileCache ) {
366
+ for ( const affected of affectedFiles ) {
367
+ pluginOptions . sourceFileCache . typeScriptFileCache . delete (
368
+ pathToFileURL ( affected . fileName ) . href ,
369
+ ) ;
370
+ }
371
+ }
358
372
359
373
function * collectDiagnostics ( ) : Iterable < ts . Diagnostic > {
360
374
// Collect program level diagnostics
@@ -364,7 +378,6 @@ export function createCompilerPlugin(
364
378
yield * builder . getGlobalDiagnostics ( ) ;
365
379
366
380
// Collect source file specific diagnostics
367
- const affectedFiles = findAffectedFiles ( builder , angularCompiler ) ;
368
381
const optimizeFor =
369
382
affectedFiles . size > 1 ? OptimizeFor . WholeProgram : OptimizeFor . SingleFile ;
370
383
for ( const sourceFile of builder . getSourceFiles ( ) ) {
@@ -434,41 +447,56 @@ export function createCompilerPlugin(
434
447
async ( ) => {
435
448
assert . ok ( fileEmitter , 'Invalid plugin execution order' ) ;
436
449
437
- const typescriptResult = await fileEmitter (
438
- pluginOptions . fileReplacements ?. [ args . path ] ?? args . path ,
450
+ // The filename is currently used as a cache key. Since the cache is memory only,
451
+ // the options cannot change and do not need to be represented in the key. If the
452
+ // cache is later stored to disk, then the options that affect transform output
453
+ // would need to be added to the key as well as a check for any change of content.
454
+ let contents = pluginOptions . sourceFileCache ?. typeScriptFileCache . get (
455
+ pathToFileURL ( args . path ) . href ,
439
456
) ;
440
- if ( ! typescriptResult ) {
441
- // No TS result indicates the file is not part of the TypeScript program.
442
- // If allowJs is enabled and the file is JS then defer to the next load hook.
443
- if ( compilerOptions . allowJs && / \. [ c m ] ? j s $ / . test ( args . path ) ) {
444
- return undefined ;
457
+
458
+ if ( contents === undefined ) {
459
+ const typescriptResult = await fileEmitter (
460
+ pluginOptions . fileReplacements ?. [ args . path ] ?? args . path ,
461
+ ) ;
462
+ if ( ! typescriptResult ) {
463
+ // No TS result indicates the file is not part of the TypeScript program.
464
+ // If allowJs is enabled and the file is JS then defer to the next load hook.
465
+ if ( compilerOptions . allowJs && / \. [ c m ] ? j s $ / . test ( args . path ) ) {
466
+ return undefined ;
467
+ }
468
+
469
+ // Otherwise return an error
470
+ return {
471
+ errors : [
472
+ {
473
+ text : `File '${ args . path } ' is missing from the TypeScript compilation.` ,
474
+ notes : [
475
+ {
476
+ text : `Ensure the file is part of the TypeScript program via the 'files' or 'include' property.` ,
477
+ } ,
478
+ ] ,
479
+ } ,
480
+ ] ,
481
+ } ;
445
482
}
446
483
447
- // Otherwise return an error
448
- return {
449
- errors : [
450
- {
451
- text : `File '${ args . path } ' is missing from the TypeScript compilation.` ,
452
- notes : [
453
- {
454
- text : `Ensure the file is part of the TypeScript program via the 'files' or 'include' property.` ,
455
- } ,
456
- ] ,
457
- } ,
458
- ] ,
459
- } ;
460
- }
484
+ const data = typescriptResult . content ?? '' ;
485
+ // The pre-transformed data is used as a cache key. Since the cache is memory only,
486
+ // the options cannot change and do not need to be represented in the key. If the
487
+ // cache is later stored to disk, then the options that affect transform output
488
+ // would need to be added to the key as well.
489
+ contents = babelDataCache . get ( data ) ;
490
+ if ( contents === undefined ) {
491
+ const transformedData = await transformWithBabel ( args . path , data , pluginOptions ) ;
492
+ contents = Buffer . from ( transformedData , 'utf-8' ) ;
493
+ babelDataCache . set ( data , contents ) ;
494
+ }
461
495
462
- const data = typescriptResult . content ?? '' ;
463
- // The pre-transformed data is used as a cache key. Since the cache is memory only,
464
- // the options cannot change and do not need to be represented in the key. If the
465
- // cache is later stored to disk, then the options that affect transform output
466
- // would need to be added to the key as well.
467
- let contents = babelDataCache . get ( data ) ;
468
- if ( contents === undefined ) {
469
- const transformedData = await transformWithBabel ( args . path , data , pluginOptions ) ;
470
- contents = Buffer . from ( transformedData , 'utf-8' ) ;
471
- babelDataCache . set ( data , contents ) ;
496
+ pluginOptions . sourceFileCache ?. typeScriptFileCache . set (
497
+ pathToFileURL ( args . path ) . href ,
498
+ contents ,
499
+ ) ;
472
500
}
473
501
474
502
return {
0 commit comments