Skip to content

{{ }} interpolation inside <script client> is HTML-escaped → invalid JS (breaks all @stacksjs/components interactive components) #1757

Description

@glennmichael123

Summary

{{ expr }} interpolation inside a <script client> block is HTML-escaped, so any interpolated value containing quotes (e.g. a string or JSON.stringify(...) output) is emitted as invalid JavaScript. " becomes &quot;, which breaks the whole client bundle with Unexpected token '&' / missing ) after argument list.

This makes every @stacksjs/components component that injects a server prop into a client signal unusable — Notification, Dialog, Drawer, Tabs, Tooltip, Switch, Select, Pagination, Accordion, etc. all use this pattern (const x = state({{ initialX }}), const cls = {{ JSON.stringify(className) }}), and dropping any of them onto a page crashes that page's JS.

Versions: @stacksjs/stx 0.2.70, bun-plugin-stx 0.2.70, @stacksjs/components 0.2.70 (note: components@0.2.70 pins stx@0.2.70 — same monorepo — so the shipped components are broken against their own engine). Also reproduced on 0.2.72. Bun 1.3.13, macOS arm64.

Minimal reproduction

components/ReproWidget.stx:

<script server>
export const initialShow = $props.show !== false
export const className = $props.className || ''
</script>

<script client>
const isVisible = state({{ initialShow }})
const extraClassName = {{ JSON.stringify(className) }}
function toggle() { isVisible.set(!isVisible()) }
</script>

<div :if="isVisible()" role="alert">widget visible</div>

views/index.stx:

<!DOCTYPE html><html><head><title>repro</title></head>
<body>
  <ReproWidget show className="foo" />
</body></html>

Serve with bun-plugin-stx/serve and open the page.

Actual compiled client JS

const isVisible = state(true);          // OK — `true` has no special chars
const extraClassName = &quot;foo&quot;;  // BROKEN — quotes HTML-escaped

Browser: Uncaught SyntaxError: Unexpected token '&'. The entire client bundle fails to execute, so the whole page is dead (no signals, no event handlers).

Expected

Interpolation inside a <script client> (and <script server>) block must not HTML-escape. The line should compile to:

const extraClassName = "foo";

i.e. {{ }} inside script context should behave like {!! !!} (raw), since script content is JavaScript, not HTML text.

Impact / why it matters

  • state({{ initialShow }}) happens to work only because booleans/numbers have no escapable characters; the moment a string or JSON.stringify result is injected, the bundle breaks.
  • This blocks all of @stacksjs/components' interactive components on a vanilla install.

Workaround

None from userland — the affected code is inside node_modules/@stacksjs/components. Consumers can only use purely server-rendered components (e.g. Spinner, Badge, Card).

Related

Same {{ }}-in-components family as #1748 ({{ }} inside x-for renders empty in components), but this one corrupts script output rather than rendering blank.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions