Package

This guide covers the clientx build pipeline, the dist/ output layout, and the difference between single-resource and composed (dependency closure) builds.

1. clientx build

CLI entry points:

node scripts/clientx.mjs build                    # default: client+userx+msgx+agix+conn+e2ee
node scripts/clientx.mjs build --resources sitex  # only sitex (closure = sitex)
node scripts/clientx.mjs build --resources hello,client   # custom resource + dependencies
node scripts/clientx.mjs build --no-clean         # keep dist (default: clean build for determinism)
node scripts/clientx.mjs --help                   # help

--resources is the list of resource ids you want to enable. The builder resolves the transitive dependency closure (resolve-closure.mjs), includes dependencies, and builds them in topological order.

2. Build pipeline

  1. shared-vendor → dist/assets/shared-vendor.js: bundles the react family (react / react-dom / react-router / react-query / zustand / sonner / next-themes) into a single instance.
  2. vendor-i18n → dist/assets/vendor-i18n.js: bundles i18next + react-i18next separately (i18next’s default export conflicts with React).
  3. vendor-packages → dist/vendor/{runtime,common}/**: emits the two @clientx packages with preserveModules for precise importmap subpath resolution.
  4. build-resource → dist/resource/<id>/**: builds each resource in the closure, dependencies first. Resource code keeps bare imports (react / @clientx/... / @/resource/<dep>/...) resolved by the importmap.
  5. build-app → dist/index.html + dist/resource/{manifest,importmap}.json: builds core-app, writes the closure manifest and importmap, and inlines the importmap into index.html’s <head>.

3. Output layout

dist/
├── index.html                 # core-app entry (inline <script type="importmap">)
├── assets/
│   ├── shared-vendor.js       # single React family instance
│   ├── vendor-i18n.js         # single i18n family instance
│   └── ...                    # remaining core-app chunks
├── vendor/
│   ├── runtime/**             # @clientx/runtime modules
│   └── common/**              # @clientx/common modules
├── resource/
│   ├── manifest.json          # closure: { resources: [{ id, entry, deps }] }
│   ├── importmap.json         # bare specifier → URL mapping
│   ├── <id>/register.js       # resource entry (loaded dynamically)
│   ├── <id>/**                # resource modules
│   └── <id>/i18n/zh.json      # locale bundle
└── downloads/**               # only when portal is in the closure

manifest.json is the single source of truth for runtime loading (see the Assemble guide):

{
  "resources": [
    { "id": "sitex", "entry": "/resource/sitex/register.js", "deps": [] },
    { "id": "portal", "entry": "/resource/portal/register.js", "deps": ["sitex"] }
  ]
}

Note: deps in the manifest are resource ids (resolved at build time), not the kind in Resource.dependsOn (such as site). portal declares dependsOn: ['site'] as a kind-level constraint; the built closure dependency is derived from source imports (@/resource/sitex/...) and is ["sitex"].

importmap.json is the key to the single-instance guarantee: the react family maps to shared-vendor.js, every @clientx/runtime|common subpath maps to vendor/<pkg>/<sub>.js, and cross-resource subpaths map to /resource/<dep>/<sub>.js.

4. Single resource vs composition

Single resource

node scripts/clientx.mjs build --resources sitex

Closure = sitex (plus the @clientx packages it imports, which go into vendor/). dist/resource/ contains only sitex/. Useful for a single pluggable unit or to verify that a resource stands alone.

node scripts/clientx.mjs build --resources hello,client

The custom hello resource depends on client, so the closure is [client, hello] (dependency first). A missing dependency fails loudly: build-app verifies that resources statically required by the app frame are in the closure.

Composition (default app / website)

node scripts/clientx.mjs build                      # default app: client+userx+msgx+agix+conn+e2ee
node scripts/clientx.mjs build --resources client,sitex,portal   # website: app shell + site framework + site

Key points:

5. Download artifacts

scripts/p3/package-downloads.mjs writes distributable artifacts to resource/portal/downloads/ (copied into dist/downloads/ at build time and served from /downloads/):

node scripts/p3/package-downloads.mjs

6. Next steps