|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## 项目概述 |
| 6 | + |
| 7 | +Script Engine 是一个基于 React + CodeMirror 6 的 Groovy 脚本编辑器组件库,提供语法高亮、动态类型自动补全、属性面板等功能。发布为 npm 包 `@coding-script/script-engine`。 |
| 8 | + |
| 9 | +**用户全局规则**:使用中文回复。 |
| 10 | + |
| 11 | +## 常用命令 |
| 12 | + |
| 13 | +```bash |
| 14 | +# 在 monorepo 根目录执行 |
| 15 | +pnpm run build:script-engine # 构建库 |
| 16 | +pnpm run watch:script-engine # 库 watch 模式(开发时常用) |
| 17 | +pnpm run dev:app-pc # 启动演示应用(http://localhost:3000) |
| 18 | +pnpm run push:script-engine # 构建并发布到 npm |
| 19 | + |
| 20 | +# 在 packages/script-engine 目录执行 |
| 21 | +pnpm run build # 构建 |
| 22 | +pnpm run dev # watch 模式 |
| 23 | +pnpm run push # 构建 + publish --access public |
| 24 | + |
| 25 | +# 在 apps/app-pc 目录执行 |
| 26 | +pnpm run dev # 启动开发服务器 |
| 27 | +pnpm run build # 生产构建 |
| 28 | +pnpm run preview # 预览生产构建 |
| 29 | + |
| 30 | +# 测试(Rstest + happy-dom,目前尚无测试文件) |
| 31 | +pnpm run test # 运行测试 |
| 32 | +pnpm run test:watch # watch 模式 |
| 33 | +``` |
| 34 | + |
| 35 | +## Monorepo 结构 |
| 36 | + |
| 37 | +``` |
| 38 | +pnpm-workspace.yaml: packages/** + apps/** |
| 39 | +
|
| 40 | +packages/script-engine/ → 库 @coding-script/script-engine (Rslib, ESM, unbundled) |
| 41 | +apps/app-pc/ → 演示应用 @script-example/app-pc (Rsbuild + React, 依赖 antd 6) |
| 42 | +``` |
| 43 | + |
| 44 | +库通过 `workspace:*` 被演示应用引用。**开发流程**:先在根目录运行 `watch:script-engine`,再运行 `dev:app-pc`。 |
| 45 | + |
| 46 | +## 关键架构 |
| 47 | + |
| 48 | +### 库的构建配置 (`packages/script-engine/rslib.config.ts`) |
| 49 | + |
| 50 | +- **`bundle: false`**:不打包,输出保持模块图结构(tree-shakeable) |
| 51 | +- **`format: 'esm'`**:仅 ESM |
| 52 | +- **`dts: true`**:生成 `.d.ts` 类型声明 |
| 53 | +- 路径别名 `@/` → `src/`(在 tsconfig 和 rslib.config 中都要配置) |
| 54 | + |
| 55 | +### CodeMirror 6 编辑器 (`src/script-code.tsx`) |
| 56 | + |
| 57 | +核心设计原则:**用 `Compartment` 热更新,避免重建编辑器**。 |
| 58 | + |
| 59 | +编辑器只在首次挂载和布局属性(`fontSize`/`minHeight`/`maxHeight`/`placeholder`/`readonly`)变化时重建。以下内容通过 Compartment 热更新: |
| 60 | + |
| 61 | +- `themeCompartmentRef`:主题(dark/light)切换 → `buildThemeExtensions(theme)` |
| 62 | +- `autocompleteCompartmentRef`:metadata 变化 → `buildAutocompleteExt(metadata)` |
| 63 | + |
| 64 | +**绝对不要**把 `theme`、`metadata`、`value` 放到编辑器创建的 useEffect 依赖数组中,否则会丢失用户已编辑的代码内容。 |
| 65 | + |
| 66 | +`onChangeRef` / `metadataRef` 使用 ref 持有回调,避免闭包过期。 |
| 67 | + |
| 68 | +### 自动补全 (`src/autocomplete/`) |
| 69 | + |
| 70 | +- `resolve.ts`:纯函数,解析点号链到具体类型 |
| 71 | + - `resolveVariableType(name, metadata)` → 在 binds/requests 中查找变量 |
| 72 | + - `resolveMemberType(typeName, memberName, metadata)` → 在类型的 fields/functions 中查找成员 |
| 73 | + - `resolveChainType(parts, metadata)` → 解析完整链 `["request", "test"]` → `MyTest` 类型 |
| 74 | + - `MAX_DEPTH = 20` 防止循环引用 |
| 75 | +- `completion-source.ts`: |
| 76 | + - `createGroovyCompletionSource(metadata)`:metadata 驱动 + Groovy 语法(有 metadata 时使用) |
| 77 | + - `createGroovyKeywordSource()`:仅 Groovy 语法(无 metadata 时使用) |
| 78 | + - 通过 `syntaxTree(context.state).resolveInner()` 检测字符串/注释位置,抑制补全 |
| 79 | + - 外层 `try/catch` 保证补全失败不崩溃编辑器 |
| 80 | + - Groovy 语法片段用 `snippet()` API(来自 `@codemirror/autocomplete`)实现 tab-stop 占位符 |
| 81 | + |
| 82 | +### 属性面板 (`src/type-panel/`) |
| 83 | + |
| 84 | +纯 React 组件,**不依赖 Ant Design**(antd 仅在演示应用中使用)。 |
| 85 | + |
| 86 | +- 使用 CSS-in-JS(React `style` 对象)+ 主题配色 map |
| 87 | +- `TypePanel` 固定高度(`minHeight`/`maxHeight` 由编辑器传入),内部可滚动 |
| 88 | +- 滚动条样式通过 `<style>` 标签一次性注入 DOM(`ensureScrollbarStyle()`) |
| 89 | +- 展示 metadata 中**所有**类型(包括 Integer/String 等基础类型) |
| 90 | +- 滚动容器必须设置 `minHeight: 0`(flex 布局关键修复) |
| 91 | + |
| 92 | +### ScriptMetadata Schema (`src/types/index.ts`) |
| 93 | + |
| 94 | +核心领域模型,描述脚本运行时的可用类型: |
| 95 | + |
| 96 | +```ts |
| 97 | +ScriptMetadata { |
| 98 | + binds: ScriptBindInfo[] // 注入变量,如 $request(name 含 $ 前缀) |
| 99 | + requests: ScriptRequestInfo[] // 函数参数,如 request |
| 100 | + returnType?: string |
| 101 | + types: Record<string, ScriptTypeInfo> // 所有可用类型(含基础类型) |
| 102 | +} |
| 103 | + |
| 104 | +ScriptTypeInfo { dataType, description?, fields[], functions[] } |
| 105 | +ScriptFieldInfo { dataType, description?, name } |
| 106 | +ScriptFunctionInfo { name, parameters[], description?, returnType? } |
| 107 | +``` |
| 108 | + |
| 109 | +`metadata` 是动态的(不同脚本有不同 schema),但结构固定。 |
| 110 | + |
| 111 | +## 类型导出 |
| 112 | + |
| 113 | +`src/index.ts` 必须同时导出组件和类型: |
| 114 | +```ts |
| 115 | +export * from "./script-code"; |
| 116 | +export * from "./types"; |
| 117 | +``` |
| 118 | + |
| 119 | +消费者通过 `import type { ScriptMetadata } from '@coding-script/script-engine'` 导入类型。 |
| 120 | + |
| 121 | +## 主题 |
| 122 | + |
| 123 | +`dark` 和 `light` 两套配色。编辑器主题、自动补全弹窗主题、属性面板主题三者必须同步切换: |
| 124 | + |
| 125 | +- 编辑器:`oneDark` + 自定义 `darkHighlightStyle` |
| 126 | +- 弹窗:`buildAutocompleteTooltipTheme(theme)` 注入 `EditorView.theme()` |
| 127 | +- 属性面板:`themes[theme]` 配色 map |
| 128 | + |
| 129 | +## 发布 |
| 130 | + |
| 131 | +发布到 npm 的 `@coding-script/script-engine` scope,使用 `--access public`。 |
| 132 | +- 构建后产物在 `packages/script-engine/dist/` |
| 133 | +- 仅发布 `dist/` 目录(`package.json` 中 `files: ["dist"]`) |
0 commit comments