Quick start
A framework with two plugins and an app on top of it, in four small files.
A framework in five steps
You write this page's code once per framework. An app on an existing framework, such as
@moku-labs/web, never imports the core.
-
Install. The core has no dependencies.
install mkdir my-framework && cd my-framework bun init -y bun add @moku-labs/core
-
Name the framework and its contract.
createCoreConfigcaptures the global config and the events. -
Write the plugins. Each one owns its config, state, API and event handlers.
-
Fix the defaults.
createCoretakes the plugin list and returnscreateApp. -
Write an app.
createAppconfigures the plugins and returns a typed, frozen object.
The code
import { createCoreConfig } from "@moku-labs/core";
type Config = { siteName: string };
type Events = { "router:navigate": { from: string; to: string } };
export const coreConfig = createCoreConfig<Config, Events>("my-framework", {
config: { siteName: "Untitled" }
});
export const { createPlugin, createCore } = coreConfig;- The id starts every kernel error.
- This
createPluginalready knowsConfigandEvents.
import { createPlugin } from "../config";
export const routerPlugin = createPlugin("router", {
config: { basePath: "/" },
createState: ctx => ({ currentPath: ctx.config.basePath }),
api: ctx => ({
navigate: (path: string) => {
const from = ctx.state.currentPath;
ctx.state.currentPath = path;
ctx.emit("router:navigate", { from, to: path });
},
current: () => ctx.state.currentPath
})
});- A wrong name or payload does not compile.
import { createPlugin } from "../config";
import { routerPlugin } from "./router";
export const viewsPlugin = createPlugin("views", {
depends: [routerPlugin],
createState: () => ({ count: 0 }),
hooks: ctx => ({
"router:navigate": () => {
ctx.state.count += 1;
}
}),
api: ctx => ({
count: () => ctx.state.count
})
});- Checked in
createApp:routermust be earlier in the list.
import { coreConfig, createCore } from "./config";
import { routerPlugin } from "./plugins/router";
import { viewsPlugin } from "./plugins/views";
export const { createApp, createPlugin } = createCore(coreConfig, {
plugins: [routerPlugin, viewsPlugin]
});- An app can configure these. It cannot remove or reorder them.
import { createApp } from "./my-framework";
const app = createApp({
config: { siteName: "My Blog" },
pluginConfigs: { router: { basePath: "/blog/" } },
onReady: ctx => console.log(`ready: ${ctx.config.siteName}`),
onStop: ctx => console.log(`views: ${ctx.views.count()}`)
});
await app.start();
app.router.navigate("/blog/about/");
console.log(`at: ${app.router.current()}`);
await app.stop();- Callbacks see every plugin API.
Run
bun app.ts ready: My Blog at: /blog/about/ views: 1
createApp is synchronous, so ready prints before app.start() runs. views prints from
onStop, after every plugin has stopped.
Let an app add a plugin
createApp also takes extra plugins. Export createPlugin from the framework, as above, and an
app writes its own. Its API lands on app, in the same types as the defaults.
import { createApp, createPlugin } from "./my-framework";
const greeterPlugin = createPlugin("greeter", {
config: { greeting: "Hello" },
api: ctx => ({
greet: (name: string) => `${ctx.config.greeting}, ${name}. Welcome to ${ctx.global.siteName}.`
})
});
const app = createApp({
plugins: [greeterPlugin],
config: { siteName: "My Blog" },
pluginConfigs: { greeter: { greeting: "Hi" } }
});
console.log(app.greeter.greet("Ada"));- Generics are all or nothing.
createCoreConfig<Config, Events>is the only call that takes them. To add core plugins as well, pass the third one too: see Write a framework. createApprunsonInit. Every plugin is initialized when it returns.app.start()is for what must wait: sockets, timers, listeners.- An app starts once. A stopped app cannot start again. Create a new one.
Next
- How it works: what
createAppdoes, in order. - Write a framework: core plugins, defaults, exports, tests.
- Three layers: who writes what.
- API reference: every export and type.