From 5c3e676ec1ae20b7b7a9ecdda5afddfd8a0b9772 Mon Sep 17 00:00:00 2001 From: David Alsh Date: Wed, 12 Jul 2023 16:05:58 +1000 Subject: [PATCH 1/3] added bundle stats reporter and dev tool Co-authored-by: Eric Eldredge Co-authored-by: Brian Do --- packages/dev/bundle-stats-cli/README.md | 4 + packages/dev/bundle-stats-cli/package.json | 25 +++++ packages/dev/bundle-stats-cli/src/cli.js | 85 ++++++++++++++++ packages/reporters/bundle-stats/package.json | 24 +++++ .../bundle-stats/src/BundleStatsReporter.js | 99 +++++++++++++++++++ 5 files changed, 237 insertions(+) create mode 100644 packages/dev/bundle-stats-cli/README.md create mode 100644 packages/dev/bundle-stats-cli/package.json create mode 100644 packages/dev/bundle-stats-cli/src/cli.js create mode 100644 packages/reporters/bundle-stats/package.json create mode 100644 packages/reporters/bundle-stats/src/BundleStatsReporter.js diff --git a/packages/dev/bundle-stats-cli/README.md b/packages/dev/bundle-stats-cli/README.md new file mode 100644 index 00000000000..f7ee10a4279 --- /dev/null +++ b/packages/dev/bundle-stats-cli/README.md @@ -0,0 +1,4 @@ +# bundle-stats-cli + +- Run `yarn link` in `packages/dev/bundle-stats-cli` +- Run `parcel-bundle-stats` in the project root, a `parcel-bundle-reports` directory will be created with the stats file inside diff --git a/packages/dev/bundle-stats-cli/package.json b/packages/dev/bundle-stats-cli/package.json new file mode 100644 index 00000000000..91a7eaa8470 --- /dev/null +++ b/packages/dev/bundle-stats-cli/package.json @@ -0,0 +1,25 @@ +{ + "name": "@parcel/bundle-stats-cli", + "version": "2.9.3", + "private": true, + "main": "lib/cli.js", + "source": "src/cli.js", + "bin": { + "parcel-bundle-stats": "bin.js" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.9.3" + }, + "dependencies": { + "@parcel/reporter-bundle-stats": "^2.9.3", + "@parcel/core": "^2.9.3", + "@parcel/utils": "^2.9.3", + "commander": "^7.0.0", + "parcel-query": "^2.9.3" + }, + "devDependencies": { + "@parcel/babel-register": "^2.9.3", + "@parcel/types": "^2.9.3" + } +} diff --git a/packages/dev/bundle-stats-cli/src/cli.js b/packages/dev/bundle-stats-cli/src/cli.js new file mode 100644 index 00000000000..2ea25add291 --- /dev/null +++ b/packages/dev/bundle-stats-cli/src/cli.js @@ -0,0 +1,85 @@ +/* eslint-disable no-console, monorepo/no-internal-import */ +// @flow strict-local + +import type {PackagedBundle} from '@parcel/types'; +import type {ParcelOptions} from '@parcel/core/src/types'; + +// $FlowFixMe[untyped-import] +import {version} from '../package.json'; + +import commander from 'commander'; +import fs from 'fs'; +import path from 'path'; + +import {DefaultMap} from '@parcel/utils'; + +import {loadGraphs} from 'parcel-query/src/index.js'; +import {getBundleStats} from '@parcel/reporter-bundle-stats/src/BundleStatsReporter'; +import {PackagedBundle as PackagedBundleClass} from '@parcel/core/src/public/Bundle'; + +function run({cacheDir, outDir}) { + // 1. load bundle graph and info via parcel~query + let {bundleGraph, bundleInfo} = loadGraphs(cacheDir); + + if (bundleGraph == null) { + console.error('Bundle Graph could not be found'); + process.exit(1); + throw new Error(); + } + + if (bundleInfo == null) { + console.error('Bundle Info could not be found'); + process.exit(1); + throw new Error(); + } + + // 2. generate stats files for each target + fs.mkdirSync(outDir, {recursive: true}); + + let projectRoot = process.cwd(); + + // $FlowFixMe[unclear-type] + let parcelOptions: ParcelOptions = ({projectRoot}: any); + + let bundlesByTarget: DefaultMap< + string /* target name */, + Array, + > = new DefaultMap(() => []); + for (let bundle of bundleGraph.getBundles()) { + bundlesByTarget + .get(bundle.target.name) + .push( + PackagedBundleClass.getWithInfo( + bundle, + bundleGraph, + parcelOptions, + bundleInfo.get(bundle.id), + ), + ); + } + + for (let [targetName, bundles] of bundlesByTarget) { + fs.writeFileSync( + path.join(outDir, `${targetName}-stats.json`), + JSON.stringify(getBundleStats(bundles, parcelOptions), null, 2), + ); + } +} + +commander + .version(version, '-V, --version') + .description('Generate a stats report for a Parcel build') + .option('-v, --verbose', 'Print verbose output') + .option( + '-c, --cache-dir ', + 'Directory to the parcel cache', + '.parcel-cache', + ) + .option( + '-o, --out-dir ', + 'Directory to write the stats to', + 'parcel-bundle-reports', + ) + .action(run); + +commander.parse(); diff --git a/packages/reporters/bundle-stats/package.json b/packages/reporters/bundle-stats/package.json new file mode 100644 index 00000000000..2e4f0cce3c8 --- /dev/null +++ b/packages/reporters/bundle-stats/package.json @@ -0,0 +1,24 @@ +{ + "name": "@parcel/reporter-bundle-stats", + "version": "2.9.3", + "publishConfig": { + "access": "public" + }, + "main": "lib/BundleStatsReporter.js", + "source": "src/BundleStatsReporter.js", + "bin": { + "parcel-bundle-stats": "bin.js" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.3.1" + }, + "dependencies": { + "@parcel/core": "^2.9.3", + "@parcel/plugin": "^2.9.3", + "@parcel/utils": "^2.9.3" + }, + "devDependencies": { + "@parcel/types": "^2.9.3" + } +} diff --git a/packages/reporters/bundle-stats/src/BundleStatsReporter.js b/packages/reporters/bundle-stats/src/BundleStatsReporter.js new file mode 100644 index 00000000000..41af0b2b968 --- /dev/null +++ b/packages/reporters/bundle-stats/src/BundleStatsReporter.js @@ -0,0 +1,99 @@ +// @flow strict-local + +import type {PackagedBundle, PluginOptions} from '@parcel/types'; + +import {Reporter} from '@parcel/plugin'; +import {DefaultMap} from '@parcel/utils'; + +import assert from 'assert'; +import path from 'path'; + +export type AssetStat = {| + size: number, + name: string, + bundles: Array, +|}; + +export type BundleStat = {| + size: number, + id: string, + assets: Array, +|}; + +export type BundleStats = {| + bundles: {[key: string]: BundleStat}, + assets: {[key: string]: AssetStat}, +|}; + +export default (new Reporter({ + async report({event, options}) { + if (event.type !== 'buildSuccess') { + return; + } + + let bundlesByTarget: DefaultMap< + string /* target name */, + Array, + > = new DefaultMap(() => []); + for (let bundle of event.bundleGraph.getBundles()) { + bundlesByTarget.get(bundle.target.name).push(bundle); + } + + let reportsDir = path.join(options.projectRoot, 'parcel-bundle-reports'); + await options.outputFS.mkdirp(reportsDir); + + await Promise.all( + [...bundlesByTarget.entries()].map(([targetName, bundles]) => + options.outputFS.writeFile( + path.join(reportsDir, `${targetName}-stats.json`), + JSON.stringify(getBundleStats(bundles, options), null, 2), + ), + ), + ); + }, +}): Reporter); + +export function getBundleStats( + bundles: Array, + options: PluginOptions, +): BundleStats { + let bundlesByName = new Map(); + let assetsById = new Map(); + + // let seen = new Map(); + + for (let bundle of bundles) { + let bundleName = path.relative(options.projectRoot, bundle.filePath); + + // If we've already seen this bundle, we can skip it... right? + if (bundlesByName.has(bundleName)) { + // Sanity check: this is the same bundle, right? + assert(bundlesByName.get(bundleName)?.size === bundle.stats.size); + continue; + } + + let assets = []; + bundle.traverseAssets(({id, filePath, stats: {size}}) => { + assets.push(id); + let assetName = path.relative(options.projectRoot, filePath); + if (assetsById.has(id)) { + assert(assetsById.get(id)?.name === assetName); + assert(assetsById.get(id)?.size === size); + assetsById.get(id)?.bundles.push(bundleName); + } else { + assetsById.set(id, {name: assetName, size, bundles: [bundleName]}); + } + }); + + bundlesByName.set(bundleName, { + id: bundle.id, + size: bundle.stats.size, + assets, + }); + } + + return { + bundles: Object.fromEntries(bundlesByName), + assets: Object.fromEntries(assetsById), + }; +} From 45c945d419f15ce32afe8946ad631b9b7632675e Mon Sep 17 00:00:00 2001 From: thebriando Date: Thu, 13 Jul 2023 12:23:23 -0700 Subject: [PATCH 2/3] Export command and add bin file --- packages/dev/bundle-stats-cli/bin.js | 12 ++++++++++++ packages/dev/bundle-stats-cli/src/cli.js | 5 ++--- 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100755 packages/dev/bundle-stats-cli/bin.js diff --git a/packages/dev/bundle-stats-cli/bin.js b/packages/dev/bundle-stats-cli/bin.js new file mode 100755 index 00000000000..24263e2c0de --- /dev/null +++ b/packages/dev/bundle-stats-cli/bin.js @@ -0,0 +1,12 @@ +#! /usr/bin/env node + +/* eslint-disable no-console */ +// @flow strict-local +'use strict'; + +// $FlowFixMe[untyped-import] +require('@parcel/babel-register'); + +const cli = require('./src/cli'); + +cli.command.parse(); diff --git a/packages/dev/bundle-stats-cli/src/cli.js b/packages/dev/bundle-stats-cli/src/cli.js index 2ea25add291..9e4aabb041a 100644 --- a/packages/dev/bundle-stats-cli/src/cli.js +++ b/packages/dev/bundle-stats-cli/src/cli.js @@ -66,7 +66,8 @@ function run({cacheDir, outDir}) { } } -commander +// $FlowFixMe[signature-verification-failure] +export const command = new commander.Command() .version(version, '-V, --version') .description('Generate a stats report for a Parcel build') .option('-v, --verbose', 'Print verbose output') @@ -81,5 +82,3 @@ commander 'parcel-bundle-reports', ) .action(run); - -commander.parse(); From 1f7f740ea2f8e30e91b5ed10321c48aef0352047 Mon Sep 17 00:00:00 2001 From: thebriando Date: Thu, 13 Jul 2023 13:49:34 -0700 Subject: [PATCH 3/3] Fix commander flow errors --- flow-libs/commander.js.flow | 2 +- packages/dev/bundle-stats-cli/src/cli.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flow-libs/commander.js.flow b/flow-libs/commander.js.flow index 7173bf7b008..a89fd6db25a 100644 --- a/flow-libs/commander.js.flow +++ b/flow-libs/commander.js.flow @@ -244,7 +244,7 @@ declare module 'commander' { [key: string]: any; } - declare class commander$Command { + declare export class commander$Command { constructor(name?: string): commander$Command; args: string[]; diff --git a/packages/dev/bundle-stats-cli/src/cli.js b/packages/dev/bundle-stats-cli/src/cli.js index 9e4aabb041a..c4deed1549c 100644 --- a/packages/dev/bundle-stats-cli/src/cli.js +++ b/packages/dev/bundle-stats-cli/src/cli.js @@ -16,6 +16,7 @@ import {DefaultMap} from '@parcel/utils'; import {loadGraphs} from 'parcel-query/src/index.js'; import {getBundleStats} from '@parcel/reporter-bundle-stats/src/BundleStatsReporter'; import {PackagedBundle as PackagedBundleClass} from '@parcel/core/src/public/Bundle'; +import type {commander$Command} from 'commander'; function run({cacheDir, outDir}) { // 1. load bundle graph and info via parcel~query @@ -66,8 +67,7 @@ function run({cacheDir, outDir}) { } } -// $FlowFixMe[signature-verification-failure] -export const command = new commander.Command() +export const command: commander$Command = new commander.Command() .version(version, '-V, --version') .description('Generate a stats report for a Parcel build') .option('-v, --verbose', 'Print verbose output')