基于 React + CodeMirror 6 的多语言脚本编辑器组件库,内置支持 Groovy 和 JavaScript,提供语法高亮、动态类型自动补全、属性面板等功能。
- 多语言支持:内置 Groovy 和 JavaScript 语言支持,支持通过
LanguageConfig扩展任意语言 - 语法高亮:基于 CodeMirror 6,通过
@codemirror/lang-*系列包提供各语言的语法着色 - 动态类型自动补全:基于
ScriptMetadata提供变量名补全和点号链式访问补全(如request.test.name) - 语言语法提示:内置各语言的常用语法片段(
if/for/while/return等),支持 tab-stop 占位符 - 代码格式化:Groovy 内置格式化器,也可通过
onFormat自定义格式化逻辑 - 属性面板:右侧侧边栏展示主函数签名、函数入参、绑定参数、数据类型(字段和方法),支持折叠/展开和拖拽调节宽度
- 脚本说明:工具栏"脚本说明"按钮,点击展开/收起脚本描述弹框,支持多行文本和
key: value格式高亮 - 主题切换:暗色/亮色两套主题,编辑器、补全弹窗、属性面板同步切换,编辑器内部管理主题状态
- 全屏模式:支持 CSS 全屏(
position: fixed覆盖视口),不影响编辑器内容 - 自定义工具栏:通过
toolbarExtra传入任意 React 节点 - 热更新:主题、语言和 metadata 变化时通过 Compartment 热更新,不重建编辑器,不丢失用户输入
npm install @coding-script/script-engine
# 或
pnpm add @coding-script/script-engineimport{ScriptCodeEditor}from'@coding-script/script-engine';importtype{ScriptMetadata}from'@coding-script/script-engine';constmetadata: ScriptMetadata={mainMethod: 'run',description: '脚本主入口,接收请求参数并返回执行结果状态码',returnType: 'Integer',binds: [{dataType: 'GroovyBindObject',name: '$request'},],requests: [{dataType: 'MyScriptRequest',description: '请求参数',name: 'request'},],types: {MyScriptRequest: {dataType: 'MyScriptRequest',description: '请求参数类型',fields: [{dataType: 'int',description: '总数量',name: 'count'},{dataType: 'MyTest',description: '测试对象',name: 'test'},],functions: [{name: 'isSupport',description: '是否匹配',parameters: [{dataType: 'int',description: '数量',name: 'count'}],},],},MyTest: {dataType: 'MyTest',description: '测试对象',fields: [{dataType: 'Long',description: 'id',name: 'id'},{dataType: 'String',description: '名称',name: 'name'},],functions: [],},Integer: {dataType: 'Integer',fields: [],functions: []},String: {dataType: 'String',fields: [],functions: []},Long: {dataType: 'Long',fields: [],functions: []},int: {dataType: 'int',fields: [],functions: []},},};functionApp(){return(<ScriptCodeEditorvalue="def run(request){\n return request.count;\n}\n"title="Groovy 脚本编辑器"language="groovy"defaultTheme="dark"metadata={metadata}enableThemeToggleenableFormatenableCompileenableFullscreenonThemeChange={(theme)=>console.log('主题切换:',theme)}onChange={(code)=>console.log('代码变化:',code)}onCompile={(code)=>console.log('编译验证:',code)}options={{minHeight: 400,maxHeight: 500}}/>);}import{ScriptCodeEditor}from'@coding-script/script-engine';functionApp(){return(<ScriptCodeEditorvalue="function run(request) {\n return request.count;\n}\n"title="JavaScript 脚本编辑器"language="javascript"defaultTheme="dark"metadata={metadata}enableThemeToggleenableFormatenableCompileenableFullscreenonFormat={()=>{// JavaScript 无内置格式化器,需自行提供// 例如使用 prettier}}/>);}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | undefined | 代码内容 |
readonly | boolean | false | 是否只读 |
onChange | (value: string) => void | undefined | 代码变化回调 |
language | string | LanguageConfig | 'groovy' | 编程语言配置,支持内置语言名或自定义配置 |
placeholder | string | 由语言配置决定 | 空内容占位符(覆盖语言默认值) |
defaultTheme | 'dark' | 'light' | 'dark' | 初始主题,编辑器内部管理状态 |
onThemeChange | (theme: 'dark' | 'light') => void | undefined | 主题切换通知回调 |
title | string | undefined | 工具栏标题 |
metadata | ScriptMetadata | undefined | 脚本元数据,提供后启用属性面板和自动补全 |
defaultSidebarOpen | boolean | metadata != null | 属性面板默认是否展开 |
所有工具栏按钮默认禁用,需通过 enable* 标志显式开启:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enableThemeToggle | boolean | false | 是否显示主题切换按钮 |
enableFormat | boolean | false | 是否显示格式化按钮(需配合 onFormat 或 language.formatter) |
enableCompile | boolean | false | 是否显示编译验证按钮(需配合 onCompile) |
enableFullscreen | boolean | false | 是否显示全屏按钮 |
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
onFormat | () => void | undefined | 格式化代码回调(优先级高于 language.formatter) |
onCompile | (code: string) => void | undefined | 编译/测试脚本回调 |
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
toolbar | ToolbarItem[] | undefined | 工具栏自定义按钮列表,渲染在内置按钮之后、toolbarExtra 之前 |
toolbarExtra | React.ReactNode | undefined | 工具栏额外内容,渲染在最后,可传入任意 JSX |
ToolbarItem 类型:
interfaceToolbarItem{key: string;// 唯一标识label: ReactNode;// 按钮内容title: string;// 鼠标悬停提示backgroundColor: string;hoverBackgroundColor: string;textColor: string;borderColor: string;onClick: ()=>void;}使用按钮列表:
<ScriptCodeEditortoolbar={[{key: 'save',label: '💾 保存',title: '保存脚本',backgroundColor: '#007bff',hoverBackgroundColor: '#0056b3',textColor: '#fff',borderColor: '#007bff',onClick: ()=>saveCode(),},]}/>使用自定义内容:
<ScriptCodeEditortoolbarExtra={<><buttononClick={save}>💾 保存</button><buttononClick={help}>❓ 帮助</button></>}/>| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
options.fontSize | number | 14 | 字体大小(px) |
options.minHeight | number | 300 | 编辑器最小高度(px) |
options.maxHeight | number | 300 | 编辑器最大高度(px) |
interfaceScriptMetadata{/** 主函数名称(可选) */mainMethod?: string;/** 脚本说明(可选,提供后在工具栏显示"脚本说明"按钮,点击展开/收起描述弹框) */description?: string;/** 注入变量(如 $request,name 含 $ 前缀) */binds: ScriptBindInfo[];/** 主函数参数 */requests: ScriptRequestInfo[];/** 主函数返回类型(可选) */returnType?: string;/** 所有可用类型定义(含基础类型如 Integer/String) */types: Record<string,ScriptTypeInfo>;}interfaceScriptTypeInfo{dataType: string;description?: string;fields: ScriptFieldInfo[];functions: ScriptFunctionInfo[];}interfaceScriptFieldInfo{name: string;dataType: string;description?: string;}interfaceScriptFunctionInfo{name: string;parameters: ScriptParameterInfo[];description?: string;returnType?: string;}interfaceScriptParameterInfo{name: string;dataType: string;description?: string;}interfaceScriptBindInfo{name: string;dataType: string;description?: string;}interfaceScriptRequestInfo{name: string;dataType: string;description?: string;}注意:
metadata必须是解析后的 JavaScript 对象,不能是 JSON 字符串。如果从 API 获取的是 JSON 字符串,需要先JSON.parse()再传入。
组件内置支持以下语言:
| 语言名 | 语法高亮 | 语法片段 | 内置格式化器 |
|---|---|---|---|
'groovy' | @codemirror/lang-java | 21 个(if/for/def/each/collect 等) | ✅ GroovyFormatter |
'javascript' | @codemirror/lang-javascript | 22 个(if/for/function/=>/class 等) | ❌(可通过 onFormat 提供) |
使用内置语言只需传入语言名:
<ScriptCodeEditorlanguage="groovy"/><ScriptCodeEditorlanguage="javascript"/>通过传入 LanguageConfig 对象可以支持任意语言:
import{python}from'@codemirror/lang-python';constPYTHON_SNIPPETS=[{label: 'def',type: 'keyword',apply: snippet('def ${name}(${params}):\n\t${}\n')},{label: 'class',type: 'keyword',apply: snippet('class ${ClassName}:\n\tdef __init__(self):\n\t\t${}\n')},{label: 'if',type: 'keyword',apply: snippet('if ${condition}:\n\t${}\n')},{label: 'for',type: 'keyword',apply: snippet('for ${item} in ${iterable}:\n\t${}\n')},{label: 'print',type: 'keyword',apply: snippet('print(${})')},{label: 'return',type: 'keyword',apply: snippet('return ${}')},// ... 更多语法片段];<ScriptCodeEditorlanguage={{name: 'python',displayName: 'Python',extension: ()=>python(),keywordSnippets: PYTHON_SNIPPETS,syntaxNodeNames: {stringNodes: ['String'],commentNodes: ['LineComment','BlockComment'],},placeholder: '请输入 Python 脚本...',formatter: (code)=>customPythonFormatter(code),}}/>interfaceLanguageConfig{/** 语言标识名(小写) */name: string;/** 显示名称 */displayName: string;/** CodeMirror 语言扩展工厂 */extension: ()=>Extension;/** 关键字和语法片段列表 */keywordSnippets: readonlyCompletion[];/** 语法树节点名(用于判断字符串/注释位置,抑制自动补全) */syntaxNodeNames: {/** 字符串类节点名 */stringNodes: string[];/** 注释类节点名 */commentNodes: string[];};/** 默认占位符文本(可选) */placeholder?: string;/** 内置格式化函数(可选,优先级低于 onFormat prop) */formatter?: (code: string)=>string;}语言切换时通过 Compartment 热更新,不重建编辑器,不丢失用户已输入的内容。
提供 metadata 后,编辑器支持以下补全能力:
| 输入 | 补全内容 |
|---|---|
re | 弹出 request、$request 等变量 |
request. | 弹出 count、test、isSupport 等字段和方法 |
request.test. | 弹出 id、name 等链式访问成员 |
if / for / while | 弹出当前语言的语法片段(含 tab-stop 占位符) |
不提供 metadata 时,仅启用当前语言的关键字和语法片段补全。
补全不会在字符串和注释内触发(通过各语言的语法树节点名判断)。
# 安装依赖
pnpm install
# 启动库 watch 模式(终端 1)
pnpm run watch:script-engine
# 启动演示应用(终端 2)
pnpm run dev:app-pc演示应用访问 http://localhost:3000
- 编辑器:CodeMirror 6(
@codemirror/view、state、autocomplete、lang-java、lang-javascript、theme-one-dark) - 构建工具:Rslib(库)+ Rsbuild(演示应用)
- 包管理:pnpm monorepo(workspaces)
- UI:纯 CSS-in-JS(React
style对象),库本身不依赖 Ant Design
Apache-2.0 license