项目结构与配置
目录结构
Cell 项目就是一个 npm 包。源码遵循少量约定,不需要在别处登记。
| 路径 | 内容 |
|---|---|
src/node/ | 后端代码:控制器、服务、中间件 |
src/browser/ | 前端代码(有前端时) |
src/common/ | 前后端都要导入的代码,例如 RPC 接口 |
src/node/module.ts | 后端 DI 模块 |
src/browser/module.ts | 前端 DI 模块 |
cell.yml、cell.<mode>.yml | 应用配置 |
cell.config.ts(可选) | 工具链配置 |
.cell/ | 生成的文件:generated/(DI 装配、合并后的配置)和 output/(构建产物);加入 .gitignore |
DI 模块
DI 模块是一个文件:导入声明了组件的文件,并导出 autoBind():
// src/node/module.ts
import './todo-controller';
import './todo-service';
import { autoBind } from '@celljs/core';
export default autoBind();autoBind() 登记此前定义的所有带装饰器的类(@Component、@Controller、@Rpc……)。没有被模块导入的文件永远不会被加载:如果 cell check 对你写的类报 CELL_DI_NO_BINDING,在这里导入它的文件。
工具链按以下顺序查找项目的模块文件,存在的都会加载:
| 目标 | 文件(在 src/ 下,扩展名 .ts、.tsx、.js 或 .mjs) |
|---|---|
| backend | common/module、node/backend-module、node/module、module、backend-module |
| frontend | common/module、browser/frontend-module、browser/module、module、frontend-module |
src/common/module 和 src/module 在前后端都会加载。包也可以在 package.json 的 cell.modules 字段里声明其他路径。
组件
package.json 带 cell 字段的依赖都是组件:安装它,就会把它的 DI 模块和 cell.yml 加进应用。装上 @celljs/security 就启用登录,装上 @celljs/serve-static 就会返回前端文件。要去掉某个依赖带进来的组件,在 cell.config.ts 里写:
export default defineConfig({ modules: ['!@celljs/serve-static'] });npx cell info 列出 Cell 解析出的组件、它们的 DI 模块、入口和 preset;--json 以数据形式给出同样的内容。
应用配置:cell.yml
# yaml-language-server: $schema=./.cell/schema/cell.yml.json
server:
port: ${env.PORT ?? 8080} # 组件的配置项直接写在顶层
typeorm:
ormConfig:
- type: mysql
password: ${env.DB_PASSWORD}
app: # 你自己的配置写在 app 下
greeting: Hello
public: # 只有 app.public 会进入浏览器
apiBase: /api- 顶层键就是配置键,没有
cell:、frontend:、backend:这类分区。用属性或构造器参数上的@Value('server.port')、@Value('app.greeting')读取。 - 每个顶层键都有归属:组件在自己的
cell.schema.json里声明它拥有的键(server属于@celljs/web,security属于@celljs/security……)。写在这些键下就是覆盖组件的默认值。 - 你自己的配置放在
app下,框架和组件都不会占用它,升级也不会撞名。 - 写错键会报错:没有归属的顶层键、组件没有的选项、类型不对,
cell build/cell dev/cell check都会报CELL_CONFIG_INVALID,带文件、行号和"你是不是要写 …"。 cell dev/cell build/cell check会生成.cell/schema/cell.yml.json;在cell.yml第一行加上面的yaml-language-server注释,编辑器(VS Code 的 YAML 插件等)就有补全和校验。
前端能看到什么
配置默认只在服务端。 浏览器只拿到组件标记为 public 的键(如 server.path、rpc、react、security 中页面跳转相关的项)和 app.public。密钥写在哪里都不会进前端产物,除非你把它放进 app.public。
- 前端的值在构建时求值,表达式可以引用服务端的键,只有结果进产物。
npx cell config get <key> --target frontend查看前端拿到的值;查询服务端独有的键会提示server-only。- 项目也可以写自己的
cell.schema.json,描述app下的键(类型、说明、"x-cell-public": true)。
合并顺序
配置由这些文件按顺序合并:各组件的 cell.yml(按依赖顺序)、项目的 cell.yml、各个生效 mode 的 cell.<mode>.yml。后面的覆盖前面的;对象深合并,数组整体替换。
mode
| 命令 | mode |
|---|---|
cell dev | development |
cell build | production |
createTestApp() | test |
用 -m <mode>(可重复)加更多:cell build -m staging 还会读取 cell.staging.yml。没有任何文件的 mode 会被 cell check 报出(CELL_MODE_UNKNOWN)。
表达式
${...} 的求值上下文是配置本身加上 env(进程环境变量):
server:
endpoint: http://localhost:${server.port} # 引用其他键
cloud:
region: ${env.REGION ?? 'eu-west-1'} # 带默认值的环境变量
typeorm:
ormConfig:
- port: ${env.DB_PORT ?? 5432} # schema 类型是整数:DB_PORT=6543 得到数字 6543
synchronize: ${env.DB_SYNC ?? false} # schema 类型是布尔:只接受 true / false- 默认值用
??,与 JavaScript 相同;空的环境变量视为未设置。不需要给值加引号。 - 按 schema 类型转换:键的类型是数字或布尔时,表达式得到的文本会转换成该类型,转换失败报错并指出键。没有 schema 的键(如
app下的)保留表达式结果的类型。 - 后端的值在服务端启动时求值,所以同一个产物可以在多个环境运行,密钥也不会写进产物。前端的值在构建时求值。
- 表达式嵌在其他文字里时得到字符串;表达式的结果不会再次求值:环境变量里即使有
${...}也按原文使用。
环境变量
任何已有的键都可以不改文件、用 CELL_ 开头的环境变量覆盖:
CELL_SERVER__PORT=8080 node .cell/output/server/index.mjs # server.port,启动时生效
CELL_SERVER__HOST=0.0.0.0 # 监听所有网卡(容器里)
CELL_APP__PUBLIC__API_BASE=https://api.example.com npx cell build # 前端的 app.public.apiBase,构建时生效- 前缀后只有一个下划线,层级之间用
__(CELL_SERVER__PORT)。CELL_PRESET、CELL_MODE、CELL_EVENT、CELL_OUT_DIR、CELL_DEBUG是工具链自己的变量,不当作配置;只有一段、又不是顶层键的变量(如别的工具的CELL_REGION)直接忽略;带__却找不到键的会报告,写成旧的CELL__…/CELL_BACKEND__…时提示正确写法。 __分隔层级,匹配时不区分大小写,并忽略_和-(ORM_CONFIG对应ormConfig);数组元素用下标。- 同一组变量对前后端都生效:服务端启动时应用;
cell build应用后再挑出 public 键进前端。 - 只能覆盖已经存在的键;新键先在
cell.yml里写上默认值。 - 值按原值(或 schema)的类型转换:数字、布尔,对象和数组用 JSON。整体是一个
${...}的值会保留并求值:CELL_SERVER__PORT='${env.PORT}'。 - 找不到键或类型不符的变量不生效并给出警告;
cell check和cell info会报出它们。
查看值从哪里来
npx cell config get server.port --why
# server.port (backend) = 8080
# server-only, owned by @celljs/web
# Port the server listens on. Override with CELL_SERVER__PORT.
# node_modules/@celljs/web/cell.yml: 3000
# cell.yml: 8080框架的常用配置项:
| 键 | 默认值 | 所在包 |
|---|---|---|
server.port / server.host | 3000 / 127.0.0.1 | @celljs/web |
server.path | / | @celljs/web(所有路由的前缀,前端可见) |
web.body.limit | 100kb | @celljs/web |
web.cors | 允许所有来源(*) | @celljs/web |
web.session / web.cookies | 见 @celljs/web 的 README | @celljs/web |
html.title / html.rootId | Cell Framework / cell-root | @celljs/core(生成的 HTML 页面) |
logger.level | info | @celljs/core |
rpc.path | api | @celljs/rpc |
serveStatic.spa | true | @celljs/serve-static |
各包的 README 列出了自己的配置项;完整的键、类型和说明在 .cell/schema/cell.yml.json。设计说明见 docs/design/config.md。
工具链配置:cell.config.ts
可选,只用于工具链。应用配置不写在这里。
import { defineConfig } from '@celljs/cli';
export default defineConfig({
preset: 'cloudflare', // 默认 node
modules: ['!@celljs/serve-static'], // 去掉一个组件
build: { external: ['sharp'], sourcemap: true },
vite: { resolve: { alias: { '~': '/src' } } }, // 合并进 Vite 配置
hooks: { 'build:before': 'prisma generate' }, // 在工具链事件上执行的 shell 命令
fc: { region: 'cn-shanghai', memory: 512 }, // preset 的选项,写在与 preset 同名的字段
});未知字段会报错并给出最接近的合法名字(CELL_CONFIG_INVALID、CELL_CONFIG_UNKNOWN_OPTION)。完整说明(包括添加 Vite 插件、检查项、MCP 工具或产物文件的 toolchain 模块)见 CLI 文档。
生成的文件
cell dev 和 cell build 把生成的代码写到 .cell/generated/:每个目标加载的 DI 模块、合并后的配置和项目模型。需要确切知道容器里装了什么时就读这些文件;不要修改它们。