Skip to content

Build donate button v4 with Vite and move to Node 24 - #462

Merged
johnemau merged 31 commits into
john-with-claude/miniflare-node24-testsfrom
john-with-claude/node-24-donate-testing-80be4e
Oct 8, 2026
Merged

johnemau merged 31 commits into
john-with-claude/miniflare-node24-testsfrom
john-with-claude/node-24-donate-testing-80be4e

Conversation

@johnemau

@johnemau johnemau commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Move donate-button-v4 to Vite 8 and the repo to Node 24 so v4 builds on Node 22+.

Problem

  • yarn build fails on Node 22 and 24: Can't resolve 'src/autoPlayMode'.

Root Cause

  • preact-cli's archived esm loader fails on Node 22+, dropping the src/ alias.
  • preact-cli#1821 reports the same Function.prototype.apply error.

Solution

  • Build v4 with Vite 8 and @preact/preset-vite; see docs: Add notice for creact-preact preactjs/preact-cli#1808.
  • Move assets to root and public/assets to match Vite defaults.
  • Use the Vercel deployment URL (VERCEL_URL) as the asset base for production builds and / otherwise.
  • Preserve MD4 icon names with --openssl-legacy-provider and retain the ES2015 target.
  • Set engines.node, .tool-versions, and CI to Node 24.

Visual check

  • Before: live demo.
  • After: this branch's yarn dev demo.

Both screenshots were captured at 1280×800 after clicking "Donate". They are byte-identical, so both columns use the same hosted image.

View Before After
Demo page widget Before: live demo donation widget After: yarn dev donation widget

Build output

yarn build in packages/donate-button-v4 on Node 24.16.0 emits 28 MD4-named icons, index.html, and index.js (142 kB, 50 kB gzip).

vite build output
button.js (Vercel, gzip) Before (bytes) After (bytes) Change (bytes)
Live → PR preview 53,648 49,920 −3,728

~3K savings by moving from babel to rollup bundlers (preact-cli -> vite)

🤖 Generated with Claude Code

Problem: On Node 22+, `yarn build` fails for donate-button-v4 and the cloudflare-worker tests fail.

Root Cause: preact-cli loads preact.config.js through the archived `esm` package, which throws "Function.prototype.apply was called on undefined" on Node 22+. preact-cli then falls back to its default config, so the `src/...` import alias is missing. preact-cli no longer sees active development and recommends Vite. Separately, Node 21+ defines a getter-only global `navigator`, so `Object.assign(global, makeServiceWorkerEnv())` throws in the worker test setup.

Solution: Replace preact-cli with Vite and @preact/preset-vite. The Vite build keeps the output of the old preact.config.js: a single classic-script bundle at dist/donate-button/0.4/index.js, absolute asset URLs from VERCEL_URL or assets.every.org, the demo index.html, and the src/assets copy. Define the worker mocks with Object.defineProperty. Set engines.node, .tool-versions and CI to Node 24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
donate-button Ready Ready Preview Oct 8, 2026 2:19pm UTC

Request Review

@socket-security

socket-security Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​service-worker-mock@​2.0.1991006880100
Addedservice-worker-mock@​2.0.59910010075100
Addedvite@​8.3.1991008298100
Added@​preact/​preset-vite@​2.10.69910010092100
Addedprettier@​3.0.3981009797100

View full report

Name emitted icons with the MD4 digest of their contents, as preact-cli's file-loader did, so the build emits the same 28 icon files at the same URLs as before. Node only exposes MD4 with --openssl-legacy-provider, so the build script sets it.

Drop changes the migration does not need: the commented-out CI job, Vite options that equal their defaults, and the build log line. Replace the worker test setup loop with deleting the getter-only navigator before Object.assign.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Node 21+ navigator fix for the cloudflare-worker tests now lives in its own PR, which this PR depends on for the Node 24 test job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the dev-only entry script in the existing pre-transform HTML hook. The production bundle and HTML retain their existing bytes.

Manual check: On Node 24, build v4 and inspect the emitted HTML. Expected: one classic index.js script and no source entry. Actual: matched. Dev HTML transformation emitted one module entry and one Vite client script.

Generated with AI under the guidance of John.
Problem
The service-worker-mock setup throws while assigning Node 24's read-only navigator global, so no worker tests run.

Root cause
The mock replaces Node globals during AVA setup and is incompatible with Node 24.

Solution
Use native Request and Response in the direct Sinon handler tests, with request-shaped CONNECT and TRACE fixtures. Dispatch the built service-worker-format bundle in Miniflare with KV and a mocked outbound fetch. Run the CI test job on Node 24.

Manual test
Expected: all eight unsupported methods return 404; KV and outbound fetch assertions pass; Miniflare returns the mocked bundle and writes the default bundle URL to KV.
Actual: 11 worker tests pass on Node 24, and the root test command passes on Node 24. Package lint on Node 18 reports only the unchanged xo.config.js import/extensions error; changed files pass targeted XO.

Docs: https://developers.cloudflare.com/workers/testing/miniflare/
Problem
The first Miniflare test migration moved CI tests to Node 24 because Miniflare 4 requires Node 22 or newer.

Root cause
The selected Miniflare version had a newer Node engine requirement than this repository's CI test job.

Solution
Pin Miniflare 3.20250718.3, which supports Node 18, and restore the CI test job to Node 18.17.1.

Manual test
Expected: the built service-worker-format test and all direct handler tests pass on Node 18 and Node 24 with no CI Node-version change.
Actual: all 11 worker tests pass on each version; the root test command passes on Node 18. Package lint on Node 18 reports only the unchanged xo.config.js import/extensions error; changed test files pass targeted XO.

Docs: https://developers.cloudflare.com/workers/testing/miniflare/
@johnemau
johnemau marked this pull request as ready for review September 30, 2026 00:13
Copilot AI balanced review requested due to automatic review settings September 30, 2026 00:13
@johnemau
johnemau marked this pull request as draft September 30, 2026 00:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Local preview loads the published bundle, and development mode cannot read script-based options.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

This PR moves the v4 donate-button build from preact-cli to Vite so it can build on newer Node versions while preserving the embed bundle and demo.

Changes:

  • Replaces the v4 build configuration and updates its dependencies and lockfile.
  • Sets Node 24 for the repository and CI.
File Description
yarn.lock Records the Vite dependency changes.
packages/​donate-button-v4/​vite.config.mjs Configures the bundle, assets, and demo page.
packages/​donate-button-v4/​preact.config.js Removes the old build configuration.
packages/​donate-button-v4/​package.json Switches build and development scripts to Vite.
package.json Requires Node 24.
.tool-versions Selects Node 24 locally.
.github/​workflows/​ci.yml Runs CI on Node 24.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/donate-button-v4/vite.config.mjs Outdated
.readFileSync(HTML_TEMPLATE, 'utf8')
.replace(
'</head>',
`<script type="text/javascript" src="${base}index.js"></script></head>`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh is this true? would be great to preview on demo page before merging

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ fixed in #464

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an AI slop. It turns out the problem not in the original preact setup, it was a problem created by my first claude prompt and then fixed, my bad!

Problem
The added Miniflare integration test expands coverage beyond the requested migration scope.

Root cause
The earlier test migration added a built-worker dispatch test and its Miniflare dependency to exercise behavior already covered by direct handler tests.

Solution
Remove the integration test, Miniflare dependency, and build-before-test step. Keep native Request and Response in the existing direct tests and retain the original ten assertions.

Manual test
Expected: existing handler assertions pass on Node 18 and Node 24 without a new test or CI Node-version change.
Actual: ten worker tests pass on both versions, and the root test command passes on Node 18. Package lint still reports only the unchanged xo.config.js import/extensions error; changed files pass targeted XO.
Problem: Node Request rejects CONNECT and TRACE, so those cases need request-shaped fixtures.
Root cause: An inline object assertion required a consistent-type-assertions suppression.
Solution: Name the fixture before asserting its Request type.

Manual test:
Expected: All 10 worker tests pass on Node 24, and the edited fixture has no lint suppression.
Actual: All 10 tests passed on Node 24; package lint reports only the existing xo.config.js import/extensions error.
Problem: Test requests used the production assets hostname.
Root cause: Native Request needs an absolute URL in Node.
Solution: Use assets.test for all three test request URLs.

Manual test:
Expected: All 10 worker tests pass on Node 24 with the reserved .test hostname.
Actual: All 10 tests passed; package lint reports only the pre-existing xo.config.js import/extensions error.
Problem: Native Request needs an absolute URL, and the previous test host did not mirror the assets host name.
Root cause: The test fixtures used a shorter reserved host.
Solution: Use assets.every.org.test in each request fixture.

Manual test:
Expected: All 10 worker tests pass on Node 24 with the requested reserved test host.
Actual: All 10 tests passed; package lint reports only the pre-existing xo.config.js import/extensions error.
Problem: The request-shaped object is a fake request, but its name called it a fixture.
Root cause: Native Request rejects CONNECT and TRACE, so those cases use a plain object.
Solution: Rename requestFixture to requestFake without changing behavior.

Manual test:
Expected: All 10 worker tests pass on Node 24 after the rename.
Actual: All 10 passed; package lint reports only the pre-existing xo.config.js import/extensions error.
Problem: The worker README says tests mock a service-worker context and use service-worker-mock.
Root cause: Test setup changed to Node's native web APIs while the README stayed unchanged.
Solution: Describe the direct handler tests and retain the end-to-end testing guidance.

Manual test:
Expected: README describes the current tests, and all 10 worker tests pass on Node 24.
Actual: README matches the test setup; all 10 tests passed on Node 24. Package lint reports only the pre-existing xo.config.js import/extensions error.
@jiwon85 jiwon85 mentioned this pull request Oct 1, 2026
2 tasks done
@johnemau
johnemau marked this pull request as ready for review October 5, 2026 15:04
Problem: PR #462 and PR #464 both target main, so the Vite migration can be reviewed or merged before its worker test prerequisite.
Root cause: Their branches diverged from the same main commit.
Solution: Merge the worker test branch into PR #462 and reconcile yarn.lock so the Vite branch contains the worker fix.

Manual test:
Expected: The combined branch installs with an immutable lockfile, passes all package tests, builds Vite, and passes root lint on Node 24.
Actual: yarn install --immutable, yarn test, yarn build, and yarn lint passed on Node 24.16.0.

PRs: #464 and #462
@johnemau
johnemau changed the base branch from main to john-with-claude/miniflare-node24-tests October 5, 2026 15:15
@johnemau
johnemau requested a review from santiher October 5, 2026 19:39
Problem
Developers cannot test explicit, formOnly, or data-every-style options with yarn dev.

Root cause
Vite runs the entry as a module, where document.currentScript is null.

Solution
Mark the injected development script and share a development-only fallback lookup. Keep classic-script precedence and remove the fallback from production builds.

Manual test
Run yarn dev in packages/donate-button-v4. Add ?explicit=1 or ?formOnly=1, or data-every-style, to the marked entry in vite.config.mjs and reload.
Expected: manual mode, form-only mode, or styled links respectively.
Actual before: options ignored. Actual after: option helpers honor the marked script.

Validation
Three regression tests fail before the change; all eight pass afterward, including Vite marker injection and environment replacement. Package lint, TypeScript, and production build pass. Production bundle contains no development selector or process.env.NODE_ENV.

Reference: #462 (comment)
Docs: https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript

Generated with AI under the guidance of John.
Reverts c7e44cc and 1ce09d3. On main, `yarn dev` injected the bundle without query parameters, so explicit, formOnly, and data-every-style could not be set from the dev page either. Removing the fallback returns the v4 source to main's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep `yarn workspace @everydotorg/donate-button-v4 test` in the root test command and the v4 `echo 'no tests'` script, as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the Vercel deployment URL as the base when VERCEL_URL is set and "/" otherwise, so development and local builds load from the local server. Serve the demo page at "/" from one middleware and emit it from the same template helper, replacing the request rewrite and the transformIndexHtml hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the demo page to the package root with a module script for src/index.tsx, so `vite` serves it at "/" with hot reload and no plugin hooks. Move src/assets to public/assets, which Vite serves in development and copies into the build. The build still emits the demo page for embeds.every.org/0.4, swapping the module script for the built index.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use index.html as the build input. Vite replaces its /src/index.tsx script with the built index.js, so the demo-page plugin is no longer needed; index.js is byte-identical to the previous build. Import package.json directly and parse the version with semver, as preact.config.js did, and declare semver as a devDependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bare JSON import makes Vite warn that the config is unsupported by its planned native config loader, and Prettier 2.8 cannot parse the `with { type: "json" }` attribute that silences it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prettier 3.0.0 is the first release that parses `import ... with {type: 'json'}`. Pin ~3.0.0 because later 3.x releases reformat 11 more v4 files. The only reformat is the demo page's doctype.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vite 8 deprecates build.rollupOptions in favor of build.rolldownOptions, and index.html at the project root is already the default build entry. The built files are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vite 8 resolve.tsconfigPaths reads the baseUrl the code and TypeScript already use for src/ imports, which removes the alias and the package directory lookup. The dev server cannot resolve src/ imports without one of the two. The built index.js is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnemau
johnemau marked this pull request as ready for review October 7, 2026 00:16
The worker test fix branch was rebased onto main, bringing in the XO config import fix from #465. Keep this branch's yarn.lock: it already drops the service-worker-mock entries that branch removes, and yarn install --immutable passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore the cloudflare-worker test setup, tests, README, and service-worker-mock dependencies to main, so the Miniflare migration can stack on this PR instead of under it. Node 21+ defines a getter-only global navigator that Object.assign cannot overwrite; delete it before installing the service-worker-mock globals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnemau
johnemau merged commit 1d7ec89 into main Oct 8, 2026
6 checks passed
@johnemau
johnemau deleted the john-with-claude/node-24-donate-testing-80be4e branch October 8, 2026 14:19

This branch was successfully deployed

1 active deployment
Preview — 4995846e Deployed Oct 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants