打包

本文介绍 clientx build 的构建流程、dist/ 产物结构,以及单资源 vs 组合(依赖闭包)两种打包方式。

1. clientx build

CLI 入口:

node scripts/clientx.mjs build                    # 默认组合 client+userx+msgx+agix+conn+e2ee
node scripts/clientx.mjs build --resources sitex  # 只打 sitex(闭包 = sitex)
node scripts/clientx.mjs build --resources hello,client   # 自研资源 + 其依赖
node scripts/clientx.mjs build --no-clean         # 不清空 dist(增量;默认每次全新构建保证确定性)
node scripts/clientx.mjs --help                   # 查看帮助

--resources 给出的是你想要启用的资源 id 列表;构建器先做传递依赖闭包解析(resolve-closure.mjs),把依赖一起带上,按拓扑序(依赖先构建)逐个产出。

2. 构建流程

五步编排(scripts/clientx.mjs 逐个子构建器执行,任一步非零退出码立即失败):

  1. shared-vendor → dist/assets/shared-vendor.js:react 家族(react / react-dom / react-router / react-query / zustand / sonner / next-themes)平铺打包,保证全应用单实例。
  2. vendor-i18n → dist/assets/vendor-i18n.js:i18next + react-i18next 单实例(i18next 的 default 与 React 冲突,单独一份)。
  3. vendor-packages → dist/vendor/{runtime,common}/**:@clientx 两包以 preserveModules 逐模块产出,供 importmap 精确子路径解析。
  4. build-resource → dist/resource/<id>/**:闭包内每资源独立构建,依赖资源先构建;资源代码保持 bare import(react / @clientx/... / @/resource/<dep>/...),由 importmap 解析。
  5. build-app → dist/index.html + dist/resource/{manifest,importmap}.json:构建 core-app(框架 external 保持 bare),生成闭包清单与 importmap,并把 importmap 内联进 index.html 的 <head>。

3. 产物结构

dist/
├── index.html                 # core-app 入口(内联 <script type="importmap">)
├── assets/
│   ├── shared-vendor.js       # react 家族单实例
│   ├── vendor-i18n.js         # i18n 家族单实例
│   └── ...                    # core-app 其余 chunk
├── vendor/
│   ├── runtime/**             # @clientx/runtime 逐模块产物
│   └── common/**              # @clientx/common 逐模块产物
├── resource/
│   ├── manifest.json          # 闭包清单:{ resources: [{ id, entry, deps }] }
│   ├── importmap.json         # bare specifier → URL 映射
│   ├── <id>/register.js       # 每资源入口(loader 动态 import)
│   ├── <id>/**                # 资源内部模块
│   └── <id>/i18n/zh.json      # 语言包
└── downloads/**               # 仅 portal 在闭包时:拷贝 resource/portal/downloads

manifest.json 是运行时加载的唯一真相(见「组装」页):

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

注意:manifest 里的 deps 是资源 id(构建期闭包解析的结果),不是 Resource.dependsOn 的类型/kind(如 site)。portal 的声明依赖 dependsOn: ['site'] 是 kind 级约束;构建期闭包 deps 由源码 import(@/resource/sitex/...)推导,实际是 ["sitex"]。

importmap.json 是单实例保证的关键:react 家族精确映射到 shared-vendor.js,@clientx/runtime|common 的每个子路径映射到 vendor/<pkg>/<sub>.js,跨资源子路径映射到 /resource/<dep>/<sub>.js。

4. 单资源 vs 组合(依赖闭包)

单资源

node scripts/clientx.mjs build --resources sitex

闭包 = sitex(+ 它 import 的 @clientx 包,那些进 vendor/)。dist/resource/ 只有 sitex/。适合「只想要一个可插拔单元」或验证资源独立成立。

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

自研资源 hello 声明依赖 client,闭包 = [client, hello](依赖先构建)。缺依赖的组合会显式失败:build-app 校验 App 帧静态依赖的资源必须在闭包内(sitex-only 等组合 fail loud,不静默产坏 bundle)。

组合(默认应用 / 官网)

node scripts/clientx.mjs build                      # 默认应用: client+userx+msgx+agix+conn+e2ee
node scripts/clientx.mjs build --resources client,sitex,portal   # 官网: 应用壳 + 站点框架 + 官网站点

要点:

5. 下载产物

scripts/p3/package-downloads.mjs 把可分发产物打到 resource/portal/downloads/(构建期拷贝进 dist/downloads/,经 /downloads/ 下载):

node scripts/p3/package-downloads.mjs

6. 下一步