=============== LIBRARY RULES =============== From library maintainers: - Never mutate the kernel during render: kernel.load/unload and scope.provide/contribute belong in event handlers, effects, or bootstrap. - Tokens are imported symbols from one contract module, never string literals at call sites. - Prefer over useService(Token)! so removal unmounts the dependent subtree. - After an await inside setup, check scope.active before provide or contribute. ### Run example todo app Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Start the example todo app in development mode. See the example's README for details. ```bash pnpm --filter yatoi-example-todo dev ``` -------------------------------- ### Install kernel app-wide with app.use(yatoi, { kernel }) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Registers the kernel for the entire Vue app. Prefer this over provideKernel in a component's setup() because Vue's provide() doesn't reach the providing instance itself. ```ts import { createApp } from 'vue' import { yatoi } from '@yatoi/vue' import { kernel } from './kernel' import App from './App.vue' createApp(App).use(yatoi, { kernel }).mount('#app') ``` -------------------------------- ### Install @yatoi/kernel and @yatoi/slots Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/slots/README.md Install the required packages using npm. ```bash npm i @yatoi/kernel @yatoi/slots ``` -------------------------------- ### Define a plugin with inject and provides Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md Defines a plugin named 'sync' that activates only when Clock and Storage are present, provides SyncStatus, and uses the scope to get injected services, defer cleanup, and provide a service. The setup function is typed and the plugin is plain data until loaded by a kernel. ```ts const syncPlugin = definePlugin({ name: 'sync', inject: [Clock, Storage], // activates only when both are present provides: [SyncStatus], // may provide only what it declares setup(scope) { const clock = scope.get(Clock) // typed non-null: it's injected scope.defer(subscribe(clock)) // reversible effect scope.provide(SyncStatus, status) // published; removed on dispose }, }) ``` -------------------------------- ### Install @yatoi/vue and vue Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/vue/README.md Install the package and its peer dependency using npm. ```bash npm i @yatoi/vue vue ``` -------------------------------- ### Run preflight checks Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md Run the complete preflight before publishing: frozen-lockfile install, tests, typecheck, and pack:check. pack:check fails if a tarball contains source maps, TypeScript build metadata, tests, examples, or unresolved workspace: dependencies. ```bash pnpm install --frozen-lockfile pnpm test pnpm typecheck pnpm pack:check ``` -------------------------------- ### Provide a nested kernel with provideKernel Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Use provideKernel in a component's setup() to provide a different kernel to a subtree. This is the right tool for a nested or scoped kernel, unlike the app-wide installation. ```ts import { provideKernel } from '@yatoi/vue' export default defineComponent({ setup() { provideKernel(nestedKernel) // ... }, }) ``` -------------------------------- ### Install @yatoi/kernel via npm Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/kernel/README.md Install the package using npm. ```bash npm i @yatoi/kernel ``` -------------------------------- ### Setup @yatoi/vue with createApp Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/vue/README.md Create a Vue app with a kernel, define a service and plugin, and use the yatoi plugin with the kernel. ```ts import { createApp, defineComponent, h } from 'vue' import { createKernel, definePlugin, defineService } from '@yatoi/kernel' import { Requires, yatoi } from '@yatoi/vue' import { Slot } from '@yatoi/vue/slots' declare module '@yatoi/slots' { interface Slots { 'sidebar.item': Record } } const Clock = defineService<{ now(): number }>('clock') const clock = definePlugin({ name: 'clock', provides: [Clock], setup(scope) { scope.provide(Clock, { now: () => Date.now() }) scope.defer(() => console.log('clock stopped')) } }) const kernel = createKernel() kernel.load(clock) const App = defineComponent(() => () => h(Requires, { of: [Clock] }, { default: ([value]) => [h('time', value.now()), h(Slot, { name: 'sidebar.item' })], })) createApp(App).use(yatoi, { kernel }).mount('#app') ``` -------------------------------- ### Install @yatoi/react Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/react/README.md Install the React bindings package along with React and React DOM. Requires React 18 or 19. ```bash npm i @yatoi/react react react-dom ``` -------------------------------- ### Async setup with scope.active check (TypeScript) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Use this pattern when setup is async. After awaiting, check scope.active before performing side effects. If the scope is inactive, close the socket and return to avoid leaks. Defer cleanup before the await when possible. ```TypeScript async setup(scope) { const socket = await connect() if (!scope.active) { socket.close(); return } // deps vanished mid-await scope.defer(() => socket.close()) scope.provide(Socket, socket) } ``` -------------------------------- ### Install dependencies with pnpm Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Install all project dependencies using pnpm. Requires Node ≥ 22.22.2 and pnpm 9. ```bash pnpm install ``` -------------------------------- ### Define a plugin that provides a service (TypeScript) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Use this to create a plugin that provides a service. The setup function creates a store, subscribes to changes, and provides the service. The kernel enforces that provided tokens must be listed in 'provides', otherwise it throws. Deferred functions run on dispose in LIFO order. ```TypeScript import { definePlugin } from '@yatoi/kernel' export const todosPlugin = definePlugin({ name: 'todos', provides: [Todos], setup(scope) { const store = createStore(loadFromLocalStorage()) const stop = store.subscribe(() => saveToLocalStorage(store.getAll())) scope.defer(stop) // undone on dispose, LIFO scope.provide(Todos, store) // must be listed in `provides` }, }) ``` -------------------------------- ### TypeScript example: create kernel, define plugin and service Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/kernel/README.md Import createKernel, definePlugin, defineService from '@yatoi/kernel'. Define a Clock service, a plugin providing the service and deferring a stop, a reader plugin injecting the Clock, and load both plugins. ```ts import { createKernel, definePlugin, defineService } from '@yatoi/kernel' const Clock = defineService<{ now(): number }>('clock') const clock = definePlugin({ name: 'clock', provides: [Clock], setup(scope) { const timer = { stop() {} } scope.provide(Clock, { now: () => Date.now() }) scope.defer(() => timer.stop()) } }) const reader = definePlugin({ name: 'reader', inject: [Clock], setup(scope) { console.log(scope.get(Clock).now()) } }) const kernel = createKernel() kernel.load(clock, reader) ``` -------------------------------- ### Install @yatoi/vue dependencies with pnpm Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Install the kernel, Vue binding, and Vue itself. Required before using any @yatoi/vue features. ```bash pnpm add @yatoi/kernel @yatoi/vue vue ``` -------------------------------- ### Install Yatoi packages with pnpm Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Install the core Yatoi packages and React dependencies. @yatoi/kernel has no dependencies, @yatoi/slots depends only on @yatoi/kernel, and @yatoi/react peer-depends on react ^18 || ^19. ```bash pnpm add @yatoi/kernel @yatoi/react react react-dom ``` -------------------------------- ### Define a plugin that depends on a service (TypeScript) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Use this to create a plugin that depends on other services. The injected tokens are typed non-null and snapshotted at activation. The setup function gets the services and sets up a periodic task, deferring cleanup. If the underlying service changes, the scope is disposed and re-run. ```TypeScript export const syncPlugin = definePlugin({ name: 'sync', inject: [Todos, Clock], setup(scope) { const todos = scope.get(Todos) // TodoStore, not TodoStore | undefined const clock = scope.get(Clock) const id = setInterval(() => push(todos.getAll(), clock.now()), 5000) scope.defer(() => clearInterval(id)) }, }) ``` -------------------------------- ### Closure over setup-time service for a slot renderer Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md A slot renderer's signature is fixed by the `Slots` contract, so it cannot accept a service parameter. Use a closure to capture the service obtained via `scope.get` during setup. The renderer is then contributed with `contribute(scope, 'todo.item.extra', ...)`. ```tsx function makeDueDateField(todos: TodoStore) { return function DueDateField({ todo }: SlotRendererProps<'todo.item.extra'>) { … } } contribute(scope, 'todo.item.extra', makeDueDateField(scope.get(Todos))) ``` -------------------------------- ### Register and unload plugins with kernel.load and kernel.unload Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md Shows the derived activation and cascade unload behavior. A plugin runs setup when all its inject tokens are present and disposes when any goes away. Load order does not matter; unloading a thread disposes dependents in reverse dependency order, and reloading re-runs setup with a fresh scope. ```javascript kernel.load(syncPlugin) // registered, inactive — Clock isn't there yet kernel.load(clockPlugin) // clock activates; sync activates right after kernel.unload(clockPlugin) // sync disposes first, then clock kernel.load(clockPlugin) // both come back, sync with a fresh scope ``` -------------------------------- ### Kernel plugin setup with temporal and spatial scoping Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Defines a Clock service and two plugins: clockPlugin uses scope.defer for temporal cleanup and scope.provide for a reversible value; syncPlugin injects Clock and subscribes spatially, activating only while Clock is present. Loading order doesn't matter; unloading disposes sync first, and reloading gives sync a fresh scope. ```ts import { createKernel, defineService, definePlugin } from '@yatoi/kernel' const Clock = defineService<{ now(): number }>('clock') // a real symbol, not a string const clockPlugin = definePlugin({ name: 'clock', provides: [Clock], setup(scope) { const id = setInterval(tick, 1000) scope.defer(() => clearInterval(id)) // temporal: undone on unload scope.provide(Clock, { now: () => Date.now() }) // temporal: removed on unload }, }) const syncPlugin = definePlugin({ name: 'sync', inject: [Clock], // spatial: runs only while Clock is present setup(scope) { scope.defer(subscribe(scope.get(Clock))) }, // typed non-null }) const kernel = createKernel() kernel.load(syncPlugin, clockPlugin) // order doesn't matter; sync activates when Clock appears kernel.unload(clockPlugin) // sync disposes first, cleanups in reverse kernel.load(clockPlugin) // both come back, sync with a fresh scope ``` -------------------------------- ### Declare and use a slot with @yatoi/slots Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/slots/README.md Shows how to declare a slot type, contribute to it in a plugin, and list contributions. The example declares a 'task.badge' slot with a taskId property, contributes a badge renderer, and logs the number of contributions. ```ts import { createKernel, definePlugin } from '@yatoi/kernel' import { slot } from '@yatoi/slots' declare module '@yatoi/slots' { interface Slots { 'task.badge': { taskId: string } } } const badges = definePlugin({ name: 'badges', setup(scope) { scope.contribute(slot('task.badge'), 'badge renderer') } }) const kernel = createKernel() kernel.load(badges) console.log(kernel.list(slot('task.badge')).length) ``` -------------------------------- ### Define a service with a key (third-party case) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md When a plugin cannot depend on the contract package, use a types-only package and augment the Services interface. Use service('key') to get a token by key. ```ts import { service } from '@yatoi/kernel' import type { TodoStore } from '@todo-app/types' declare module '@yatoi/kernel' { interface Services { todos: TodoStore } } const todos = service('todos') // ServiceToken ``` -------------------------------- ### Install and uninstall plugins from an event handler Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Loads a plugin asynchronously from a module map and unloads it. Unloading cascades to dependents and is a no-op if the plugin is not loaded. The plugin object is the identity; loading the same object twice throws. ```ts // an install button async function install(id: string) { const mod = await pluginModules[id]() kernel.load(mod.default) } function uninstall(plugin: AnyPlugin) { kernel.unload(plugin) // dependents cascade; no-op if not loaded } ``` -------------------------------- ### Distinguish absent from starting up with useServiceState Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Returns a discriminated union indicating whether the service is present, absent, or loading. Use this when you need to differentiate between a service that is not yet available and one that is starting up. ```tsx const s = useServiceState(Clock) // { status: 'present', value } | { status: 'absent' } | { status: 'loading' } ``` -------------------------------- ### Factory pattern for closing over services (makeCalendarView) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Creates a renderer component that closes over a service obtained from setup. Inline arrow functions do this naturally; for larger components, a factory keeps things readable. This is the idiomatic way to hand a setup-time dependency to a component React will call later, after setup has returned. ```tsx function makeCalendarView(todos: TodoStore) { return function CalendarView() { const list = useTodos(todos) … } } scope.contribute(Views, { id: 'calendar', label: 'Calendar', View: makeCalendarView(todos) }) ``` -------------------------------- ### Apply changesets and install dependencies Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md Apply pending changesets and update the lockfile. Review changelogs, versions, and internal dependency ranges. On a patch, unrelated packages keep their versions; on a minor or major, all four land on the same x.y.0. ```bash pnpm version-packages pnpm install ``` -------------------------------- ### Registry-based plugin loader (TypeScript) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md The default loader: a registry maps plugin IDs to dynamic imports. It validates the default export's shape (name and setup) and throws an error if invalid. The kernel then loads the plugin. This is bundled with the app, so Vite code-splits it like any other dynamic import. ```ts // the "loader": id → code. The default shape — bundled with the app, so // Vite code-splits this like any other dynamic import. const registry: Record Promise> = { calendar: () => import('./plugins/calendar/index.js'), } const mod = await registry[entry.id]?.() if (typeof mod?.default?.name !== 'string' || typeof mod?.default?.setup !== 'function') { throw new Error(`plugin "${entry.id}" has no valid default export`) } kernel.load(mod.default) ``` -------------------------------- ### useService hook to get a service Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md Retrieves a service from the kernel. The return type is `Clock | undefined` because the service may not be available. ```tsx const clock = useService(Clock) // Clock | undefined ``` -------------------------------- ### Kernel observation API usage Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Use these kernel methods to inspect plugin states, retrieve contributions, subscribe to changes, and get the version. The version property is useful for a cheap useSyncExternalStore snapshot. ```TypeScript kernel.pluginState(plugin) // 'inactive' | 'starting' | 'active' | 'disposing' | 'failed' kernel.state(Clock) // { status: 'present', value } | { status: 'absent' } | { status: 'loading' } kernel.get(Clock) // Clock | undefined kernel.list(Views) // readonly Contribution[] kernel.subscribe(listener) // fires once per batch of changes kernel.version // bumps on every change — a cheap useSyncExternalStore snapshot ``` -------------------------------- ### Use useContributionValues to get contribution values Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Returns a Readonly>. Use this instead of useContributions when you only need the values, avoiding the double .value indirection. ```ts const views = useContributionValues(Views) // Readonly> ``` -------------------------------- ### Use KernelProvider, Requires, and Slot with @yatoi/react Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/react/README.md Shows how to create a kernel, define a service and plugin, load the plugin, and render a React component using KernelProvider, Requires, and Slot. The plugin provides a Clock service and defers a cleanup log. The kernel is created outside the component tree and React observes it without owning it. ```tsx import { createKernel, definePlugin, defineService } from '@yatoi/kernel' import { KernelProvider, Requires } from '@yatoi/react' import { Slot } from '@yatoi/react/slots' declare module '@yatoi/slots' { interface Slots { 'sidebar.item': Record } } const Clock = defineService<{ now(): number }>('clock') const clock = definePlugin({ name: 'clock', provides: [Clock], setup(scope) { scope.provide(Clock, { now: () => Date.now() }) scope.defer(() => console.log('clock stopped')) } }) const kernel = createKernel() kernel.load(clock) export function App() { return {(value) => } } ``` -------------------------------- ### Build all packages Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Build all packages using tsc -b, producing dist/ in each package. ```bash pnpm build ``` -------------------------------- ### Run tests for all packages Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Run the test suite for all packages. Kernel and slots tests run in Node, while react and vue tests run in jsdom. ```bash pnpm test ``` -------------------------------- ### Define and load plugins with @yatoi/kernel Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md Shows the core kernel API: defining services, plugins, and loading them. The 'inject' declaration drives the lifecycle graph; unload disposes dependents first and deferred cleanups in reverse. The 'provides' array must be declared, otherwise provide() throws. ```typescript import { createKernel, defineService, defineCollection, definePlugin } from '@yatoi/kernel' // A token is a real imported symbol, so go-to-definition works. const Clock = defineService<{ now(): number }>('clock') const clockPlugin = definePlugin({ name: 'clock', provides: [Clock], // must be declared — provide() of anything else throws setup(scope) { const id = setInterval(tick, 1000) scope.defer(() => clearInterval(id)) // reversible effect; disposers run LIFO scope.provide(Clock, { now: () => Date.now() }) }, }) const syncPlugin = definePlugin({ name: 'sync', inject: [Clock, Storage], // drives the lifecycle graph setup(scope) { const clock = scope.get(Clock) // typed non-null, because declared scope.defer(subscribe(clock)) }, }) const kernel = createKernel() kernel.load(clockPlugin, syncPlugin) // registration; activation is derived ``` -------------------------------- ### Create a changeset Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md Run pnpm changeset to add a changeset file for a user-visible package change. Choose the semantic-version impact and explain the behavior. For a patch, name only the changed packages; for a minor or major (e.g., 0.2), name all four published packages. ```bash pnpm changeset ``` -------------------------------- ### Subscribe to a service's own state with useSyncExternalStore Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md A service is just a value; if it has state, subscribe to it like any external store. Keep `getAll()` returning the same array reference until a mutation to keep the subscription cheap. ```ts export function useTodos(store: TodoStore) { return useSyncExternalStore(store.subscribe, store.getAll) } ``` -------------------------------- ### Run pnpm release to publish packages Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md Publishes the packages to npm. Must be run only from the exact reviewed release commit while authenticated as an npm publisher. ```bash pnpm release ``` -------------------------------- ### Check npm package availability for yatoi Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md Run before committing to a name to verify availability on npm. `npm view` exiting with 404 means the package is available. The scoped package `@yatoi/kernel` is the binding constraint and should be checked first. ```bash npm view yatoi # bare package npm view @yatoi/kernel # scope — the binding constraint, check this first ``` -------------------------------- ### Create a kernel (kernel.ts) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Create a kernel at module scope, outside any component. Attach an error handler to log plugin failures. ```ts // kernel.ts import { createKernel } from '@yatoi/kernel' export const kernel = createKernel() kernel.on('error', (error, plugin) => { console.error(`plugin "${plugin.name}" failed:`, error) }) ``` -------------------------------- ### Define plugin with isolated token and load twice Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/proposals/0001-namespace-isolation.md Shows a plugin that isolates the Todos token, provides it, and loads a calendar plugin. Loading the same plugin twice currently errors but is intended to create separate namespaces. ```typescript const listPlugin = definePlugin({ name: 'todo-list', isolate: [Todos], // this subtree gets its own Todos provides: [Todos], setup(scope) { scope.provide(Todos, createStore(scope.id)) scope.load(calendarPlugin) // resolves Todos → this scope's }, }) kernel.load(listPlugin) // list A kernel.load(listPlugin) // list B — currently an error (§5.4); see open question 1 ``` -------------------------------- ### Kernel testing without React Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Use this snippet to test the kernel in a plain Node environment. It loads plugins, checks plugin states, and waits for async operations to settle. ```TypeScript const kernel = createKernel() kernel.load(clockPlugin, syncPlugin) expect(kernel.pluginState(syncPlugin)).toBe('active') kernel.unload(clockPlugin) expect(kernel.pluginState(syncPlugin)).toBe('inactive') await kernel.settle() // wait for any async setup/disposal await kernel.dispose() // unload everything and settle ``` -------------------------------- ### Load a plugin at component scope with usePlugin Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Loads a plugin when the component mounts and unloads it on unmount. The plugin must be defined at module scope for a stable identity. Under StrictMode, the kernel handles load → unload → load, resulting in exactly one active instance. Two mounted components using the same plugin object is an error. ```tsx const spellcheckPlugin = definePlugin({ name: 'spellcheck', setup(scope) { … } }) function Editor() { usePlugin(spellcheckPlugin) // loaded on mount, unloaded on unmount return } ``` -------------------------------- ### Run typecheck Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Run TypeScript type checking across the project. ```bash pnpm typecheck ``` -------------------------------- ### Load plugins at kernel scope in bootstrap.ts Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Loads plugins once before the first render. The order of plugins does not matter. Loading the same plugin object twice at top level throws, so keep a Map if loading from a manifest. ```ts // bootstrap.ts — runs once before the first render kernel.load(todosPlugin, clockPlugin, syncPlugin) // order doesn't matter ``` -------------------------------- ### React projection with and Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Shows yatoi's render-tree composability in React. makes a subtree depend on a service, unmounting when it goes away; lets a plugin contribute UI without the host importing it. The comment contrasts with a stale context read that can crash. ```tsx // React today: a read that can go stale const clock = useContext(ClockContext) // Clock | undefined once the provider is gone clock!.now() // and here is the crash // yatoi: a requirement the subtree cannot exist without }> {(clock) => } {/* non-null; unmounts when Clock goes */} // yatoi: UI a plugin pushes into a surface it doesn't own {/* host never imports the contributors */} ``` -------------------------------- ### Host declares slot and renders it Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md The host declares a slot with a props contract using module augmentation and renders it with the Slot component. Props are type-checked on both sides. ```tsx // host declare module '@yatoi/slots' { interface Slots { 'todo.item.extra': { todo: Todo } } } ``` -------------------------------- ### Use useService for optional service access Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md For the cheap case where absence is genuinely fine to render. Returns the service value or undefined. Use optional chaining to handle the absence. ```tsx const clock = useService(Clock) // Clock | undefined return {clock ? clock.now() : '—'} ``` -------------------------------- ### Check package tarballs Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md Build and audit the four npm tarballs without publishing them. ```bash pnpm pack:check ``` -------------------------------- ### Four-layer architecture diagram Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md ASCII diagram showing the four layers of yatoi, from React-specific bindings down to the framework-agnostic kernel. Each layer lists its exports and dependencies. ```text @yatoi/react/slots contribute(), for React React @yatoi/react KernelProvider, useService, React @yatoi/slots Slots interface, SlotName, slot() no React, no Vue @yatoi/kernel plugins, services, cascade unload no React, no DOM ``` -------------------------------- ### Define and use a collection with useContributions Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Defines a ViewDescriptor interface and a Views collection, then uses useContributions in a Shell component to render navigation and the active view. The contribution object includes value, priority, mode, and owner. ```tsx // host export interface ViewDescriptor { id: string; label: string; View: ComponentType } export const Views = defineCollection('views') function Shell() { const views = useContributions(Views) // readonly Contribution[] const active = views.find((c) => c.value.id === activeId) return ( <> {active ? : } ) } ``` -------------------------------- ### URL-based plugin loader (TypeScript) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Alternative loader that resolves an id to a URL and dynamically imports it, as for a CDN-hosted plugin. Uses @vite-ignore to prevent Vite from analyzing the URL. The same validation and kernel.load call apply. A missing file becomes a load failure the UI can show without crashing. ```ts const url = `${pluginBase}/${entry.id}.js` const mod = await import(/* @vite-ignore */ url) // same validation, same kernel.load(mod.default) ``` -------------------------------- ### Set repository metadata for yatoi Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md Repository metadata to set on day one for the yatoi project. The Description field and Topics list are used by GitHub and npm search for discovery. ```text Description: Plugin kernel for React. Reversible effects, service discovery, and reactive unload — build fully plugin-based frontends. Topics: react, plugin-architecture, plugin-system, extension-points, dependency-injection, micro-kernel, slots, lifecycle ``` -------------------------------- ### Plugin manifest JSON (public/plugins.json) Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md A JSON array describing a plugin's metadata (id, name, version, description). The marketplace UI renders cards from this data alone. An id described here but missing from the registry shows as unavailable. ```json // public/plugins.json [{ "id": "calendar", "name": "Calendar", "version": "0.1.0", "description": "…" }] ``` -------------------------------- ### Consume a service with render prop Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md The recommended way to consume a service in React. The render prop's arguments are non-null, and the subtree unmounts when a service disappears. `of` accepts any number of tokens; arguments arrive in the same order. ```tsx Loading…

}> {(todos) => }
``` -------------------------------- ### Plugin wraps its own lazy component in Suspense Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md A plugin contributes a lazy component wrapped in its own `` boundary so it controls its own placeholder. `` does not add a Suspense boundary, so without this the host must provide one. The composed renderer is memoised on the contributions array, keeping the lazy component's identity stable across host re-renders. ```tsx contribute(scope, 'sidebar.item', () => ( }> )) ``` -------------------------------- ### Use with a default slot and fallback Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md Renders the default slot with the provided values as a tuple when all contributions are available, otherwise the fallback. Use explicit