Skip to content

从 Cell 3.x 迁移到 4.0 ​

本文面向已有的 Cell 3.x 项目,说明迁移到 4.0 要改什么。4.0 的工具链是重新设计的(设计见 design/architecture.md),不兼容 3.x 的命令、hook、配置表达式和产物目录,也没有兼容层,所以要按本文逐项修改。运行时 API(@Component、@Autowired、@Value、MVC、RPC、typeorm 等)不变,业务代码一般不用改。

0. 4.0 包含的能力 ​

能力状态
cell dev、cell build、cell info、cell preview、cell.config.ts、hooks、--json可用
node preset(Node.js HTTP 服务)可用,默认
cell check可用
cell mcp、cell dev 的 /_cell/mcp 端点、cell config get、cell docs可用
cloudflare preset 与 cell deploy(Cloudflare Workers)可用,见 @celljs/cli 的 README
云平台 preset 与 cell deploy(fc / scf / lambda / vercel)可用,见 @celljs/cli 的 README
--single、--compile 单文件与可执行文件可用
cell init 与项目模板(backend、fullstack)、cell agent sync可用
用环境变量覆盖任意配置项(CELL_BACKEND__* / CELL_FRONTEND__*)可用
进程内测试工具 createTestApp()(@celljs/cli)可用

1. 环境 ​

  • Node.js ^20.19.0 || >=22.12.0(Vite 8 的要求)。构建出的服务端 bundle 可在 Node.js 18 及以上运行(fc、scf 镜像里的 Node.js 较旧)。
  • 包管理器不限。仓库自身用 Bun 开发。

2. 依赖 ​

jsonc
{
  "dependencies": {
    "@celljs/core": "^4.0.0",
    "@celljs/express-adapter": "^4.0.0",   // 新增:提供 HTTP 入口
    "@celljs/mvc": "^4.0.0"
  },
  "devDependencies": {
    "@celljs/cli": "^4.0.0"                // 项目内安装,不再全局安装
  }
}
  • 加上 @celljs/express-adapter。 3.x 的 HTTP 入口由运行时插件隐式带进来;4.0 由 preset 按名字(node preset 用 http)从组件声明的 cell.entries 里选入口,这个入口由 @celljs/express-adapter 提供。缺少时 cell build 会给出警告,产物启动后不监听端口。
  • 删除 @celljs/cli-service、@celljs/cli-common、@celljs/cli-runtime、@celljs/dev-server、@celljs/frameworks、@celljs/testing 和 @celljs/*-plugin,以及只为 webpack 服务的依赖(各种 *-loader、mini-css-extract-plugin、babel 等)。
  • 用 Sass / Less / Stylus 时自己安装预处理器(如 sass),Vite 检测到就会使用。
  • 带安装脚本的原生依赖(better-sqlite3 等)要确保包管理器执行了它的安装脚本(Bun 需要列进 trustedDependencies),否则构建能过、运行时报找不到 .node 文件。

3. 命令 ​

3.x4.0
cell servecell dev:前后端同一个端口,前端 HMR,后端改文件自动重载,cell.yml / cell.config.ts / package.json 变化时自动重启
cell build -m prodcell build -m prod
运行 .cell/dist/backend/index.jscell preview,或直接 node .cell/output/server/index.mjs
cell deploy -m fccell deploy --preset fc(以及 scf、lambda、vercel、cloudflare;--dry-run 只给出计划)。3.x 的 cell.fc.yml 等平台配置改为 cell.config.ts 的 fc: { region, memory, ... },凭据从环境变量读取,不再读 ~/.cell 或 profile 文件
cell initcell init <template> <dir>:新模板 backend、fullstack(React + RPC),自带 AGENTS.md、.mcp.json;3.x 的模板不再提供
cell props / cell config / cell update / cell runtime删除;查看项目和配置用 cell info(--app-config 输出合并后的 cell.yml),单个配置项及其来源用新的 cell config get <key> --why

所有命令支持 --json:stdout 只输出一行带 schemaVersion 的结果,失败时退出码为 1(用法错误为 2)。完整说明见 @celljs/cli 的 README。

4. 产物 ​

3.x4.0
.cell/dist/backend/index.js(webpack,CommonJS).cell/output/server/index.mjs:一个文件,依赖全部内联(--format cjs 时为 index.cjs)
.cell/dist/frontend/.cell/output/public/
部署前再 npm install 外部依赖只有原生模块、build.external 和组件声明的可选依赖拷到 server/node_modules
—.cell/output/manifest.json:preset、格式、入口,供 cell preview 和部署器读取
—.cell/generated/:DI 装配代码、合并后的配置、项目模型,便于排查

.cell/output 可以整个拷到别处运行,不依赖项目的 node_modules(Dockerfile 见 examples/docker-app)。端口取 cell.server.port(默认 3000),监听地址取 cell.server.host(默认 127.0.0.1,容器里要设成 0.0.0.0),都可以用环境变量覆盖(见 5.3),cell preview --port 也可以改端口。3.x 的 SERVER__PORT / SERVER__HOST 不再生效,改为 CELL_BACKEND__SERVER__PORT / CELL_BACKEND__SERVER__HOST。

把 .cell 加进 .gitignore。

5. 配置文件 ​

4.0 把配置分成两类,职责不重叠:

文件内容
cell.yml、cell.<mode>.yml应用配置,即 @Value 读取的内容
cell.config.ts(可选)工具链配置:preset、targets、modules、build、vite、hooks

5.1 cell.yml 里要删掉的内容 ​

3.x 写法4.0
mode: [database]删除。mode 只来自命令行:cell build -m database,写进 package.json 的 build 脚本
targets: [backend]、entry: ...删除。目标默认是 backend,加上组件声明的目标(装了 @celljs/react 就会经 @celljs/widget 构建前端);要改就在 cell.config.ts 写 targets
webpack:、devServer:、webpackHooks、serveHooks删除。改 Vite 配置用 cell.config.ts 的 vite 字段
cell.webpack.filterWarningsPlugin(可选依赖)组件在 package.json 的 cell.optionalDependencies 里声明,解析不到时自动 external
cell-test.yml 这类 mode 文件只识别 cell.<mode>.yml(如 cell.test.yml),cell-<mode>.yml 要改名

cell build 自动带上 production mode,cell dev 带上 development,开发/生产差异写在 cell.production.yml / cell.development.yml。合并顺序:组件的 cell.yml(按依赖顺序)→ 项目 cell.yml → 各 mode 文件(按 -m 顺序)。对象深合并,数组整体替换。frontend: / backend: 分区保留,含义不变。

5.2 表达式 ​

  • 只保留 ${...} 插值,上下文是 { ...配置, env }:"${env.DB_HOST ?: 'localhost'}"、"${cell.server.path}"。
  • 后端在运行时求值:同一个产物可以部署到多个环境,密钥不会写进产物。前端在构建时求值。
  • 删除了 onTarget、cliContext、currentMode 等只在构建时存在的上下文。按目标区分写在 frontend / backend 分区,按环境区分写在 mode 文件。
  • 含 ?: 的 YAML 值必须加引号。
  • 密钥只能放在 backend: 分区下。 顶层公共键和 frontend: 分区的值会原样打进前端产物,浏览器能下载到;前端配置里写 ${env.X},这个环境变量的值也会进前端产物。
  • 表达式的结果不会再被求值(4.1.0 起):环境变量的值里即使带 ${...} 也按原文使用,前端构建时同样如此。配置之间的引用照常解析,顺序不限;需要再求值时显式写 ${env.X | eval}。

5.3 用环境变量覆盖配置项 ​

不改 cell.yml、不加 ${env.X} 插值,也能用环境变量改任意已有的配置项:

bash
CELL_BACKEND__SERVER__PORT=8080 node server/index.mjs            # cell.server.port
CELL_BACKEND__SERVER__HOST=0.0.0.0                                # cell.server.host
CELL_BACKEND__TYPEORM__ORM_CONFIG__0__HOST=db.internal            # cell.typeorm.ormConfig[0].host
CELL_FRONTEND__API_BASE=https://api.example.com cell build        # 前端的 cell.apiBase,构建时生效
  • 前缀决定目标:CELL_BACKEND__ 由服务端在启动时应用(同一个产物在不同环境用不同的值),CELL_FRONTEND__ 在 cell build 时应用到前端配置。

  • 其余部分用 __ 分隔层级。第一段先在顶层找,找不到再到 cell 下找:顶层通常没有 server,所以 CELL_BACKEND__SERVER__PORT 就是 cell.server.port;顶层键(如 app)直接写 CELL_BACKEND__APP__TITLE。CELL_BACKEND__CELL__… 总是指 cell 下的键,项目顶层有与之同名的键时用它(cell info 会提示)。匹配时不区分大小写,也忽略 _ 和 -(ORM_CONFIG 对应 ormConfig,SERVE_STATIC 对应 serve-static),数组元素用下标。

  • 只能覆盖已经存在的键(组件或项目的 cell*.yml 里有的)。新键先在 cell.yml 里写上默认值。

  • 值按 cell.yml 里原值的类型转换:

    原值接受的写法
    数字JSON 写法的有限数:8080、-1、0.5、1e3。Infinity、0x10、+1、08、空串、首尾空格、超过 2^53 的整数都报错
    布尔true / false,不区分大小写;1、yes 报错
    对象、数组JSON,且种类要一致(数组只能换成数组)
    null是 JSON 就按 JSON(5、true、null、{...}),否则当作文本
    字符串原样使用

    值如果整体是一个 ${...} 表达式,不论原值是什么类型都直接保留,和写在 cell.yml 里一样参与求值,类型由求值结果决定。例如平台通过 PORT 给端口时写 CELL_BACKEND__SERVER__PORT='${env.PORT}'。表达式外还有其他文字(80${env.X})时按上表处理。

    原值本身是 ${...} 表达式时,它在 cell.yml 里是字符串,覆盖值也就按文本处理:false 会是字符串 "false"。要覆盖成布尔值或数字,写成表达式:'${false}'、'${5432}'。

  • @celljs/typeorm 的默认数据库配置读取 DB_TYPE、DB_HOST、DB_PORT、DB_SYNC(只有 true 才开启表结构同步)、DB_DATABASE,并转换成正确的类型。项目自己在 cell.yml 里写了 ormConfig 时用 CELL_BACKEND__TYPEORM__ORM_CONFIG__0__* 覆盖。

  • 数组元素用规范下标(0、1、12),00、01 视为不存在的键。

  • 一个键只能由一个变量设置:两个变量指向同一个键(SERVER__PORT 和 CELL__SERVER__PORT),或一个指向某个键、另一个指向它里面的键(整体替换 …ORM_CONFIG 又改 …ORM_CONFIG__0__HOST),这些变量都不生效并报出来,结果与变量顺序无关。要替换整个对象或数组又改其中一项,直接写进 JSON。

  • 后端还有 paths.public:前端产物目录,默认 public,相对 .cell/output(服务端启动时解析为绝对路径;serve-static 的 root: ${paths.public} 引用它)。可以在 cell.yml 里写,也可以用 CELL_BACKEND__PATHS__PUBLIC=/srv/www 覆盖。

  • 覆盖发生在 ${...} 求值之前,所以 endpoint: http://localhost:${cell.server.port} 会用覆盖后的端口;值里的 ${...} 也会像写在 cell.yml 里一样求值。

  • 找不到键或类型不符的变量不会生效:服务端启动时在 stderr 打印 [cell] CELL_BACKEND__… is ignored: …,cell build 对前端变量给出警告,cell info 两种都检查,并在 envOverrides 里列出每个变量对应的配置路径(不输出值)。

5.4 cell.config.ts ​

ts
import { defineConfig } from '@celljs/cli';

export default defineConfig({
    preset: 'node',                                   // 默认 node;也可 --preset、CELL_PRESET
    targets: ['frontend', 'backend'],                 // 一般不用写
    modules: ['!@celljs/serve-static'],               // 去掉某个组件
    build: { format: 'esm', external: ['sharp'], sourcemap: true },
    vite: { resolve: { alias: { '~': '/src' } } },    // 合并进 Vite 配置
    hooks: { 'build:before': 'prisma generate' },
});

配置在加载后逐项校验,写错字段报 CELL_CONFIG_INVALID,并给出字段路径和"你是不是要写"。应用配置不能写进 cell.config.ts。

6. hook ​

3.x 的约定文件 hook(src/hooks/webpack.ts、serve.ts、deploy.ts、build.ts 等)全部失效:

  • 构建前后要跑命令:写在 cell.config.ts 的 hooks 里。命令在项目根目录执行,PATH 里有项目的 node_modules/.bin,环境变量带 CELL_EVENT、CELL_PRESET、CELL_MODE、CELL_OUT_DIR。build:before 里命令失败会取消构建。可订阅的事件:modules:done、config:resolved、build:before、output:done、close,cell dev 还有 dev:ready、dev:reload,cell deploy 有 deploy:before、deploy:plan、deploy:done。
  • 改 webpack 配置:改为 cell.config.ts 的 vite 字段,或写 Vite 插件。
  • 需要代码逻辑的扩展(改产物、注册 preset 等):写 toolchain 模块(工厂函数,见 design/modules.md)。本地模块写在 cell.config.ts 的 modules 里(TypeScript 不用预编译),能力清单见 @celljs/cli 的 README。

例如 prisma 项目(见 examples/prisma-app):

ts
hooks: {
    'build:before': 'prisma generate',
    // 内联后的 prisma client 在 server/index.mjs 旁边查找 schema 和查询引擎
    'output:done': 'cp prisma-client/schema.prisma prisma-client/libquery_engine-* "$CELL_OUT_DIR/server/"',
}

7. 测试 ​

@celljs/testing(Test.createHttpServer()、Test.createContainer())已删除,改用 @celljs/cli 的 createTestApp():在测试进程里从源码启动后端(DI 容器和 HTTP 处理链),不构建、不监听端口,cell.config.ts、toolchain 模块和 preset 的入口与 cell dev 一致,mode 为 test(读 cell.test.yml)。

ts
import { afterAll, beforeAll, expect, it } from 'bun:test';
import { createTestApp, type TestApp } from '@celljs/cli';

let app: TestApp;
beforeAll(async () => (app = await createTestApp()));   // 项目根目录默认取当前目录,也可传 root
afterAll(() => app.close());

it('lists users', async () => {
    const res = await app.fetch('/users');                // 标准 Response;init 同 fetch
    expect(await res.json()).toEqual([]);
});
  • app.fetch(path, init) 支持方法、头、请求体,响应保留状态码、重定向和多个 Set-Cookie(不跟随重定向)。
  • app.container / app.get(id) 取 DI 容器里的对象。
  • 只测 DI 和服务、不需要 HTTP 时,也可以用 ApplicationFactory.create({}, AppModule),见 examples/standalone-app/test。
  • @celljs/cli 只有 ES 模块。CommonJS 项目(package.json 没有 "type": "module")在 TypeScript 里导入它,要把 tsconfig 的 module / moduleResolution 设为 nodenext(TypeScript ≥ 5.8,按 Node 22 的 require(esm) 处理),否则报 TS1479。
  • 原来用 supertest 起服务的测试改成 app.fetch,见 examples/backend-app/test。

8. 组件作者 ​

组件的全部声明都在 package.json 的 cell 字段里,cell.yml 只放默认的应用配置:

jsonc
{
  "cell": {
    "modules": { "backend": ["lib/node/module"] },          // DI 运行时模块(frontend / backend)
    "staticModules": { "frontend": ["lib/browser/static-module"] },
    "targets": ["frontend"],                                  // 装上本组件就要构建的目标(只写额外需要的)
    "entries": { "http": "lib/node/http-application-entry" }, // 具名后端入口,由 preset 按名字选用
    "optionalDependencies": ["pg-native"],                    // 可能没装的依赖,解析不到时 external
    "toolchain": "lib/cell-module"                            // toolchain 模块
  }
}
  • 删除组件里的 src/hooks、cell.remote.yml,以及 cell.yml 里的 targets、entry、webpack。
  • 框架包以 ES 模块构建,打包时按 exports 的 import 条件解析到 esm/。

9. 迁移后的检查清单 ​

  1. cell info 的 warnings 为空(部署环境里的 CELL_BACKEND__* 变量也可以带上一起检查),targets、components 与预期一致(例如 mode 拼错会在这里提示)。
  2. cell build --json 输出 "ok": true,warnings 为空,entry 是 @celljs/express-adapter/lib/node/http-application-entry(或你选的入口)。
  3. cell preview 后接口和页面正常。
  4. 把 .cell/output 拷到项目外的空目录,用 node server/index.mjs 运行,确认不依赖项目的 node_modules。
  5. 用到原生模块或运行时读文件的库(prisma、sqlite 驱动等)时,确认需要的文件在 server/ 或 server/node_modules 里。

10. 4.0.0 之后的变化(尚未发布,main 上已合入) ​

这些改动不提供兼容层,升级时逐项检查:

  • 删除的包:@celljs/config、@celljs/rpc-core、@celljs/ai-openai、@celljs/ai-anthropic、@celljs/ai-ollama、@celljs/ai-deepseek、@celljs/shell、@celljs/grommet、@celljs/vue。模型统一用 @celljs/ai-pi(模型名 pi:<provider>/<model>);前端用 @celljs/react。
  • @celljs/security 不再内置用户。 原来依赖内置 admin 账号的应用(这个账号的哈希从 2022 年起实际就已无法登录)要自己配置:在 cell.yml 里写 cell.security.username / password(PasswordEncoder 生成的哈希),或者绑定自己的 UserService,见 @celljs/security。没有用户来源时启动会打警告。
  • 密码哈希升级不可回滚。 4.0.x 存下的哈希仍能校验;UserService 实现了 updatePassword() 时,用户登录成功后哈希会换成新格式(pbkdf2-sha256$…)。换过之后再回退到 4.0.x,这个用户就登录不了了。
  • HTTP Basic 现在能正常认证(4.0.x 中所有密码都校验失败),默认仍关闭。它每个请求都算一次 PBKDF2(约 45 ms),只适合低频的机器对机器调用。
  • OpenID Connect:ID Token 必须带回授权请求里的 nonce,否则以 invalid_nonce 拒绝;同一个授权响应只接受一次。
  • 访问日志默认不输出:每个请求的 starting / ending 日志改为 debug 级别。要看就设 cell.logger.level: debug(或 CELL_BACKEND__CELL__LOGGER__LEVEL=debug)。
  • mysql2 需要自己安装:它改为 @celljs/typeorm 的可选 peer 依赖,用哪个数据库就装哪个驱动;缺少时启动报 CELL_TYPEORM_DRIVER_MISSING,并给出安装命令。