@@ -17,7 +17,7 @@ import {
1717import { createHash } from 'crypto' ;
1818import * as fs from 'fs' ;
1919import * as path from 'path' ;
20- import { RawSourceMap } from 'source-map' ;
20+ import { RawSourceMap , SourceMapConsumer , SourceMapGenerator } from 'source-map' ;
2121import { minify } from 'terser' ;
2222import * as v8 from 'v8' ;
2323import { SourceMapSource } from 'webpack-sources' ;
@@ -27,6 +27,9 @@ import { I18nOptions } from './i18n-options';
2727const cacache = require ( 'cacache' ) ;
2828const deserialize = ( ( v8 as unknown ) as { deserialize ( buffer : Buffer ) : unknown } ) . deserialize ;
2929
30+ // If code size is larger than 500KB, consider lower fidelity but faster sourcemap merge
31+ const FAST_SOURCEMAP_THRESHOLD = 500 * 1024 ;
32+
3033export interface ProcessBundleOptions {
3134 filename : string ;
3235 code : string ;
@@ -143,12 +146,18 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
143146 downlevelCode = transformResult . code ;
144147
145148 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 (
147153 sourceCode ,
148154 sourceMap ,
149155 downlevelCode ,
150156 transformResult . map ,
151157 filename ,
158+ // When not optimizing, the sourcemaps are significantly less complex
159+ // and can use the higher fidelity merge
160+ ! ! options . optimize && fastSourceMaps ,
152161 ) ;
153162 }
154163 }
@@ -173,14 +182,19 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
173182 return result ;
174183}
175184
176- // SourceMapSource produces high-quality sourcemaps
177- function mergeSourceMaps (
185+ async function mergeSourceMaps (
178186 inputCode : string ,
179187 inputSourceMap : RawSourceMap ,
180188 resultCode : string ,
181189 resultSourceMap : RawSourceMap ,
182190 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
184198 // The last argument is not yet in the typings
185199 // tslint:disable-next-line: no-any
186200 return new ( SourceMapSource as any ) (
@@ -193,6 +207,58 @@ function mergeSourceMaps(
193207 ) . map ( ) ;
194208}
195209
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+
196262async function processBundle (
197263 options : Omit < ProcessBundleOptions , 'map' > & { isOriginal : boolean ; map ?: string | RawSourceMap } ,
198264) : Promise < ProcessBundleFile > {
@@ -220,7 +286,7 @@ async function processBundle(
220286 }
221287
222288 if ( optimize ) {
223- result = terserMangle ( code , {
289+ result = await terserMangle ( code , {
224290 filename,
225291 map : rawMap ,
226292 compress : ! isOriginal , // We only compress bundles which are downlevelled.
@@ -265,7 +331,7 @@ async function processBundle(
265331 return fileResult ;
266332}
267333
268- function terserMangle (
334+ async function terserMangle (
269335 code : string ,
270336 options : { filename ?: string ; map ?: RawSourceMap ; compress ?: boolean ; ecma ?: 5 | 6 } = { } ,
271337) {
@@ -301,12 +367,13 @@ function terserMangle(
301367
302368 let outputMap ;
303369 if ( options . map && minifyOutput . map ) {
304- outputMap = mergeSourceMaps (
370+ outputMap = await mergeSourceMaps (
305371 code ,
306372 options . map ,
307373 outputCode ,
308374 minifyOutput . map as unknown as RawSourceMap ,
309375 options . filename || '0' ,
376+ code . length > FAST_SOURCEMAP_THRESHOLD ,
310377 ) ;
311378 }
312379
@@ -479,7 +546,7 @@ export async function inlineLocales(options: InlineOptions) {
479546 // If locale data is provided, load it and prepend to file
480547 const localeDataPath = i18n . locales [ locale ] && i18n . locales [ locale ] . dataPath ;
481548 if ( localeDataPath ) {
482- const localDataContent = loadLocaleData ( localeDataPath , true ) ;
549+ const localDataContent = await loadLocaleData ( localeDataPath , true ) ;
483550 // The semicolon ensures that there is no syntax error between statements
484551 content . prepend ( localDataContent + ';' ) ;
485552 }
@@ -501,6 +568,7 @@ export async function inlineLocales(options: InlineOptions) {
501568 output ,
502569 contentMap ,
503570 options . filename ,
571+ options . code . length > FAST_SOURCEMAP_THRESHOLD ,
504572 ) ;
505573
506574 fs . writeFileSync ( outputPath + '.map' , JSON . stringify ( outputMap ) ) ;
@@ -612,13 +680,13 @@ function findLocalizePositions(
612680 return positions ;
613681}
614682
615- function loadLocaleData ( path : string , optimize : boolean ) : string {
683+ async function loadLocaleData ( path : string , optimize : boolean ) : Promise < string > {
616684 // The path is validated during option processing before the build starts
617685 const content = fs . readFileSync ( path , 'utf8' ) ;
618686
619687 // NOTE: This can be removed once the locale data files are preprocessed in the framework
620688 if ( optimize ) {
621- const result = terserMangle ( content , {
689+ const result = await terserMangle ( content , {
622690 compress : true ,
623691 ecma : 5 ,
624692 } ) ;
0 commit comments