Island server
Di halaman ini
sitelo membangun HTML statis. Island server mengisi bagian yang harus segar — jam, komentar, stok, apa pun yang butuh permintaannya. Resep ini membangun halaman dengan island waktu, lalu menjalankan server Node kecil yang menyajikan dist/ dan /_sitelo/islands.
Salinan proyek ini ada di repositori sitelo, di bawah examples/islands/.
Yang Anda dapat
- Halaman beranda statis dengan penampung island dan HTML cadangan
- Modul island khusus server di bawah
src/islands/yang tidak pernah sampai ke peramban - Pemuat klien yang menukar masuk fragmen dari
/_sitelo/islands/<name> - Sebuah
server.jsberbentuk produksi — berkas statis +createIslandsNodeHandler
Tata letak proyek
my-site/
sitelo.config.js
server.js # Host Node: dist statis + island
netlify.toml # rewrite Netlify → function
vercel.json # rewrite Vercel → rute api
package.json
netlify/functions/
islands.mjs # handler island Netlify
api/islands/
[...path].js # handler island Vercel
src/
index.ht.js # halaman dengan placeholder island
js/
islands.js # loader klien (dibundel ke dist/)
islands/
time.js # modul fragmen khusus server
css/
styles.cssexport default {
site: 'https://example.com',
}1. Modul island
.js biasa (bukan .ht.js). Menerima { name, props, request } dan mengembalikan string HTML. Yang ini memakai waktu permintaan dan user-agent agar Anda bisa melihat ia dirender per permintaan.
export default function time({ props, request }) {
const label = typeof props?.label === 'string' ? props.label : 'Waktu server'
const now = new Date().toISOString()
const ua = request?.headers?.get?.('user-agent') ?? 'unknown'
return `
<p><strong>${label}:</strong> <time datetime="${now}">${now}</time></p>
<p class="muted">Dirender saat diminta untuk <code>${escapeHtml(ua.slice(0, 48))}</code></p>
`
}
function escapeHtml(value) {
return value
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
}2. Tempatkan island di sebuah halaman
island() menyematkan props ke penampungnya. Build mengirim cadangannya; pemuat menggantinya ketika endpoint menjawab.
import { island } from 'sitelo/islands'
export default () => `
<html lang="id">
<head>
<title>Demo island server</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>Halaman statis, island hidup</h1>
<p>HTML ini dibangun sekali. Kotak di bawah diisi saat permintaan datang.</p>
${island(
'time',
{ label: 'Sekarang' },
'<p>Memuat waktu server…</p>',
)}
<script type="module" src="/js/islands.js"></script>
</body>
</html>
`import { html, head, title, link, body, h1, p, script } from 'javascript-to-html'
import { island } from 'sitelo/islands'
export default () =>
html({ lang: 'id' },
head(
title('Demo island server'),
link({ rel: 'stylesheet', href: '/styles.css' }),
),
body(
h1('Halaman statis, island hidup'),
p('HTML ini dibangun sekali. Kotak di bawah diisi saat permintaan datang.'),
island(
'time',
{ label: 'Sekarang' },
'<p>Memuat waktu server…</p>',
),
script({ type: 'module', src: '/islands.js' }),
),
)import { island } from 'sitelo/islands'
export default function Home() {
return (
<html lang="id">
<head>
<title>Demo island server</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<h1>Halaman statis, island hidup</h1>
<p>HTML ini dibangun sekali. Kotak di bawah diisi saat permintaan datang.</p>
{island(
'time',
{ label: 'Sekarang' },
'<p>Memuat waktu server…</p>',
)}
<script type="module" src="/js/islands.js" />
</body>
</html>
)
}3. Pemuat klien
import { mountIslands } from 'sitelo/islands/client'
mountIslands()body {
font-family: system-ui, sans-serif;
max-width: 36rem;
margin: 2rem auto;
padding: 0 1rem;
line-height: 1.5;
}
[data-sitelo-island] {
margin: 1.5rem 0;
padding: 1rem 1.25rem;
border: 1px solid #ccc;
}
[data-sitelo-island-state='loading'] {
opacity: 0.7;
}
.muted {
color: #666;
font-size: 0.9rem;
}4. Host Node
Setelah sitelo build, proses ini menyajikan dist/ dan merender island dengan createIslandsNodeHandler dari sitelo/islands/server. Modul island tetap di luar dist/ — host mengimpornya dari src/.
import fs from 'node:fs'
import http from 'node:http'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { createIslandsFromDirectory, createIslandsNodeHandler } from 'sitelo/islands/server'
const root = path.dirname(fileURLToPath(import.meta.url))
const dist = path.join(root, 'dist')
const port = Number(process.env.PORT) || 3000
const handleIslands = createIslandsNodeHandler({
islands: createIslandsFromDirectory(path.join(root, 'src/islands')),
})
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.ico': 'image/x-icon',
'.xml': 'application/xml',
'.json': 'application/json',
}
function sendFile(res, filePath) {
const ext = path.extname(filePath)
res.statusCode = 200
res.setHeader('Content-Type', MIME[ext] ?? 'application/octet-stream')
fs.createReadStream(filePath).pipe(res)
}
function resolveStatic(urlPath) {
const clean = decodeURIComponent(urlPath.split('?')[0])
const relative = clean === '/' ? 'index.html' : clean.replace(/^\/+/, '')
const candidate = path.normalize(path.join(dist, relative))
if (!candidate.startsWith(dist + path.sep) && candidate !== dist) {
return null
}
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
return candidate
}
const asIndex = path.join(candidate, 'index.html')
if (fs.existsSync(asIndex) && fs.statSync(asIndex).isFile()) {
return asIndex
}
return null
}
const server = http.createServer(async (req, res) => {
await handleIslands(req, res, () => {
const file = resolveStatic(req.url ?? '/')
if (file) {
sendFile(res, file)
return
}
const notFound = path.join(dist, '404.html')
res.statusCode = 404
if (fs.existsSync(notFound)) {
sendFile(res, notFound)
} else {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('Tidak ditemukan')
}
})
})
server.listen(port, () => {
console.log(`Mendengarkan di http://localhost:${port}`)
})5. Build dan jalankan
npm install
sitelo build
node server.jsBuka http://localhost:3000. Anda mestinya melihat cadangannya sekejap, lalu waktu server. Tekan muat ulang — cap waktunya berubah. Di sitelo (pengembangan) dan sitelo preview Anda tidak butuh server.js: CLI-nya sudah menyajikan /_sitelo/islands.
Penerapan
Contoh ini menyertakan kerangka host di samping server Node:
Node—npm run build && npm start(setelPORTdi platformnya). MemakaicreateIslandsFromDirectory.Netlify—netlify.tomlmenulis ulang/_sitelo/islands/*kenetlify/functions/islands.mjs.Vercel—vercel.jsonmenulis ulang keapi/islands/[...path].js.- Hosting statis saja (GitHub Pages, S3 biasa) — penampungnya mempertahankan cadangan sampai Anda menambahkan sebuah fungsi.
Untuk tanpa server atau edge di tempat lain, gunakan createIslandsHandler (Request → Response web) — lihat dokumentasi Island server. Arahkan mountIslands({ endpoint }) ke URL fungsi itu bila tidak satu asal.
Catatan
Hosting statis saja
GitHub Pages, S3 biasa, dan hosting sejenis tidak punya proses server. Tanpa endpoint island, HTML cadangan cukup tetap di tempatnya — halamannya tetap jalan, hanya tanpa fragmen langsungnya.
Jaga props tetap kecil
Props berjalan di atribut HTML dan string kueri permintaan. Jangan menaruh rahasia atau muatan besar di sana — ambil semua itu di dalam modul island, di server.
Dokumentasi Island server · Situs dasar / penerapan · Semua contoh