Markdown编辑框架
参考文档: https://blog.csdn.net/gitblog_00200/article/details/151257287
milkdown——这款基于ProseMirror和Remark构建的插件驱动型所见即所得(WYSIWYG)Markdown编辑框架,正以其革命性的插件系统彻底改变这一现状。本文将深入剖析milkdown的核心架构、插件生态与实战应用,带你掌握如何构建高度定制化的编辑体验。
通过本文将获得:
- 理解milkdown的插件驱动架构设计理念
- 掌握核心模块的协作机制与扩展点
- 学会从零构建自定义插件与编辑器配置
- 实现高级功能如协作编辑、自定义快捷键和主题定制
- 了解企业级应用的最佳实践与性能优化策略
架构总览:插件驱动的设计哲学
milkdown采用分层架构设计,通过上下文(Context)系统实现模块解耦,其核心可概括为"三横三纵"结构:
核心架构图

核心模块协作流程
milkdown的初始化遵循严格的生命周期管理,各模块通过上下文系统有序交互:

核心技术解析:从Context到插件生态
Context系统:应用状态的神经中枢
Context(上下文)系统是milkdown的核心,采用依赖注入模式管理应用状态。每个模块通过唯一键(Key)注册和访问上下文,实现松耦合通信:
// 定义上下文键
import { createCtx, Ctx } from '@milkdown/ctx';
const countCtx = createCtx(0);
// 在插件中使用上下文
function counterPlugin(ctx: Ctx) {
// 获取当前值
const count = ctx.get(countCtx);
// 更新值
ctx.set(countCtx, count + 1);
// 监听变化
return () => {
const unsubscribe = ctx.onUpdate(countCtx, (newCount) => {
console.log('Count updated:', newCount);
});
return unsubscribe;
};
}
核心上下文键分类:
| 类别 | 关键上下文 | 作用 |
|---|---|---|
| 编辑器状态 | editorStateCtx | 管理ProseMirror状态 |
| 文档模型 | schemaCtx | 存储节点和标记定义 |
| 命令系统 | commandsCtx | 注册和执行命令 |
| 视图配置 | editorViewOptionsCtx | 自定义编辑器视图 |
| 插件配置 | pluginConfigCtx | 存储插件特定配置 |
插件架构:功能扩展的原子单元
milkdown插件采用"工厂函数+类"混合模式,每个插件包含注册逻辑和生命周期钩子:
import { Plugin, Editor } from '@milkdown/core';
import { SlashProvider } from '@milkdown/plugin-slash';
// 创建插件工厂
const mySlashPlugin = () => {
return Plugin.create('my-slash-plugin', (ctx) => {
// 初始化阶段
return {
// 插件加载时执行
load: () => {
ctx.set(slashConfigCtx, {
trigger: '/',
items: [/* 自定义命令项 */]
});
},
// 编辑器视图创建后执行
view: (editorView) => {
const provider = new SlashProvider(editorView);
return {
update: () => provider.update(),
destroy: () => provider.destroy()
};
}
};
});
};
// 使用插件
Editor.make()
.use(mySlashPlugin())
.create();
插件生命周期:

文档模型:Markdown的结构化表示
milkdown使用Schema定义文档结构,包含节点(Nodes)和标记(Marks)两类元素:
// 定义自定义节点
import { NodeSchema } from '@milkdown/core';
const customNodeSchema: NodeSchema = {
name: 'callout',
content: 'block+',
group: 'block',
defining: true,
attrs: {
type: { default: 'note' },
title: { default: '' }
},
parseDOM: [{
tag: 'div.callout',
getAttrs: (dom) => ({
type: dom.dataset.type || 'note',
title: dom.getAttribute('title') || ''
})
}],
toDOM: (node) => [
'div',
{ class: `callout callout-${node.attrs.type}`, title: node.attrs.title },
0
]
};
核心节点类型:
| 节点类型 | 作用 | 示例 |
|---|---|---|
| paragraph | 段落文本 | 普通文本块 |
| heading | 标题 | # 标题文本 |
| code_block | 代码块 | js ... |
| list_item | 列表项 | - 列表内容 |
| table | 表格 | 表头,内容 |
实战指南:从零构建定制编辑器
快速开始:基础编辑器搭建
<!DOCTYPE html>
<html>
<head>
<!-- 使用国内CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div id="editor"></div>
<script type="module">
import { Editor } from 'https://cdn.jsdelivr.net/npm/@milkdown/core@7/+esm';
import { commonmark } from 'https://cdn.jsdelivr.net/npm/@milkdown/preset-commonmark@7/+esm';
import { crepe } from 'https://cdn.jsdelivr.net/npm/@milkdown/crepe@7/+esm';
Editor.make()
.use(commonmark) // 基础Markdown支持
.use(crepe) // 基础UI主题
.select('#editor') // 绑定DOM元素
.create(); // 创建编辑器
</script>
</body>
</html>
插件组合:构建全功能编辑器
import { Editor } from '@milkdown/core';
import { commonmark } from '@milkdown/preset-commonmark';
import { gfm } from '@milkdown/preset-gfm';
import { nord } from '@milkdown/theme-nord';
import { slash } from '@milkdown/plugin-slash';
import { history } from '@milkdown/plugin-history';
import { table } from '@milkdown/plugin-table';
import { tooltip } from '@milkdown/plugin-tooltip';
import { clipboard } from '@milkdown/plugin-clipboard';
// 全功能编辑器配置
const editor = Editor.make()
.use(nord) // Nord主题
.use(commonmark) // 基础Markdown
.use(gfm) // GitHub风格扩展
.use(history) // 撤销/重做
.use(clipboard) // 增强剪贴板
.use(table) // 表格支持
.use(tooltip) // 悬浮提示
.use(slash.configure((ctx) => {
// 自定义slash命令
ctx.set(slash.config, {
items: [
{
id: 'custom-command',
title: '插入自定义块',
description: '添加带图标和样式的自定义内容块',
command: (editor) => {
editor
.chain()
.insertNode('callout', { type: 'tip', title: '提示' })
.run();
}
}
]
});
}))
.select('#app')
.create();
自定义节点:扩展Markdown语法
import { Node, Mark, createNode } from '@milkdown/core';
import { InputRule } from '@milkdown/prose';
// 定义警告框节点
const calloutNode = createNode((utils) => {
const id = 'callout';
const attrs = {
type: 'note',
title: ''
};
return {
id,
attrs,
schema: {
attrs: {
type: { default: attrs.type },
title: { default: attrs.title }
},
content: 'block+',
group: 'block',
defining: true,
parseDOM: [{
tag: `div[data-type="${id}"]`,
getAttrs: (dom) => ({
type: dom.dataset.type || attrs.type,
title: dom.getAttribute('title') || attrs.title
})
}],
toDOM: (node) => [
'div',
{
'data-type': id,
'data-type': node.attrs.type,
class: `callout callout-${node.attrs.type}`,
title: node.attrs.title
},
['div', { class: 'callout-title' }, node.attrs.title || '提示'],
['div', { class: 'callout-content' }, 0]
]
},
// 添加输入规则:/// 触发警告框
inputRules: (nodeType) => [
new InputRule(/^\/\/\/\s*(\w*)\s*(.*)$/, (state, match, start, end) => {
const [_, type, title] = match;
const attrs = {
type: type || 'note',
title: title || ''
};
return state.tr
.replaceRangeWith(start, end, nodeType.create(attrs))
.setSelection(state.selection.near(state.tr.doc.resolve(end)));
})
],
// 添加命令
commands: (nodeType) => ({
insertCallout: (attrs) => (state, dispatch) => {
const node = nodeType.create(attrs);
const transaction = state.tr.replaceSelectionWith(node);
dispatch(transaction);
return true;
}
})
};
});
// 在编辑器中使用
Editor.make()
.use(commonmark)
.use(calloutNode)
.create();
高级应用:协作编辑与性能优化
实时协作实现
milkdown通过collab插件支持基于Yjs的协作编辑:
import * as Y from 'yjs';
import { WebrtcProvider } from 'y-webrtc';
import { collab, ySyncPlugin } from '@milkdown/plugin-collab';
// 创建Yjs文档
const ydoc = new Y.Doc();
// 配置WebRTC提供者
const provider = new WebrtcProvider(
'milkdown-collab-demo', // 房间ID
ydoc, // Yjs文档
{ params: { room: 'my-room' } } // 自定义参数
);
// 创建共享类型
const yXmlFragment = ydoc.getXmlFragment('document');
Editor.make()
.use(commonmark)
.use(collab.configure((ctx) => {
ctx.set(collab.config, {
doc: ydoc,
fragment: yXmlFragment,
// 配置用户信息
user: {
name: '用户' + Math.floor(Math.random() * 1000),
color: `hsl(${Math.random() * 360}, 70%, 60%)`
}
});
}))
.select('#editor')
.create();
// 监听连接状态
provider.on('status', (event) => {
console.log('协作状态:', event.status);
if (event.status === 'connected') {
showNotification('已连接到协作会话');
}
});
协作编辑架构:

性能优化策略
大型文档优化技巧:
// 1. 启用文档分块渲染
Editor.make()
.use(commonmark)
.configure((ctx) => {
ctx.set(editorViewOptionsCtx, {
// 仅渲染可视区域附近的内容
nodeViews: {
// 为大型节点实现延迟加载
code_block: (node, view, getPos) => {
const dom = document.createElement('div');
dom.className = 'code-block lazy-load';
// 初始只显示占位符
dom.innerHTML = '<div class="loading">加载代码块...</div>';
// 延迟加载实际内容
setTimeout(() => {
// 实际渲染逻辑
dom.innerHTML = `<pre><code>${node.textContent}</code></pre>`;
}, 100);
return { dom };
}
}
});
})
// 2. 限制历史记录大小
.use(history.configure({
maxHistoryLength: 50 // 限制历史记录为50步
}))
// 3. 禁用不必要的输入规则
.use(commonmark.configure((ctx) => {
ctx.update(commonmark.config, config => ({
...config,
inputRules: config.inputRules.filter(rule =>
// 只保留必要的输入规则
['heading', 'list', 'code_block'].includes(rule.nodeType)
)
}));
}))
.create();
性能监控与分析:
// 监控编辑器性能
Editor.make()
.use(commonmark)
.use(Plugin.create('performance-monitor', (ctx) => {
let lastUpdateTime = 0;
return {
view: (view) => {
return {
update: (prevState) => {
const now = performance.now();
const duration = now - lastUpdateTime;
// 记录长时更新
if (duration > 50) { // 超过50ms的更新视为慢更新
console.warn(`Slow update detected: ${duration.toFixed(2)}ms`);
// 分析状态变化
const transactions = view.state.transactions;
transactions.forEach(tr => {
if (tr.docChanged) {
console.log('Document changes:', tr.steps.length, 'steps');
}
});
}
lastUpdateTime = now;
}
};
}
};
}))
.create();
框架集成:React组件封装
import React, { useRef, useEffect, useState } from 'react';
import { Editor, EditorInstance } from '@milkdown/core';
import { commonmark } from '@milkdown/preset-commonmark';
import { crepe } from '@milkdown/crepe';
import { useEditor } from '@milkdown/react';
interface MilkdownEditorProps {
defaultValue?: string;
onChange?: (value: string) => void;
className?: string;
}
export const MilkdownEditor: React.FC<MilkdownEditorProps> = ({
defaultValue = '',
onChange,
className = ''
}) => {
const editorRef = useRef<EditorInstance | null>(null);
const [loading, setLoading] = useState(true);
const render = useEditor((root) => {
return Editor.make()
.use(commonmark)
.use(crepe)
.select(root)
.whenReady((editor) => {
editorRef.current = editor;
setLoading(false);
// 设置初始内容
if (defaultValue) {
editor.setContent(defaultValue);
}
// 监听内容变化
const unsubscribe = editor.onUpdate(() => {
editor.getContent().then(content => {
onChange && onChange(content);
});
});
return unsubscribe;
});
});
return (
<div className={`milkdown-editor ${className}`}>
{loading && <div className="loading">初始化编辑器中...</div>}
<div ref={render} />
</div>
);
};
// 使用组件
const App = () => {
const [content, setContent] = useState('');
return (
<div className="app">
<h1>React Milkdown编辑器</h1>
<MilkdownEditor
defaultValue="# 开始编辑..."
onChange={setContent}
className="editor-container"
/>
<div className="preview">
<h2>预览</h2>
<pre>{content}</pre>
</div>
</div>
);
};
生态系统与未来展望
核心插件矩阵
milkdown生态提供丰富的官方插件:
| 插件类别 | 核心插件 | 功能描述 |
|---|---|---|
| 基础功能 | commonmark | 基础Markdown语法支持 |
| 扩展语法 | gfm | GitHub Flavored Markdown |
| 编辑体验 | history | 撤销/重做历史 |
| 编辑体验 | tooltip | 悬浮提示与工具条 |
| 编辑体验 | slash | 命令菜单与自动补全 |
| 内容元素 | table | 表格编辑支持 |
| 内容元素 | emoji | 表情符号选择器 |
| 内容元素 | image | 图片上传与预览 |
| 协作功能 | collab | 基于Yjs的协作编辑 |
| 代码支持 | prism | 代码高亮与语法提示 |
| 交互增强 | clipboard | 增强剪贴板操作 |
| 交互增强 | listener | 文档事件监听 |
主题系统
milkdown提供灵活的主题系统,官方主题包括:
- crepe:轻量级基础主题
- nord:基于Nord配色方案的现代主题
- frame:简洁框架风格主题
自定义主题示例:
import { Theme, createTheme } from '@milkdown/core';
import { nord } from '@milkdown/theme-nord';
const myTheme = createTheme((utils) => {
// 继承nord主题
const baseTheme = nord(utils);
return {
...baseTheme,
// 自定义CSS变量
cssVars: {
...baseTheme.cssVars,
primary: '#4f46e5', // 主色调:靛蓝色
secondary: '#10b981', // 辅助色:绿色
neutral: '#1f2937', // 中性色:深灰
'neutral-content': '#f9fafb' // 中性内容色:浅灰
},
// 自定义组件样式
components: {
...baseTheme.components,
button: {
className: 'my-button',
// 自定义渲染函数
render: (props) => {
return utils.h('button', {
className: `my-button ${props.className}`,
style: { backgroundColor: utils.cssVar('primary') },
...props.attrs
}, props.children);
}
}
}
};
});
// 使用自定义主题
Editor.make()
.use(myTheme)
.create();
未来发展方向
milkdown团队计划在以下方向持续迭代:
- AI集成:内置AI辅助编辑功能,支持智能补全和格式优化
- 扩展生态:提供更多领域特定插件(绘图、公式、图表等)
- 性能优化:改进大型文档处理能力,支持百万字级文档流畅编辑
- 移动端适配:增强触摸交互支持,优化移动设备编辑体验
- 本地化支持:完善多语言包和RTL(从右到左)文本支持
结论:重新定义Markdown编辑体验
milkdown通过插件驱动架构,打破了传统编辑器的功能边界,为开发者提供了构建高度定制化编辑体验的能力。其核心优势在于:
- 极致灵活:从基础文本到复杂富媒体,从个人博客到企业协作系统,满足全场景需求
- 性能卓越:基于ProseMirror的高效文档模型,支持大型文档流畅编辑
- 易于扩展:完善的插件生态和详细的API文档,降低定制开发门槛
- 现代架构:TypeScript原生支持,与React/Vue等现代框架无缝集成
无论是构建简单的Markdown编辑器,还是开发复杂的富文本协作系统,milkdown都提供了坚实的基础和灵活的扩展能力。随着生态系统的不断完善,milkdown有望成为下一代富文本编辑技术的标准解决方案。