Case study
This case study follows the minimal hello example resource: first the complete source generated by clientx create, then a file-by-file explanation, then build and assembly into an app.
1. Generate hello
node scripts/clientx.mjs create hello --deps client
Output (excerpt):
[clientx] created resource "hello" at src/resource/hello/
package.json { name: @me/hello, clientx: { id: hello, kind: app, deps: [client], entry: register.ts } }
register.ts declareHello() + activateHello()
i18n/zh.json + i18n/en.json {}
[clientx] next: node scripts/clientx.mjs build --resources hello
2. Complete source
src/resource/hello/package.json
{
"name": "@me/hello",
"clientx": {
"id": "hello",
"kind": "app",
"deps": ["client"],
"entry": "register.ts"
}
}
The clientx field describes the resource: id matches the directory, kind is app, deps declares a dependency on client (merged with source-import inference), and entry is always register.ts.
src/resource/hello/register.ts
// src/resource/hello/register.ts
// hello resource skeleton generated by clientx create.
// declareHello: synchronous identity registration (lightweight, main chunk).
// activateHello: asynchronous heavy imports (only when not disabled).
import { resourceRegistry } from '@clientx/runtime/lib/kernel/instance'
import type { Resource } from '@clientx/runtime/lib/kernel/types'
export const HELLO_RESOURCE: Resource = { id: 'hello', kind: 'app', source: { type: 'config' } }
// Dual convention: uniform declare/activate (loader preference) + named exports (dev/legacy).
export function declare(): void {
resourceRegistry.declare(HELLO_RESOURCE)
}
export function declareHello(): void {
declare()
}
export async function activate(): Promise<void> {
// No heavy implementation in the skeleton; dynamically import business modules here.
}
export async function activateHello(): Promise<void> {
await activate()
}
HELLO_RESOURCEdescribes the resource:id,kind,source.source: { type: 'config' }means a config-type resource.- declare (synchronous, lightweight): registers identity in
resourceRegistry. Both the production loader and the dev registration path prefer uniformdeclare/activate, with named exports as a fallback, so the skeleton exports both. - activate (asynchronous, heavy): the skeleton has no business logic; import components/capabilities/slots here. It runs only when the resource is not disabled.
src/resource/hello/i18n/zh.json / en.json
{}
Locale bundle templates. They are built into dist/resource/hello/i18n/ and injected into i18next for enabled resources only.
3. Make hello do something
The skeleton builds and loads with the closure, but has no UI. Adding a page requires registering a route (lazy component) in declare and importing/registering capabilities or slots in activate. Follow the built-in resources (for example, msgx/register.ts):
export function declareHello(): void {
resourceRegistry.declare(HELLO_RESOURCE)
routeRegistry.register('hello', [
{ id: 'hello-page', path: 'hello', parent: ROUTES.app, component: () => import('./modules/hello-page') },
])
}
export async function activateHello(): Promise<void> {
const { HelloBadge } = await import('./components/hello-badge')
slotRegistry.register('nav:hello', HelloBadge)
}
Then write a page component in src/resource/hello/modules/hello-page.tsx. Resource code can use @clientx/*, allowed cross-resource imports (@/resource/client/*) and t().
4. Build hello
node scripts/clientx.mjs build --resources hello,client
Closure = [client, hello] (dependency first). Output:
dist/resource/client/** # client resource bundle (dependency)
dist/resource/hello/ # hello resource bundle
dist/resource/manifest.json # { resources: [{ id: hello, entry: /resource/hello/register.js, deps: [client] }] }
5. Assemble hello into an app
Option A: dev server
node scripts/clientx.mjs run --resources client,hello
Open http://localhost:3007; the hello resource is declared/activated with the closure.
Option B: assemble from the download package
Download resource-hello-example.tar.gz (the source generated by clientx create hello --deps client) and extract it:
tar xzf resource-hello-example.tar.gz # extracts hello/
cp -r hello/ <your-core>/src/resource/hello/
Then in the core repository:
cd <your-core>
npm install
node scripts/clientx.mjs build --resources client,hello
node scripts/clientx.mjs run --resources client,hello
6. Assemble the website
# Download core source → extract → npm install
node scripts/clientx.mjs build --resources client,sitex,portal # full website (includes client)
node scripts/clientx.mjs run --resources client,sitex,portal # local development
The portal resource provides the marketing home, downloads and changelog. Download artifacts are served from /downloads/ (clientx-core-source.tar.gz / clientx-core-prebuild.tar.gz / resource-<id>-source.tar.gz / resource-hello-example.tar.gz). The developer docs and help center are independent Astro static resources (docs / help) and do not enter the runtime resource closure.