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
.epcomponent'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.