Composing a Vue app
In a Vue app, each feature — or a group of features — is a Task. A Task can return anything — this example uses { ui, api }.
Each feature is split into a View (.vue SFC) and a Task: the View takes the feature’s atom as a prop and stays a plain SFC; the Task creates the atom, wraps the View into a Widget closure that supplies it, and returns both ui.Widget and api.$atom.
One Task at the end — a Render Task — mounts the UI into the page. It reads upstream Tasks directly: every Task is known here, so a Tag adds nothing.
App.vue only hosts the mount target (<div id="root">) and runs compose().
What the example does:
ThemeSelectis a feature — it returns the chosen theme and a way to choose it.LanguageSelectis a feature — it returns the chosen language and a way to choose it.Appearanceis a layout — through its context it reads the language from a shared Tag and the widgets from another Tag.ThemeProvideris a provider — through its context it reads the chosen theme.- The Render Task mounts the
LayoutfromAppearancewrapped in theProviderfromThemeProvider. compose()wires it all; the Render Task runs last.
nanostores here is only for the task.api demo. Features can use any state manager.
<script setup>import { compose, createTask, createWire, literal } from "@grlt-hub/app-compose"import { createApp, h, onMounted } from "vue"import { Appearance, appearanceWidgets } from "./appearance.js"import { LanguageSelect, ThemeSelect } from "./features.js"import { selectedLanguage, selectedTheme } from "./shared-tags.js"import { ThemeProvider } from "./theme-provider.js"
// page-level — reads Tasks directlyconst renderApp = createTask({ name: "render-app", run: { context: { AppearanceLayout: Appearance.result.ui.Layout, ThemeProvider: ThemeProvider.result.ui.Provider, }, fn: (ctx) => { createApp({ render: () => h(ctx.ThemeProvider, null, () => h(ctx.AppearanceLayout)), }).mount("#root") }, },})
onMounted(() => { compose() .step([ThemeSelect, LanguageSelect]) // fill features data into shared tags .step([ createWire({ from: { theme: ThemeSelect.result.api.$theme, language: LanguageSelect.result.api.$language, }, to: { theme: selectedTheme, language: selectedLanguage }, }), ]) // collect feature widgets into one tag .step( createWire({ from: [ { Widget: ThemeSelect.result.ui.Widget, key: literal(ThemeSelect.name) }, { Widget: LanguageSelect.result.ui.Widget, key: literal(LanguageSelect.name) }, ], to: appearanceWidgets, }), ) .step([Appearance, ThemeProvider]) .step(renderApp) .run()})</script>
<template> <div id="root"></div></template>