TypeScript
TypeScript setup, JSX pragmas, and type imports in Avalon.
Avalon is built with TypeScript and provides types for all its APIs. Pages, layouts, and islands are all .tsx or .ts files by default.
Type imports
The main types come from @useavalon/avalon:
import type { LayoutProps } from '@useavalon/avalon';
Client-side component types come from @useavalon/avalon/client:
import type { ImageProps } from '@useavalon/avalon/client';
JSX pragmas
Avalon uses Preact as the default JSX runtime for pages and layouts. For islands using other frameworks, add a JSX pragma at the top of the file:
// React island
/** @jsxImportSource react */
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
// Solid island
/** @jsxImportSource solid-js */
import { createSignal } from 'solid-js';
export default function Counter() {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count()}</button>;
}
Preact islands don't need a pragma since it's the default jsxImportSource.
Framework-specific file extensions
Some frameworks use their own file extensions instead of JSX pragmas:
| Framework | Extension | Pragma needed? |
|---|---|---|
| Preact | .tsx | No (default) |
| React | .tsx | Yes: /** @jsxImportSource react */ |
| Solid | .solid.tsx | Convention-based, or use pragma |
| Vue | .vue | No |
| Svelte | .svelte | No |
| Lit | .ts | No (uses decorators, not JSX) |
| Qwik | .qwik.tsx | Convention-based |
tsconfig
A minimal tsconfig.json for an Avalon project:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strict": true,
"skipLibCheck": true,
"paths": {
"@shared/*": ["./app/shared/*"],
"@modules/*": ["./app/modules/*"]
}
},
"include": ["app/**/*"]
}
The paths should match the resolve.alias entries in your Vite config.
Typing page props
Pages that use dynamic routes receive an event prop:
interface PageProps {
event: {
context: {
params?: Record<string, string>;
};
};
}
export default async function BlogPost({ event }: PageProps) {
const slug = event.context.params?.slug;
// ...
}
Typing layout props
import type { LayoutProps } from '@useavalon/avalon';
export default function MyLayout({ children, frontmatter }: LayoutProps) {
const title = frontmatter?.title as string | undefined;
// children: ComponentChildren
// frontmatter: Record<string, unknown>
}
Typing island props
Island props must be serializable (no functions, no class instances). TypeScript helps enforce this:
interface ChartProps {
data: number[];
label: string;
color?: string;
}
export default function Chart({ data, label, color = '#1F6AD3' }: ChartProps) {
// ...
}