@@ -17,7 +17,7 @@ import {
17
17
import { createHash } from 'crypto' ;
18
18
import * as fs from 'fs' ;
19
19
import * as path from 'path' ;
20
- import { RawSourceMap } from 'source-map' ;
20
+ import { RawSourceMap , SourceMapConsumer , SourceMapGenerator } from 'source-map' ;
21
21
import { minify } from 'terser' ;
22
22
import * as v8 from 'v8' ;
23
23
import { SourceMapSource } from 'webpack-sources' ;
@@ -27,6 +27,9 @@ import { I18nOptions } from './i18n-options';
27
27
const cacache = require ( 'cacache' ) ;
28
28
const deserialize = ( ( v8 as unknown ) as { deserialize ( buffer : Buffer ) : unknown } ) . deserialize ;
29
29
30
+ // If code size is larger than 500KB, consider lower fidelity but faster sourcemap merge
31
+ const FAST_SOURCEMAP_THRESHOLD = 500 * 1024 ;
32
+
30
33
export interface ProcessBundleOptions {
31
34
filename : string ;
32
35
code : string ;
@@ -143,12 +146,18 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
143
146
downlevelCode = transformResult . code ;
144
147
145
148
if ( sourceMap && transformResult . map ) {
146
- downlevelMap = mergeSourceMaps (
149
+ // String length is used as an estimate for byte length
150
+ const fastSourceMaps = sourceCode . length > FAST_SOURCEMAP_THRESHOLD ;
151
+
152
+ downlevelMap = await mergeSourceMaps (
147
153
sourceCode ,
148
154
sourceMap ,
149
155
downlevelCode ,
150
156
transformResult . map ,
151
157
filename ,
158
+ // When not optimizing, the sourcemaps are significantly less complex
159
+ // and can use the higher fidelity merge
160
+ ! ! options . optimize && fastSourceMaps ,
152
161
) ;
153
162
}
154
163
}
@@ -173,14 +182,19 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
173
182
return result ;
174
183
}
175
184
176
- // SourceMapSource produces high-quality sourcemaps
177
- function mergeSourceMaps (
185
+ async function mergeSourceMaps (
178
186
inputCode : string ,
179
187
inputSourceMap : RawSourceMap ,
180
188
resultCode : string ,
181
189
resultSourceMap : RawSourceMap ,
182
190
filename : string ,
183
- ) : RawSourceMap {
191
+ fast = false ,
192
+ ) : Promise < RawSourceMap > {
193
+ if ( fast ) {
194
+ return mergeSourceMapsFast ( inputSourceMap , resultSourceMap ) ;
195
+ }
196
+
197
+ // SourceMapSource produces high-quality sourcemaps
184
198
// The last argument is not yet in the typings
185
199
// tslint:disable-next-line: no-any
186
200
return new ( SourceMapSource as any ) (
@@ -193,6 +207,58 @@ function mergeSourceMaps(
193
207
) . map ( ) ;
194
208
}
195
209
210
+ async function mergeSourceMapsFast ( first : RawSourceMap , second : RawSourceMap ) {
211
+ const sourceRoot = first . sourceRoot ;
212
+ const generator = new SourceMapGenerator ( ) ;
213
+
214
+ // sourcemap package adds the sourceRoot to all position source paths if not removed
215
+ delete first . sourceRoot ;
216
+
217
+ await SourceMapConsumer . with ( first , null , originalConsumer => {
218
+ return SourceMapConsumer . with ( second , null , newConsumer => {
219
+ newConsumer . eachMapping ( mapping => {
220
+ if ( mapping . originalLine === null ) {
221
+ return ;
222
+ }
223
+ const originalPosition = originalConsumer . originalPositionFor ( {
224
+ line : mapping . originalLine ,
225
+ column : mapping . originalColumn ,
226
+ } ) ;
227
+ if (
228
+ originalPosition . line === null ||
229
+ originalPosition . column === null ||
230
+ originalPosition . source === null
231
+ ) {
232
+ return ;
233
+ }
234
+ generator . addMapping ( {
235
+ generated : {
236
+ line : mapping . generatedLine ,
237
+ column : mapping . generatedColumn ,
238
+ } ,
239
+ name : originalPosition . name || undefined ,
240
+ original : {
241
+ line : originalPosition . line ,
242
+ column : originalPosition . column ,
243
+ } ,
244
+ source : originalPosition . source ,
245
+ } ) ;
246
+ } ) ;
247
+ } ) ;
248
+ } ) ;
249
+
250
+ const map = generator . toJSON ( ) ;
251
+ map . file = second . file ;
252
+ map . sourceRoot = sourceRoot ;
253
+
254
+ // Put the sourceRoot back
255
+ if ( sourceRoot ) {
256
+ first . sourceRoot = sourceRoot ;
257
+ }
258
+
259
+ return map ;
260
+ }
261
+
196
262
async function processBundle (
197
263
options : Omit < ProcessBundleOptions , 'map' > & { isOriginal : boolean ; map ?: string | RawSourceMap } ,
198
264
) : Promise < ProcessBundleFile > {
@@ -220,7 +286,7 @@ async function processBundle(
220
286
}
221
287
222
288
if ( optimize ) {
223
- result = terserMangle ( code , {
289
+ result = await terserMangle ( code , {
224
290
filename,
225
291
map : rawMap ,
226
292
compress : ! isOriginal , // We only compress bundles which are downlevelled.
@@ -265,7 +331,7 @@ async function processBundle(
265
331
return fileResult ;
266
332
}
267
333
268
- function terserMangle (
334
+ async function terserMangle (
269
335
code : string ,
270
336
options : { filename ?: string ; map ?: RawSourceMap ; compress ?: boolean ; ecma ?: 5 | 6 } = { } ,
271
337
) {
@@ -301,12 +367,13 @@ function terserMangle(
301
367
302
368
let outputMap ;
303
369
if ( options . map && minifyOutput . map ) {
304
- outputMap = mergeSourceMaps (
370
+ outputMap = await mergeSourceMaps (
305
371
code ,
306
372
options . map ,
307
373
outputCode ,
308
374
minifyOutput . map as unknown as RawSourceMap ,
309
375
options . filename || '0' ,
376
+ code . length > FAST_SOURCEMAP_THRESHOLD ,
310
377
) ;
311
378
}
312
379
@@ -479,7 +546,7 @@ export async function inlineLocales(options: InlineOptions) {
479
546
// If locale data is provided, load it and prepend to file
480
547
const localeDataPath = i18n . locales [ locale ] && i18n . locales [ locale ] . dataPath ;
481
548
if ( localeDataPath ) {
482
- const localDataContent = loadLocaleData ( localeDataPath , true ) ;
549
+ const localDataContent = await loadLocaleData ( localeDataPath , true ) ;
483
550
// The semicolon ensures that there is no syntax error between statements
484
551
content . prepend ( localDataContent + ';' ) ;
485
552
}
@@ -501,6 +568,7 @@ export async function inlineLocales(options: InlineOptions) {
501
568
output ,
502
569
contentMap ,
503
570
options . filename ,
571
+ options . code . length > FAST_SOURCEMAP_THRESHOLD ,
504
572
) ;
505
573
506
574
fs . writeFileSync ( outputPath + '.map' , JSON . stringify ( outputMap ) ) ;
@@ -612,13 +680,13 @@ function findLocalizePositions(
612
680
return positions ;
613
681
}
614
682
615
- function loadLocaleData ( path : string , optimize : boolean ) : string {
683
+ async function loadLocaleData ( path : string , optimize : boolean ) : Promise < string > {
616
684
// The path is validated during option processing before the build starts
617
685
const content = fs . readFileSync ( path , 'utf8' ) ;
618
686
619
687
// NOTE: This can be removed once the locale data files are preprocessed in the framework
620
688
if ( optimize ) {
621
- const result = terserMangle ( content , {
689
+ const result = await terserMangle ( content , {
622
690
compress : true ,
623
691
ecma : 5 ,
624
692
} ) ;
0 commit comments