2026 Uplift

Abierto
#140 0 comentarios 0 reacciones 0 asignados Ver en GitHub

@ryan-roemer ya está trabajando en esto.

Desde el 6/11/2026.

  • #146 de @ryan-roemer — abierto

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
25/100
Tipo de issue
Refactorización
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
github-actions, javascript, typescript

Línea de trabajo

Read the Phase 2 checklist and the linked patterns from FormidableLabs/victory and nearform/llm-splitter. Start with the oxlint + oxfmt config PR, then review its format and lint churn plans, preserving the stated ES5 and upstream comment constraints in index.js. The tracking issue is complete when the remaining phases and merge steps are done; it spans tooling, types, packaging, and release work, so it is not a bounded first contribution.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Living tracking issue, modelled on FormidableLabs/victory#3093 (and nearform/llm-splitter for the single-package, JSDoc-typed setup), but much smaller. We'll keep editing it as we go, and record what changed in the Decision log at the bottom.

Status (2026-11-05): Phase 1 is underway on the integration branch chore/2026-uplift. Merged: #141 (pnpm 12, Node 26, vendored test cases, Codecov and AppVeyor removed, mocha 11 so test-node runs on Node 26). Also on the branch: changesets v3 + OIDC release.yml with canary (65c9439, pushed without a PR), publishConfig.provenance dropped, CONTRIBUTING release section. npm-publish environment and npm trusted publisher are set up, and a canary from the branch published 3.2.3-canary-20261105232110 to next through OIDC with provenance (run). Phase 1 is done apart from deleting the NPM_TOKEN secret. Next: Phase 2, starting with the oxlint + oxfmt config PR.

Why

  • Nothing has been released since 3.2.2 (Mar 2023). release.yml publishes with NPM_TOKEN and gives its one job broad write permissions; no OIDC.
  • The dev stack is from 2020: yarn 1, builder, mocha + nyc, Karma + webpack + Babel + core-js (for IE11), ESLint 8, TS 4.9, Node 18 in CI, Codecov.
  • Correctness tests mostly come from a git dependency, fast-deep-equal-git (epoberezkin/fast-deep-equal#v3.1.3): 62 generic + 49 ES6 suites from its spec/. Upstream hasn't released since 2020.
  • Open dev-only security alerts (#70) and three stale Dependabot PRs (#122, #124, #128) all come from the Karma/webpack tree.
  • New bug: #139, Temporal values throw (valueOf). It's the first source change after the uplift, and the first release from the new pipeline.

By the numbers (Nov 2026)

  • ~107M downloads/month. Last week by version: 3.2.2 ~27.5M, 2.0.4 ~6.1M, 3.2.0 ~1.45M. Most users are pinned through dependents' ranges (^3, ^2).
  • Published surface: index.js (CJS, module.exports = isEqual, ES5) and index.d.ts (export =). No exports map, no ESM, no sideEffects. There's no UMD bundle to drop, unlike victory.
  • Quick probe (2026-11-05) of current index.js:
    • React 19.3.1 (dev and prod): elements compare correctly. In dev, _owner is enumerable and still skipped; the _debug* props are non-enumerable.
    • Preact 10.29 / 11.1 with preact/compat: correct.
    • Plain preact h() vnodes with compat not loaded compare false (no $$typeof, so __v is traversed). This is pre-existing and the same in 10 and 11; only compat is documented. A later source item, not uplift work.
  • JSDoc spike (TS 7.0.2, allowJs + checkJs + strict, emitDeclarationOnly): a 5-line JSDoc block on isEqual emits exactly today's index.d.ts (declare function isEqual<B>(a: any, b: B): a is B; export = isEqual;). strict reports 7 errors inside equal() (implicit any params, ArrayBufferView indexing, catch (error) as unknown). All can be fixed with JSDoc annotations or casts, with no runtime change.

Principles

  1. Infra first, source last. Mechanical source churn (oxfmt, lint fixes, JSDoc types) lands in its own PR with no behavior change, and goes into .git-blame-ignore-revs.
  2. The uplift doesn't change what users get. Same files, same module.exports = isEqual, same ES5 code, same declarations. Only formatting and comments may change in index.js, and pack:check proves the rest (see Phase 2). ESM, exports and a modern target wait for 4.0.0.
  3. Types come from JSDoc, as in llm-splitter. Source stays plain JS with no build step for the code. tsc type-checks it (checkJs) and, from 4.0, generates the .d.ts.
  4. Plain package.json scripts. No task runner, since this is a single package.
  5. Crib from victory and llm-splitter where they fit a one-file library.

Versioning (decided 2026-11-05)

  • Uplift → master with no published change. No changeset is needed for the infra PRs; there's nothing to release from them alone.
  • #139 (Temporal) → 3.x (patch or minor), released from the new OIDC pipeline. It reaches every ^3 user, and it's the pipeline's first real run, like victory's interim 37.4.0. #137 (sideEffects: false) can ride along; it's a safe manifest addition.
  • 4.0.0 later: ESM + CJS, exports map, modern target (drop ES5/IE), .d.ts generated from JSDoc. Its own design section when we get there (see "4.0.0" below).

Branching and merge strategy

  • Default branch stays master (decided 2026-11-05).
  • Integration branch: chore/2026-uplift, cut from master. Small PRs into it (squash is fine). CI runs on master + chore/2026-uplift*.
  • Final merge: one PR into master with a merge commit, which keeps the blame-ignore SHAs valid. Repo settings to change first (same traps as victory):
    • Allow merge commits (currently off: mergeCommitAllowed: false).
    • "Automatically delete head branches" is on. Turn it off, or protect chore/2026-uplift* with a ruleset.
  • Release gate for the merge: green CI, and pack:check shows the packed tarball matches 3.2.2 (see Phase 2). A canary to next from the branch proves OIDC before #139 needs it.

Phase 1: Package manager and release pipeline (OIDC)

  • pnpm 12 for development (#141) (packageManager, pnpm-workspace.yaml settings, minimumReleaseAge: 10080, allowBuilds). Remove yarn.lock. README install instructions say only npm install react-fast-compare; CONTRIBUTING says pnpm.
  • Repo files, as in victory (.nvmrc, .editorconfig, .gitignore done in #141; .vscode/ moved to Phase 2 with oxc):
    • .nvmrc → 26, used by CI via node-version-file and documented in CONTRIBUTING. Still no engines field.
    • .editorconfig (2 spaces, LF, final newline; Markdown keeps trailing whitespace).
    • .vscode/extensions.json (oxc.oxc-vscode) and .vscode/settings.json (oxc as formatter). Narrow the .vscode/ gitignore entry to allow these two files.
    • Later phases add .oxlintrc.json, .oxfmtrc.json, .git-blame-ignore-revs, .ncurc.cjs (dep:check), .github/zizmor.yml and .github/pinact.yml, plus AGENTS.md / CLAUDE.md.
    • .gitignore: trim the 2017 template (bower, grunt, jspm, lib-cov, …). pnpm-lock.yaml is committed; package-lock.json and yarn.lock are ignored.
  • changesets v3 + official @changesets/changelog-github (disableThanks), replacing the deprecated @svitejs/changesets-changelog-github-compact. snapshot.useCalculatedVersion for canaries. baseBranch: master.
  • release.yml on changesets/action v2 split jobs (llm-splitter's single-package version is the closest template; victory's adds pnpm and a canary): select-mode → version | pack → publish. permissions: {} with per-job grants, id-token: write only on publish/canary, package-manager-cache: false, persist-credentials: false. One v{version} tag + gh release create --generate-notes.
    • Canary via workflow_dispatch in the same file → X.Y.Z-canary-<datetime> on next.
  • Drop publishConfig.provenance (automatic under trusted publishing).
npm and GitHub setup (manual, needs a maintainer)

One package, so this is done by hand in the npm and GitHub UIs (no npm-trust.sh like victory's).

  • GitHub: create the npm-publish environment (Settings → Environments), deployment branches master + chore/2026-uplift. Required reviewers optional. Not Production, to keep it separate from any deployment environments (victory decision log, 2026-10-26).
  • npm: on npmjs.com → react-fast-compare → Settings → Trusted Publisher → GitHub Actions: organization FormidableLabs, repository react-fast-compare, workflow release.yml, environment npm-publish. Renaming the workflow file or the environment breaks publishing until this is updated.
  • Validate: canary from the integration branch (gh workflow run release.yml --ref chore/2026-uplift) publishes X.Y.Z-canary-<datetime> to next with provenance. It needs at least one pending changeset; use a throwaway one and delete it afterwards. Done 2026-11-05: 3.2.3-canary-20261105232110, published by [email protected], SLSA v1 provenance for release.yml on chore/2026-uplift.
  • npm → Publishing access → "Require two-factor authentication and disallow tokens" (done 2026-11-05, after the canary proved OIDC; earlier than planned).
  • Delete the NPM_TOKEN repo secret (unusable now that tokens are disallowed).
  • Before the final merge: allow merge commits, and turn off "Automatically delete head branches" (or protect chore/2026-uplift*); see Branching.
  • After the final merge: remove chore/2026-uplift from the npm-publish deployment branches and delete the branch.

Phase 2: Tooling

  • oxlint + oxfmt replace ESLint 8 (+ @typescript-eslint/parser, eslint-plugin-react). Exact-pinned (0.x). The config PR also adds .vscode/extensions.json + settings.json (oxc) and narrows the .vscode/ gitignore entry. Config PR, then a format-churn PR, then a lint-churn PR, with each churn SHA in .git-blame-ignore-revs.
    • index.js stays ES5 in 3.x: no no-var/prefer-const/ES2015 autofixes on it (test and tooling files can be modern).
    • index.js's START:/END: fast-deep-equal comment blocks must survive the reformat; they're how upstream diffs are tracked (CONTRIBUTING).
  • TypeScript 7 + JSDoc (llm-splitter pattern):
    • tsconfig.json: allowJs, checkJs, strict, noEmit, module/moduleResolution: nodenext, lib with dom (for Element). It currently has ESLint keys (env, parserOptions) in it; those go.
    • JSDoc on isEqual (@template B, @param {any} a, @param {B} b, @returns {a is B}), plus annotations in equal() for the 7 strict errors. Its own PR, no runtime change.
    • Keep the hand-written index.d.ts as the published file in 3.x. A types:check step emits declarations from JSDoc to a temp dir and fails if they differ from index.d.ts, so the two can't drift. In 4.0 the generated file replaces it.
    • Type probe (llm-splitter's type-probe idea, but wired into CI since it's cheap here): a small .ts fixture compiled against the packed tarball under node10, node16/nodenext and bundler resolution. It covers React.memo(C, isEqual), shouldComponentUpdate, useSelector-style (a, b) => boolean callbacks, the a is B narrowing, and import isEqual from with and without esModuleInterop, with @ts-expect-error negatives. Replaces test-ts-defs/test-ts-usage and the react-redux/redux deps.
  • pack:check (simplified from victory's): on pnpm pack output:
    • publint + attw.
    • Isolated require() and import(): require('react-fast-compare')(a, b) must stay callable, and import isEqual from must work in Node ESM.
    • Compatibility with 3.2.2: same file list and manifest fields (main, types/typings, files), identical index.d.ts, and index.js minified byte-identical to 3.2.2's, so formatting, comments and JSDoc are the only changes. Dropped once 4.0 work starts.
  • Size report, replacing terser | gzip | wc -c and the hand-edited README badge: a small script (oxc-minify or terser, min + gzip) printed locally and in CI's job summary. Reported, not gated.
  • Remove cruft (codecov + README badge and AppVeyor/IE11 Karma config and references done in #141): builder, nyc, sinon (use vi.spyOn), jsdom-global, Babel, core-js, webpack, Karma and its launchers, karma.conf.ie.js, test/mocha.opts, .eslintrc.yml/.eslintignore.
  • Minimal dev dependencies. Keep only what a script, test or CI step strictly needs; audit with knip at the end of each phase. builder goes first.
  • Small script surface, like victory. The entry points are check, format and test:
    • check: everything CI gates on, run in sequence (format, lint, types, test, pack:check).
    • format / format:fix: oxfmt check / write.
    • test: Vitest.
    • Only the pieces check calls stay as scripts (lint/lint:fix, types:check, pack:check), plus dep:check, bench, size and changeset. The rest go (eslint, tslint, test-*, compress, size-min-gz, preversion, benchmark).
  • AGENTS.md (+ CLAUDE.md pointer), as in victory and llm-splitter. It should cover: ES5 + no published change in 3.x, JSDoc types, the upstream comment blocks, changeset rules.
  • dep:check (npm-check-updates, 7-day cooldown) and a documented manual update routine. No Renovate/Dependabot version PRs; turn on Dependabot security alerts after the final merge. Close #70.

Phase 3: Tests

One set of spec files, run in Node and in real browsers.

  • Vitest 5 with two projects (decided 2026-11-05).
    • node: plain Node; jsdom only for the specs that need a DOM.
    • browser: Vitest browser mode + Playwright, Chromium, Firefox and WebKit headless. This replaces Karma and closes #12 (Sauce Labs).
  • Vendor the upstream test cases (#141; pulled forward: pnpm packs git dependencies, which honors upstream's files list, so spec/ wasn't installed): copy [email protected]'s spec/tests.js and spec/es6tests.js into test/fixtures/ with its MIT notice, and drop the fast-deep-equal-git git dependency. They're frozen upstream anyway, and #139/#50 will add our own cases next to them.
  • Drop comparative tests and deps: lodash, nano-equal, shallow-equal-fuzzy.
  • React tests on React 19 and a CI job on React 18.0.0, plus preact/compat 10 and 11. Replace react-test-renderer (deprecated in 19) with react-dom (renderToString / createRoot). Keep React 16/17 out of CI. The library doesn't depend on React, and its React handling ($$typeof + _owner) is the same shape across versions.
  • Coverage via @vitest/coverage-v8 in the CI job summary (no Codecov).
  • CI: ubuntu with Node .nvmrc + browsers; keep windows for the Node project only. zizmor in CI, hybrid action pinning (pinact), permissions: {}. Build-free, so the CI step that matters most besides tests is pack:check.

Phase 4: Benchmarks (decided 2026-11-05: self-regression)

  • vitest bench (tinybench) over the vendored generic + React fixtures, comparing:
    • the working tree,
    • the last published version (aliased devDep, e.g. react-fast-compare-published@npm:[email protected], bumped after each release), and
    • [email protected] as the upstream baseline we forked from.
  • pnpm bench locally, and a report-only CI job writing the table to the job summary. Not gated; CI runners are too noisy for that.
  • Remove benchmark/index.js, benchmark (the npm package), assets/benchmarking.png, the README chart and the Google Sheet step in CONTRIBUTING. The README keeps a short "Benchmarking" section on how to run it.
  • Why keep it at all: speed is the library's whole pitch, and the next source changes (#139 Temporal, #50 Sets) touch the hot path.

Phase 5: Docs and housekeeping

  • README: npm-only install; fix "tracks [email protected]" (it's 3.1.3); fix the broken LICENSE link (blob/readme/LICENSE); update badges; settle maintenance status/branding (Formidable → Nearform?). The "ES5 / IE9" claims stay true in 3.x and change in 4.0.
  • CONTRIBUTING rewritten for pnpm, Vitest, oxlint/oxfmt, JSDoc types, changesets, canary, dep:check, bench.
  • .github/pull_request_template.md: drop the bundle-badge and test-ts-* items.
  • Close or settle: Dependabot PRs #122/#124/#128 (Karma tree, gone), #12 (Sauce Labs → Playwright), #24 (Backstroke, obsolete), #70 (alerts).
  • Delete stale remote branches: ci-provenance, experiment/es6-circ-refs, feature/es6-extras, fix/missing-patch, task/update-dependencies, viper1104-dom, the dependabot ones, and changeset-release/master when idle.
  • Optional: move react-fast-compare-Hero.png (~510 KB, not published) into .github/.

After the uplift (source, PRs straight to master)

  • #139 Temporal → 3.x release from the new pipeline. Check toString-based Temporal types before valueOf (or detect Temporal.* and use .equals). Bench before/after.
  • #137 sideEffects: false: rides along with #139 (manifest-only, safe for 3.x).
  • PR #135 (NaN comment): rebase after the oxfmt churn and merge.
  • Later and undecided: #50 (Set by value; perf cost), #68 (debug mode), #114 / PR #115 (function comparison, declined so far as unsafe by default), plain-Preact vnodes (see probe).

4.0.0 (later; sketch only)

  • ESM + CJS with an exports map, sideEffects: false, modern target (drop ES5/IE; for...of instead of the hand-lowered iterators), .d.ts generated from JSDoc (.d.cts with export =, .d.mts with export default).
  • Open question for then: author ESM and generate the CJS (tsdown/rolldown, keeping module.exports = isEqual), or keep CJS source with a thin ESM wrapper (no dual-package hazard, since the function is stateless).
  • Changeset migration note: deep imports (react-fast-compare/index.js) stop resolving under exports.

Decision log

Date Decision Why
2026-11-05 Integration branch chore/2026-uplift, PRs into it, merge commit to master Same as victory; keeps master releasable.
2026-11-05 Keep master as the default branch No churn for contributors, links and dependents' tooling.
2026-11-05 Uplift has no published change; #139 ships as 3.x from the new pipeline; ESM/exports/modern target in a later 4.0.0 Most of the ~107M monthly downloads come through ^3/^2 ranges. A 3.x fix reaches them; a 4.0 wouldn't until dependents bump. Keeps the pipeline's first release low-risk.
2026-11-05 Types via JSDoc + checkJs on plain JS (llm-splitter pattern), not a TS port No build step for the code. TS 7 emits today's index.d.ts exactly from a 5-line JSDoc block.
2026-11-05 Benchmarks: self-regression (tree vs last published vs fast-deep-equal), report-only Speed is the pitch, and upcoming fixes touch the hot path. Other libraries' numbers don't help maintain this one.
2026-11-05 No task runner; plain scripts Single package, sub-second tasks.
2026-11-05 Tokens disallowed on npm right after the canary, not after #139 The canary proved OIDC end to end; no reason to keep a token path open.
2026-11-05 Canary 3.2.3-canary-* stays on next Harmless; the next canary replaces it.
2026-11-05 Scripts: check, format, test as the entry points, plus only the sub-scripts they call Same shape as victory; fewer names to learn and keep working.
2026-11-05 Vitest (Node + browser mode/Playwright) over node:test + another browser runner One spec set, one tool, both environments; matches victory.
2026-11-05 Vendor fast-deep-equal's spec cases, drop the git dependency Upstream frozen since 2020; git deps are slow and fragile.
2026-11-05 npm trusted publishing and the GitHub environment are set up by hand, not scripted One package; victory's npm-trust.sh only pays off across 29.
2026-11-05 Dev dependencies kept to the strict minimum; builder removed Every dependency is upkeep (alerts, dep:check, release-age holds). A single-file library needs few tools.
2026-11-05 Codecov removed early (#141), not replaced until Vitest coverage The codecov@3 uploader crashes in CI; nyc still prints a summary.
2026-11-05 Dev files follow victory: .nvmrc (26), .editorconfig, tracked .vscode/ recommendations, oxc configs, .ncurc.cjs, zizmor/pinact configs Same habits across our repos.
Lenguaje dominante
JavaScript
Estrellas
1.7k
Forks
54
Merge medio
1 h 11 min
PR fusionados (30 d)
2

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de FormidableLabs/react-fast-compare

Todos los issues de FormidableLabs/react-fast-compare

Issues similares

Más issues de JavaScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.