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;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |