Skip to content

Refactor binary size script. #4399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 6, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 65 additions & 89 deletions scripts/size_report/report_binary_size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@
* limitations under the License.
*/

import { resolve as pathResolve } from 'path';
import * as fs from 'fs';
import { execSync } from 'child_process';
import * as path from 'path';
import * as rollup from 'rollup';
import * as terser from 'terser';

import { execSync } from 'child_process';
import {
upload,
runId,
RequestBody,
RequestEndpoint
} from './size_report_helper';
import * as rollup from 'rollup';
import { glob } from 'glob';

import commonjs from '@rollup/plugin-commonjs';

interface Report {
Expand All @@ -37,10 +40,10 @@ interface BinarySizeRequestBody extends RequestBody {
metric: string;
results: Report[];
}
// CDN scripts

function generateReportForCDNScripts(): Report[] {
const reports = [];
const firebaseRoot = pathResolve(__dirname, '../../packages/firebase');
const firebaseRoot = path.resolve(__dirname, '../../packages/firebase');
const pkgJson = require(`${firebaseRoot}/package.json`);

const special_files = [
Expand All @@ -60,109 +63,82 @@ function generateReportForCDNScripts(): Report[] {
for (const file of files) {
const { size } = fs.statSync(file);
const fileName = file.split('/').slice(-1)[0];
reports.push(makeReportObject('firebase', fileName, size));
reports.push({ sdk: 'firebase', type: fileName, value: size });
}

return reports;
}

// NPM packages
async function generateReportForNPMPackages(): Promise<Report[]> {
const reports: Report[] = [];
const fields = [
'main',
'module',
'esm2017',
'browser',
'react-native',
'lite',
'lite-esm2017'
];

const packageInfo = JSON.parse(
execSync('npx lerna ls --json --scope @firebase/*').toString()
);

const taskPromises: Promise<void>[] = [];
for (const pkg of packageInfo) {
// we traverse the dir in order to include binaries for submodules, e.g. @firebase/firestore/memory
// Currently we only traverse 1 level deep because we don't have any submodule deeper than that.
traverseDirs(pkg.location, collectBinarySize, 0, 1);
for (const info of packageInfo) {
const packages = await findAllPackages(info.location);
for (const pkg of packages) {
reports.push(...(await collectBinarySize(pkg)));
}
}

await Promise.all(taskPromises);

return reports;
}

function collectBinarySize(path: string) {
const packageJsonPath = `${path}/package.json`;
if (!fs.existsSync(packageJsonPath)) {
return;
}

const promise = new Promise<void>(async resolve => {
const packageJson = require(packageJsonPath);

for (const field of fields) {
if (packageJson[field]) {
const filePath = pathResolve(path, packageJson[field]);
// Need to create a bundle and get the size of the bundle instead of reading the size of the file directly.
// It is because some packages might be split into multiple files in order to share code between entry points.
const bundle = await rollup.rollup({
input: filePath,
plugins: [commonjs()]
});

const { output } = await bundle.generate({ format: 'es' });
const rawCode = output[0].code;

// remove comments and whitespaces, then get size
const { code } = await terser.minify(rawCode, {
format: {
comments: false
},
mangle: false,
compress: false
});

const size = Buffer.byteLength(code!, 'utf-8');
reports.push(makeReportObject(packageJson.name, field, size));
async function findAllPackages(root: string): Promise<string[]> {
return new Promise((resolve, reject) => {
glob(
'**/package.json',
{ cwd: root, ignore: '**/node_modules/**' },
(err, files) => {
if (err) {
reject(err);
} else {
resolve(files.map(x => path.resolve(root, x)));
}
}

resolve();
});
taskPromises.push(promise);
}
);
});
}

function traverseDirs(
path: string,
executor: Function,
level: number,
levelLimit: number
) {
if (level > levelLimit) {
return;
}

executor(path);

for (const name of fs.readdirSync(path)) {
const p = `${path}/${name}`;

if (fs.lstatSync(p).isDirectory()) {
traverseDirs(p, executor, level + 1, levelLimit);
async function collectBinarySize(pkg: string): Promise<Report[]> {
const reports: Report[] = [];
const fields = [
'main',
'module',
'esm2017',
'browser',
'react-native',
'lite',
'lite-esm2017'
];
const json = require(pkg);
for (const field of fields) {
if (json[field]) {
const artifact = path.resolve(path.dirname(pkg), json[field]);

// Need to create a bundle and get the size of the bundle instead of reading the size of the file directly.
// It is because some packages might be split into multiple files in order to share code between entry points.
const bundle = await rollup.rollup({
input: artifact,
plugins: [commonjs()]
});

const { output } = await bundle.generate({ format: 'es' });
const rawCode = output[0].code;

// remove comments and whitespaces, then get size
const { code } = await terser.minify(rawCode, {
format: {
comments: false
},
mangle: false,
compress: false
});

const size = Buffer.byteLength(code!, 'utf-8');
reports.push({ sdk: json.name, type: field, value: size });
}
}
}

function makeReportObject(sdk: string, type: string, value: number): Report {
return {
sdk,
type,
value
};
return reports;
}

async function generateSizeReport(): Promise<BinarySizeRequestBody> {
Expand Down