mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
Fix health metrics (#5509)
* ignore folders without package.json * simplify implementation * handle files with no exports * fix binary size report * remove code for debugging * address comments * remove unused code * fix tests
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@firebase/messaging",
|
||||
"name": "@firebase/messaging-sw",
|
||||
"description": "",
|
||||
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
|
||||
"module": "../dist/index.sw.esm2017.js",
|
||||
|
||||
@@ -23,7 +23,7 @@ import * as terser from 'terser';
|
||||
import * as ts from 'typescript';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import { deepCopy } from '@firebase/util';
|
||||
import { projectRoot } from '../../scripts/utils';
|
||||
|
||||
export const enum ErrorCode {
|
||||
INVALID_FLAG_COMBINATION = 'Invalid command flag combinations!',
|
||||
@@ -45,6 +45,7 @@ export interface MemberList {
|
||||
functions: string[];
|
||||
variables: string[];
|
||||
enums: string[];
|
||||
unknown: string[];
|
||||
}
|
||||
/** Contains the dependencies and the size of their code for a single export. */
|
||||
export interface ExportData {
|
||||
@@ -53,6 +54,7 @@ export interface ExportData {
|
||||
functions: string[];
|
||||
variables: string[];
|
||||
enums: string[];
|
||||
unknown: string[];
|
||||
externals: { [key: string]: string[] };
|
||||
size: number;
|
||||
sizeWithExtDeps: number;
|
||||
@@ -68,8 +70,7 @@ export interface Report {
|
||||
*/
|
||||
export async function extractDependenciesAndSize(
|
||||
exportName: string,
|
||||
jsBundle: string,
|
||||
map: Map<string, string>
|
||||
jsBundle: string
|
||||
): Promise<ExportData> {
|
||||
const input = tmp.fileSync().name + '.js';
|
||||
const externalDepsResolvedOutput = tmp.fileSync().name + '.js';
|
||||
@@ -103,9 +104,8 @@ export async function extractDependenciesAndSize(
|
||||
file: externalDepsNotResolvedOutput,
|
||||
format: 'es'
|
||||
});
|
||||
const dependencies: MemberList = extractDeclarations(
|
||||
externalDepsNotResolvedOutput,
|
||||
map
|
||||
const dependencies: MemberList = extractAllTopLevelSymbols(
|
||||
externalDepsNotResolvedOutput
|
||||
);
|
||||
|
||||
const externalDepsResolvedOutputContent = fs.readFileSync(
|
||||
@@ -143,6 +143,7 @@ export async function extractDependenciesAndSize(
|
||||
functions: [],
|
||||
variables: [],
|
||||
enums: [],
|
||||
unknown: [],
|
||||
externals: {},
|
||||
size: 0,
|
||||
sizeWithExtDeps: 0
|
||||
@@ -170,53 +171,23 @@ export async function extractDependenciesAndSize(
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all function, class and variable declarations using the TypeScript
|
||||
* compiler API.
|
||||
* @param map maps every symbol listed in dts file to its type. eg: aVariable -> variable.
|
||||
* map is null when given filePath is a path to d.ts file.
|
||||
* map is populated when given filePath points to a .js bundle file.
|
||||
*
|
||||
* Examples of Various Type of Exports
|
||||
* FunctionDeclaration: export function aFunc(): string {...};
|
||||
* ClassDeclaration: export class aClass {};
|
||||
* EnumDeclaration: export enum aEnum {};
|
||||
* VariableDeclaration: export let aVariable: string; import * as tmp from 'tmp'; export declare const aVar: tmp.someType.
|
||||
* VariableStatement: export const aVarStatement: string = "string"; export const { a, b } = { a: 'a', b: 'b' };
|
||||
* ExportDeclaration:
|
||||
* named exports: export {foo, bar} from '...'; export {foo as foo1, bar} from '...'; export {LogLevel};
|
||||
* export everything: export * from '...';
|
||||
* Check what symbols are being pulled into a bundle
|
||||
*/
|
||||
export function extractDeclarations(
|
||||
filePath: string,
|
||||
map?: Map<string, string>
|
||||
): MemberList {
|
||||
export function extractAllTopLevelSymbols(filePath: string): MemberList {
|
||||
const program = ts.createProgram([filePath], { allowJs: true });
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
const sourceFile = program.getSourceFile(filePath);
|
||||
if (!sourceFile) {
|
||||
throw new Error(`${ErrorCode.FILE_PARSING_ERROR} ${filePath}`);
|
||||
}
|
||||
|
||||
let declarations: MemberList = {
|
||||
const declarations: MemberList = {
|
||||
functions: [],
|
||||
classes: [],
|
||||
variables: [],
|
||||
enums: []
|
||||
enums: [],
|
||||
unknown: []
|
||||
};
|
||||
const namespaceImportSet: Set<string> = new Set();
|
||||
// define a map here which is used to handle export statements that have no from clause.
|
||||
// As there is no from clause in such export statements, we retrieve symbol location by parsing the corresponding import
|
||||
// statements. We store the symbol and its defined location as key value pairs in the map.
|
||||
const importSymbolCurrentNameToModuleLocation: Map<
|
||||
string,
|
||||
string
|
||||
> = new Map();
|
||||
const importSymbolCurrentNameToOriginalName: Map<string, string> = new Map(); // key: current name value: original name
|
||||
const importModuleLocationToExportedSymbolsList: Map<
|
||||
string,
|
||||
MemberList
|
||||
> = new Map(); // key: module location, value: a list of all exported symbols of the module
|
||||
|
||||
ts.forEachChild(sourceFile, node => {
|
||||
if (ts.isFunctionDeclaration(node)) {
|
||||
declarations.functions.push(node.name!.text);
|
||||
@@ -253,304 +224,82 @@ export function extractDeclarations(
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (ts.isImportDeclaration(node) && node.importClause) {
|
||||
const symbol = checker.getSymbolAtLocation(node.moduleSpecifier);
|
||||
if (symbol && symbol.valueDeclaration) {
|
||||
const importFilePath = symbol.valueDeclaration.getSourceFile().fileName;
|
||||
// import { a, b } from '@firebase/dummy-exp'
|
||||
// import {a as A, b as B} from '@firebase/dummy-exp'
|
||||
if (
|
||||
node.importClause.namedBindings &&
|
||||
ts.isNamedImports(node.importClause.namedBindings)
|
||||
) {
|
||||
node.importClause.namedBindings.elements.forEach(each => {
|
||||
const symbolName: string = each.name.getText(sourceFile); // import symbol current name
|
||||
importSymbolCurrentNameToModuleLocation.set(
|
||||
symbolName,
|
||||
importFilePath
|
||||
);
|
||||
// if imported symbols are renamed, insert an entry to importSymbolCurrentNameToOriginalName Map
|
||||
// with key the current name, value the original name
|
||||
if (each.propertyName) {
|
||||
importSymbolCurrentNameToOriginalName.set(
|
||||
symbolName,
|
||||
each.propertyName.getText(sourceFile)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// import * as fs from 'fs'
|
||||
} else if (
|
||||
node.importClause.namedBindings &&
|
||||
ts.isNamespaceImport(node.importClause.namedBindings)
|
||||
) {
|
||||
const symbolName: string = node.importClause.namedBindings.name.getText(
|
||||
sourceFile
|
||||
);
|
||||
namespaceImportSet.add(symbolName);
|
||||
|
||||
// import a from '@firebase/dummy-exp'
|
||||
} else if (
|
||||
node.importClause.name &&
|
||||
ts.isIdentifier(node.importClause.name)
|
||||
) {
|
||||
const symbolName: string = node.importClause.name.getText(sourceFile);
|
||||
importSymbolCurrentNameToModuleLocation.set(
|
||||
symbolName,
|
||||
importFilePath
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// re-exports handler: handles cases like :
|
||||
// export {LogLevel};
|
||||
// export * from '..';
|
||||
// export {foo, bar} from '..';
|
||||
// export {foo as foo1, bar} from '...';
|
||||
else if (ts.isExportDeclaration(node)) {
|
||||
// this clause handles the export statements that have a from clause (referred to as moduleSpecifier in ts compiler).
|
||||
// examples are "export {foo as foo1, bar} from '...';"
|
||||
// and "export * from '..';"
|
||||
if (node.moduleSpecifier) {
|
||||
if (ts.isStringLiteral(node.moduleSpecifier)) {
|
||||
const reExportsWithFromClause: MemberList = handleExportStatementsWithFromClause(
|
||||
checker,
|
||||
node,
|
||||
node.moduleSpecifier.getText(sourceFile)
|
||||
);
|
||||
// concatenate re-exported MemberList with MemberList of the dts file
|
||||
for (const key of Object.keys(declarations) as Array<
|
||||
keyof MemberList
|
||||
>) {
|
||||
declarations[key].push(...reExportsWithFromClause[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// export {LogLevel};
|
||||
// exclusively handles named export statements that has no from clause.
|
||||
handleExportStatementsWithoutFromClause(
|
||||
node,
|
||||
importSymbolCurrentNameToModuleLocation,
|
||||
importSymbolCurrentNameToOriginalName,
|
||||
importModuleLocationToExportedSymbolsList,
|
||||
namespaceImportSet,
|
||||
declarations
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
declarations = dedup(declarations);
|
||||
|
||||
if (map) {
|
||||
declarations = mapSymbolToType(map, declarations);
|
||||
}
|
||||
|
||||
//Sort to ensure stable output
|
||||
Object.values(declarations).map(each => {
|
||||
Object.values(declarations).forEach(each => {
|
||||
each.sort();
|
||||
});
|
||||
return declarations;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node compiler representation of an export statement
|
||||
*
|
||||
* This function exclusively handles export statements that have a from clause. The function uses checker argument to resolve
|
||||
* module name specified in from clause to its actual location. It then retrieves all exported symbols from the module.
|
||||
* If the statement is a named export, the function does an extra step, that is, filtering out the symbols that are not listed
|
||||
* in exportClause.
|
||||
* Extract exports of a module
|
||||
*/
|
||||
function handleExportStatementsWithFromClause(
|
||||
checker: ts.TypeChecker,
|
||||
node: ts.ExportDeclaration,
|
||||
moduleName: string
|
||||
): MemberList {
|
||||
const symbol = checker.getSymbolAtLocation(node.moduleSpecifier!);
|
||||
let declarations: MemberList = {
|
||||
export function extractExports(filePath: string): MemberList {
|
||||
const exportDeclarations: MemberList = {
|
||||
functions: [],
|
||||
classes: [],
|
||||
variables: [],
|
||||
enums: []
|
||||
enums: [],
|
||||
unknown: []
|
||||
};
|
||||
if (symbol && symbol.valueDeclaration) {
|
||||
const reExportFullPath = symbol.valueDeclaration.getSourceFile().fileName;
|
||||
// first step: always retrieve all exported symbols from the source location of the re-export.
|
||||
declarations = extractDeclarations(reExportFullPath);
|
||||
// if it's a named export statement, filter the MemberList to keep only those listed in exportClause.
|
||||
// named exports: eg: export {foo, bar} from '...'; and export {foo as foo1, bar} from '...';
|
||||
declarations = extractSymbolsFromNamedExportStatement(node, declarations);
|
||||
}
|
||||
// if the module name in the from clause cant be resolved to actual module location,
|
||||
// just extract symbols listed in the exportClause for named exports, put them in variables first, as
|
||||
// they will be categorized later using map argument.
|
||||
else if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
||||
node.exportClause.elements.forEach(exportSpecifier => {
|
||||
declarations.variables.push(exportSpecifier.name.escapedText.toString());
|
||||
});
|
||||
}
|
||||
// handles the case when exporting * from a module whose location can't be resolved
|
||||
else {
|
||||
console.log(
|
||||
`The public API extraction of ${moduleName} is not complete, because it re-exports from ${moduleName} using * export but we couldn't resolve ${moduleName}`
|
||||
);
|
||||
|
||||
const program = ts.createProgram([filePath], {
|
||||
allowJs: true,
|
||||
baseUrl: path.resolve(`${projectRoot}/node_modules`)
|
||||
});
|
||||
const checker = program.getTypeChecker();
|
||||
const sourceFile = program.getSourceFile(filePath)!;
|
||||
const module = checker.getSymbolAtLocation(sourceFile);
|
||||
// no export from the file
|
||||
if (!module) {
|
||||
return exportDeclarations;
|
||||
}
|
||||
|
||||
return declarations;
|
||||
}
|
||||
const exports = checker.getExportsOfModule(module);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node compiler representation of a named export statement
|
||||
* @param exportsFullList a list of all exported symbols retrieved from the location given in the export statement.
|
||||
*
|
||||
* This function filters on exportsFullList and keeps only those symbols that are listed in the given named export statement.
|
||||
*/
|
||||
function extractSymbolsFromNamedExportStatement(
|
||||
node: ts.ExportDeclaration,
|
||||
exportsFullList: MemberList
|
||||
): MemberList {
|
||||
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
||||
const actualExports: string[] = [];
|
||||
node.exportClause.elements.forEach(exportSpecifier => {
|
||||
const reExportedSymbol: string = extractOriginalSymbolName(
|
||||
exportSpecifier
|
||||
);
|
||||
// eg: export {foo as foo1 } from '...';
|
||||
// if export is renamed, replace with new name
|
||||
// reExportedSymbol: stores the original symbol name
|
||||
// exportSpecifier.name: stores the renamed symbol name
|
||||
if (isExportRenamed(exportSpecifier)) {
|
||||
actualExports.push(exportSpecifier.name.escapedText.toString());
|
||||
// reExportsMember stores all re-exported symbols in its orignal name. However, these re-exported symbols
|
||||
// could be renamed by the re-export. We want to show the renamed name of the symbols in the final analysis report.
|
||||
// Therefore, replaceAll simply replaces the original name of the symbol with the new name defined in re-export.
|
||||
replaceAll(
|
||||
exportsFullList,
|
||||
reExportedSymbol,
|
||||
exportSpecifier.name.escapedText.toString()
|
||||
);
|
||||
} else {
|
||||
actualExports.push(reExportedSymbol);
|
||||
}
|
||||
});
|
||||
// for named exports: requires a filter step which keeps only the symbols listed in the export statement.
|
||||
filterAllBy(exportsFullList, actualExports);
|
||||
}
|
||||
return exportsFullList;
|
||||
}
|
||||
/**
|
||||
* @param node compiler representation of a named export statement
|
||||
* @param importSymbolCurrentNameToModuleLocation a map with imported symbol current name as key and the resolved module location as value. (map is populated by parsing import statements)
|
||||
* @param importSymbolCurrentNameToOriginalName as imported symbols can be renamed, this map stores imported symbols current name and original name as key value pairs.
|
||||
* @param importModuleLocationToExportedSymbolsList a map that maps module location to a list of its exported symbols.
|
||||
* @param namespaceImportSymbolSet a set of namespace import symbols.
|
||||
* @param parentDeclarations a list of exported symbols extracted from the module so far
|
||||
* This function exclusively handles named export statements that has no from clause, i.e: statements like export {LogLevel};
|
||||
* first case: namespace export
|
||||
* example: import * as fs from 'fs'; export {fs};
|
||||
* The function checks if namespaceImportSymbolSet has a namespace import symbol that of the same name, append the symbol to declarations.variables if exists.
|
||||
*
|
||||
* second case: import then export
|
||||
* example: import {a} from '...'; export {a}
|
||||
* The function retrieves the location where the exported symbol is defined from the corresponding import statements.
|
||||
*
|
||||
* third case: declare first then export
|
||||
* examples: declare const apps: Map<string, number>; export { apps };
|
||||
* function foo(){} ; export {foo as bar};
|
||||
* The function parses export clause of the statement and replaces symbol with its current name (if the symbol is renamed) from the declaration argument.
|
||||
*/
|
||||
function handleExportStatementsWithoutFromClause(
|
||||
node: ts.ExportDeclaration,
|
||||
importSymbolCurrentNameToModuleLocation: Map<string, string>,
|
||||
importSymbolCurrentNameToOriginalName: Map<string, string>,
|
||||
importModuleLocationToExportedSymbolsList: Map<string, MemberList>,
|
||||
namespaceImportSymbolSet: Set<string>,
|
||||
parentDeclarations: MemberList
|
||||
): void {
|
||||
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
||||
node.exportClause.elements.forEach(exportSpecifier => {
|
||||
// export symbol could be renamed, we retrieve both its current/renamed name and original name
|
||||
const exportSymbolCurrentName = exportSpecifier.name.escapedText.toString();
|
||||
const exportSymbolOriginalName = extractOriginalSymbolName(
|
||||
exportSpecifier
|
||||
);
|
||||
// import * as fs from 'fs'; export {fs};
|
||||
if (namespaceImportSymbolSet.has(exportSymbolOriginalName)) {
|
||||
parentDeclarations.variables.push(exportSymbolOriginalName);
|
||||
replaceAll(
|
||||
parentDeclarations,
|
||||
exportSymbolOriginalName,
|
||||
exportSymbolCurrentName
|
||||
);
|
||||
}
|
||||
// handles import then exports
|
||||
// import {a as A , b as B} from '...'
|
||||
// export {A as AA , B as BB };
|
||||
else if (
|
||||
importSymbolCurrentNameToModuleLocation.has(exportSymbolOriginalName)
|
||||
|
||||
for (const expt of exports) {
|
||||
// get the source declaration where we can determine the type of the export. e.g. class vs function
|
||||
let sourceSymbol = expt;
|
||||
if (sourceSymbol.declarations[0].kind === ts.SyntaxKind.ExportSpecifier) {
|
||||
sourceSymbol = checker.getAliasedSymbol(expt);
|
||||
}
|
||||
|
||||
if (!sourceSymbol.declarations || sourceSymbol.declarations.length === 0) {
|
||||
console.log('Could not find the source symbol for ', expt.name);
|
||||
continue;
|
||||
}
|
||||
const sourceDeclaration = sourceSymbol.declarations[0];
|
||||
|
||||
if (ts.isFunctionDeclaration(sourceDeclaration)) {
|
||||
exportDeclarations.functions.push(expt.name);
|
||||
} else if (ts.isClassDeclaration(sourceDeclaration)) {
|
||||
exportDeclarations.classes.push(expt.name);
|
||||
} else if (ts.isVariableDeclaration(sourceDeclaration)) {
|
||||
exportDeclarations.variables.push(expt.name);
|
||||
} else if (ts.isEnumDeclaration(sourceDeclaration)) {
|
||||
// `const enum`s should not be analyzed. They do not add to bundle size and
|
||||
// creating a file that imports them causes an error during the rollup step.
|
||||
if (
|
||||
// Identifies if this enum had a "const" modifier attached.
|
||||
!sourceDeclaration.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ConstKeyword)
|
||||
) {
|
||||
const moduleLocation: string = importSymbolCurrentNameToModuleLocation.get(
|
||||
exportSymbolOriginalName
|
||||
)!;
|
||||
let reExportedSymbols: MemberList;
|
||||
if (importModuleLocationToExportedSymbolsList.has(moduleLocation)) {
|
||||
reExportedSymbols = deepCopy(
|
||||
importModuleLocationToExportedSymbolsList.get(moduleLocation)!
|
||||
);
|
||||
} else {
|
||||
reExportedSymbols = extractDeclarations(
|
||||
importSymbolCurrentNameToModuleLocation.get(
|
||||
exportSymbolOriginalName
|
||||
)!
|
||||
);
|
||||
importModuleLocationToExportedSymbolsList.set(
|
||||
moduleLocation,
|
||||
deepCopy(reExportedSymbols)
|
||||
);
|
||||
}
|
||||
|
||||
let nameToBeReplaced = exportSymbolOriginalName;
|
||||
// if current exported symbol is renamed in import clause. then we retrieve its original name from
|
||||
// importSymbolCurrentNameToOriginalName map
|
||||
if (
|
||||
importSymbolCurrentNameToOriginalName.has(exportSymbolOriginalName)
|
||||
) {
|
||||
nameToBeReplaced = importSymbolCurrentNameToOriginalName.get(
|
||||
exportSymbolOriginalName
|
||||
)!;
|
||||
}
|
||||
|
||||
filterAllBy(reExportedSymbols, [nameToBeReplaced]);
|
||||
// replace with new name
|
||||
replaceAll(
|
||||
reExportedSymbols,
|
||||
nameToBeReplaced,
|
||||
exportSymbolCurrentName
|
||||
);
|
||||
|
||||
// concatenate re-exported MemberList with MemberList of the dts file
|
||||
for (const key of Object.keys(parentDeclarations) as Array<
|
||||
keyof MemberList
|
||||
>) {
|
||||
parentDeclarations[key].push(...reExportedSymbols[key]);
|
||||
}
|
||||
exportDeclarations.enums.push(expt.name);
|
||||
}
|
||||
// handles declare first then export
|
||||
// declare const apps: Map<string, number>;
|
||||
// export { apps as apps1};
|
||||
// function a() {};
|
||||
// export {a};
|
||||
else {
|
||||
if (isExportRenamed(exportSpecifier)) {
|
||||
replaceAll(
|
||||
parentDeclarations,
|
||||
exportSymbolOriginalName,
|
||||
exportSymbolCurrentName
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(`export of unknown type: ${expt.name}`);
|
||||
exportDeclarations.unknown.push(expt.name);
|
||||
}
|
||||
}
|
||||
|
||||
Object.values(exportDeclarations).forEach(each => {
|
||||
each.sort();
|
||||
});
|
||||
|
||||
return exportDeclarations;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -572,7 +321,8 @@ export function mapSymbolToType(
|
||||
functions: [],
|
||||
classes: [],
|
||||
variables: [],
|
||||
enums: []
|
||||
enums: [],
|
||||
unknown: []
|
||||
};
|
||||
|
||||
for (const key of Object.keys(memberList) as Array<keyof MemberList>) {
|
||||
@@ -587,23 +337,6 @@ export function mapSymbolToType(
|
||||
return newMemberList;
|
||||
}
|
||||
|
||||
function extractOriginalSymbolName(
|
||||
exportSpecifier: ts.ExportSpecifier
|
||||
): string {
|
||||
// if symbol is renamed, then exportSpecifier.propertyName is not null and stores the orignal name, exportSpecifier.name stores the renamed name.
|
||||
// if symbol is not renamed, then exportSpecifier.propertyName is null, exportSpecifier.name stores the orignal name.
|
||||
if (exportSpecifier.propertyName) {
|
||||
return exportSpecifier.propertyName.escapedText.toString();
|
||||
}
|
||||
return exportSpecifier.name.escapedText.toString();
|
||||
}
|
||||
|
||||
function filterAllBy(memberList: MemberList, keep: string[]): void {
|
||||
for (const key of Object.keys(memberList) as Array<keyof MemberList>) {
|
||||
memberList[key] = memberList[key].filter(each => keep.includes(each));
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceAll(
|
||||
memberList: MemberList,
|
||||
original: string,
|
||||
@@ -630,10 +363,6 @@ function replaceWith(
|
||||
return rv;
|
||||
}
|
||||
|
||||
function isExportRenamed(exportSpecifier: ts.ExportSpecifier): boolean {
|
||||
return exportSpecifier.propertyName != null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This functions writes generated json report(s) to a file
|
||||
@@ -787,21 +516,6 @@ function retrieveBundleFileLocation(pkgJson: {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
*
|
||||
* This function creates a map from a MemberList object which maps symbol names (key) listed
|
||||
* to its type (value)
|
||||
*/
|
||||
export function buildMap(api: MemberList): Map<string, string> {
|
||||
const map: Map<string, string> = new Map();
|
||||
|
||||
for (const type of Object.keys(api) as Array<keyof MemberList>) {
|
||||
api[type].forEach((element: string) => {
|
||||
map.set(element, type);
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* A recursive function that locates and generates reports for sub-modules
|
||||
@@ -833,7 +547,7 @@ async function traverseDirs(
|
||||
fs.lstatSync(p).isDirectory() &&
|
||||
fs.existsSync(`${p}/package.json`) &&
|
||||
JSON.parse(fs.readFileSync(`${p}/package.json`, { encoding: 'utf-8' }))[
|
||||
generateSizeAnalysisReportPkgJsonField
|
||||
generateSizeAnalysisReportPkgJsonField
|
||||
]
|
||||
) {
|
||||
const subModuleReports: Report[] = await traverseDirs(
|
||||
@@ -860,8 +574,7 @@ async function traverseDirs(
|
||||
export async function buildJsonReport(
|
||||
moduleName: string,
|
||||
publicApi: MemberList,
|
||||
jsFile: string,
|
||||
map: Map<string, string>
|
||||
jsFile: string
|
||||
): Promise<Report> {
|
||||
const result: Report = {
|
||||
name: moduleName,
|
||||
@@ -869,7 +582,7 @@ export async function buildJsonReport(
|
||||
};
|
||||
for (const exp of publicApi.classes) {
|
||||
try {
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map));
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -877,14 +590,14 @@ export async function buildJsonReport(
|
||||
|
||||
for (const exp of publicApi.functions) {
|
||||
try {
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map));
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
for (const exp of publicApi.variables) {
|
||||
try {
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map));
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -892,7 +605,7 @@ export async function buildJsonReport(
|
||||
|
||||
for (const exp of publicApi.enums) {
|
||||
try {
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map));
|
||||
result.symbols.push(await extractDependenciesAndSize(exp, jsFile));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -920,9 +633,9 @@ export async function generateReport(
|
||||
throw new Error(ErrorCode.INPUT_BUNDLE_FILE_DOES_NOT_EXIST);
|
||||
}
|
||||
|
||||
const publicAPI = extractDeclarations(resolvedDtsFile);
|
||||
const map: Map<string, string> = buildMap(publicAPI);
|
||||
return buildJsonReport(name, publicAPI, bundleFile, map);
|
||||
console.log('generating report for ', name);
|
||||
const publicAPI = extractExports(resolvedBundleFile);
|
||||
return buildJsonReport(name, publicAPI, bundleFile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ import { bundleWithRollup } from './bundle/rollup';
|
||||
import { bundleWithWebpack } from './bundle/webpack';
|
||||
import { calculateContentSize } from './util';
|
||||
import { minify } from './bundle/minify';
|
||||
import { extractDeclarations, MemberList } from './analysis-helper';
|
||||
import { extractAllTopLevelSymbols, MemberList } from './analysis-helper';
|
||||
|
||||
interface BundleAnalysisArgs {
|
||||
input: string;
|
||||
@@ -392,7 +392,7 @@ async function analyzeBundleWithBundler(
|
||||
analysisResult.debugInfo = {
|
||||
pathToBundle: bundleFilePath,
|
||||
pathToMinifiedBundle: minifiedBundleFilePath,
|
||||
dependencies: extractDeclarations(bundleFilePath)
|
||||
dependencies: extractAllTopLevelSymbols(bundleFilePath)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ yargs
|
||||
type: 'array',
|
||||
alias: 'im',
|
||||
desc:
|
||||
'The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions-exp" "firebase/auth-exp"'
|
||||
'The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions" "firebase/auth"'
|
||||
},
|
||||
inputDtsFile: {
|
||||
type: 'string',
|
||||
@@ -90,4 +90,4 @@ yargs
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
argv => runBundleAnalysis(argv as any)
|
||||
)
|
||||
.help().argv;
|
||||
.help().parseSync();
|
||||
|
||||
@@ -73,6 +73,11 @@ export async function analyzePackageSize(
|
||||
`${projectRoot}/packages/*`
|
||||
]);
|
||||
allModulesLocation = allModulesLocation.filter(path => {
|
||||
const pkgJsonPath = `${path}/package.json`;
|
||||
if (!fs.existsSync(pkgJsonPath)){
|
||||
return false;
|
||||
}
|
||||
|
||||
const json = JSON.parse(
|
||||
fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' })
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ const deps = Object.keys(
|
||||
Object.assign({}, pkg.peerDependencies, pkg.dependencies)
|
||||
);
|
||||
|
||||
const nodeInternals = ['fs', 'path'];
|
||||
const nodeInternals = ['fs', 'path', 'util'];
|
||||
|
||||
export default [
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import {
|
||||
extractDeclarations,
|
||||
MemberList,
|
||||
dedup,
|
||||
mapSymbolToType,
|
||||
@@ -27,8 +26,9 @@ import {
|
||||
ErrorCode,
|
||||
writeReportToDirectory,
|
||||
extractExternalDependencies,
|
||||
buildMap,
|
||||
Report
|
||||
Report,
|
||||
extractExports,
|
||||
extractAllTopLevelSymbols
|
||||
} from '../analysis-helper';
|
||||
|
||||
import {
|
||||
@@ -39,14 +39,14 @@ import {
|
||||
import * as fs from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
describe('extractDeclarations on .d.ts file', () => {
|
||||
describe('extractExports', () => {
|
||||
let testModuleDtsFile: string;
|
||||
let extractedDeclarations: MemberList;
|
||||
before(() => {
|
||||
const start = Date.now();
|
||||
testModuleDtsFile = getTestModuleDtsFilePath();
|
||||
extractedDeclarations = extractDeclarations(testModuleDtsFile);
|
||||
console.log('extractDeclarations on .d.ts file took ', Date.now() - start);
|
||||
extractedDeclarations = extractExports(testModuleDtsFile);
|
||||
console.log('extractExports took ', Date.now() - start);
|
||||
});
|
||||
// export {tar as tarr, tar1 as tarr1} from '..'
|
||||
it('test export rename', () => {
|
||||
@@ -187,42 +187,34 @@ describe('extractDeclarations on .d.ts file', () => {
|
||||
});
|
||||
|
||||
// import * as fs from 'fs'
|
||||
// import * as tmp from 'tmp'
|
||||
// export declare const aVar: tmp.FileOptions;
|
||||
// export { fs as fs1 };
|
||||
it('test namespace export', () => {
|
||||
expect(extractedDeclarations.variables).to.include.members(['fs1']);
|
||||
expect(extractedDeclarations.variables).to.not.include.members(['tmp']);
|
||||
expect(extractedDeclarations.variables).to.include.members(['aVar']);
|
||||
expect(extractedDeclarations.unknown).to.include.members(['fs1']);
|
||||
});
|
||||
});
|
||||
describe('extractDeclarations on js bundle file', () => {
|
||||
|
||||
describe('extractAllTopLevelSymbols', () => {
|
||||
let subsetExportsBundleFile: string;
|
||||
let extractedDeclarations: MemberList;
|
||||
before(function () {
|
||||
this.timeout(120000);
|
||||
const start = Date.now();
|
||||
const testModuleDtsFile: string = getTestModuleDtsFilePath();
|
||||
const map: Map<string, string> = buildMap(
|
||||
extractDeclarations(testModuleDtsFile)
|
||||
);
|
||||
subsetExportsBundleFile = getSubsetExportsBundleFilePath();
|
||||
extractedDeclarations = extractDeclarations(subsetExportsBundleFile, map);
|
||||
extractedDeclarations = extractAllTopLevelSymbols(subsetExportsBundleFile);
|
||||
console.log(
|
||||
'extractDeclarations on js bundle file took ',
|
||||
Date.now() - start
|
||||
);
|
||||
});
|
||||
it('test variable extractions', () => {
|
||||
const variablesArray = ['aVar', 'fs1'];
|
||||
const variablesArray = ['aVar'];
|
||||
variablesArray.sort();
|
||||
expect(extractedDeclarations.variables).to.have.members(variablesArray);
|
||||
expect(extractedDeclarations.variables).to.include.members(variablesArray);
|
||||
});
|
||||
|
||||
it('test functions extractions', () => {
|
||||
const functionsArray = [
|
||||
'tar',
|
||||
'tarr1',
|
||||
'tar1',
|
||||
'basicFuncExportEnumDependencies',
|
||||
'd1',
|
||||
'd2',
|
||||
@@ -236,16 +228,15 @@ describe('extractDeclarations on js bundle file', () => {
|
||||
it('test enums extractions', () => {
|
||||
const enumsArray = [
|
||||
'BasicEnumExport',
|
||||
'LogLevel2',
|
||||
'BasicEnumExportBar',
|
||||
'BasicEnumExportFar'
|
||||
];
|
||||
enumsArray.sort();
|
||||
expect(extractedDeclarations.enums).to.have.members(enumsArray);
|
||||
expect(extractedDeclarations.variables).to.include.members(enumsArray);
|
||||
});
|
||||
|
||||
it('test classes extractions', () => {
|
||||
const classesArray = ['Logger1'];
|
||||
const classesArray = ['BasicClassExport'];
|
||||
classesArray.sort();
|
||||
expect(extractedDeclarations.classes).to.have.members(classesArray);
|
||||
});
|
||||
@@ -257,7 +248,8 @@ describe('test dedup helper function', () => {
|
||||
functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'],
|
||||
classes: ['aClass', 'bClass', 'aClass', 'cClass'],
|
||||
variables: ['aVar', 'bVar', 'cVar', 'aVar'],
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum']
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'],
|
||||
unknown: []
|
||||
};
|
||||
memberList = dedup(memberList);
|
||||
|
||||
@@ -287,7 +279,8 @@ describe('test dedup helper function', () => {
|
||||
functions: [],
|
||||
classes: [],
|
||||
variables: ['aVar', 'bVar', 'cVar', 'aVar'],
|
||||
enums: []
|
||||
enums: [],
|
||||
unknown: []
|
||||
};
|
||||
memberList = dedup(memberList);
|
||||
expect(memberList.functions).to.have.length(0);
|
||||
@@ -308,7 +301,8 @@ describe('test replaceAll helper function', () => {
|
||||
functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'],
|
||||
classes: ['aClass', 'bClass', 'aClass', 'cClass'],
|
||||
variables: ['aVar', 'bVar', 'cVar', 'aVar'],
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum']
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'],
|
||||
unknown: []
|
||||
};
|
||||
const original: string = 'aFunc';
|
||||
const replaceTo: string = 'replacedFunc';
|
||||
@@ -331,7 +325,8 @@ describe('test replaceAll helper function', () => {
|
||||
functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'],
|
||||
classes: ['aClass', 'bClass', 'aClass', 'cClass'],
|
||||
variables: ['aVar', 'bVar', 'cVar', 'aVar'],
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum']
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'],
|
||||
unknown: []
|
||||
};
|
||||
const replaceTo: string = 'replacedClass';
|
||||
const original: string = 'bClass';
|
||||
@@ -354,7 +349,8 @@ describe('test replaceAll helper function', () => {
|
||||
functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'],
|
||||
classes: ['aClass', 'bClass', 'aClass', 'cClass'],
|
||||
variables: ['aVar', 'bVar', 'cVar', 'aVar'],
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum']
|
||||
enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'],
|
||||
unknown: []
|
||||
};
|
||||
const replaceTo: string = 'replacedEnum';
|
||||
const original: string = 'eEnum';
|
||||
@@ -377,7 +373,8 @@ describe('test mapSymbolToType helper function', () => {
|
||||
functions: ['aVar', 'bFunc', 'cFunc'],
|
||||
classes: ['bClass', 'cClass'],
|
||||
variables: ['aClass', 'bVar', 'cVar', 'aEnum'],
|
||||
enums: ['bEnum', 'cEnum', 'dEnum', 'aFunc']
|
||||
enums: ['bEnum', 'cEnum', 'dEnum', 'aFunc'],
|
||||
unknown: []
|
||||
};
|
||||
|
||||
const map: Map<string, string> = new Map([
|
||||
|
||||
@@ -14,13 +14,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
aVar,
|
||||
LogLevel2,
|
||||
Logger1,
|
||||
tarr1,
|
||||
fs1,
|
||||
basicFuncExportEnumDependencies,
|
||||
basicFuncExportFuncDependenciesBar
|
||||
basicFuncExportFuncDependenciesBar,
|
||||
BasicClassExport
|
||||
} from './index';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"target": "es5",
|
||||
"target": "es2017",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"strict": true
|
||||
|
||||
@@ -45,18 +45,21 @@ function generateReportForCDNScripts(): Report[] {
|
||||
const reports = [];
|
||||
const firebaseRoot = path.resolve(__dirname, '../../packages/firebase');
|
||||
const pkgJson = require(`${firebaseRoot}/package.json`);
|
||||
const compatPkgJson = require(`${firebaseRoot}/compat/package.json`);
|
||||
|
||||
const special_files = [
|
||||
'firebase-performance-standalone.es2017.js',
|
||||
'firebase-performance-standalone.js',
|
||||
'firebase-firestore.memory.js',
|
||||
'firebase.js'
|
||||
'firebase-performance-standalone-compat.es2017.js',
|
||||
'firebase-performance-standalone-compat.js',
|
||||
'firebase-compat.js'
|
||||
];
|
||||
|
||||
const files = [
|
||||
...special_files.map((file: string) => `${firebaseRoot}/${file}`),
|
||||
...pkgJson.components.map(
|
||||
(component: string) => `${firebaseRoot}/firebase-${component}.js`
|
||||
(component: string) => `${firebaseRoot}/firebase-${component.replace('/', '-')}.js`
|
||||
),
|
||||
...compatPkgJson.components.map(
|
||||
(component: string) => `${firebaseRoot}/firebase-${component}-compat.js`
|
||||
)
|
||||
];
|
||||
|
||||
@@ -77,7 +80,16 @@ async function generateReportForNPMPackages(): Promise<Report[]> {
|
||||
for (const info of packageInfo) {
|
||||
const packages = await findAllPackages(info.location);
|
||||
for (const pkg of packages) {
|
||||
reports.push(...(await collectBinarySize(pkg)));
|
||||
try {
|
||||
reports.push(...(await collectBinarySize(pkg)));
|
||||
} catch (e) {
|
||||
// log errors and continue to the next package
|
||||
console.log(
|
||||
`failed to generate report for ${pkg}.
|
||||
error: ${e}`
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return reports;
|
||||
@@ -104,11 +116,12 @@ async function collectBinarySize(pkg: string): Promise<Report[]> {
|
||||
const fields = [
|
||||
'main',
|
||||
'module',
|
||||
'esm2017',
|
||||
'browser',
|
||||
'esm5',
|
||||
'react-native',
|
||||
'cordova',
|
||||
'lite',
|
||||
'lite-esm2017'
|
||||
'lite-esm5'
|
||||
];
|
||||
const json = require(pkg);
|
||||
for (const field of fields) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
RequestBody,
|
||||
RequestEndpoint
|
||||
} from './size_report_helper';
|
||||
import { existsSync } from 'fs';
|
||||
interface ModularExportBinarySizeRequestBody extends RequestBody {
|
||||
modules: Report[];
|
||||
}
|
||||
@@ -37,7 +38,12 @@ async function generateReport(): Promise<ModularExportBinarySizeRequestBody> {
|
||||
]);
|
||||
|
||||
allModulesLocation = allModulesLocation.filter(path => {
|
||||
const json = require(`${path}/package.json`);
|
||||
const pkgJsonPath = `${path}/package.json`;
|
||||
if (!existsSync(pkgJsonPath)){
|
||||
return false;
|
||||
}
|
||||
|
||||
const json = require(pkgJsonPath);
|
||||
return (
|
||||
json.name.startsWith('@firebase') &&
|
||||
!json.name.includes('-compat') &&
|
||||
|
||||
Reference in New Issue
Block a user