Node 22. Node 23 breaks headless-gl, and early 22.x hits a vinyl-fs bug, so pin something recent within 22.
npm run make runs build → beautify → minify → build-tests, from plain Node
scripts in scripts/. It rewrites dist/ and regenerates test/all.html, so
run it before anything that loads the browser bundle, and commit dist/ with
the change. Individual steps are available as npm run build, minify,
beautify, build-tests; npm run dev serves the repo for test/all.html.
Bundling is rolldown, minifying is terser. Two things there are load-bearing:
gland (for the core build)acornare aliased toscripts/empty-module.js. That is browserify's old.ignore(). They must be aliased, notexternal— external leaves a liverequire()that throws in a browser.- both the bundle and the minified bundle are run through terser with
ascii_only. acorn ships its unicode tables already escaped, but rolldown decodes string literals and prints the characters, which puts ~68k raw UTF-8 bytes in the bundle. Serving that without a matching charset is #743/#744.
The banner interpolates new Date(), so two builds are never byte-identical —
compare dist/ with the @date lines normalized. All four bundles of one run
share a timestamp: the minify step passes the banner through (terser keeps it
via output.comments, since it carries @license) rather than prepending its
own. Prepending is what used to put the banner in the minified files twice,
seconds apart.
Note beautify reformats src/, so npm run make can produce unrelated
whitespace churn in files you did not touch. That is expected.
npm test runs qunit over test/issues test/internal test/features.
The suite has platform-dependent failures that are not regressions:
- Linux/Mesa: a set of failures baselined in
.github/known-test-failures.txt. CI compares against it with.github/compare-test-failures.jsand fails only on new failures, so the raw exit code is not the signal. test/internal/math.random.js"unique every time" is flaky (seed collisions, related to #850). A single failure there is not meaningful.
Confirm any new regression test actually catches the bug by reverting the fix and watching it fail — a test that passes both ways is worse than none.
Bugs that only reproduce on real GPU drivers are a recurring theme here; three were found this way in 2.19.8 alone, none of which any CI runner could have caught. See the Testing section of README.md for how to run it.
npm run make # devices load dist/, so build first
npm run test:browserstack # real iOS/Android
node test/browserstack/run.js --dry-run --browsers=desktop # inspect caps, no sessionCredentials live in gitignored test/browserstack/.credentials.json, and as
BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY repo secrets for CI.
BrowserStack groups runs into one build by buildName, distinguishing them by
buildIdentifier. Keep buildName stable — a name that varies per run creates
a separate build every time and there is no single gpu.js entry to find in
the dashboard.
Every step below is required; skipping the npm publish is the usual mistake — 2.19.5 and 2.19.7 have tags and GitHub releases but were never published, so their release notes tell people to install a version that does not exist.
npm version <version> --no-git-tag-version # package.json only
npm run make # dist/ carries the version header
npm test # expect the known baseline
git add -A && git commit -m "chore: Release <version>"
git tag <version>
git push origin develop && git push origin <version>
gh release create <version> --title "<version>" --notes-file <notes>npm publish requires 2FA and prompts for a one-time code. Two things that
otherwise waste time:
npm view gpu.js versionlags a publish by up to a minute.curl -s https://registry.npmjs.org/gpu.jsis authoritative.E404 PUT ... not foundon publish means the npm token expired, not that anything is wrong with the package. Runnpm login.
Changes under .github/workflows/ are rejected unless the credential pushing
them carries the workflow scope. Push with one that does, or add the file
through the GitHub API.