组装
本文介绍如何把 core 与资源组装成网站/应用:开发用 clientx run,生产用 clientx build 后由 core 运行时从 manifest.json 加载(目录即真相),并且全程只有一个 React 实例。
1. 组装成网站/应用
「组装」= 选一组资源(闭包)+ 构建产物 + 由 core 运行时把它们装起来。因为资源是解耦的,同一个 core 可以组装出不同形态:
# 形态一:完整应用(消息/用户/AI/连接/加密)
node scripts/clientx.mjs build --resources client,userx,msgx,agix,conn,e2ee
# 形态二:官网(应用壳 + 站点框架 + 官网站点)
node scripts/clientx.mjs build --resources client,sitex,portal
# 形态三:只出一个资源组合(资源级独立成立)
node scripts/clientx.mjs build --resources sitex
node scripts/clientx.mjs build --resources portal
产物统一落在 dist/。部署时把 dist/ 交给任意静态服务器即可(见「安装」页方式 B)。
2. clientx run:开发服务器
node scripts/clientx.mjs run # 默认组合: client+userx+msgx+agix+conn+e2ee
node scripts/clientx.mjs run --resources sitex # 只声明/激活 sitex 闭包(VITE_RESOURCES=sitex)
node scripts/clientx.mjs run --resources client,sitex,portal # 本地调试官网
--resources 会设置 VITE_RESOURCES,dev 的注册路径(src/resource/register.ts 的 glob 驱动)只 declare/activate 指定闭包;未设时回退默认组合。clientx run 直连本地 vite(透传 stdio 与信号,Ctrl+C 干净关停),默认端口 3007。
dev 与 prod 行为对齐:declare 同步、轻量;activate 异步、按依赖序;用户关闭的资源在 import/declare/activate 之前过滤(不下载、不激活)。
3. core 运行时从 manifest 加载(目录即真相)
生产 boot 由 src/core-loader.ts 驱动,core 里不出现任何具体资源名 —— 全部来自 dist/resource/manifest.json:
关键步骤:
- 拉 manifest:
GET /resource/manifest.json得到{ resources: [{ id, entry, deps }] }。目录即真相 ——dist/resource/里有什么,应用就装什么;改 manifest(或直接增删资源目录)即改变组装结果。 - 注入 importmap:
index.html已内联(构建期注入),loader 幂等跳过。 - 预载 vendor:
shared-vendor.js+vendor-i18n.js,保证资源模块解析到同一实例。 - 过滤禁用:持久化覆盖(用户关闭)在 import/declare/activate 之前过滤。
- 拓扑加载:按
deps拓扑序import('/resource/<id>/register.js'),先依赖后依赖者。 - declare → activate:全部 declare 后
applyPersistedOverrides(),再依依赖序逐个 activate。
4. 单一 React 实例
组装的核心保证是全应用只有一个 React(及其配套)实例,由 importmap 实现。以下为 --resources client,sitex,portal 闭包生成 importmap 的示意摘录(真实产物 64 个条目,此处只列代表性键;@/resource/<id>/ 结尾是目录前缀兜底):
{
"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/"
}
}
react家族精确映射到shared-vendor.js→ core 与每个资源共享同一个 React context / router / query client / zustand store / i18next。@clientx/*精确子路径映射到vendor/*→ 内核注册表(resourceRegistry/slotRegistry/featureRegistry/routeRegistry)是全局单例,资源之间借此互联。- 跨资源子路径精确映射到
/resource/<dep>/...→ 一个资源 import 另一个资源时得到同一份模块(如msgximport@/resource/client/lib/queries)。
5. 组合清单速查
| 组合 | 命令 | 产物 |
|---|---|---|
| 默认应用 | clientx build | client+userx+msgx+agix+conn+e2ee |
| 官网 | clientx build --resources client,sitex,portal | 应用壳 + 站点框架 + 官网站点 |
| 资源独立 | clientx build --resources portal | 闭包 [sitex, portal],无需 client |
| 只出框架 | clientx build --resources sitex | 仅 sitex |
6. 下一步
- 「案例」页:一个
hello资源从create到打包、再组装进应用的完整流程。