Assemble
This guide covers assembling core and resources into a website or app: use clientx run in development, and clientx build plus manifest-driven loading in production, with a single React instance throughout.
1. Assemble a website or app
“Assemble” means choosing a resource closure, building the output, and letting the core runtime mount it. Because resources are decoupled, the same core can produce different shapes:
# Shape 1: full app (messaging / users / AI / connectors / encryption)
node scripts/clientx.mjs build --resources client,userx,msgx,agix,conn,e2ee
# Shape 2: website (app shell + site framework + website)
node scripts/clientx.mjs build --resources client,sitex,portal
# Shape 3: a single resource composition
node scripts/clientx.mjs build --resources sitex
node scripts/clientx.mjs build --resources portal
All output lands in dist/. Deploy dist/ to any static server (see install Option B).
2. clientx run: the dev server
node scripts/clientx.mjs run # default: client+userx+msgx+agix+conn+e2ee
node scripts/clientx.mjs run --resources sitex # only the sitex closure (VITE_RESOURCES=sitex)
node scripts/clientx.mjs run --resources client,sitex,portal # debug the website locally
--resources sets VITE_RESOURCES; the dev registration path (glob-driven in src/resource/register.ts) only declares/activates the specified closure. If omitted, it falls back to the default composition. clientx run drives the local Vite server (stdio and signals are passed through, Ctrl+C shuts down cleanly) and defaults to port 3007.
Dev and production behave the same way: declare is synchronous and lightweight; activate is asynchronous and dependency-ordered; resources disabled by the user are filtered before import/declare/activate.
3. Manifest-driven loading in the core runtime
Production boot is driven by src/core-loader.ts. The core contains no concrete resource names — everything comes from dist/resource/manifest.json:
- Fetch the manifest:
GET /resource/manifest.jsonreturns{ resources: [{ id, entry, deps }] }. The directory is the truth — whatever is indist/resource/is what the app mounts. Change the manifest (or add/remove resource directories) to change the composition. - Inject the importmap:
index.htmlalready inlines it (injected at build time); the loader skips it idempotently. - Preload vendor:
shared-vendor.js+vendor-i18n.jsguarantee that resource modules resolve to the same instances. - Filter disabled resources: persisted overrides (user-disabled resources) are applied before import/declare/activate.
- Load in topological order:
import('/resource/<id>/register.js')indepsorder, dependencies first. - declare → activate: declare everything, apply persisted overrides, then activate each resource in dependency order.
4. One React instance
The core guarantee is that the whole application has one React instance (and one router, query client, store and i18n). This is implemented by the importmap. The following is an illustrative excerpt for the --resources client,sitex,portal closure (the real output has more entries):
{
"imports": {
"react": "/assets/shared-vendor.js",
"react/jsx-runtime": "/assets/shared-vendor.js",
"react-dom": "/assets/shared-vendor.js",
"@tanstack/react-router": "/assets/shared-vendor.js",
"@tanstack/react-query": "/assets/shared-vendor.js",
"zustand": "/assets/shared-vendor.js",
"i18next": "/assets/vendor-i18n.js",
"react-i18next": "/assets/vendor-i18n.js",
"@clientx/runtime/lib/kernel/instance": "/vendor/runtime/lib/kernel/instance.js",
"@clientx/common/components/markdown-content": "/vendor/common/components/markdown-content.js",
"@/resource/sitex/blocks": "/resource/sitex/blocks/index.js",
"@/resource/sitex/": "/resource/sitex/",
"@/resource/portal/": "/resource/portal/"
}
}
- The
reactfamily maps precisely toshared-vendor.js→ core and every resource share one React context / router / query client / zustand store / i18next. @clientx/*subpaths map tovendor/*→ kernel registries (resourceRegistry/slotRegistry/featureRegistry/routeRegistry) are global singletons.- Cross-resource subpaths map to
/resource/<dep>/...→ importing another resource yields the same module instance.
5. Composition cheat sheet
| Composition | Command | Output |
|---|---|---|
| Default app | clientx build | client+userx+msgx+agix+conn+e2ee |
| Website | clientx build --resources client,sitex,portal | App shell + site framework + website |
| Standalone resource | clientx build --resources portal | Closure [sitex, portal], no client |
| Framework only | clientx build --resources sitex | sitex only |
6. Next steps
- Case study: a
helloresource fromcreateto package and assembly.