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:

  1. Fetch the manifest: GET /resource/manifest.json returns { resources: [{ id, entry, deps }] }. The directory is the truth — whatever is in dist/resource/ is what the app mounts. Change the manifest (or add/remove resource directories) to change the composition.
  2. Inject the importmap: index.html already inlines it (injected at build time); the loader skips it idempotently.
  3. Preload vendor: shared-vendor.js + vendor-i18n.js guarantee that resource modules resolve to the same instances.
  4. Filter disabled resources: persisted overrides (user-disabled resources) are applied before import/declare/activate.
  5. Load in topological order: import('/resource/<id>/register.js') in deps order, dependencies first.
  6. 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/"
  }
}

5. Composition cheat sheet

CompositionCommandOutput
Default appclientx buildclient+userx+msgx+agix+conn+e2ee
Websiteclientx build --resources client,sitex,portalApp shell + site framework + website
Standalone resourceclientx build --resources portalClosure [sitex, portal], no client
Framework onlyclientx build --resources sitexsitex only

6. Next steps