PaddleOCR JS Monorepo 工程约定解析:workspace 命令、版本发布与代码质量体系
【免费下载链接】PaddleOCRTurn any PDF or image document into structured data for your AI. A powerful, lightweight OCR toolkit that bridges the gap between images/PDFs and LLMs. Supports 100+ languages.项目地址: https://gitcode.com/GitHub_Trending/pa/PaddleOCR
本篇技术指南围绕 PaddleOCR 仓库中paddleocr-js子项目的 Monorepo 工程约定展开,系统讲解其 workspace 划分、单包命令执行、Changesets 版本发布流程以及分级 Lint 与测试规范。读完本文,你将掌握在paddleocr-js多包仓库中精准操作packages/core(npm 包@paddleocr/paddleocr-js)与apps/demo的正确姿势,并理解其工程化决策背后的源码依据。
一、Monorepo 整体结构:两个 Workspace 家族的职责划分
paddleocr-js采用 npm workspaces 组织多包仓库,根目录的 package.json 中明确声明了 workspace 的范围:
"workspaces": [ "packages/*", "apps/*" ]依据 monorepo 约定文档,整个仓库被划分为两类角色,职责泾渭分明:
| Workspace 家族 | 角色定位 | 代表成员 | 是否发布到 npm |
|---|---|---|---|
packages/* | 可复用包(reusable packages) | packages/core(SDK 源码与发布清单) | 是,作为公开包@paddleocr/paddleocr-js发布 |
apps/* | 私有应用(private applications) | apps/demo(演示应用) | 否,不参与 npm 发布 |
这里有一个值得注意的"目录名 ≠ 包名"设计:SDK 物理上位于packages/core目录,但其npm 包名依然是@paddleocr/paddleocr-js。这一点可以从 packages/core/package.json 中得到源码级印证:
{ "name": "@paddleocr/paddleocr-js", "version": "0.4.2", "description": "Browser-based OCR SDK powered by PaddleOCR, ONNX Runtime Web and OpenCV.js", "license": "Apache-2.0", "publishConfig": { "access": "public" } }也就是说,使用者在npm install时安装的是@paddleocr/paddleocr-js,而在仓库内维护时操作的是packages/core目录,二者通过 workspace 机制绑定。目录名core只是工程内部的组织语义,对外暴露的包名才是消费者真正 import 的标识符。
相应地,apps/demo/package.json 中"private": true明确标记了 demo 为私有应用,它通过"@paddleocr/paddleocr-js": "*"依赖 SDK,作为 SDK 的消费方与验证场存在。
二、命令执行约定:如何精准操作单个 Workspace
Monorepo 最常见的困扰是"我只想构建一个包,却把整个仓库都跑了一遍"。本文档给出了明确的约定:在仓库根目录使用带显式路径的 workspace 命令。
npm run build --workspace packages/core npm run dev --workspace apps/demo其中--workspace <path>接受的是目录路径;当包名没有歧义时,也可以直接使用 workspace 的包名:
npm run dev --workspace demo注意:npm run dev --workspace demo之所以可行,是因为apps/demo的包名恰好就是demo(见 apps/demo/package.json),且整个仓库中不存在其他同名包。为避免歧义,文档推荐优先使用显式路径写法。
根目录提供的便捷脚本
为了让日常工作更省心,根 package.json 还包装了一批跨 workspace 的聚合脚本,将"拓扑顺序"(先 SDK 后 demo)固化在脚本里:
| 脚本 | 实际执行内容 | 用途 |
|---|---|---|
npm run build | build:sdk && build:demo | 按拓扑顺序依次构建 SDK 与 demo |
npm run build:sdk | npm run build --workspace packages/core | 仅构建 SDK |
npm run build:demo | npm run build --workspace apps/demo | 仅构建 demo |
npm run dev:demo | npm run dev --workspace apps/demo -- | 启动 demo 的 Vite 开发服务器 |
npm run typecheck | npm run typecheck --workspaces --if-present | 对所有声明了 typecheck 的 workspace 做类型检查 |
npm run check | format:check → lint → build:sdk → typecheck → test → build:demo | 提交前的一站式质量门禁 |
npm run release | npm run build:sdk && npm publish --workspace packages/core | 构建并发布 SDK |
其中check脚本串联了格式化校验、Lint、SDK 构建、类型检查、测试与 demo 构建,是 CI 或提交前的完整检查链。关于构建与测试的更多细节,可参考 development.md。
三、版本管理与发布流程:Changesets + prepublishOnly
版本与发布是 Monorepo 工程化的核心环节,本文档给出了三条关键约定:
1. 版本由 Changesets 管理,demo 包被显式忽略
Changesets 是当前 JS 生态常见的多包版本管理工具,通过变更集(changeset)文件记录每个 PR 的版本影响,再统一 bump 版本并生成 changelog。按文档说明,demo 包会在.changeset/config.json中被忽略——这与其"私有应用、不发布"的角色一致:demo 的版本号波动不应进入公共包的发布节奏。
2.npm run release构建并发布
文档约定npm run release会先构建 SDK,再执行发布。对应到当前仓库根 package.json 中的实际脚本为:
npm run release # 实际执行:npm run build:sdk && npm publish --workspace packages/core即先构建packages/core,再仅对 core 这一个 workspace 执行npm publish,demo 不在发布范围内。
3.prepublishOnly自动构建,杜绝"发布未构建产物"
packages/core/package.json 中声明了发布前钩子:
"scripts": { "build": "vite build", "typecheck": "tsc --noEmit", "prepublishOnly": "npm run build" }prepublishOnly会在npm publish与npm pack之前自动执行,确保无论通过何种方式发布,dist/产物都来自最新的源码构建。配合files: ["dist", "README.md", "README_cn.md"]的发布白名单,最终打进 npm 包的只有构建产物与说明文档。
此外,core 包的exports字段暴露了两个子路径入口:
"exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.mjs" } }, "./viz": { "import": { "types": "./dist/viz/index.d.ts", "default": "./dist/viz.mjs" } } }消费者既可以import主入口,也可以按需引入@paddleocr/paddleocr-js/viz子路径(可视化能力),这为按需加载与 tree-shaking 留出了空间(sideEffects: false)。
四、Lint 与测试约定:分级规则集与浏览器导向的全局环境
paddleocr-js的代码质量体系采用"按文件角色分级"的策略,不同位置的 TypeScript 文件适用不同严格程度的规则集。这份分级规则定义在 eslint.config.js 中,与文档描述完全对应:
| 文件范围 | 使用的规则集 | 全局变量 | 说明 |
|---|---|---|---|
packages/**/src/**/*.ts | strictTypeChecked | 仅浏览器(browser) | SDK 源码,最严格 |
apps/**/src/**/*.ts | strictTypeChecked | 仅浏览器(browser) | demo 源码,与 SDK 同标准 |
packages/**/test/**/*.ts | recommendedTypeChecked | 浏览器 + Node | 测试代码,放宽部分规则 |
apps/**/*.js、根配置*.config.{js,ts}、包配置packages/**/*.config.* | 基础 ESLint 规则 | 浏览器 + Node | 配置文件,最宽松 |
测试文件之所以放宽,是因为测试场景常需要处理动态类型(如 mock 数据)。eslint.config.js 中可以看到具体被关闭的规则,例如no-unsafe-*系列与no-explicit-any:
{ files: ["packages/**/test/**/*.ts"], extends: [...tseslint.configs.recommendedTypeChecked], languageOptions: { globals: { ...globals.browser }, parserOptions: { project: "./tsconfig.eslint.json", tsconfigRootDir: import.meta.dirname } }, rules: { "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/require-await": "off", "@typescript-eslint/no-extraneous-class": "off", "@typescript-eslint/unbound-method": "off" } }这种"源码从严、测试从宽、配置最宽"的三级策略,既保证了核心代码的类型安全与可维护性,又避免了测试代码被过度约束拖慢迭代。
全局变量为何"面向浏览器"
SDK 是运行在浏览器中的 OCR 工具(基于 ONNX Runtime Web 与 OpenCV.js),因此packages/**/src与apps/**/src只挂载globals.browser,避免在浏览器代码中误用 Node 全局(如process、Buffer)。而测试代码和配置文件因运行在 Node 环境(Vitest、ESLint 自身),需要同时挂载 Node 与浏览器全局。
测试框架与覆盖范围
vitest.config.js 显示测试使用 Vitest + jsdom 环境,覆盖率统计聚焦在 SDK 源码上:
export default defineConfig({ test: { environment: "jsdom", coverage: { provider: "v8", include: ["packages/core/src/**/*.ts"] } } });根目录的npm run test即vitest run。测试策略上,development.md 明确了三层定位:配置解析与注册表的单元测试、浏览器平台辅助函数的轻量 jsdom 检查,以及默认不在 CI 中运行大规模真实模型推理——这保证了 CI 的轻量快速,同时把重负载的模型验证留在本地或专门的测试链路。
五、跨 Workspace 协作机制:类型检查与开发期源码直连
Monorepo 的另一个工程细节是 workspace 之间的依赖如何被解析。根 tsconfig.json 采用 Project References 组织:
{ "files": [], "references": [{ "path": "packages/core" }, { "path": "apps/demo" }] }npm run typecheck会通过tsc --noEmit对两个 workspace 分别做类型检查。值得强调的是:demo 的tsconfig.json通过paths映射直接对 SDK 的 TypeScript 源码做类型检查,因此在开发期不需要先执行build:sdk就能完成类型检查。
类似的"源码直连"思路也体现在 demo 的 vite.config.js 中:开发模式(serve)下将@paddleocr/paddleocr-js与@paddleocr/paddleocr-js/viz通过 Vite alias 直接指向packages/core/src的 TS 源码,实现即时 HMR;生产构建时则回落到 workspace 链接,消费 SDK 预构建的dist/产物。
六、延伸阅读
- Monorepo 约定(简体中文版):本文档的中文版本;
- Development 开发指南:安装、构建、测试与发布的具体命令;
- Architecture 架构说明:SDK 内部模块划分与运行时设计;
- SDK 源码与发布清单:包名、入口、依赖与发布配置;
- 根工程配置:workspaces 声明与全部聚合脚本。
总而言之,paddleocr-js的 Monorepo 约定是一套逻辑自洽的工程化方案:packages/*与apps/*的角色分离明确了"哪些可复用、哪些可发布";根目录显式路径的 workspace 命令解决了多包操作的心智负担;Changesets 加prepublishOnly保证了版本与发布产物的可靠性;分级的 TypeScript Lint 规则集则在严格度与效率之间取得了平衡。理解这些约定,是向paddleocr-js贡献代码、二次开发或集成 SDK 的第一步。
【免费下载链接】PaddleOCRTurn any PDF or image document into structured data for your AI. A powerful, lightweight OCR toolkit that bridges the gap between images/PDFs and LLMs. Supports 100+ languages.项目地址: https://gitcode.com/GitHub_Trending/pa/PaddleOCR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考