Skip to content

fix(astro): register middleware by package subpath, not chunk-relative URL - #254

Merged
tenphi merged 1 commit into
mainfrom
fix/astro-integration-middleware-entrypoint
Aug 5, 2026
Merged

fix(astro): register middleware by package subpath, not chunk-relative URL#254
tenphi merged 1 commit into
mainfrom
fix/astro-integration-middleware-entrypoint

Conversation

@tenphi

@tenphi tenphi commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Problem

tastyIntegration() is broken in the published 3.0.0 and fails the build outright for every Astro consumer.

It registered its middleware with:

addMiddleware({ entrypoint: new URL('./astro-middleware.js', import.meta.url), order: 'pre' })

import.meta.url points at the emitted chunk, not the source file. The v3 build hoists tastyIntegration out of dist/ssr/astro.js into a shared chunk at the dist/ root (dist/astro-B53trJyX.js), one directory up — so the URL resolved to dist/astro-middleware.js, which doesn't exist. The real file shipped fine at dist/ssr/astro-middleware.js.

Reported downstream; the reporter worked around it with the documented manual-middleware path (src/middleware.ts calling tastyMiddleware({ transferCache: false }), integration dropped from astro.config.ts).

Second bug found while fixing this

tastyIntegration({ islands: false }) silently kept emitting the class-list transfer script — so the "ships zero client JS" promise in the docs did not hold.

The flag travelled through module-level state in astro-transfer-cache.ts: written by the integration when the Astro config is loaded, read by the middleware at request time. Those are different module instances, and for built output different processes entirely, so the middleware always saw the true default. This means the reporter's manual workaround was in fact more correct than the integration it replaced, not merely equivalent.

Fix

  • Entrypoint is now a package subpath (@tenphi/tasty/ssr/astro-middleware), resolved through the exports map. Immune to chunk layout, which is free to change at any time.
  • Two self-contained entrypoints instead of shared mutable state, since addMiddleware() cannot pass options. astro-middleware bakes in transferCache: true, astro-middleware-static bakes in false. astro-transfer-cache.ts is deleted.
  • New exports ./ssr/astro-middleware and ./ssr/astro-middleware-static exist only so Astro can resolve them by specifier; manual setups should keep using tastyMiddleware(). Documented as such in docs/ssr.md and AGENTS.md.

Consumers on the manual workaround can revert to tastyIntegration() once on this release, or keep the manual setup — both are supported.

Tests

New src/ssr/astro-integration.test.ts. The important one asserts the entrypoint is actually reachable — declared in package.json exports and produced by a tsdown entry — rather than just that some string was passed, which is the class of bug that shipped. Both guards were verified to fail against the broken code before being committed:

  • reinstating the new URL(...) entrypoint → 4 tests fail
  • removing the exports entry → @tenphi/tasty/ssr/astro-middleware is not declared in package.json "exports"

The transfer-cache tests render through each onRequest with a collected style chunk and assert the islands variant emits window.__TASTY__ while the static one emits no <script> at all.

Verification

  • pnpm test — 66 files, 1939 passed
  • pnpm hygiene (lint + format + typecheck), pnpm knip, pnpm size — all clean
  • Public API snapshot updated: adds exactly the two new subpaths, each exporting onRequest
  • End-to-end against the real artifact: npm pack, installed the tarball into a clean consumer, ran the integration hook and dynamically imported whatever entrypoint it handed back — resolves and loads for both islands: true and islands: false

🤖 Generated with Claude Code

…e URL

tastyIntegration() registered its middleware with
new URL('./astro-middleware.js', import.meta.url). That URL resolves
against the emitted chunk, and the v3 build hoists tastyIntegration out
of dist/ssr/astro.js into a shared chunk at the dist/ root — so it
pointed at dist/astro-middleware.js, which does not exist, while the
real file shipped at dist/ssr/astro-middleware.js. Every Astro build on
3.0.0 failed. Astro now gets a package subpath, resolved through the
exports map and immune to chunk layout.

Also fixes islands: false silently keeping the class-list transfer
script. The flag travelled through module-level state in
astro-transfer-cache.ts, written by the integration when the Astro
config loads and read by the middleware at request time — different
module instances, and different processes entirely for built output, so
the middleware always saw the true default. Replaced with two
self-contained entrypoints; addMiddleware() cannot pass options.

Adds ssr/astro-middleware and ssr/astro-middleware-static exports.
They exist so Astro can resolve the middleware by specifier; manual
setups should keep using tastyMiddleware().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📦 Snapshot release

Published 0.0.0-snapshot.2394160.

pnpm add @tenphi/tasty@0.0.0-snapshot.2394160

@tenphi
tenphi merged commit 5a176cc into main Aug 5, 2026
6 of 7 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 5, 2026
tenphi added a commit to tenphi/tenphi.me that referenced this pull request Aug 5, 2026
Nothing in the v2 -> v3 rename set applied here: no camelCase at-rule keys,
no renamed or removed exports, no `configure({ funcs })`, no `/next` subpath,
and every directional group was already single-value.

Two substantive changes:

- `flex: '1 1 auto'` -> the flexGrow/flexShrink/flexBasis triple used
  everywhere else in the codebase. Caught by eslint-plugin-tasty 1.0.0, which
  now detects `styles={{ ... }}` JSX props; the value had been invisible to
  every rule until now.

- The output no longer carries Tasty's `window.__TASTY__` class-list script.
  `islands: false` never actually took effect before: the flag travelled
  through module-level state written when the Astro config loaded and read at
  request time, which are different module instances, so the middleware always
  saw the default. Fixed upstream in 3.0.1 (tenphi/tasty#254) by splitting the
  integration into two self-contained middleware entrypoints. The site ships
  no `client:*` directives, so this is dead weight removed from every page.

3.0.0 is unusable with Astro and skipped: tastyIntegration() resolved its
middleware via `new URL('./astro-middleware.js', import.meta.url)`, and the v3
build hoisted the integration into a shared chunk one directory above the file
that shipped, failing the build for every consumer.

Verified by diffing normalized CSS and markup across all 7 pages against a
2.11.0 baseline build: byte-identical apart from the flex longhands and the
removed transfer script. Also confirmed `islands: true` still emits the script,
so both entrypoints resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant