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.
Where
aegiscomes from. Every snippet below importsfrom 'aegis'. That bare name is resolved by the import map in your base template — one block, copied once, pinned to a version with an integrity hash: see getting started. With a bundler it resolves fromnode_modules; with neither, importhttps://aegisjs.com/0.7.0/aegis.min.jsdirectly.
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.