105 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			105 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * @license | ||
|  |  * Copyright Google Inc. All Rights Reserved. | ||
|  |  * | ||
|  |  * 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'; | ||
|  | import {SourceMapConsumer} from 'source-map'; | ||
|  | 
 | ||
|  | 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'); | ||
|  |     this.consumer = new SourceMapConsumer(JSON.parse(readFileSync(sourceMapPath, 'utf8'))); | ||
|  |     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; | ||
|  |   } | ||
|  | } |