@@ -13,6 +13,8 @@ import { removeSourceMappingURL } from '../../utils/source-map';
1313import { WorkerPool , WorkerPoolOptions } from '../../utils/worker-pool' ;
1414import { Cache } from './cache' ;
1515
16+ const SOURCEMAP_COMMENT_BYTES = Buffer . from ( 'sourceMappingURL=' ) ;
17+
1618/**
1719 * Transformation options that should apply to all transformed files and data.
1820 */
@@ -132,50 +134,39 @@ export class JavaScriptTransformer {
132134 return this . #runWithThrottle( async ( ) => {
133135 const data = await readFile ( filename ) ;
134136
135- let result ;
136- let cacheKey ;
137+ let cacheKey : string | undefined ;
137138 if ( this . cache ) {
138139 // Create a cache key from the file data and options that effect the output.
139140 // NOTE: If additional options are added, this may need to be updated.
140- // TODO: Consider xxhash or similar instead of SHA256
141141 const hash = createHash ( 'sha256' ) ;
142142 hash . update ( `${ ! ! skipLinker } --${ ! ! sideEffects } ` ) ;
143143 hash . update ( data ) ;
144144 hash . update ( this . #fileCacheKeyBase) ;
145145 cacheKey = hash . digest ( 'hex' ) ;
146146
147147 try {
148- result = await this . cache ?. get ( cacheKey ) ;
148+ const cached = await this . cache . get ( cacheKey ) ;
149+ if ( cached !== undefined ) {
150+ return cached ;
151+ }
149152 } catch {
150153 // Failure to get the value should not fail the transform
151154 }
152155 }
153156
154- if ( result === undefined ) {
155- // If there is no cache or no cached entry, process the file
156- result = ( await this . #ensureWorkerPool( ) . run (
157- {
158- filename,
159- data,
160- skipLinker,
161- sideEffects,
162- instrumentForCoverage,
163- ...this . #commonOptions,
164- } ,
165- {
166- // The below is disable as with Yarn PNP this causes build failures with the below message
167- // `Unable to deserialize cloned data`.
168- transferList : process . versions . pnp ? undefined : [ data . buffer ] ,
169- } ,
170- ) ) as Uint8Array ;
171-
172- // If there is a cache then store the result
173- if ( this . cache && cacheKey ) {
174- try {
175- await this . cache . put ( cacheKey , result ) ;
176- } catch {
177- // Failure to store the value in the cache should not fail the transform
178- }
157+ const result = await this . transformData (
158+ filename ,
159+ data ,
160+ ! ! skipLinker ,
161+ sideEffects ,
162+ instrumentForCoverage ,
163+ ) ;
164+
165+ if ( this . cache && cacheKey ) {
166+ try {
167+ await this . cache . put ( cacheKey , result ) ;
168+ } catch {
169+ // Failure to store the value in the cache should not fail the transform
179170 }
180171 }
181172
@@ -194,7 +185,7 @@ export class JavaScriptTransformer {
194185 */
195186 async transformData (
196187 filename : string ,
197- data : string ,
188+ data : string | Uint8Array ,
198189 skipLinker : boolean ,
199190 sideEffects ?: boolean ,
200191 instrumentForCoverage ?: boolean ,
@@ -206,18 +197,52 @@ export class JavaScriptTransformer {
206197 this . #commonOptions. sourcemap &&
207198 ( ! ! this . #commonOptions. thirdPartySourcemaps || ! / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] / . test ( filename ) ) ;
208199
209- return Buffer . from ( keepSourcemap ? data : removeSourceMappingURL ( data ) , 'utf-8' ) ;
200+ if ( typeof data === 'string' ) {
201+ return Buffer . from ( keepSourcemap ? data : removeSourceMappingURL ( data ) , 'utf-8' ) ;
202+ }
203+
204+ if ( keepSourcemap ) {
205+ return data ;
206+ }
207+
208+ const dataBuffer = Buffer . isBuffer ( data )
209+ ? data
210+ : Buffer . from ( data . buffer , data . byteOffset , data . byteLength ) ;
211+
212+ // Fast check on raw ASCII bytes to avoid UTF-8 string decoding if no comment exists.
213+ if ( dataBuffer . indexOf ( SOURCEMAP_COMMENT_BYTES ) === - 1 ) {
214+ return data ;
215+ }
216+
217+ const text = dataBuffer . toString ( 'utf-8' ) ;
218+ const stripped = removeSourceMappingURL ( text ) ;
219+
220+ return stripped === text ? data : Buffer . from ( stripped , 'utf-8' ) ;
210221 }
211222
212- return this . #runWithThrottle( ( ) =>
213- this . #ensureWorkerPool( ) . run ( {
223+ // Only standalone (non-pooled) ArrayBuffers can be transferred across worker threads.
224+ // Node.js shares an internal 8KB ArrayBuffer pool for small buffers, and transferring
225+ // a pooled buffer will throw a DataCloneError because detaching it invalidates other slices.
226+ // In addition, SharedArrayBuffers cannot be transferred, and Yarn PnP has deserialization issues.
227+ const isTransferable =
228+ typeof data !== 'string' &&
229+ data . buffer instanceof ArrayBuffer &&
230+ data . byteOffset === 0 &&
231+ data . byteLength === data . buffer . byteLength &&
232+ ! process . versions . pnp ;
233+
234+ return this . #ensureWorkerPool( ) . run (
235+ {
214236 filename,
215237 data,
216238 skipLinker,
217239 sideEffects,
218240 instrumentForCoverage,
219241 ...this . #commonOptions,
220- } ) ,
242+ } ,
243+ {
244+ transferList : isTransferable ? [ data . buffer ] : undefined ,
245+ } ,
221246 ) ;
222247 }
223248
0 commit comments