A Vue island, in Aegis
The case: a server-rendered page with one Vue component mounted into a <div id="app">, built with Vite, shipping vue.runtime plus the component. The same component in Aegis is one file, no build, and the server markup is the first render.
Before#
<div id="likes" data-count="12"></div>
<script type="module" src="/dist/likes.js"></script>likes.js (compiled from a .vue single-file component by Vite):
import { createApp, ref } from 'vue';
import Likes from './Likes.vue';
const el = document.getElementById('likes');
createApp(Likes, { count: Number(el.dataset.count) }).mount(el);Likes.vue:
<script setup>
import { ref } from 'vue';
const props = defineProps({ count: Number });
const count = ref(props.count);
const pending = ref(false);
async function like() {
count.value++; pending.value = true;
try { await fetch(`/api/like`, { method: 'POST' }); }
catch { count.value--; }
finally { pending.value = false; }
}
</script>
<template>
<button :disabled="pending" @click="like">{{ count }} likes</button>
</template>After#
<div data-aegis="likes" data-count="12"><button>12 likes</button></div>
<script type="module">
import { island, mutation, api } from 'aegis';
island('likes', ({ props, signal, html }) => {
const count = signal(props.count);
const like = mutation(() => api.post('/api/like'), {
optimistic: () => { count.value++; },
onError: () => { count.value--; },
});
return html`<button ?disabled=${like.pending} @click=${like}>${count} likes</button>`;
}, { types: { count: Number } });
</script>The diff, line by line#
| Vue | Aegis |
|---|---|
createApp(Component, props).mount(el) per island |
island(name, setup, { types }) once; every [data-aegis="likes"] on the page mounts, including ones inserted later |
<div id="likes"></div> is empty until JavaScript runs |
the server renders the first view inside the host; the island replaces it in place |
defineProps + Number(el.dataset.count) by hand |
{ types: { count: Number } } coerces data-* |
ref() |
signal() |
{{ count }} in a template compiled at build time |
${count} in `html```, parsed once at runtime, CSP-safe |
pending + try/catch/finally by hand |
mutation() with optimistic / onError and a pending signal |
Vite, vue.runtime, .vue compiler |
one file, no build; the pinned URL with a hash |
What you give up#
- Single-file components with scoped CSS: Aegis has `css``` for adopted stylesheets, not the SFC format.
- Vue's ecosystem: component libraries, Pinia, vue-router, DevTools. Aegis has its own router, cache and an in-page inspector, but not the library shelf.
- Petite-Vue, if that is what you used: it is smaller than Aegis. Move only if the component needs data, forms or optimistic updates.