This is the frontend half. A pack implements page ids. It never owns
paths. Two ways in: scaffold from a running host, or Vite + keelPack
from a blank tree. Packs can target any of the seven adapters — Svelte, React,
Vue, Solid, Preact, Lit, or Angular. Svelte is the default and the examples
below use it; see
Framework adapters
for the per-framework APIs and build caveats.
Have a host answering GET /__keel/schema first —
Host (Ktor).
Option A — keel-scaffold
The host is the source of truth. Scaffold writes blank pages for every id in
the schema, plus page-types.ts, Vite, and keelPack.
pnpm add -D @kolektiv/keel-pack
Pick the adapter with --framework. Omit it and you get Svelte.
pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework svelte
cd pack && pnpm install && pnpm buildYou get +page.svelte / +layout.svelte, +page.ts for id overrides, and +head.svelte.
pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework react
cd pack && pnpm install && pnpm buildSwap react for preact or solid. Pages are +page.tsx / +layout.tsx, id in +page.ts, head in +head.html.
pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework vue
cd pack && pnpm install && pnpm buildPages are +page.vue / +layout.vue, id in +page.ts, head in +head.html.
pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework lit
cd pack && pnpm install && pnpm buildSwap lit for angular. Pages are +page.ts / +layout.ts, id override in +page.id.ts, head in +head.html. Angular also emits tsconfig.app.json and pins typescript@~5.8.3.
localhost / 127.* use http://; other hosts default to https://.
--framework accepts angular, lit, preact, react, solid, svelte
(the default), and vue; --id names the pack (default: the output
directory), --version pins the manifest version (default 0.1.0), and
--schema <file.json> scaffolds from a saved contract with no host running.
Re-run with --force to overwrite.
File layout by adapter (same id-keyed folders):
| Framework | Page / layout | Id override | Head template |
|---|---|---|---|
| Svelte | +page.svelte, +layout.svelte |
+page.ts |
+head.svelte |
| React | +page.tsx, +layout.tsx |
+page.ts |
+head.html |
| Preact | +page.tsx, +layout.tsx |
+page.ts |
+head.html |
| Solid | +page.tsx, +layout.tsx |
+page.ts |
+head.html |
| Vue | +page.vue, +layout.vue |
+page.ts |
+head.html |
| Lit | +page.ts, +layout.ts |
+page.id.ts |
+head.html |
| Angular | +page.ts, +layout.ts |
+page.id.ts |
+head.html |
Lit and Angular pages are themselves TypeScript modules, so their id override
moves to +page.id.ts. Angular scaffolds also emit tsconfig.app.json with
noEmit: false and pin typescript@~5.8.3; Angular 19 rejects TypeScript
≥ 5.9. Point the host at dist/<id>.feb. pnpm dev runs the same build in
watch mode; a host with watchPacks = true swaps the result — see
Pack hot reload.
Option B — Vite plugin by hand
Same result, files you type. After Client setup:
Option B below uses the Svelte Vite plugin. For other adapters, pair keelPack with that framework’s Vite plugin from Client setup, and set framework in keelPack({ … }) to match.
import { svelte } from "@sveltejs/vite-plugin-svelte"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [
svelte(),
keelPack({
id: "app",
version: "0.1.0",
framework: "svelte",
pagesDir: "src/pages",
bootstrap: "src/bootstrap.ts",
contract: "src/lib/page-types.json",
notFound: "app.notFound",
pack: "dist/app.feb",
}),
],
})import { svelte } from "@sveltejs/vite-plugin-svelte"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [
svelte(),
keelPack({
id: "app",
version: "0.1.0",
framework: "svelte",
pagesDir: "src/pages",
bootstrap: "src/bootstrap.ts",
contract: "src/lib/page-types.json",
notFound: "app.notFound",
pack: "dist/app.feb",
}),
],
})pagesDir is keyed by id, not URL. src/pages/app/home/+page.svelte is
app.home. A sibling +page.ts may export const id = "app.home" if the
folder name should not be the id.
import { bootstrap } from "@kolektiv/keel-svelte"
void bootstrap()import { bootstrap } from "@kolektiv/keel-svelte"
void bootstrap()<script lang="ts">
import { Head, page } from "@kolektiv/keel-svelte"
import type { HomePage } from "../../../lib/page-types"
const ctx = page<HomePage>()
</script>
<Head />
<h1>{ctx.data.greeting}</h1><script>
import { Head, page } from "@kolektiv/keel-svelte"
const ctx = page()
</script>
<Head />
<h1>{ctx.data.greeting}</h1>Copy page-types.ts / .json from GET /__keel/schema (scaffold does this)
or from Gradle generateKeelTypes. Point contract at the JSON so a pack
that implements an unknown id fails the build.
pnpm build
# dist/app.feb → FrontendBundle.fromFile(Path.of("pack/dist/app.feb"))
Load it on the host
val prod = FrontendBundle.fromFile(Path.of("pack/dist/app.feb"))
val dev = FrontendBundle.fromDirectory(Path.of("pack/dist"))Then register a page if you have not already, and restart the host. Document GET should show the pack UI; a visit returns the seed JSON.