6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
+ import remapping from '@ampproject/remapping' ;
9
10
import {
10
11
NodePath ,
11
12
ParseResult ,
@@ -21,14 +22,16 @@ import * as cacache from 'cacache';
21
22
import { createHash } from 'crypto' ;
22
23
import * as fs from 'fs' ;
23
24
import * as path from 'path' ;
24
- import { RawSourceMap , SourceMapConsumer , SourceMapGenerator } from 'source-map' ;
25
25
import { minify } from 'terser' ;
26
26
import { workerData } from 'worker_threads' ;
27
27
import { allowMangle , allowMinify , shouldBeautify } from './environment-options' ;
28
28
import { I18nOptions } from './i18n-options' ;
29
29
30
30
type LocalizeUtilities = typeof import ( '@angular/localize/src/tools/src/source_file_utils' ) ;
31
31
32
+ // Extract Sourcemap input type from the remapping function since it is not currently exported
33
+ type SourceMapInput = Exclude < Parameters < typeof remapping > [ 0 ] , unknown [ ] > ;
34
+
32
35
// Lazy loaded webpack-sources object
33
36
// Webpack is only imported if needed during the processing
34
37
let webpackSources : typeof import ( 'webpack' ) . sources | undefined ;
@@ -114,10 +117,7 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
114
117
const downlevelFilename = filename . replace ( / \- ( e s 2 0 \d { 2 } | e s n e x t ) / , '-es5' ) ;
115
118
const downlevel = ! options . optimizeOnly ;
116
119
const sourceCode = options . code ;
117
- const sourceMap = options . map ? JSON . parse ( options . map ) : undefined ;
118
120
119
- let downlevelCode ;
120
- let downlevelMap ;
121
121
if ( downlevel ) {
122
122
const { supportedBrowsers : targets = [ ] } = options ;
123
123
@@ -158,36 +158,17 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
158
158
] ,
159
159
minified : allowMinify && ! ! options . optimize ,
160
160
compact : ! shouldBeautify && ! ! options . optimize ,
161
- sourceMaps : ! ! sourceMap ,
161
+ sourceMaps : ! ! options . map ,
162
162
} ) ;
163
163
164
164
if ( ! transformResult || ! transformResult . code ) {
165
165
throw new Error ( `Unknown error occurred processing bundle for "${ options . filename } ".` ) ;
166
166
}
167
- downlevelCode = transformResult . code ;
168
-
169
- if ( sourceMap && transformResult . map ) {
170
- // String length is used as an estimate for byte length
171
- const fastSourceMaps = sourceCode . length > FAST_SOURCEMAP_THRESHOLD ;
172
-
173
- downlevelMap = await mergeSourceMaps (
174
- sourceCode ,
175
- sourceMap ,
176
- downlevelCode ,
177
- transformResult . map ,
178
- filename ,
179
- // When not optimizing, the sourcemaps are significantly less complex
180
- // and can use the higher fidelity merge
181
- ! ! options . optimize && fastSourceMaps ,
182
- ) ;
183
- }
184
- }
185
167
186
- if ( downlevelCode ) {
187
168
result . downlevel = await processBundle ( {
188
169
...options ,
189
- code : downlevelCode ,
190
- map : downlevelMap ,
170
+ code : transformResult . code ,
171
+ downlevelMap : ( transformResult . map as SourceMapInput ) ?? undefined ,
191
172
filename : path . join ( basePath , downlevelFilename ) ,
192
173
isOriginal : false ,
193
174
} ) ;
@@ -203,156 +184,59 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
203
184
return result ;
204
185
}
205
186
206
- async function mergeSourceMaps (
207
- inputCode : string ,
208
- inputSourceMap : RawSourceMap ,
209
- resultCode : string ,
210
- resultSourceMap : RawSourceMap ,
211
- filename : string ,
212
- fast = false ,
213
- ) : Promise < RawSourceMap > {
214
- // Webpack 5 terser sourcemaps currently fail merging with the high-quality method
215
- if ( fast ) {
216
- return mergeSourceMapsFast ( inputSourceMap , resultSourceMap ) ;
217
- }
218
-
219
- // Load Webpack only when needed
220
- if ( webpackSources === undefined ) {
221
- webpackSources = ( await import ( 'webpack' ) ) . sources ;
222
- }
223
-
224
- // SourceMapSource produces high-quality sourcemaps
225
- // Final sourcemap will always be available when providing the input sourcemaps
226
- const finalSourceMap = new webpackSources . SourceMapSource (
227
- resultCode ,
228
- filename ,
229
- resultSourceMap ,
230
- inputCode ,
231
- inputSourceMap ,
232
- true ,
233
- ) . map ( ) ;
234
-
235
- return finalSourceMap as RawSourceMap ;
236
- }
237
-
238
- async function mergeSourceMapsFast ( first : RawSourceMap , second : RawSourceMap ) {
239
- const sourceRoot = first . sourceRoot ;
240
- const generator = new SourceMapGenerator ( ) ;
241
-
242
- // sourcemap package adds the sourceRoot to all position source paths if not removed
243
- delete first . sourceRoot ;
244
-
245
- await SourceMapConsumer . with ( first , null , ( originalConsumer ) => {
246
- return SourceMapConsumer . with ( second , null , ( newConsumer ) => {
247
- newConsumer . eachMapping ( ( mapping ) => {
248
- if ( mapping . originalLine === null ) {
249
- return ;
250
- }
251
- const originalPosition = originalConsumer . originalPositionFor ( {
252
- line : mapping . originalLine ,
253
- column : mapping . originalColumn ,
254
- } ) ;
255
- if (
256
- originalPosition . line === null ||
257
- originalPosition . column === null ||
258
- originalPosition . source === null
259
- ) {
260
- return ;
261
- }
262
- generator . addMapping ( {
263
- generated : {
264
- line : mapping . generatedLine ,
265
- column : mapping . generatedColumn ,
266
- } ,
267
- name : originalPosition . name || undefined ,
268
- original : {
269
- line : originalPosition . line ,
270
- column : originalPosition . column ,
271
- } ,
272
- source : originalPosition . source ,
273
- } ) ;
274
- } ) ;
275
- } ) ;
276
- } ) ;
277
-
278
- const map = generator . toJSON ( ) ;
279
- map . file = second . file ;
280
- map . sourceRoot = sourceRoot ;
281
-
282
- // Add source content if present
283
- if ( first . sourcesContent ) {
284
- // Source content array is based on index of sources
285
- const sourceContentMap = new Map < string , number > ( ) ;
286
- for ( let i = 0 ; i < first . sources . length ; i ++ ) {
287
- // make paths "absolute" so they can be compared (`./a.js` and `a.js` are equivalent)
288
- sourceContentMap . set ( path . resolve ( '/' , first . sources [ i ] ) , i ) ;
289
- }
290
- map . sourcesContent = [ ] ;
291
- for ( let i = 0 ; i < map . sources . length ; i ++ ) {
292
- const contentIndex = sourceContentMap . get ( path . resolve ( '/' , map . sources [ i ] ) ) ;
293
- if ( contentIndex === undefined ) {
294
- map . sourcesContent . push ( '' ) ;
295
- } else {
296
- map . sourcesContent . push ( first . sourcesContent [ contentIndex ] ) ;
297
- }
298
- }
299
- }
300
-
301
- // Put the sourceRoot back
302
- if ( sourceRoot ) {
303
- first . sourceRoot = sourceRoot ;
304
- }
305
-
306
- return map ;
307
- }
308
-
309
187
async function processBundle (
310
- options : Omit < ProcessBundleOptions , 'map' > & { isOriginal : boolean ; map ?: string | RawSourceMap } ,
188
+ options : ProcessBundleOptions & {
189
+ isOriginal : boolean ;
190
+ downlevelMap ?: SourceMapInput ;
191
+ } ,
311
192
) : Promise < ProcessBundleFile > {
312
193
const {
313
194
optimize,
314
195
isOriginal,
315
196
code,
316
197
map,
198
+ downlevelMap,
317
199
filename : filepath ,
318
200
hiddenSourceMaps,
319
201
cacheKeys = [ ] ,
320
202
integrityAlgorithm,
321
203
} = options ;
322
204
323
- const rawMap = typeof map === 'string' ? ( JSON . parse ( map ) as RawSourceMap ) : map ;
324
205
const filename = path . basename ( filepath ) ;
206
+ let resultCode = code ;
325
207
326
- let result : {
327
- code : string ;
328
- map : RawSourceMap | undefined ;
329
- } ;
330
-
331
- if ( rawMap ) {
332
- rawMap . file = filename ;
333
- }
334
-
208
+ let optimizeResult ;
335
209
if ( optimize ) {
336
- result = await terserMangle ( code , {
210
+ optimizeResult = await terserMangle ( code , {
337
211
filename,
338
- map : rawMap ,
212
+ sourcemap : ! ! map ,
339
213
compress : ! isOriginal , // We only compress bundles which are downlevelled.
340
214
ecma : isOriginal ? 2015 : 5 ,
341
215
} ) ;
342
- } else {
343
- result = {
344
- map : rawMap ,
345
- code,
346
- } ;
216
+ resultCode = optimizeResult . code ;
347
217
}
348
218
349
219
let mapContent : string | undefined ;
350
- if ( result . map ) {
220
+ if ( map ) {
351
221
if ( ! hiddenSourceMaps ) {
352
- result . code += `\n//# sourceMappingURL=${ filename } .map` ;
222
+ resultCode += `\n//# sourceMappingURL=${ filename } .map` ;
223
+ }
224
+
225
+ const partialSourcemaps : SourceMapInput [ ] = [ ] ;
226
+ if ( optimizeResult && optimizeResult . map ) {
227
+ partialSourcemaps . push ( optimizeResult . map ) ;
228
+ }
229
+ if ( downlevelMap ) {
230
+ partialSourcemaps . push ( downlevelMap ) ;
353
231
}
354
232
355
- mapContent = JSON . stringify ( result . map ) ;
233
+ if ( partialSourcemaps . length > 0 ) {
234
+ partialSourcemaps . push ( map ) ;
235
+ const fullSourcemap = remapping ( partialSourcemaps , ( ) => null ) ;
236
+ mapContent = JSON . stringify ( fullSourcemap ) ;
237
+ } else {
238
+ mapContent = map ;
239
+ }
356
240
357
241
await cachePut (
358
242
mapContent ,
@@ -361,21 +245,21 @@ async function processBundle(
361
245
fs . writeFileSync ( filepath + '.map' , mapContent ) ;
362
246
}
363
247
364
- const fileResult = createFileEntry ( filepath , result . code , mapContent , integrityAlgorithm ) ;
248
+ const fileResult = createFileEntry ( filepath , resultCode , mapContent , integrityAlgorithm ) ;
365
249
366
250
await cachePut (
367
- result . code ,
251
+ resultCode ,
368
252
cacheKeys [ isOriginal ? CacheKey . OriginalCode : CacheKey . DownlevelCode ] ,
369
253
fileResult . integrity ,
370
254
) ;
371
- fs . writeFileSync ( filepath , result . code ) ;
255
+ fs . writeFileSync ( filepath , resultCode ) ;
372
256
373
257
return fileResult ;
374
258
}
375
259
376
260
async function terserMangle (
377
261
code : string ,
378
- options : { filename ?: string ; map ?: RawSourceMap ; compress ?: boolean ; ecma ?: 5 | 2015 } = { } ,
262
+ options : { filename ?: string ; sourcemap ?: boolean ; compress ?: boolean ; ecma ?: 5 | 2015 } = { } ,
379
263
) {
380
264
// Note: Investigate converting the AST instead of re-parsing
381
265
// estree -> terser is already supported; need babel -> estree/terser
@@ -393,7 +277,7 @@ async function terserMangle(
393
277
wrap_func_args : false ,
394
278
} ,
395
279
sourceMap :
396
- ! ! options . map &&
280
+ ! ! options . sourcemap &&
397
281
( {
398
282
asObject : true ,
399
283
// typings don't include asObject option
@@ -402,21 +286,7 @@ async function terserMangle(
402
286
} ) ;
403
287
404
288
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
405
- const outputCode = minifyOutput . code ! ;
406
-
407
- let outputMap ;
408
- if ( options . map && minifyOutput . map ) {
409
- outputMap = await mergeSourceMaps (
410
- code ,
411
- options . map ,
412
- outputCode ,
413
- minifyOutput . map as unknown as RawSourceMap ,
414
- options . filename || '0' ,
415
- code . length > FAST_SOURCEMAP_THRESHOLD ,
416
- ) ;
417
- }
418
-
419
- return { code : outputCode , map : outputMap } ;
289
+ return { code : minifyOutput . code ! , map : minifyOutput . map as SourceMapInput | undefined } ;
420
290
}
421
291
422
292
function createFileEntry (
@@ -644,7 +514,6 @@ export async function inlineLocales(options: InlineOptions) {
644
514
}
645
515
646
516
const diagnostics = [ ] ;
647
- const inputMap = options . map && ( JSON . parse ( options . map ) as RawSourceMap ) ;
648
517
for ( const locale of i18n . inlineLocales ) {
649
518
const isSourceLocale = locale === i18n . sourceLocale ;
650
519
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -675,7 +544,7 @@ export async function inlineLocales(options: InlineOptions) {
675
544
configFile : false ,
676
545
plugins,
677
546
compact : ! shouldBeautify ,
678
- sourceMaps : ! ! inputMap ,
547
+ sourceMaps : ! ! options . map ,
679
548
} ) ;
680
549
681
550
diagnostics . push ( ...localeDiagnostics . messages ) ;
@@ -691,15 +560,8 @@ export async function inlineLocales(options: InlineOptions) {
691
560
) ;
692
561
fs . writeFileSync ( outputPath , transformResult . code ) ;
693
562
694
- if ( inputMap && transformResult . map ) {
695
- const outputMap = await mergeSourceMaps (
696
- options . code ,
697
- inputMap ,
698
- transformResult . code ,
699
- transformResult . map ,
700
- options . filename ,
701
- options . code . length > FAST_SOURCEMAP_THRESHOLD ,
702
- ) ;
563
+ if ( options . map && transformResult . map ) {
564
+ const outputMap = remapping ( [ transformResult . map as SourceMapInput , options . map ] , ( ) => null ) ;
703
565
704
566
fs . writeFileSync ( outputPath + '.map' , JSON . stringify ( outputMap ) ) ;
705
567
}
@@ -725,7 +587,7 @@ async function inlineLocalesDirect(ast: ParseResult, options: InlineOptions) {
725
587
return inlineCopyOnly ( options ) ;
726
588
}
727
589
728
- const inputMap = options . map && ( JSON . parse ( options . map ) as RawSourceMap ) ;
590
+ const inputMap = ! ! options . map && ( JSON . parse ( options . map ) as { sourceRoot ?: string } ) ;
729
591
// Cleanup source root otherwise it will be added to each source entry
730
592
const mapSourceRoot = inputMap && inputMap . sourceRoot ;
731
593
if ( inputMap ) {
@@ -741,8 +603,7 @@ async function inlineLocalesDirect(ast: ParseResult, options: InlineOptions) {
741
603
for ( const locale of i18n . inlineLocales ) {
742
604
const content = new ReplaceSource (
743
605
inputMap
744
- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
745
- new SourceMapSource ( options . code , options . filename , inputMap as any )
606
+ ? new SourceMapSource ( options . code , options . filename , inputMap )
746
607
: new OriginalSource ( options . code , options . filename ) ,
747
608
) ;
748
609
@@ -784,7 +645,7 @@ async function inlineLocalesDirect(ast: ParseResult, options: InlineOptions) {
784
645
785
646
const { source : outputCode , map : outputMap } = outputSource . sourceAndMap ( ) as {
786
647
source : string ;
787
- map : RawSourceMap ;
648
+ map : { file : string ; sourceRoot ?: string } ;
788
649
} ;
789
650
const outputPath = path . join (
790
651
options . outputPath ,
0 commit comments