Sunucu adaları

sitelo statik HTML kurar. Sunucu adaları taze olması gereken parçaları doldurur — bir saat, yorumlar, stok, isteğe ihtiyaç duyan her şey. Bu tarif bir zaman adası olan bir sayfa kurar, sonra dist/ ve /_sitelo/islands adreslerini sunan küçük bir Node sunucusu çalıştırır.

Bu projenin bir kopyası sitelo deposunda examples/islands/ altında bulunur.

Elinize geçenler

Proje düzeni

my-site/
  sitelo.config.js
  server.js              # Node sunucusu: statik dist + adalar
  netlify.toml           # Netlify yeniden yazımı → function
  vercel.json            # Vercel yeniden yazımı → api rotası
  package.json
  netlify/functions/
    islands.mjs          # Netlify ada işleyicisi
  api/islands/
    [...path].js         # Vercel ada işleyicisi
  src/
    index.ht.js          # ada yer tutucusu olan sayfa
    js/
      islands.js         # istemci yükleyicisi (dist/ içine paketlenir)
    islands/
      time.js            # yalnızca sunucuda çalışan parça modülü
    css/
      styles.css
export default {
  site: 'https://example.com',
}

1. Ada modülü

Düz .js (.ht.js değil). { name, props, request } alır ve bir HTML dizesi döndürür. Bu ada, her istekte işlendiğini görebilmeniz için istek zamanını ve tarayıcı kimliğini kullanır.

export default function time({ props, request }) {
  const label = typeof props?.label === 'string' ? props.label : 'Sunucu saati'
  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">İstek üzerine şunun için işlendi: <code>${escapeHtml(ua.slice(0, 48))}</code></p>
  `
}

function escapeHtml(value) {
  return value
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
}

2. Adayı bir sayfaya yerleştirin

island() propları yer tutucuya gömer. Derleme yedeği yayımlar; uç nokta yanıt verdiğinde yükleyici onu değiştirir.

import { html, head, title, link, body, h1, p, script } from 'javascript-to-html'
import { island } from 'sitelo/islands'

export default () =>
  html({ lang: 'tr' },
    head(
      title('Sunucu adaları demosu'),
      link({ rel: 'stylesheet', href: '/styles.css' }),
    ),
    body(
      h1('Statik sayfa, canlı ada'),
      p('Bu HTML bir kez derlendi. Aşağıdaki kutu istek anında doldurulur.'),
      island(
        'time',
        { label: 'Şu anda' },
        '<p>Sunucu saati yükleniyor…</p>',
      ),
      script({ type: 'module', src: '/islands.js' }),
    ),
  )

3. İstemci yükleyicisi

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. Node sunucusu

sitelo build sonrasında bu süreç dist/ dizinini sunar ve adaları sitelo/islands/server içindeki createIslandsNodeHandler ile işler. Ada modülleri dist/ dışında kalır — sunucu onları src/ içinden içe aktarır.

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('Bulunamadı')
    }
  })
})

server.listen(port, () => {
  console.log(`Dinleniyor: http://localhost:${port}`)
})

5. Derleyin ve çalıştırın

npm install
sitelo build
node server.js

http://localhost:3000 adresini açın. Kısa bir an yedeği, sonra sunucu saatini görmelisiniz. Yenileyin — zaman damgası değişir. sitelo (geliştirme) ve sitelo preview içinde server.js gerekmez: CLI /_sitelo/islands adresini zaten sunar.

Dağıtım

Bu örnek, Node sunucusunun yanında sunucu taslaklarıyla gelir:

Başka yerlerde sunucusuz ya da kenar için createIslandsHandler kullanın (web Request → Response) — bkz. Sunucu adaları belgeleri. Aynı kaynakta değilse mountIslands({ endpoint }) ifadesini o fonksiyonun URL’ine yöneltin.

Notlar

Tek başına statik sunucular

GitHub Pages, düz S3 ve benzeri sunucuların bir sunucu süreci yoktur. Ada uç noktası olmadan yedek HTML olduğu gibi kalır — sayfalar yine çalışır, yalnızca canlı parça olmadan.

Propları küçük tutun

Proplar HTML özniteliğinde ve istek sorgu dizesinde yolculuk eder. Oraya sır ya da büyük yükler koymayın — onları adanın içinde, sunucuda getirin.

Sunucu adaları belgeleri · Temel site / dağıtım · Tüm örnekler