Guía
Sitios estáticos
Una página HTML por archivo de src/routes/. stil run build deja el sitio listo para subir, y solo las páginas con islas llevan JavaScript.
Una línea en vite.config.ts
import ascua from "vite-plugin-ascua";
export default { plugins: [ascua({ site: true })] };Con eso, cada archivo de src/routes/ es una página, vite la sirve en
desarrollo y vite build escribe en dist/ un HTML por ruta. No hay servidor
que escribir ni entradas de cliente y servidor: queda una carpeta de archivos
para Hull, busybox httpd, tower-http o cualquier CDN.
src/routes/index.ts → dist/index.html
src/routes/acerca.ts → dist/acerca/index.html
src/routes/pedidos/index.ts → dist/pedidos/index.html
src/routes/pedidos/[id].ts → dist/pedidos/7/index.html, una por id
src/routes/docs/[...rest].ts → dist/docs/guia/inicio/index.html, con sus barras
src/routes/404.ts → dist/404.html
src/routes/_layout.ts envuelve todas las páginas
src/routes/pedidos/_layout.ts envuelve las de pedidos/, dentro del de la raízUn archivo que empieza por _ no es una página. [...rest] va al final y
atrapa lo que quede de la URL: /docs/guia/inicio le da rest = "guia/inicio".
El nombre entre corchetes lo eliges tú: es el del parámetro que llega a la página.
Una página
// src/routes/acerca.ts
export const title = "Acerca";
export default function Acerca() {
return view`<section><h1>Acerca</h1></section>`;
}El default es el componente de la página y title va al <title>. El código
de las páginas corre al construir y no llega al navegador: queda como HTML.
Con parámetros
// src/routes/pedidos/[id].ts
export const title = ({ id }: { id: string }) => `Pedido ${id}`;
export function paths() {
return PEDIDOS.map((p) => ({ id: p.id }));
}
export default function Pedido({ id }: { id: string }) {
return view`<article><h1>Pedido ${id}</h1></article>`;
}[id] es un segmento variable, y la página lo recibe como prop. paths()
dice qué páginas generar —una por objeto— y puede ser asíncrona. Es
obligatoria: sin ella, el build no sabría qué ids existen.
Datos: load()
// src/routes/pedidos/[id].ts
import { notFound } from "vite-plugin-ascua/site";
export async function load({ id }: { id: string }) {
const pedido = await leerPedido(id);
if (!pedido) throw notFound();
return pedido;
}
type Props = { id: string; data: Awaited<ReturnType<typeof load>> };
export const title = ({ data }: Props) => `Pedido ${data.numero}`;
export const description = ({ data }: Props) => `${data.lineas.length} líneas`;
export default function Pedido({ data }: Props) {
return view`<article><h1>Pedido ${data.numero}</h1></article>`;
}load() corre al construir —en desarrollo, en cada petición— y lo que
devuelve llega a la página, al título y a la descripción como data. Es el
sitio para leer archivos, una base de datos o una API: nada de eso llega al
navegador. description va a <meta name="description">.
throw notFound() responde con la página 404 en desarrollo y con servidor.
Rutas que no son páginas
Un archivo cuyo nombre, sin el .ts, conserva una extensión es ese archivo:
// src/routes/sitemap.xml.ts → dist/sitemap.xml
export default function Mapa() {
return `<?xml version="1.0"?><urlset>…</urlset>`;
}El default devuelve el contenido, que puede ser asíncrono; con parámetros y
load(), igual que una página. Sirve para un sitemap.xml, un
feed.xml, un índice de búsqueda en .js o un .json.
Lo interactivo: islas
Una página es HTML. Lo que tenga que responder al usuario va en una isla, que la página usa como cualquier componente:
// src/islands/contador.ts
export const IslaContador = defineIsland("contador", { inicial: p.number }, Contador);
// src/routes/index.ts
view`<section><IslaContador inicial=${3}/></section>`;Las islas de src/islands/ se registran solas. Una página que usa alguna recibe
el script que las hidrata y solo los módulos de las islas que usa, con sus
chunks y sus hojas pedidos por delante; una que no usa ninguna sale sin
JavaScript. Por eso conviene una isla por archivo: si dos islas se
definen en el mismo módulo, la página que usa una descarga las dos. Cómo
funcionan las islas está en SSR e islas.
El layout
// src/routes/_layout.ts
import type { Children } from "ascua";
export default function Layout(props: { path: string; children: Children }) {
const marco = view`<div><header>…</header><main></main></div>`;
props.children(marco.querySelector("main")!);
return marco;
}Es un componente con hijos: recibe la ruta (path), sus parámetros (params) y
dónde poner la página (children). Puede
haber uno por carpeta, y se anidan: el de la raíz envuelve al de pedidos/,
que envuelve a sus páginas.
Más cosas en el <head>
export const head = ({ data }: Props) => `
<meta property="og:title" content="${data.titulo}">
<link rel="canonical" href="https://ejemplo.cl/pedidos/${data.id}/">`;head —una cadena o una función de los props— va al <head> tal cual,
sin escapar: es HTML que escribe la ruta, no texto de un usuario.
El esqueleto: shell
Si hay un index.html en la raíz, es el esqueleto de todas las páginas. Pasa
por Vite como siempre, así que sus <link> a hojas globales salen con su hash.
La página va donde esté <!--ascua-->, o al principio de <body>. Sin
index.html, se usa uno mínimo.
Si la raíz del sitio es otra cosa —una portada hecha a mano—, el esqueleto
puede ser otro archivo —la opción shell—, y index.html sigue siendo una página más de Vite. Así
está hecha esta documentación:
ascua({ site: { routes: "rutas", shell: "docs/esqueleto.html" } });A cada página se le añaden su <title>, un <style> con los estilos de los
componentes que aparecen en ella —y solo esos— y, si tiene islas, su script.
Al subirlo
/pedidos/7 es dist/pedidos/7/index.html, que cualquier servidor de archivos
entrega como índice de la carpeta. Para la página de error, en busybox httpd:
E404:/404.htmlen el httpd.conf del sitio.
Con servidor
Si las páginas tienen que renderizarse en cada petición —datos que cambian a cada rato, una página por usuario—, el mismo sitio sale como servidor:
ascua({ site: { mode: "server" } });stil run build
node dist/server/index.mjs # PORT=3000 por defectovite build deja el cliente en dist/client/ y el servidor en
dist/server/index.mjs, con todo dentro: no necesita node_modules para
arrancar. Sirve los archivos del cliente —con caché larga para los que llevan
hash— y renderiza cada página al pedirla. Las rutas variables ya no necesitan
paths(): cualquier valor llega a load(), que decide con
notFound(). Para montarlo dentro de otro servidor, handle(url) devuelve
el estado, el tipo y el cuerpo (serve(puerto) es el que arranca el suyo):
import { handle } from "./dist/server/index.mjs";
const { status, type, body } = await handle("/pedidos/7");Opciones
| Por defecto | ||
|---|---|---|
routes |
src/routes |
Dónde están las páginas |
islands |
src/islands |
Lo que se hidrata; sin islas no hay script |
shell |
index.html |
La base de cada página |
mode |
static |
O server |
Y lo que puede exportar una ruta:
default |
La página: un componente de los props. En una ruta-archivo, el contenido |
title |
El <title>: una cadena o una función de los props |
description |
<meta name="description">, igual |
head |
HTML para el <head>, igual |
paths() |
Qué páginas generar en una ruta variable |
load() |
Los datos: llegan como data |