Skip to content

Core concepts

Packs and manifests

A versioned .feb zip of modules keyed by page id.

A pack is a .feb: a zip whose root is manifest.json plus the modules it names. The manifest maps page ids to modules — the pack’s implementation of the host contract.

Authors write a file tree of pages (ids, not URL routes). The tree below is Svelte; other adapters swap the component and head extensions and keep the same folder ids — see Framework adapters:

src/pages/
  +layout.svelte
  harbor/
    +layout.svelte
    home/+page.svelte        → harbor.home
    home/+head.svelte        → harbor.home document head
    user/+page.svelte        → harbor.user
    notFound/+page.svelte    → harbor.notFound

The folder ids are identical. React, Solid, and Preact use +layout.tsx and +page.tsx; Vue uses +layout.vue and +page.vue; Lit and Angular use +layout.ts and +page.ts. All six carry the head in a sibling +head.html. Lit and Angular page modules have no component extension, so a page that needs a different id adds a sibling +page.id.ts.

The id is the directory path with / turned into .. A sibling +page.ts may export const id = "harbor.home" when the folder name should not be the id. +layout.svelte files wrap descendant pages, root-first. Route params like [slug] are not ids — the host owns URLs. A sibling +head.svelte (Svelte) or +head.html (other adapters) compiles into the page’s manifest head — see Document head and SEO.

src/bootstrap.ts
import { bootstrap } from "@kolektiv/keel-svelte"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-react"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-vue"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-solid"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-preact"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-lit"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-angular"

void bootstrap()

keelPack discovers those files, emits a module per id, writes the manifest, and can zip the .feb.

vite.config.ts
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: "harbor",
    version: "0.1.0",
    framework: "svelte",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})
import react from "@vitejs/plugin-react"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  react(),
  keelPack({
    id: "harbor",
    version: "0.1.0",
    framework: "react",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})
import vue from "@vitejs/plugin-vue"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  vue(),
  keelPack({
    id: "harbor",
    version: "0.1.0",
    framework: "vue",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})
import solid from "vite-plugin-solid"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  solid(),
  keelPack({
    id: "harbor",
    version: "0.1.0",
    framework: "solid",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})
import preact from "@preact/preset-vite"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  preact(),
  keelPack({
    id: "harbor",
    version: "0.1.0",
    framework: "preact",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  keelPack({
    id: "harbor",
    version: "0.1.0",
    framework: "lit",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})
import analog from "@analogjs/vite-plugin-angular"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  analog({ tsconfig: "tsconfig.app.json" }),
  keelPack({
    id: "harbor",
    version: "0.1.0",
    framework: "angular",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "harbor.notFound",
    pack: "dist/harbor.feb",
  }),
],
})

The host loads that artifact as a FrontendBundle from a jar resource, a file, or an exploded directory (dev / watch).

FrontendBundle
val harbor = FrontendBundle.fromResource("keel/harbor.feb")
val unpacked = FrontendBundle.fromDirectory(Path.of("pack/dist"))
val file = FrontendBundle.fromFile(Path.of("harbor.feb"))

Asset URLs are per bundle: /__keel/pack/{bundleId}/…. Seed entry / css are rewritten to that prefix so multiple bundles on one app do not clash.

Pass contract (typegen’s keel/1 JSON) so a pack that implements an unknown id fails the build. A pack may implement a subset; the call site passes the pack per route.

The .feb layout, PageModule, and RouterAdapter are specified under Pack adapter.