6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
+ import { createHash } from 'node:crypto' ;
10
+ import { readFile } from 'node:fs/promises' ;
9
11
import Piscina from 'piscina' ;
12
+ import { Cache } from './cache' ;
10
13
11
14
/**
12
15
* Transformation options that should apply to all transformed files and data.
@@ -28,12 +31,12 @@ export interface JavaScriptTransformerOptions {
28
31
export class JavaScriptTransformer {
29
32
#workerPool: Piscina | undefined ;
30
33
#commonOptions: Required < JavaScriptTransformerOptions > ;
31
- #pendingfileResults?: Map < string , Promise < Uint8Array > > ;
34
+ #fileCacheKeyBase: Uint8Array ;
32
35
33
36
constructor (
34
37
options : JavaScriptTransformerOptions ,
35
38
readonly maxThreads : number ,
36
- reuseResults ?: boolean ,
39
+ private readonly cache ?: Cache < Uint8Array > ,
37
40
) {
38
41
// Extract options to ensure only the named options are serialized and sent to the worker
39
42
const {
@@ -48,11 +51,7 @@ export class JavaScriptTransformer {
48
51
advancedOptimizations,
49
52
jit,
50
53
} ;
51
-
52
- // Currently only tracks pending file transform results
53
- if ( reuseResults ) {
54
- this . #pendingfileResults = new Map ( ) ;
55
- }
54
+ this . #fileCacheKeyBase = Buffer . from ( JSON . stringify ( this . #commonOptions) , 'utf-8' ) ;
56
55
}
57
56
58
57
#ensureWorkerPool( ) : Piscina {
@@ -75,27 +74,56 @@ export class JavaScriptTransformer {
75
74
* @param sideEffects If false, and `advancedOptimizations` is enabled tslib decorators are wrapped.
76
75
* @returns A promise that resolves to a UTF-8 encoded Uint8Array containing the result.
77
76
*/
78
- transformFile (
77
+ async transformFile (
79
78
filename : string ,
80
79
skipLinker ?: boolean ,
81
80
sideEffects ?: boolean ,
82
81
) : Promise < Uint8Array > {
83
- const pendingKey = `${ ! ! skipLinker } --${ filename } ` ;
84
- let pending = this . #pendingfileResults?. get ( pendingKey ) ;
85
- if ( pending === undefined ) {
86
- // Always send the request to a worker. Files are almost always from node modules which means
87
- // they may need linking. The data is also not yet available to perform most transformation checks.
88
- pending = this . #ensureWorkerPool( ) . run ( {
89
- filename,
90
- skipLinker,
91
- sideEffects,
92
- ...this . #commonOptions,
93
- } ) ;
94
-
95
- this . #pendingfileResults?. set ( pendingKey , pending ) ;
82
+ const data = await readFile ( filename ) ;
83
+
84
+ let result ;
85
+ let cacheKey ;
86
+ if ( this . cache ) {
87
+ // Create a cache key from the file data and options that effect the output.
88
+ // NOTE: If additional options are added, this may need to be updated.
89
+ // TODO: Consider xxhash or similar instead of SHA256
90
+ const hash = createHash ( 'sha256' ) ;
91
+ hash . update ( `${ ! ! skipLinker } --${ ! ! sideEffects } ` ) ;
92
+ hash . update ( data ) ;
93
+ hash . update ( this . #fileCacheKeyBase) ;
94
+ cacheKey = hash . digest ( 'hex' ) ;
95
+
96
+ try {
97
+ result = await this . cache ?. get ( cacheKey ) ;
98
+ } catch {
99
+ // Failure to get the value should not fail the transform
100
+ }
96
101
}
97
102
98
- return pending ;
103
+ if ( result === undefined ) {
104
+ // If there is no cache or no cached entry, process the file
105
+ result = ( await this . #ensureWorkerPool( ) . run (
106
+ {
107
+ filename,
108
+ data,
109
+ skipLinker,
110
+ sideEffects,
111
+ ...this . #commonOptions,
112
+ } ,
113
+ { transferList : [ data . buffer ] } ,
114
+ ) ) as Uint8Array ;
115
+
116
+ // If there is a cache then store the result
117
+ if ( this . cache && cacheKey ) {
118
+ try {
119
+ await this . cache . put ( cacheKey , result ) ;
120
+ } catch {
121
+ // Failure to store the value in the cache should not fail the transform
122
+ }
123
+ }
124
+ }
125
+
126
+ return result ;
99
127
}
100
128
101
129
/**
@@ -140,8 +168,6 @@ export class JavaScriptTransformer {
140
168
* @returns A void promise that resolves when closing is complete.
141
169
*/
142
170
async close ( ) : Promise < void > {
143
- this . #pendingfileResults?. clear ( ) ;
144
-
145
171
if ( this . #workerPool) {
146
172
try {
147
173
await this . #workerPool. destroy ( ) ;
0 commit comments