Client-side behavior

epresso is no-JavaScript-by-default. When you need client-side behavior, put it in a .ep component's <script> block. There is no separate "islands" directory.

A <script> block in a .ep file is extracted, bundled with esbuild (falling back to a raw module when esbuild isn't installed — JS stays optional), and injected before </body> on every page that uses the component. Identical script blocks dedupe to one file.

  
epresso
------<style>.count { color: red; }</style><script> document.querySelectorAll('.count').forEach((el) => { el.addEventListener('click', () => alert('hi')); });</script><button class="count">{{ content }}</button>

This is page-level client JS: it runs once when the page loads and can enhance any element, including the component's own server-rendered output.

Relationship to components

  • A .ep component's <script> is plain page-level client JS (bundled, deduped). Good for broad behavior — theme toggles, search, analytics, or enhancing all matching elements (e.g. code blocks).
  • Scoped CSS (<style>) scopes the component's styles to its own output.

What happened to "islands"?

Earlier epresso versions had a separate islands/<name>.js mechanism — a mount(el, props) module placed with {% island %}. That was removed. Client JS now simply lives in .ep component <script> blocks that live with the component itself.

No client: directives

epresso has no per-element hydration directives (client:load, client:idle, client:visible, client:media, client:only). A component <script> runs once when the page loads and enhances the whole page. For deferred/explicit work, gate the logic inside the script (e.g. IntersectionObserver, user-gesture listeners) rather than declaring an element-level directive.