-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bazel): move js mapping size tracking tool to dev-infra
Moves the JS mapping size tracking tool from framework into the dev-infra repo. The following additonal changes have been made: * Reworked the API to not require manifest paths, but to support validatable Bazel labels as rule parameters. * Adjustments to make it shippable from `bazel/` * Additional testing to verify the Starlark code * Update to account for the latest source-map types. i.e. making size-tracking asynchronous.
- Loading branch information
1 parent
96d3b40
commit 4627840
Showing
20 changed files
with
947 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
load("@npm//@bazel/concatjs:index.bzl", "ts_library") | ||
|
||
package(default_visibility = ["//visibility:public"]) | ||
|
||
ts_library( | ||
name = "map-size-tracking", | ||
srcs = glob(["**/*.ts"]), | ||
# A tsconfig needs to be specified as otherwise `ts_library` will look for the config | ||
# in `//:package.json` and this breaks when the BUILD file is copied to `@npm//`. | ||
tsconfig = "//:tsconfig.json", | ||
deps = [ | ||
"@npm//@bazel/runfiles", | ||
"@npm//@types/node", | ||
"@npm//source-map", | ||
], | ||
) | ||
|
||
# Make source files available for distribution via pkg_npm | ||
filegroup( | ||
name = "files", | ||
srcs = glob(["*"]), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/** | ||
* @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 {DirectorySizeEntry, FileSizeData, getChildEntryNames} from './file_size_data.js'; | ||
|
||
export interface SizeDifference { | ||
filePath?: string; | ||
message: string; | ||
} | ||
|
||
export interface Threshold { | ||
/** | ||
* Maximum difference percentage. Exceeding this causes a reported size | ||
* difference. Percentage difference is helpful for small files where | ||
* the byte threshold is not exceeded but the change is relatively large | ||
* for the small file and should be reported. | ||
*/ | ||
maxPercentageDiff: number; | ||
/** | ||
* Maximum byte difference. Exceeding this causes a reported size difference. | ||
* The max byte threshold works good for large files where change is relatively | ||
* small but still needs to reported as it causes an overall size regression. | ||
*/ | ||
maxByteDiff: number; | ||
} | ||
|
||
/** Compares two file size data objects and returns an array of size differences. */ | ||
export function compareFileSizeData( | ||
actual: FileSizeData, | ||
expected: FileSizeData, | ||
threshold: Threshold, | ||
) { | ||
return [ | ||
...compareSizeEntry(actual.files, expected.files, '/', threshold), | ||
...compareActualSizeToExpected(actual.unmapped, expected.unmapped, '<unmapped>', threshold), | ||
]; | ||
} | ||
|
||
/** Compares two file size entries and returns an array of size differences. */ | ||
function compareSizeEntry( | ||
actual: DirectorySizeEntry | number, | ||
expected: DirectorySizeEntry | number, | ||
filePath: string, | ||
threshold: Threshold, | ||
) { | ||
if (typeof actual !== 'number' && typeof expected !== 'number') { | ||
return compareDirectorySizeEntry(actual, expected, filePath, threshold); | ||
} else { | ||
return compareActualSizeToExpected(<number>actual, <number>expected, filePath, threshold); | ||
} | ||
} | ||
|
||
/** | ||
* Compares two size numbers and returns a size difference if the difference | ||
* percentage exceeds the specified maximum percentage or the byte size | ||
* difference exceeds the maximum byte difference. | ||
*/ | ||
function compareActualSizeToExpected( | ||
actualSize: number, | ||
expectedSize: number, | ||
filePath: string, | ||
threshold: Threshold, | ||
): SizeDifference[] { | ||
const diffPercentage = getDifferencePercentage(actualSize, expectedSize); | ||
const byteDiff = Math.abs(expectedSize - actualSize); | ||
const diffs: SizeDifference[] = []; | ||
if (diffPercentage > threshold.maxPercentageDiff) { | ||
diffs.push({ | ||
filePath: filePath, | ||
message: | ||
`Differs by ${diffPercentage.toFixed(2)}% from the expected size ` + | ||
`(actual = ${actualSize}, expected = ${expectedSize})`, | ||
}); | ||
} | ||
if (byteDiff > threshold.maxByteDiff) { | ||
diffs.push({ | ||
filePath: filePath, | ||
message: | ||
`Differs by ${byteDiff}B from the expected size ` + | ||
`(actual = ${actualSize}, expected = ${expectedSize})`, | ||
}); | ||
} | ||
return diffs; | ||
} | ||
|
||
/** | ||
* Compares two size directory size entries and returns an array of found size | ||
* differences within that directory. | ||
*/ | ||
function compareDirectorySizeEntry( | ||
actual: DirectorySizeEntry, | ||
expected: DirectorySizeEntry, | ||
filePath: string, | ||
threshold: Threshold, | ||
): SizeDifference[] { | ||
const diffs: SizeDifference[] = [ | ||
...compareActualSizeToExpected(actual.size, expected.size, filePath, threshold), | ||
]; | ||
|
||
getChildEntryNames(expected).forEach((childName) => { | ||
if (actual[childName] === undefined) { | ||
diffs.push({ | ||
filePath: filePath + childName, | ||
message: 'Expected file/directory is not included.', | ||
}); | ||
return; | ||
} | ||
|
||
diffs.push( | ||
...compareSizeEntry(actual[childName], expected[childName], filePath + childName, threshold), | ||
); | ||
}); | ||
|
||
getChildEntryNames(actual).forEach((childName) => { | ||
if (expected[childName] === undefined) { | ||
diffs.push({ | ||
filePath: filePath + childName, | ||
message: 'Unexpected file/directory included (not part of golden).', | ||
}); | ||
} | ||
}); | ||
|
||
return diffs; | ||
} | ||
|
||
/** Gets the difference of the two size values in percentage. */ | ||
function getDifferencePercentage(actualSize: number, expectedSize: number) { | ||
return (Math.abs(actualSize - expectedSize) / ((expectedSize + actualSize) / 2)) * 100; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** | ||
* @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 | ||
*/ | ||
|
||
export interface DirectorySizeEntry { | ||
size: number; | ||
[filePath: string]: DirectorySizeEntry | number; | ||
} | ||
|
||
export interface FileSizeData { | ||
unmapped: number; | ||
files: DirectorySizeEntry; | ||
} | ||
|
||
/** Returns a new file size data sorted by keys in ascending alphabetical order. */ | ||
export function sortFileSizeData({unmapped, files}: FileSizeData): FileSizeData { | ||
return {unmapped, files: _sortDirectorySizeEntryObject(files)}; | ||
} | ||
|
||
/** Gets the name of all child size entries of the specified one. */ | ||
export function getChildEntryNames(entry: DirectorySizeEntry): string[] { | ||
// The "size" property is reserved for the stored size value. | ||
return Object.keys(entry).filter((key) => key !== 'size'); | ||
} | ||
|
||
/** | ||
* Returns the first size-entry that has multiple children. This is also known as | ||
* the omitting of the common path prefix. | ||
* */ | ||
export function omitCommonPathPrefix(entry: DirectorySizeEntry): DirectorySizeEntry { | ||
let current: DirectorySizeEntry = entry; | ||
while (getChildEntryNames(current).length === 1) { | ||
const newChild = current[getChildEntryNames(current)[0]]; | ||
// Only omit the current node if it is a size entry. In case the new | ||
// child is a holding a number, then this is a file and we don'twant | ||
// to incorrectly omit the leaf file entries. | ||
if (typeof newChild === 'number') { | ||
break; | ||
} | ||
current = newChild; | ||
} | ||
return current; | ||
} | ||
|
||
function _sortDirectorySizeEntryObject(oldObject: DirectorySizeEntry): DirectorySizeEntry { | ||
return Object.keys(oldObject) | ||
.sort(_sortSizeEntryKeys) | ||
.reduce((result, key) => { | ||
if (typeof oldObject[key] === 'number') { | ||
result[key] = oldObject[key]; | ||
} else { | ||
result[key] = _sortDirectorySizeEntryObject(oldObject[key] as DirectorySizeEntry); | ||
} | ||
return result; | ||
}, {} as DirectorySizeEntry); | ||
} | ||
|
||
function _sortSizeEntryKeys(a: string, b: string) { | ||
// The "size" property should always be the first item in the size entry. | ||
// This makes it easier to inspect the size of an entry in the golden. | ||
if (a === 'size') { | ||
return -1; | ||
} else if (a < b) { | ||
return -1; | ||
} else if (a > b) { | ||
return 1; | ||
} | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# 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 | ||
|
||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "nodejs_test") | ||
|
||
nodejs_args = ["--nobazel_run_linker"] | ||
|
||
def js_mapping_size_test( | ||
name, | ||
src, | ||
source_map, | ||
golden_file, | ||
max_percentage_diff, | ||
max_byte_diff, | ||
**kwargs): | ||
"""Track the size of a given input file by inspecting the corresponding source map. | ||
A golden file is used to compare the current file size data against previously approved file size data | ||
Args: | ||
name: Name of the test target | ||
src: Label pointing to the script to be analyzed. | ||
source_map: Label pointing to the JavaScript map file. | ||
golden_file: Label pointing to the golden JSON file. | ||
max_percentage_diff: Limit percentage difference that would result in test failures. | ||
max_byte_diff: Limit relative byte difference that would result in test failures. | ||
**kwargs: Additional arguments being passed to the NodeJS test target. | ||
""" | ||
|
||
all_data = ["//bazel/map-size-tracking", src, source_map, golden_file] | ||
entry_point = "//bazel/map-size-tracking:index.ts" | ||
|
||
nodejs_test( | ||
name = name, | ||
data = all_data, | ||
entry_point = entry_point, | ||
templated_args = nodejs_args + [ | ||
"$(rootpath %s)" % src, | ||
"$(rootpath %s)" % source_map, | ||
"$(rootpath %s)" % golden_file, | ||
"%d" % max_percentage_diff, | ||
"%d" % max_byte_diff, | ||
"false", | ||
], | ||
**kwargs | ||
) | ||
|
||
nodejs_binary( | ||
name = "%s.accept" % name, | ||
testonly = True, | ||
data = all_data, | ||
entry_point = entry_point, | ||
templated_args = nodejs_args + [ | ||
"$(rootpath %s)" % src, | ||
"$(rootpath %s)" % source_map, | ||
"$(rootpath %s)" % golden_file, | ||
"0", | ||
"0", | ||
"true", | ||
], | ||
**kwargs | ||
) |
Oops, something went wrong.