| 
									
										
										
										
											2019-04-23 20:50:11 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2019-04-23 20:50:11 +02:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {readFileSync} from 'fs'; | 
					
						
							| 
									
										
										
										
											2021-04-03 09:52:53 -04:00
										 |  |  | import {RawSourceMap, SourceMapConsumer} from 'source-map'; | 
					
						
							| 
									
										
										
										
											2019-04-23 20:50:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import {DirectorySizeEntry, FileSizeData, omitCommonPathPrefix, sortFileSizeData} from './file_size_data'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class SizeTracker { | 
					
						
							|  |  |  |   private fileContent: string; | 
					
						
							|  |  |  |   private consumer: SourceMapConsumer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Retraced size result that can be used to inspect where bytes in the input file | 
					
						
							|  |  |  |    * originated from and how much each file contributes to the input file. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   readonly sizeResult: FileSizeData; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(private filePath: string, private sourceMapPath: string) { | 
					
						
							|  |  |  |     this.fileContent = readFileSync(filePath, 'utf8'); | 
					
						
							| 
									
										
										
										
											2021-04-03 09:52:53 -04:00
										 |  |  |     this.consumer = | 
					
						
							|  |  |  |         new SourceMapConsumer(JSON.parse(readFileSync(sourceMapPath, 'utf8')) as RawSourceMap); | 
					
						
							| 
									
										
										
										
											2019-04-23 20:50:11 +02:00
										 |  |  |     this.sizeResult = this._computeSizeResult(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Computes the file size data by analyzing the input file through the specified | 
					
						
							|  |  |  |    * source-map. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private _computeSizeResult(): FileSizeData { | 
					
						
							|  |  |  |     const lines = this.fileContent.split(/(\r?\n)/); | 
					
						
							|  |  |  |     const result: FileSizeData = { | 
					
						
							|  |  |  |       unmapped: 0, | 
					
						
							|  |  |  |       files: {size: 0}, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Walk through the columns for each line in the input file and find the
 | 
					
						
							|  |  |  |     // origin source-file of the given character. This allows us to inspect
 | 
					
						
							|  |  |  |     // how the given input file is composed and how much each individual file
 | 
					
						
							|  |  |  |     // contributes to the overall bundle file.
 | 
					
						
							|  |  |  |     for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { | 
					
						
							|  |  |  |       const lineText = lines[lineIdx]; | 
					
						
							|  |  |  |       for (let colIdx = 0; colIdx < lineText.length; colIdx++) { | 
					
						
							|  |  |  |         // Note that the "originalPositionFor" line number is one-based.
 | 
					
						
							|  |  |  |         let {source} = this.consumer.originalPositionFor({line: lineIdx + 1, column: colIdx}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Increase the amount of total bytes.
 | 
					
						
							|  |  |  |         result.files.size += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!source) { | 
					
						
							|  |  |  |           result.unmapped += 1; | 
					
						
							|  |  |  |           continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const pathSegments = this._resolveMappedPath(source).split('/'); | 
					
						
							|  |  |  |         let currentEntry = result.files; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Walk through each path segment and update the size entries with
 | 
					
						
							|  |  |  |         // new size. This makes it possibly to create na hierarchical tree
 | 
					
						
							|  |  |  |         // that matches the actual file system.
 | 
					
						
							|  |  |  |         pathSegments.forEach((segmentName, index) => { | 
					
						
							|  |  |  |           // The last segment always refers to a file and we therefore can
 | 
					
						
							|  |  |  |           // store the size verbatim as property value.
 | 
					
						
							|  |  |  |           if (index === pathSegments.length - 1) { | 
					
						
							|  |  |  |             currentEntry[segmentName] = (<number>currentEntry[segmentName] || 0) + 1; | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             // Append a trailing slash to the segment so that it
 | 
					
						
							|  |  |  |             // is clear that this size entry represents a folder.
 | 
					
						
							|  |  |  |             segmentName = `${segmentName}/`; | 
					
						
							|  |  |  |             const newEntry = <DirectorySizeEntry>currentEntry[segmentName] || {size: 0}; | 
					
						
							|  |  |  |             newEntry.size += 1; | 
					
						
							|  |  |  |             currentEntry = currentEntry[segmentName] = newEntry; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Omit size entries which are not needed and just bloat up the file
 | 
					
						
							|  |  |  |     // size data. e.g. if all paths start with "../../", we want to omit
 | 
					
						
							|  |  |  |     // this prefix to make the size data less confusing.
 | 
					
						
							|  |  |  |     result.files = omitCommonPathPrefix(result.files); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return sortFileSizeData(result); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _resolveMappedPath(filePath: string): string { | 
					
						
							|  |  |  |     // We only want to store POSIX-like paths in order to avoid path
 | 
					
						
							|  |  |  |     // separator failures when running the golden tests on Windows.
 | 
					
						
							|  |  |  |     filePath = filePath.replace(/\\/g, '/'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Workaround for https://github.com/angular/angular/issues/30060
 | 
					
						
							|  |  |  |     if (process.env['BAZEL_TARGET'].includes('test/bundling/core_all:size_test')) { | 
					
						
							|  |  |  |       return filePath.replace(/^(\.\.\/)+external/, 'external') | 
					
						
							|  |  |  |           .replace(/^(\.\.\/)+packages\/core\//, '@angular/core/') | 
					
						
							|  |  |  |           .replace(/^(\.\.\/){3}/, '@angular/core/'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return filePath; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |