From 71ef5dd66b68aadc47bb70c7b6a4df43cc0b61dc Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Sat, 7 Nov 2026 09:17:14 -0800 Subject: [PATCH] [uplift] chore: size report (### Size: `index.js` | | This tree (B) | |---|--:| | Raw | 5378 | | Minified | 1741 | | Min + gzip | 653 | | Min + brotli | 567 | Minified with terser (compress, top-level mangle)., CI job summary) --- .github/pull_request_template.md | 3 +- .github/workflows/ci.yml | 7 +++ CONTRIBUTING.md | 23 ++++---- README.md | 7 ++- package.json | 5 +- scripts/size.mjs | 90 ++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 23 deletions(-) create mode 100644 scripts/size.mjs diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4fb322a..d23e9a0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,5 +14,4 @@ Please include a summary of the change and which issue is fixed. Please also inc - [ ] All tests are passing - [ ] Type definitions, if updated, pass `types:check` and `pack:check` - [ ] Benchmark performance has not significantly decreased -- [ ] Bundle size has not been significantly impacted -- [ ] The bundle size badge has been updated to reflect the new size +- [ ] Bundle size (`pnpm size`, CI job summary) has not been significantly impacted diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d9d580..08e094d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,9 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false + # The parent commit too, for the size comparison. On pull requests + # HEAD is a merge commit, so HEAD^1 is the base it merged onto. + fetch-depth: 2 - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - uses: actions/setup-node@v7 with: @@ -40,6 +43,10 @@ jobs: - name: Check if: matrix.os == 'ubuntu-latest' run: pnpm check + # Report only: adds the size table to the job summary, never fails. + - name: Size + if: always() && matrix.os == 'ubuntu-latest' + run: pnpm size --base HEAD^1 # Format, lint and the packed tarball don't depend on the OS, so Windows # only runs the tests. - name: Test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38fd666..f9be70c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -132,18 +132,18 @@ The snapshot was generated from the published 3.2.2 tarball. In 3.x, changes to ### Size -You can check how we do with minification + compression with: +`pnpm size` reports the size of `index.js`: raw, minified (terser, with +top-level mangling), and minified with gzip and brotli. Pass `--base ` +to compare with another version: ```sh -# Show minified output -$ pnpm -s compress - -# Display minified + gzip'ed size in bytes. -$ pnpm size-min-gz +$ pnpm size +$ pnpm size --base master ``` -**Note**: If the min+gz size increases, please note it in the README. If it is a significant increase, -please flag to your reviewers and have a discussion about whether or not the size addition is justified. +CI runs it against the base commit and adds the table to the job summary. It's +a report, not a check: if the min+gz size grows significantly, flag it to your +reviewers so you can discuss whether the addition is worth it. ## Before submitting a PR... @@ -158,12 +158,7 @@ please flag to your reviewers and have a discussion about whether or not the siz 2. Confirm we don't have any significant performance regressions (check out `master` for a baseline comparison on _your_ machine). -3. Confirm you aren't impacting our bundle size. - If you _do_ affect the bundle size, please update the bundle badge in the Readme by - - Following the steps outlined in [size](#size): - `pnpm -s compress && pnpm size-min-gz` - - Grabbing that output and replacing the current size in the bundle_img: (`https://img.shields.io/badge/minzipped%20size-%20B-flatgreen.svg`) - For example, if the new size is `650`, the new bundle_img will be `https://img.shields.io/badge/minzipped%20size-650%20B-flatgreen.svg` +3. Confirm you aren't significantly impacting our bundle size (see [size](#size)). - _Org members:_ Update the README's benchmark comparison png using this [internal Google Sheet template](https://docs.google.com/spreadsheets/d/1GuqpO0wgPjQ9usx6sR3t0Y_HTmAdRqjXkSjs3SBsmTc/edit?usp=sharing_eip&ts=5ed1642f). ## Releasing diff --git a/README.md b/README.md index ab24219..0cb4dc5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![React Fast Compare — Formidable, We build the modern web](https://raw.githubusercontent.com/FormidableLabs/react-fast-compare/master/react-fast-compare-Hero.png)](https://formidable.com/open-source/) [![Downloads][downloads_img]][npm_site] -[![Bundle Size][bundle_img]](#bundle-size) [![GH Actions Status][actions_img]][actions_site] [![npm version][npm_img]][npm_site] [![Maintenance Status][maintenance_img]](#maintenance-status) @@ -132,8 +131,9 @@ This version of `react-fast-compare` tracks `fast-deep-equal@3.1.1`. ## Bundle Size There are a variety of ways to calculate bundle size for JavaScript code. -You can see our size test code in the `compress` script in -[`package.json`](https://github.com/FormidableLabs/react-fast-compare/blob/master/package.json). +`pnpm size` ([`scripts/size.mjs`](./scripts/size.mjs)) minifies `index.js` with +terser, mangling top-level names, and reports the raw, minified, gzip and +brotli sizes. CI adds the same report to each run's job summary. [Bundlephobia's calculation](https://bundlephobia.com/result?p=react-fast-compare) is slightly higher, as they [do not mangle during minification](https://github.com/pastelsky/package-build-stats/blob/v6.1.1/src/getDependencySizeTree.js#L139). @@ -153,6 +153,5 @@ Please see our [contributions guide](./CONTRIBUTING.md). [actions_site]: https://github.com/formidablelabs/react-fast-compare/actions/workflows/ci.yml [npm_img]: https://badge.fury.io/js/react-fast-compare.svg [npm_site]: http://badge.fury.io/js/react-fast-compare -[bundle_img]: https://img.shields.io/badge/minzipped%20size-656%20B-flatgreen.svg [downloads_img]: https://img.shields.io/npm/dm/react-fast-compare.svg [maintenance_img]: https://img.shields.io/badge/maintenance-active-flatgreen.svg diff --git a/package.json b/package.json index 6cd6eea..1a69d3a 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,9 @@ "test-node": "mocha \"test/node/*.spec.js\"", "test-node-cov": "nyc mocha \"test/node/*.spec.js\"", "test": "builder concurrent --buffer test-node-cov test-browser", - "compress": "terser --compress --mangle=\"toplevel:true\" -- index.js", - "size-min-gz": "pnpm -s compress | gzip -9 | wc -c", "changeset": "changeset", - "pack:check": "node scripts/pack-check.mjs" + "pack:check": "node scripts/pack-check.mjs", + "size": "node scripts/size.mjs" }, "repository": { "type": "git", diff --git a/scripts/size.mjs b/scripts/size.mjs new file mode 100644 index 0000000..1d3b116 --- /dev/null +++ b/scripts/size.mjs @@ -0,0 +1,90 @@ +// Reports the size of `index.js` (`pnpm size`): raw, minified, and minified +// with gzip and brotli. Minified with terser (compress + top-level mangle). +// Report only: it never fails on a size change. +// +// `--base ` also measures `index.js` at that ref and shows the +// difference (e.g. `pnpm size --base master`). If the ref can't be read, the +// report leaves the comparison out. +// +// In GitHub Actions the table is also appended to the job summary. +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import zlib from 'node:zlib'; +import { minify } from 'terser'; + +const { log, warn } = console; + +const args = process.argv.slice(2); +const baseRef = args.includes('--base') ? args[args.indexOf('--base') + 1] : ''; +const rootDir = path.resolve(import.meta.dirname, '..'); +const FILE = 'index.js'; + +const measure = async (source) => { + const { code } = await minify(source, { + compress: true, + mangle: { toplevel: true }, + }); + return { + raw: Buffer.byteLength(source), + min: Buffer.byteLength(code), + gzip: zlib.gzipSync(code, { level: 9 }).length, + brotli: zlib.brotliCompressSync(code, { + params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 }, + }).length, + }; +}; + +const readBase = (ref) => { + try { + return execFileSync('git', ['show', `${ref}:${FILE}`], { + cwd: rootDir, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + } catch { + warn(`size: can't read ${FILE} at ${ref}; no comparison.`); + return null; + } +}; + +const current = await measure( + fs.readFileSync(path.join(rootDir, FILE), 'utf8') +); +const baseSource = baseRef ? readBase(baseRef) : null; +const base = baseSource === null ? null : await measure(baseSource); + +const ROWS = [ + ['raw', 'Raw'], + ['min', 'Minified'], + ['gzip', 'Min + gzip'], + ['brotli', 'Min + brotli'], +]; +const diff = (a, b) => { + const d = a - b; + return d === 0 ? '0' : `${d > 0 ? '+' : ''}${d}`; +}; + +const lines = [`### Size: \`${FILE}\``, '']; +if (base) { + lines.push( + `| | \`${baseRef}\` (B) | This tree (B) | Diff (B) |`, + '|---|--:|--:|--:|', + ...ROWS.map( + ([key, label]) => + `| ${label} | ${base[key]} | ${current[key]} | ${diff(current[key], base[key])} |` + ) + ); +} else { + lines.push( + '| | This tree (B) |', + '|---|--:|', + ...ROWS.map(([key, label]) => `| ${label} | ${current[key]} |`) + ); +} +lines.push('', 'Minified with terser (compress, top-level mangle).', ''); +const report = lines.join('\n'); + +log(report); +if (process.env.GITHUB_STEP_SUMMARY) + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, report + '\n');