首页
/ Ghost 单仓库(Monorepo)架构导航:pnpm Workspace + Nx 驱动的全栈发布流水线

Ghost 单仓库(Monorepo)架构导航:pnpm Workspace + Nx 驱动的全栈发布流水线

2026-09-07 22:17:04作者:董宙帆

Ghost(README)是当前采用 pnpm workspace + Nx 组织的单仓库(Monorepo):pnpm 负责把 apps/ghost/core/koenig/packages/ 等子项目链接成一份本地依赖图,Nx 则基于这份依赖图调度 build、lint、test、dev 等任务并缓存产物。阅读本指南后,你将掌握:在仓库中定位任一模块(前端应用 / 服务端 Core / 编辑器 / 共享库)的方法;workspace:catalog: 依赖声明的区别与用法;以及"开发期直接跑 TypeScript 源码、生产期用编译产物"这套双轨构建机制是如何实现的。

Monorepo 概览:pnpm 提供依赖图,Nx 驱动任务

根 package.json 把仓库声明为名为 ghost-monorepo 的私有根包("private": true),并锁定包管理器与运行环境:

  • "packageManager": "pnpm@12.2.1+...,且通过 preinstall 钩子执行 scripts/enforce-package-manager.js 强制使用 pnpm;
  • "engines": { "node": "^22.23.1 || ^24.20.0" } 约束 Node 版本。

pnpm-workspace.yaml 是"哪些目录是工作区"的唯一事实来源,它通过 glob 声明了全部工作区:

packages:
  - 'ghost/*'
  - 'apps/*'
  - 'e2e'
  - 'koenig/*'
  - 'packages/**'
  - '!packages/_template'   # 新包模板本身不参与工作区
  - 'configs/*'
  - 'scripts'

文件里还包含一批现代 pnpm 加固配置:strictDepBuilds: truecatalogMode: strict、通过 allowBuilds 对原生依赖构建做版本级白名单(如 better-sqlite3@12.11.1sharp@0.35.3)、通过 overrides 统一收敛传递依赖版本(典型例子是把 knex-migrator>knex 钉回 2.4.2,与 ghost/core 使用的 knex 主版本对齐)。这些配置共同决定了依赖解析的安全边界。

nx.json 定义了 Nx 的运行行为:parallel: 4 控制并行度、cacheDirectory: ".nxcache" 存放缓存、namedInputs 中的 sharedGlobals 把根级配置文件(pnpm-workspace.yamlpnpm-lock.yamlghost/tsconfig.json 等)纳入每个任务的输入指纹。修改某个应用或包之前,先阅读它旁边(同目录)的 README,这是仓库内约定俗成的守则。

顶层目录总览

目录 内容
apps/ Admin 应用、面向浏览器的公开应用与前端库
ghost/core/ Ghost 服务端、前端渲染、数据库迁移与服务端测试
koenig/ Koenig 编辑器,以及内容存取 / 转换 / 渲染相关的 kg-*
packages/ 共享库、schema、翻译、测试数据与适配器(adapter)契约
configs/ 共享的 ESLint、TypeScript、Vite 与 Vitest 配置包
e2e/ 覆盖完整 Admin 与公开站点旅程的 Playwright 测试
docker/ 本地开发与 CI 用容器及配套服务
scripts/ 仓库初始化、校验、构建与发布工具链

前端应用布局:apps/ 下的三类工程

apps/ 下并存着定位截然不同的前端工程,改动前必须分清它们属于哪一类:

公开应用的运行模型与普通 SPA 不同:它们把浏览器产物打包成 UMD(例如 apps/portal/package.jsonfiles 只包含 umd/LICENSEREADME.md),页面通过 script 标签加载,运行时配置来自 DOM 的 data-attributes。Ghost Core 则通过主题助手渲染这些集成点——主题里常见的 {{ghost_head}}{{comments}} 就在服务端完成拼装(以 ghost_head.js 为例)。

Admin 的整合边界apps/admin/README.md 有专门说明:React 侧通过 admin-x-framework 提供 API hooks 与路由,Shade 提供应用外壳与设计系统;已迁移的路由渲染 React 组件,未迁移的路由回退到旧 Ember Admin,两者共享同一 UI 空间——这就是所谓的 Ember Bridge 渐进迁移策略。

Ghost Core:服务端与渲染核心

ghost/core/ 是名为 ghost 的主包,最常见的子路径如下:

路径 内容
ghost/core/core/server/ API、模型、服务、数据访问与服务端启动逻辑
ghost/core/core/frontend/ 主题渲染、helpers、中间件与公共静态资源
ghost/core/core/shared/ 跨服务端边界共享的配置与代码
ghost/core/content/ 默认主题、适配器、设置、图片与运行时内容
ghost/core/test/ 单元、集成与服务端 E2E 测试

新增 Core 服务时必须遵守的约定记录在 services README:服务的构建由 boot 流程负责——需要初始化逻辑的新服务必须暴露显式的 init(),并在 boot.js 的相应启动阶段被调用,不能让"第一个请求"去懒构造服务;持有资源的服务要把关闭/清理挂进 boot 生命周期。编写新服务默认使用 TypeScript + 具名导出,只有在 require() 边界确实需要时才保留薄薄的 CommonJS 包装。

Admin 构建产物在发布时会复制进 ghost/core/core/built/admin/ 供 Ghost Core 托管(源码目录中通常不存在该目录,属生成物)。因此请把 built/build/dist/umd/ 一律视为生成输出,除非旁边的 README 明确说明例外。

Koenig:编辑器与内容转换管线的落位

koenig/ 是从原独立仓库整体迁入本 Monorepo(保留完整 git 历史)的内容编辑与渲染家族。其中 Lexical 编辑器 UI(koenig-lexical) 在构建期被打包进 Admin;而 kg-* 包被 Ghost Core 以本地工作区依赖的方式消费,用于服务端内容转换与渲染。整个目录下的依赖全部通过 workspace: 规格解析——dev、CI 与发布归档都不会从 npm 安装这些包。完整的包地图与开发命令见 koenig/README.md

其中 kg-default-nodes 是所有卡片(card)的渲染唯一事实来源:编辑器与服务端都经由它渲染 HTML,因此它必须保持"浏览器安全"(会在浏览器和 Node 两端同时运行)。开发编辑器时推荐两种模式:在 koenig/koenig-lexical 内跑 pnpm dev 进入独立 demo(http://localhost:5173,最快反馈);或在仓库根跑 pnpm dev:lexical 启动完整 Ghost 开发环境 + 编辑器重建监听器,让 Admin 直接加载本地编辑器构建。

共享 packages 与 configs:内部包的"黄金路径"

packages/ 承载被 Ghost Core 与各应用共享的库。packages/_template 是新增内部包的起点模板,且已被 pnpm-workspace.yaml 显式排除在工作区之外(避免它被当作正式包参与构建)。新增或现代化一个内部包之前必读 packages/README.md,其中定义了"黄金路径"契约:

  • 包名使用 @tryghost/<name>"version": "0.0.0""private": true
  • 声明 "ghostPackage": {"goldenPath": "compliant"},状态包括 compliant(机械校验通过)、migration(历史导入过渡期)与 exempt(长期豁免,如纯测试包),后两者必须写明 reason
  • 纯 TypeScript ESM,源码在 src/**/*.ts,测试在 test/**/*.ts,编译产物输出到 build/
  • 每个入口导出按 sourcetypesdefault 顺序声明(其作用见下文"源码与生产构建");
  • files 只含 build,不发布独立 npm 版本。

packages/adapters 下的 cache-basejobs-baseredirects-basescheduling-basesso-basestorage-base 等基础包,定义了存储、调度、重定向、缓存等可替换服务的接口契约,是 Ghost 依赖注入/可插拔设计在 monorepo 层面的体现。

configs/ 则把共享配置做成按包名依赖的内部包,而不是让各工程各自复制一份。其中 configs/eslint/README.md 说明了两个核心工厂的用法:@internal/cfg-eslint 提供 nodeLibConfig() 给 Node 库,@internal/cfg-eslint-react 提供 reactAppConfig() 给 React 应用;Ghost Core、Ember Admin、Admin Toolbar 因规则集特殊而保留独立配置,但仍可直接 import 共享的规则原子(如 correctnessRulesnodeLibRules)。

工作区依赖:workspace:catalog: 双轨管理

monorepo 内部包之间用 workspace: 版本号声明本地依赖(例如 portal 的 @tryghost/i18n: "workspace:*")。pnpm 会把这些包直接从当前 checkout 链接进来,开发与 CI 不会额外安装一份 npm 副本;发布时由发布脚本将 workspace: 范围改写为已发布的版本。

外部依赖的版本则统一收口在 pnpm-workspace.yamlcatalog: 目录中。各 workspace 的 manifest 里写 "react": "catalog:"(或 "catalog:react17" 指向命名目录)即可,避免同一个依赖在多处重复维护版本号。仓库还用命名 catalog(如 react17tailwind3)支撑 Portal 这类仍跑 React 17 / Tailwind v3 的遗留应用。遇到目录中没有、又想统一管理的第三方依赖版本,改 catalog 而不是逐个改 package.json。

Nx 任务编排:依赖图驱动的构建、测试与开发

Nx 读取整个依赖图并按拓扑顺序执行任务:例如构建某个项目前,会先构建它依赖的项目;nx.jsontargetDefaults"build": {"dependsOn": ["^build"]} 正是这一语义的声明,dev 目标则声明 continuous: true 表示长驻进程。同时 Nx 会缓存声明过的任务输出(build 默认把 {projectRoot}/builddistestypesumd 列为 outputs),命中缓存时直接跳过重跑。

从仓库根目录最常用的几条命令:

pnpm nx show projects                  # 列出所有可识别的工作区项目
pnpm nx show project <project-name>    # 查看单个项目的 targets 与依赖
pnpm nx graph                          # 浏览器中打开依赖关系图
pnpm nx run <project-name>:<target>    # 运行指定项目的某个 target,如 pnpm nx run @tryghost/admin:build

package.json 里的 pnpm buildpnpm lintpnpm test 本质都是 pnpm nx run-many -t <target>——即对全仓各项目执行同名 target;例如 pnpm build = run-many -t build,而 pnpm lint 除了 run-many 还会追加 lint:boundaries(依赖巡航边界检查)与 lint:packages(校验内部包黄金路径)。日常开发可先执行一次 pnpm setup(安装 + 初始化 submodule),再按需 pnpm dev(基于 compose.dev.yaml 的 Docker 开发栈)。

源码与生产构建:source 导出条件的双轨机制

这是本仓库最具特色的构建设计。部分 TypeScript 包在导出中把 source 条件放在编译产物之前(见 packages/README.md 的示例 exports:"source": "./src/index.ts""types""default")。Ghost Core 的开发服务器与测试会启用 source 条件,从而直接加载 @tryghost/kg-default-nodes 这类包的原始 TypeScript——每次改动无需 tsc 重编译即可被正在运行的 dev server 和 core 测试命中。

生产环境不启用 source:Node 走 default 条件加载 build/ 里的编译产物,Ghost 发布归档包含的也是这些编译文件而非包源码;浏览器应用同样使用各自的常规构建输出。一个直接推论是:源码改动可能在开发环境立刻生效,但涉及产物内容的生产构建仍需要重新执行 pnpm build——当你改动包导出、构建配置或任何进入发布产物的代码时,务必补跑构建。

Koenig 已发布的 kg-*作为既有对外契约,保留了独立的 ESM 与 CommonJS 双输出:importbuild/esm/ 解析,requirebuild/cjs/ 解析。它们的 files 列表包含 build/ 但不包含 src/,因此 source 条件所用的原始 TypeScript 永远不会被发布出去;而新建的内部包一律采用 ESM-only 契约。此外,由于 Ghost Core 本身是 CommonJS 却跑在支持 require(esm) 的 Node 上(仓库 engines 要求 Node ≥ 22),内部 ESM 包可同时服务 importrequire() 两类消费者——前提是整个被引用的模块图不能出现顶层 await(ESLint 会强制这一限制)。

仓库之外的关联项目

Ghost 的若干组成部分仍然维护在独立仓库中:gscan 负责校验 Ghost 主题;Ghost-CLI 用于安装与管理生产环境站点;SourceCasperThemes 存放官方主题;framework 承载 Ghost 使用的共享 Node.js 包;SDK 提供围绕 Ghost API 的工具链。本文只聚焦本仓库内部结构,这些外部仓库的具体用法以它们各自的文档为准。

对本仓库而言,最值得记住的实践结论是:改包之前先看对应目录的 README,改外部依赖版本先去 pnpm-workspace.yaml 的 catalog,改包导出/构建配置后记得跑 pnpm build——遵循这三条,你就能在 Ghost 这个庞大的 monorepo 里安全地穿梭于编辑器、服务端与共享库之间。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388