Skip to content

Update size analysis cli to support analyzing default export, side effect export and namespace export #4349

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 6 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
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
81 changes: 77 additions & 4 deletions repo-scripts/size-analysis/bundle-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ interface DebugOptions {

interface BundleDefinition {
name: string;
description?: string;
dependencies: BundleDependency[];
}

Expand Down Expand Up @@ -74,6 +75,12 @@ enum Mode {
Local = 'local'
}

enum SpecialImport {
Default = 'default import',
Sizeeffect = 'side effect import',
Namespace = 'namespace import'
}

export async function run({
input,
bundler,
Expand Down Expand Up @@ -247,7 +254,9 @@ async function analyzeBundle(
): Promise<BundleAnalysis> {
const analysis: BundleAnalysis = {
name: bundleDefinition.name,
results: []
description: bundleDefinition.description ?? '',
results: [],
dependencies: bundleDefinition.dependencies
};

let moduleDirectory: string | undefined;
Expand Down Expand Up @@ -392,15 +401,23 @@ async function analyzeBundleWithBundler(

function createEntryFileContent(bundleDefinition: BundleDefinition): string {
const contentArray = [];
// cache used symbols. Used to avoid symbol collision when multiple modules export symbols with the same name.
const symbolsCache = new Set<string>();
for (const dep of bundleDefinition.dependencies) {
for (const imp of dep.imports) {
if (typeof imp === 'string') {
contentArray.push(`export {${imp}} from '${dep.packageName}';`);
contentArray.push(
...createImportExport(imp, dep.packageName, symbolsCache)
);
} else {
// Import object
// submodule imports
for (const subImp of imp.imports) {
contentArray.push(
`export {${subImp}} from '${dep.packageName}/${imp.path}';`
...createImportExport(
subImp,
`${dep.packageName}/${imp.path}`,
symbolsCache
)
);
}
}
Expand All @@ -410,8 +427,64 @@ function createEntryFileContent(bundleDefinition: BundleDefinition): string {
return contentArray.join('\n');
}

function createImportExport(
symbol: string,
modulePath: string,
symbolsCache: Set<string>
): string[] {
const contentArray = [];

switch (symbol) {
case SpecialImport.Default: {
const nameToUse = createSymbolName('default_import', symbolsCache);
contentArray.push(`import ${nameToUse} from '${modulePath}';`);
contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken
break;
}
case SpecialImport.Namespace: {
const nameToUse = createSymbolName('namespace', symbolsCache);
contentArray.push(`import * as ${nameToUse} from '${modulePath}';`);
contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken
break;
}
case SpecialImport.Sizeeffect:
contentArray.push(`import '${modulePath}';`);
break;
default:
// named imports
const nameToUse = createSymbolName(symbol, symbolsCache);

if (nameToUse !== symbol) {
contentArray.push(
`export {${symbol} as ${nameToUse}} from '${modulePath}';`
);
} else {
contentArray.push(`export {${symbol}} from '${modulePath}';`);
}
}

return contentArray;
}

/**
* In case a symbol with the same name is already imported from another module, we need to give this symbol another name
* using "originalname as anothername" syntax, otherwise it returns the original symbol name.
*/
function createSymbolName(symbol: string, symbolsCache: Set<string>): string {
let nameToUse = symbol;
const max = 100;
while (symbolsCache.has(nameToUse)) {
nameToUse = `${symbol}_${Math.floor(Math.random() * max)}`;
}

symbolsCache.add(nameToUse);
return nameToUse;
}

interface BundleAnalysis {
name: string; // the bundle name defined in the bundle definition
description: string;
dependencies: BundleDependency[];
results: BundleAnalysisResult[];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[
{
"name": "test",
"desecription": "",
"dependencies": [
{
"packageName": "@firebase/app",
Expand All @@ -19,5 +20,25 @@
]
}
]
},
{
"name": "test2",
"description": "default imports and side effect imports",
"dependencies": [
{
"packageName": "@firebase/app",
"versionOrTag": "latest",
"imports": [
"default import"
]
},
{
"packageName": "@firebase/firestore",
"versionOrTag": "latest",
"imports": [
"side effect import"
]
}
]
}
]
20 changes: 17 additions & 3 deletions repo-scripts/size-analysis/package-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

import { resolve, basename } from 'path';
import { resolve, basename, dirname } from 'path';
import {
generateReport,
generateReportForModules,
Expand All @@ -24,10 +24,10 @@ import {
ErrorCode,
writeReportToDirectory
} from './analysis-helper';
import { mapWorkspaceToPackages } from '../../scripts/release/utils/workspace';
import { projectRoot } from '../../scripts/utils';
import glob from 'glob';
import * as fs from 'fs';

const projectRoot = dirname(resolve(__dirname, '../package.json'));
/**
* Support Command Line Options
* -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules.
Expand Down Expand Up @@ -112,3 +112,17 @@ export async function analyzePackageSize(
throw new Error(ErrorCode.INVALID_FLAG_COMBINATION);
}
}

function mapWorkspaceToPackages(workspaces: string[]): Promise<string[]> {
return Promise.all<string[]>(
workspaces.map(
workspace =>
new Promise(resolve => {
glob(workspace, (err, paths) => {
if (err) throw err;
resolve(paths);
});
})
)
).then(paths => paths.reduce((arr, val) => arr.concat(val), []));
}
3 changes: 2 additions & 1 deletion repo-scripts/size-analysis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test": "run-p lint test:node",
"test:ci": "node ../../scripts/run_tests_in_ci.js -s test:node",
"pretest:node": "tsc -p test/test-inputs && rollup -c",
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha **/*.test.ts --config ../../config/mocharc.node.js --timeout 60000"
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha **/*.test.ts --config ../../config/mocharc.node.js --timeout 60000",
"build": "rollup -c"
},
"dependencies": {
"rollup": "2.35.1",
Expand Down
30 changes: 30 additions & 0 deletions repo-scripts/size-analysis/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const deps = Object.keys(
Object.assign({}, pkg.peerDependencies, pkg.dependencies)
);

const nodeInternals = ['fs', 'path'];

export default [
{
input: 'test/test-inputs/subsetExports.ts',
Expand All @@ -49,5 +51,33 @@ export default [
})
],
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
},
{
input: 'cli.ts',
output: [
{
file: 'dist/cli.js',
format: 'cjs',
sourcemap: false
}
],
plugins: [
typescriptPlugin({
typescript,
tsconfigOverride: {
compilerOptions: {
target: 'es2017',
module: 'es2015'
}
}
}),
json({
preferConst: true
})
],
external: id =>
[...deps, ...nodeInternals].some(
dep => id === dep || id.startsWith(`${dep}/`)
)
}
];