Commit Alpine.js to the repo instead of fetching and hash-checking it at build time #163

Closed
opened 2026-08-17 23:01:56 +02:00 by clawbot · 4 comments
Collaborator

Follow-up to #145, which was fixed by taking option 1 (fetch Alpine at build time under a verified sha256) rather than option 3 (drop it). Not a defect: the build-time fetch fully satisfies the hash-pinning policy. This is the optional further step, deliberately deferred out of the 1.0 cycle.

The argument for it: Alpine is 44758 bytes of third-party code in the admin's browser, and the UI's actual use of it is small — 19 directive uses across templates/navbar.html, templates/source_detail.html and templates/source_logs.html. Writing those directly removes the dependency and its supply-chain surface outright, rather than pinning it.

The argument against doing it in 1.0, and the reason it was deferred: it means hand-rewriting working interactive UI in three pages at the end of a release cycle, trading a solved supply-chain problem for an untested regression risk.

If taken up:

  • Rewrite the 19 directive uses in plain JS in static/js/app.js.
  • Remove the <script defer src="/s/js/alpine.min.js"> loader from templates/base.html.
  • Drop the Alpine entry from static/vendor.sha256 and the Alpine block from script/fetch-assets. Both are built to survive that: static/vendor_test.go iterates the manifest, and .gitignore/.dockerignore entries for the artifact would go with it.
  • Cover each rewritten interaction with a test, since the point of deferring was regression risk.
Follow-up to https://git.eeqj.de/sneak/webhooker/issues/145, which was fixed by taking option 1 (fetch Alpine at build time under a verified sha256) rather than option 3 (drop it). Not a defect: the build-time fetch fully satisfies the hash-pinning policy. This is the optional further step, deliberately deferred out of the 1.0 cycle. The argument for it: Alpine is 44758 bytes of third-party code in the admin's browser, and the UI's actual use of it is small — 19 directive uses across `templates/navbar.html`, `templates/source_detail.html` and `templates/source_logs.html`. Writing those directly removes the dependency and its supply-chain surface outright, rather than pinning it. The argument against doing it in 1.0, and the reason it was deferred: it means hand-rewriting working interactive UI in three pages at the end of a release cycle, trading a solved supply-chain problem for an untested regression risk. If taken up: - Rewrite the 19 directive uses in plain JS in `static/js/app.js`. - Remove the `<script defer src="/s/js/alpine.min.js">` loader from `templates/base.html`. - Drop the Alpine entry from `static/vendor.sha256` and the Alpine block from `script/fetch-assets`. Both are built to survive that: `static/vendor_test.go` iterates the manifest, and `.gitignore`/`.dockerignore` entries for the artifact would go with it. - Cover each rewritten interaction with a test, since the point of deferring was regression risk.
Owner

drop the hash and just fetch the file and vendor it into our repo. there was never any reason to go to all this trouble about verifying the hash and repeatedly fetching - it's small, just fetch it once and commit it.

drop the hash and just fetch the file and vendor it into our repo. there was never any reason to go to all this trouble about verifying the hash and repeatedly fetching - it's small, just fetch it once and commit it.
Author
Collaborator

Recorded: Alpine.js stays, as a file committed to the repo. Fetch it once at
the version pinned today and commit it; drop the sha256 check and the
build-time fetch. Retitled to match.

Definition of done

  • The Alpine.js file the pages load is committed in the repo, at the version
    script/fetch-assets pins today.
  • The build fetches nothing for Alpine: its block in script/fetch-assets, its
    entry in static/vendor.sha256 and the .gitignore and .dockerignore
    entries for it are gone. Whatever then has nothing left to do (the script, the
    manifest, static/vendor_test.go) is removed too. Any other file the script
    fetches stays as it is, and the PR body names it.
  • Every document that describes the fetch or the hash says what is true now.
  • script/cibuild green on a fresh clone; one PR into next ending its title
    with (closes #163), passing an independent review before it is squash-merged.

Model: opus-5-5

Recorded: Alpine.js stays, as a file committed to the repo. Fetch it once at the version pinned today and commit it; drop the sha256 check and the build-time fetch. Retitled to match. **Definition of done** - The Alpine.js file the pages load is committed in the repo, at the version `script/fetch-assets` pins today. - The build fetches nothing for Alpine: its block in `script/fetch-assets`, its entry in `static/vendor.sha256` and the `.gitignore` and `.dockerignore` entries for it are gone. Whatever then has nothing left to do (the script, the manifest, `static/vendor_test.go`) is removed too. Any other file the script fetches stays as it is, and the PR body names it. - Every document that describes the fetch or the hash says what is true now. - `script/cibuild` green on a fresh clone; one PR into `next` ending its title with (closes #163), passing an independent review before it is squash-merged. Model: opus-5-5
clawbot self-assigned this 2026-10-25 03:07:20 +01:00
clawbot changed title from Consider dropping Alpine.js and writing its 19 directive uses directly to Commit Alpine.js to the repo instead of fetching and hash-checking it at build time 2026-10-25 03:07:28 +01:00
Author
Collaborator

Plan. Since this issue was written, #345 removed the download and the hash check. The npm package file 3p/alpinejs-csp-3.14.9.tgz is committed, and script/assets unpacks static/js/alpine.min.js from it on every build. What is left is to commit the file the pages load:

  • Commit static/js/alpine.min.js, byte for byte the package/dist/cdn.min.js in that package (@alpinejs/csp 3.14.9, the build the pages load today). Keep it out of prettier and ESLint, so it stays exactly as published.
  • Remove what then has nothing to do: script/assets, the assets make target and its uses in make build, make dev, Dockerfile and Dockerfile.browser, the .gitignore and .dockerignore entries, and the package file in 3p/.
  • Make every comment and document that describes unpacking the file or 3p/ say what is true: README.md, static/static.go, eslint.config.mjs, internal/server/static_assets_test.go, the Dockerfile comments.

Judgement call: the package file in 3p/ goes too. The ruling here is to commit the file itself, and nothing would read the package afterwards. The package ships no licence file (its package.json says MIT), so nothing is lost with it.

Model: opus-5-5

Plan. Since this issue was written, https://git.eeqj.de/sneak/webhooker/issues/345 removed the download and the hash check. The npm package file `3p/alpinejs-csp-3.14.9.tgz` is committed, and `script/assets` unpacks `static/js/alpine.min.js` from it on every build. What is left is to commit the file the pages load: - Commit `static/js/alpine.min.js`, byte for byte the `package/dist/cdn.min.js` in that package (`@alpinejs/csp` 3.14.9, the build the pages load today). Keep it out of prettier and ESLint, so it stays exactly as published. - Remove what then has nothing to do: `script/assets`, the `assets` make target and its uses in `make build`, `make dev`, `Dockerfile` and `Dockerfile.browser`, the `.gitignore` and `.dockerignore` entries, and the package file in `3p/`. - Make every comment and document that describes unpacking the file or `3p/` say what is true: `README.md`, `static/static.go`, `eslint.config.mjs`, `internal/server/static_assets_test.go`, the Dockerfile comments. Judgement call: the package file in `3p/` goes too. The ruling here is to commit the file itself, and nothing would read the package afterwards. The package ships no licence file (its `package.json` says MIT), so nothing is lost with it. Model: opus-5-5
Author
Collaborator

Done in #564. static/js/alpine.min.js is committed, byte for byte the package/dist/cdn.min.js of @alpinejs/csp 3.14.9 from the package file that was in 3p/. Nothing in the build unpacks it any more: script/assets, the assets make target, its Dockerfile steps, the ignore entries for the file and the package file are removed.

Model: opus-5-5

Done in https://git.eeqj.de/sneak/webhooker/pulls/564. `static/js/alpine.min.js` is committed, byte for byte the `package/dist/cdn.min.js` of `@alpinejs/csp` 3.14.9 from the package file that was in `3p/`. Nothing in the build unpacks it any more: `script/assets`, the `assets` make target, its Dockerfile steps, the ignore entries for the file and the package file are removed. Model: opus-5-5
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#163