Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 218 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,42 @@ npm i --save @kne/example-driver

### 概述

### 项目概述

@kne/example-driver 是一个用于在线展示和编辑 React 组件的工具库,特别适合用于组件库文档、教程演示和技术文档中。它提供了实时代码预览和编辑功能,让用户可以直接在浏览器中查看和修改组件代码,无需搭建完整的开发环境。

核心特性包括实时代码编辑、即时代码预览、语法高亮显示、错误边界处理和灵活的布局控制。支持两种展示模式:LiveCode 模式提供完整的在线编辑和实时预览能力,MiniCode 模式则通过二维码引导用户在移动端查看示例。内置 Monaco Editor 提供专业的代码编辑体验,使用 Prism 实现代码语法高亮,并通过 Debounce 优化性能,避免频繁重新渲染。
### 主要特性

#### 实时编辑与预览

- **LiveCode 模式**:提供完整的在线编辑和实时预览能力,内置 Monaco Editor 提供专业的代码编辑体验
- **MiniCode 模式**:通过二维码引导用户在移动端查看示例,适用于移动端场景
- **语法高亮**:使用 Prism 实现代码语法高亮
- **Debounce 优化**:避免频繁重新渲染,提升编辑体验

#### 性能与加载

- **视口懒加载**:组件进入视口时才挂载渲染,离开视口自动卸载并保留占位高度,减少页面初始负载
- **懒编译机制**:使用 Babel Standalone 实现浏览器端代码编译,支持 ES2015 和 React 预设,无需后端转换;采用 LRU 缓存和编译队列优化编译性能
- **优先级调度**:首次可见时优先编译,后续编辑变更走 debounce 500ms 延迟编译

适用于组件库文档网站、在线教程和培训、技术博客和文档、以及 React 组件展示平台。通过动态加载和实时编译,让文档中的示例代码真正"活"起来,用户可以直接修改代码并立即看到效果,大大提升了学习效率和用户体验。
#### 错误处理与布局

技术亮点方面,项目采用 Babel Standalone 实现浏览器端的代码编译,支持 ES2015 和 React 预设,无需后端转换。错误边界机制确保代码错误不会影响整个页面,提供友好的错误提示。支持自定义上下文组件,方便在不同场景中嵌入示例代码。Monaco Editor 配置暴露给外部,允许深度定制编辑器行为。响应式布局设计,支持单列和双列模式,适应不同屏幕尺寸。
- **错误边界**:代码错误不会影响整个页面,提供友好的错误提示
- **响应式布局**:支持单列(全宽)和双列模式,自动适应不同屏幕尺寸
- **自定义上下文**:支持全局和单个示例级别的自定义上下文组件,方便在不同场景中嵌入示例代码
- **编辑器可配置**:Monaco Editor 配置暴露给外部,允许深度定制编辑器行为(如自定义 CDN 路径)

#### 国际化

- 内置中文(zh-CN)和英文(en-US)语言支持,自动适配编辑器加载提示、扫码提示等文案

### 使用场景

- **组件库文档网站**:在线展示组件示例,支持用户直接修改代码查看效果
- **在线教程和培训**:让学员在浏览器中即时实践代码
- **技术博客和文档**:嵌入可交互的代码示例,提升阅读体验
- **React 组件展示平台**:通过动态加载和实时编译,让文档中的示例代码真正"活"起来


### 示例(全屏)
Expand Down Expand Up @@ -270,6 +299,84 @@ render(<ExampleDriver list={[{

```

- 高度稳定性测试
- 通过 state 控制组件挂载与卸载,验证视口检测和占位高度的一致性
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)

```jsx
const {default: ExampleDriver} = _ExampleDriver;
const {useState} = React;
const {Button, Card, Space, Switch, Tag} = antd;

const code = `
const { Button, Card, Space } = antd;
const { useState } = React;

const Component = () => {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '20px' }}>
<Card title="高度测试组件" style={{ minHeight: '200px' }}>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ fontSize: '32px', textAlign: 'center', padding: '20px 0' }}>
{count}
</div>
<Space>
<Button onClick={() => setCount(count - 1)}>-</Button>
<Button onClick={() => setCount(count + 1)}>+</Button>
<Button onClick={() => setCount(0)} danger>重置</Button>
</Space>
</Space>
</Card>
</div>
);
};

render(<Component />);
`;

const HeightTestExample = () => {
const [mounted, setMounted] = useState(true);

const list = [{
title: '高度稳定性测试',
description: '通过开关控制组件挂载/卸载,观察卸载后占位高度与渲染高度是否一致',
code,
mounted,
scope: [{
name: 'antd', packageName: 'antd', component: antd
}]
}];

return (
<Space direction="vertical" style={{width: '100%'}} size="large">
<Card size="small">
<Space>
<Switch checked={mounted} onChange={setMounted} checkedChildren="挂载" unCheckedChildren="卸载"/>
<Tag color={mounted ? 'green' : 'red'}>{mounted ? '已挂载' : '已卸载'}</Tag>
<Button size="small" onClick={() => {
setMounted(false);
setTimeout(() => setMounted(true), 100);
}}>快速切换</Button>
<Button size="small" onClick={() => {
for (let i = 0; i < 3; i++) {
setTimeout(() => setMounted(prev => !prev), i * 200);
}
}}>连续切换3次</Button>
<span style={{color: '#999', fontSize: 12}}>
切换后观察页面是否抖动,占位高度是否与渲染高度一致
</span>
</Space>
</Card>
<ExampleDriver isFull list={list}/>
</Space>
);
};

render(<HeightTestExample/>);

```

- 双列布局
- 使用 isFull 属性控制布局,默认双列展示多个示例
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
Expand Down Expand Up @@ -373,7 +480,7 @@ render(<ExampleDriver list={[{
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| list | array | 是 | - | 示例列表数组,每个元素代表一个示例 |
| isFull | boolean | 否 | false | 是否全宽显示,true 时单列显示,false 时双列显示 |
| isFull | boolean | 否 | false | 是否全宽显示,true 时单列显示,false 时双列显示。当 list 仅有一项时自动为 true |
| contextComponent | React Component | 否 | - | 自定义上下文组件,用于包裹渲染的代码内容 |
| className | string | 否 | - | 自定义 CSS 类名 |
| ...props | object | 否 | - | 其他 HTML div 元素的属性 |
Expand All @@ -386,27 +493,31 @@ render(<ExampleDriver list={[{
| title | string | 是 | - | 示例标题 |
| description | string | 是 | - | 示例描述,支持 HTML 格式 |
| qrcodeUrl | string | 否 | - | 二维码图片 URL,传入此项则使用 MiniCode 模式 |
| contextComponent | React Component | 否 | - | 单个示例的自定义上下文组件 |
| contextComponent | React Component | 否 | - | 单个示例的自定义上下文组件,优先级高于 ExampleDriver 的 contextComponent |
| mounted | boolean | 否 | - | 控制组件是否挂载。传入时由该值决定;不传时由视口检测决定是否挂载 |
| useInView | boolean | 否 | true | 是否启用视口检测懒加载,仅在 mounted 未传入时生效。设为 false 则组件始终挂载 |

#### scope 数组项属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| component | React Component | 是 | - | 要暴露给示例代码的组件 |
| name | string | | - | 组件名称,在代码中使用 |
| component | any | 否 | - | 要暴露给示例代码的组件,不传时仅生成 import 语句(如 CSS 导入) |
| name | string | | - | 组件名称,在代码中使用。component 为真值且 name 为非空字符串时才注入到示例作用域 |
| packageName | string | 否 | - | 包名,用于生成导入语句 |
| importStatement | string | 否 | - | 自定义导入语句,覆盖自动生成的导入 |
| importStatement | string | 否 | - | 自定义导入语句,提供后不再自动生成。优先级最高 |

### LiveCode
提供实时代码编辑和预览功能的子组件(通过 ExampleDriver 自动使用)。

#### 属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| code | string | | - | 要展示的代码 |
| scope | array | | - | 作用域数组 |
| code | string | | '' | 要展示的代码 |
| scope | array | | [] | 作用域数组 |
| title | string | 是 | - | 标题 |
| description | string | 是 | - | 描述 |
| contextComponent | React Component | 否 | - | 上下文组件 |
| description | string | 是 | - | 描述,支持 HTML |
| contextComponent | React Component | 否 | - | 上下文组件,包裹渲染的示例内容 |
| mounted | boolean | 否 | - | 控制组件是否挂载。传入时由该值决定;不传时由视口检测决定 |
| useInView | boolean | 否 | true | 是否启用视口检测懒加载,仅在 mounted 未传入时生效 |

### MiniCode
显示二维码预览的只读模式子组件(通过 ExampleDriver 自动使用)。
Expand All @@ -420,11 +531,103 @@ render(<ExampleDriver list={[{
| title | string | 是 | - | 标题 |
| description | string | 是 | - | 描述 |

### CodePanel
代码编辑面板子组件,用于显示和编辑代码。

#### 属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| code | string | 是 | - | 代码字符串 |
| scope | array | 是 | - | 作用域数组,用于生成 import 语句 |
| error | Error \| null | 否 | - | 编译错误对象 |
| editable | boolean | 是 | - | 代码是否可编辑 |
| onChange | function | 否 | - | 代码变更回调 `(newCode: string) => void` |

### DescriptionBar
示例描述栏子组件,显示标题、描述和代码面板切换按钮。

#### 属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| title | string | 是 | - | 示例标题 |
| description | string | 是 | - | 示例描述,支持 HTML |
| codeOpen | boolean | 是 | - | 代码面板是否打开 |
| onToggle | function | 是 | - | 切换代码面板的回调 |

### HighlightCode
代码高亮展示子组件,使用 Prism 实现语法高亮。

#### 属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| code | string | 是 | - | 要高亮显示的代码字符串 |

### ErrorComponent
错误展示子组件,用于显示编译或运行时错误。

#### 属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| error | string \| Error | 否 | - | 错误信息,字符串直接显示,Error 对象显示 .message |

### Hooks

#### useInView
视口检测 Hook,用于判断元素是否进入视口,实现懒加载。

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| ref | React.RefObject | 是 | - | 要监听的 DOM 容器 ref |
| options | object | 否 | - | 配置选项 |
| options.disabled | boolean | 否 | false | 是否禁用视口检测 |

| 返回值 | 类型 | 说明 |
|--------|------|------|
| shouldRender | boolean | 元素是否在视口内 |
| heightRef | React.MutableRefObject\<number\> | 记录元素最后一次渲染高度的 ref |

内部使用全局共享的 IntersectionObserver 实例,rootMargin 为 200px 预加载,离开视口且 intersectionRatio 为 0 时卸载并保留占位高度。SSR 环境或浏览器不支持 IntersectionObserver 时直接返回 shouldRender 为 true。

#### useLazyCompile
懒编译 Hook,使用 Babel Standalone 在浏览器端编译代码,支持缓存和优先级调度。

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| code | string | 是 | - | 要编译的代码字符串 |
| shouldCompile | boolean | 是 | - | 是否应该编译,通常关联 useInView 的 shouldRender |

| 返回值 | 类型 | 说明 |
|--------|------|------|
| compiledCode | string \| null | 编译后的代码,未编译时为 null |
| error | Error \| null | 编译错误,无错误时为 null |

内部采用 LRU 缓存(上限 200 条),编译队列最大并发为 1。首次可见时作为优先任务立即编译,后续编辑变更走 debounce 500ms。非优先任务使用 requestIdleCallback 延迟执行。

#### useReactRoot
React Root 管理 Hook,使用 react-dom/client 的 createRoot API 管理组件挂载/卸载。

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| containerRef | React.RefObject | 是 | - | 渲染容器 DOM ref |
| shouldRender | boolean | 是 | - | 是否应该渲染 |
| renderJsx | ReactNode \| null | 是 | - | 要渲染的 React 元素 |
| heightRef | React.MutableRefObject\<number\> | 是 | - | 用于记录/恢复高度的 ref |

卸载时将 runner 替换为 placeholder 并保留上次高度,使用 ResizeObserver 监控高度变化。组件卸载时自动清理。

### config
Monaco Editor 的配置对象,用于自定义编辑器行为
Monaco Editor 的配置方法,用于自定义编辑器加载行为

#### 方法说明
| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| config | options: object | void | 配置 Monaco Editor 的加载选项,如模块路径等 |
| config | options: { paths?: Record\<string, string\>, [key: string]: any } | void | 配置 Monaco Editor 的加载选项,最常用场景是通过 paths 自定义 Monaco 的 CDN 路径 |

### 国际化
内置中文(zh-CN)和英文(en-US)语言支持,默认语言为 zh-CN。

| Key | zh-CN | en-US |
|-----|-------|-------|
| CodePanel.loading | 正在加载代码编辑器... | Loading code editor... |
| MiniCode.example | 示例 | Example |
| MiniCode.scanQrcode | 请扫描二维码查看示例程序 | Please scan the QR code to view the example |

Loading
Loading