106 lines
4.0 KiB
TypeScript
106 lines
4.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC 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 {RawSourceMap, 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')) as RawSourceMap);
|
|
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;
|
|
}
|
|
}
|