Follow-up to #1780. Imports in <script client> work now: each component's client script is bundled with its imports (client-script-bundler.js runs one Bun.build per script, entrypoints: [tmpEntry], no splitting). The consequence is that two components importing the same module each get their own inlined copy of it.
That has two effects:
- Module state exists once per component, not once per page. A cache, a registry or an in-flight promise in a shared module is silently duplicated. Nothing warns about it, and it reads like ordinary module code.
- Pages ship the same code several times. Nothing is shared or cached between bundles.
What it looked like in a real app (stx 0.2.286)
WildLoop's profile page is 456 KB of HTML, 389 KB of it inline JS across 30 scripts. Our auth client module is inlined into four bundles: the page and layout, the nav, the mobile header and the sign-in sheet. The nav, the mobile header and the sign-in sheet render on every page, so every page carries at least three copies.
Because each copy had its own state:
- the session was restored once per copy, so one page load made 5
GET /api/mecalls and dispatched 5 auth-ready events - the nav and the mobile header refetched notifications on each event: 13
GET /api/notificationsfor one badge - a sign-out hook registered through one copy was invisible to the copy that performed sign-out, so it never ran
We worked around it by keeping that state on a globalThis singleton. Duplicate requests went from 21 to 6 on that page, but the page weight is unchanged.
Minimal shape
// resources/functions/counter.ts
let count = 0
export function bump() { return ++count }<!-- components/A.stx and components/B.stx, both on one page -->
<script client>
import { bump } from '../functions/counter'
console.log(bump())
</script>Both log 1, and view-source shows counter.ts twice. With one shared module instance, the second would log 2.
Expected
Shared imports are bundled once per page and executed once, so module-level state is page-wide the way @stores and @composables already are. They are external to the per-component bundles today.
Possible directions
- Build all client scripts on a page in one
Bun.buildcall withsplitting: true. Emit the shared chunk as one content-hashed asset, as #1878 did for the stylesheet, so it is also cached across pages. This assumes the component scripts can run as modules. - Or keep per-component bundles, but mark project-local modules imported by more than one component as external and serve them from a page-level module registry, like
window.__composables. - At minimum, document that each component gets its own module instance, and warn when the same module is inlined into more than one bundle on a page.
A related but separate size issue: @stacksjs/mobile ships as a single dist/index.js, and importing only secureStorage pulls in about 25 KB of the bridge. In our auth module that's 25 KB of the 38 KB, so every duplicated copy multiplies it.