Skip to content
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
3 changes: 1 addition & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
23 changes: 9 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <git ref>`
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...

Expand All @@ -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-<NEW_SIZE>%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
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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).

Expand All @@ -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
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
90 changes: 90 additions & 0 deletions scripts/size.mjs
Original file line number Diff line number Diff line change
@@ -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 <git ref>` 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');
Loading