ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

WANGEDITOR源码获取与二次开发:从克隆构建到自定义菜单与报错排查

2026/10/1 10:49:44 拓冰建站 浏览量
WANGEDITOR源码获取与二次开发:从克隆构建到自定义菜单与报错排查 做富文本编辑器的几乎没人不知道 WANGEDITOR。这个从 2015 年一路走到现在的国产开源项目在 GitHub 上已经积累了相当高的知名度也是很多团队做选型时的第一批候选对象。经常有人在社群里问互联网大厂是怎么拿到 WANGEDITOR 完整源码做二次开发的是不是有什么内部渠道其实答案很直接——WANGEDITOR 本身就是 Apache-2.0 协议开源的完整源码、构建脚本、示例工程全都公开在官方仓库里。你要做的不是费劲去“找”而是按正确姿势拉取、构建、阅读然后在这个基础上做自己的定制。这篇文章把我实际折腾过的路径完整梳理一遍从源码获取方式、仓库结构拆解、本地构建到基于源码的菜单注册、只读模式配置再顺带把最近群里高频出现的报错uncaught (in promise) error: unable to find a host window el的完整排查思路写出来。适合想要在团队里引入富文本编辑器并做二次封装的前端负责人也适合那些想通过阅读开源项目源码来提升自己的开发者。整体内容偏实践照着做基本能复现。1. 获取完整源码前先把这三件事搞清楚1.1 开源协议决定你拿源码的方式合不合规先聊一个很多人忽略但大厂技术委员会卡得最死的问题合规。WANGEDITOR 使用的是 Apache-2.0 协议这意味着你可以自由地商用、修改、再分发前提是保留原作者的版权声明并且如果你修改了源码需要在分发时明确说明。所以大厂获取源码的方式百分之百是走官方仓库也就是 GitHub 上的wangeditor-team/wangEditor或者官方提供的 Gitee 镜像。我见过不少团队图省事从网上的“源码汇总站”下载压缩包回来用。这个做法相当危险。第三方转发的包一是版本滞后你拿到的可能是几个月前的代码线上 bug 修没修都不知道二是存在被篡改的风险真被塞进一段挖矿代码或者数据上报脚本生产环境出事的时候你连排查方向都没有。从我踩过的坑来说源码永远只从官方仓库和官方 npm 包获取这是第一条铁律。1.2 v4 和 v5两个版本两套完全不同的源码世界很多人搜“wangeditor 怎么设置只读”搜出一堆答案试了却不生效十有八九是版本搞混了。WANGEDITOR 的 v4 和 v5 虽然是同一个项目但底层架构完全不一样。v4 是单体仓库核心代码集中在一个src目录下使用方式偏传统比如new WangEditor(selector)配置也是挂在editor.config上。v5 则做了彻底的重构采用 Monorepo 架构用 pnpm workspace 管理多个子包暴露出来的是全新的 API比如createEditorcreateToolbarnpm 包名也从wangeditor变成了wangeditor/editor。这里我直接给一张对比表方便你快速判断自己在哪个版本对比项v4v5npm 包名wangeditorwangeditor/editor初始化方式new WangEditor(selector)createEditor({...})源码结构单包结构Monorepo多包管理框架适配需要自己封装官方提供 Vue/React 子包只读 APIconfig.readOnlyeditor.disable()/editor.enable()扩展菜单自定义菜单类Boot.registerMenu注册看文档、搜 issue、查源码之前第一件事永远是把版本钉死。版本不统一后面所有操作都是乱码。1.3 三种拿到完整源码的具体路径第一种也是我最推荐的方式直接从 GitHub 官方仓库拉取。git clone https://github.com/wangeditor-team/wangEditor.git这种方式拿到的是最原始、最完整的工程目录包括源码、构建脚本、测试用例、文档示例什么都没有丢。如果你在访问 GitHub 时有困难用 Gitee 的官方镜像也一样代码同步频率够高搞二次开发足够了。第二种方式是通过 npm 包看“半套源码”。你执行npm install wangeditor/editor之后去node_modules/wangeditor/editor目录里翻能看到dist下面一堆编译后的 JS 产物。注意一点npm 包里默认放的是构建产物不是 TS 源码。产物被压缩和转译过读起来非常痛苦定位问题也不方便。第三种方式是在线看源码。像 GitHub 网页端自带的代码浏览、按下句点号打开的网页版编辑器临时看单个文件还可以真要大规模搜索、跟踪某个菜单的实现逻辑效率太低了。长期做二次开发的人本地 clone 永远是第一选择。2. 完整源码结构拆解Monorepo 里到底藏着什么2.1 为什么从单体仓库拆成 Monorepo拿到源码之后打开仓库根目录你首先看到的是一个packages目录里面躺着好几个独立发布的 npm 包。这就是 Monorepo 的典型结构。WANGEDITOR 拆成了核心层、编辑器层、框架适配层等等。核心层管的是编辑器内部的数据模型、渲染机制、选区处理这些“重逻辑”编辑器层则负责工具栏、菜单、UI 这些偏视觉的模块框架适配层就是给 Vue 和 React 用的封装组件。拆包最直接的好处是Vue 项目和 React 项目可以共用同一个核心层内核的任何能力升级两个框架生态都能同步享受到。对于做二次开发的人来说这意味着你改一次核心包Vue 和 React 两端的效果会同时变化。你能改动的最小单元变小了但能影响的范围反而变大了。2.2 核心包源码的关键目录进入到packages/editor/src之后你会看到一串功能明确的目录。这里挑几个最常打交道的列出来menus每个菜单一个目录。想改按钮文案、调整图标、给它加新的点击行为都来这里。plugins编辑器的内置插件包括图片上传、表情、代码块等能力的实现。render把编辑器的数据模型渲染成真实 DOM 的逻辑。parse反过来把 HTML 字符串解析成编辑器内部数据结构。locale国际化文案。utils通用工具函数DOM 操作、事件绑定、校验逻辑都在这里。举个例子你想看“加粗”这个菜单是怎么实现的直接进menus目录找到对应的模块里面的类会清晰告诉你它的触发命令、执行逻辑、以及如何更新自己的选中状态。这种目录组织方式对新人很友好你完全可以从一个菜单入手顺着调用链把数据流读通。2.3 编译产物与源码的差距开发业务页面时用 npm 包里的 dist 产物没什么问题但如果你想做深度定制真正要读的是src目录下的 TS 源码。dist 里的代码已经被打包工具做了压缩和变量重命名函数名可能变成a、b、c断点调试进去像在看天书。源码则保持完整的类型、注释和结构许多核心函数上面还留着作者的思路说明。这也解释了为什么要强调“完整源码”——npm dist 只是编译后的结果源码才是拿来理解和改动的起点。理解了这层关系你就不会在 dist 文件里死磕一天了。3. 从源码构建一份自己的 WANGEDITOR完整实操3.1 环境准备pnpm 和 Node 版本因为仓库是 pnpm workspace所以第一步一定是装 pnpm。直接用 npm 全局安装就行。npm install -g pnpmNode 版本建议用 18 以上。如果你机器上有多套 Node建议用 nvm 切到项目要求的版本再构建避免开一堆莫名其妙的兼容性问题。很多人第一次构建失败十有八九是 Node 版本太老或者包管理器不对。3.2 拉代码、装依赖、跑构建具体命令如下。git clone https://github.com/wangeditor-team/wangEditor.git cd wangEditor pnpm install pnpm run buildpnpm install会按照 workspace 的配置把各个子包的依赖统一装到根目录的node_modules下。pnpm run build会触发整个工程的构建流程把各个packages的源码编译成可发布的产物。构建完成之后进入packages/editor/dist目录你会看到一堆index.js、style.css之类的输出文件这就是可以实际引用的成品了。如果只想单独构建某一层可以用 pnpm 的 filter 参数精确指定。pnpm --filter wangeditor/editor build3.3 本地把构建产物接到业务项目里构建好源码之后最常见的需求是把它接到自己的业务项目里联调。这里有三种做法第一种用pnpm link做软链接。在packages/editor目录下执行pnpm link然后在业务项目里执行pnpm link wangeditor/editor。好处是改动源码后业务项目能实时感知适合日常开发调式。第二种用file:协议直接指定本地路径。在业务项目的package.json里把依赖改为wangeditor/editor: file:../wangEditor/packages/editor这种做法最直观但注意它会把整个目录复制过去后续源码更新了需要重新执行安装命令。第三种直接引入构建产物的绝对路径。比如在 Vite 项目里配置 alias把wangeditor/editor指向你构建出来的文件。这种方式自由度最高但对构建工具的配置要求也多不建议新手上来就搞。我个人最常用的是pnpm link联调效率最高改完源码刷新页面就能看到效果。3.4 源码构建时容易踩的几个坑依赖版本冲突是最常见的问题。pnpm 的严格依赖模式对工作区场景支持很好但如果你发现某些依赖装不上可以检查一下仓库根目录有没有.npmrc配置文件里面可能写着shamefully-hoisttrue之类的设置这个设置决定依赖是否会被提升到根目录。样式文件丢失是另一个坑。WANGEDITOR 的样式并不是全在 JS 里构建后你会得到一个单独的 CSS 文件比如style.css。手动引入源码产物时很多人忘了加载 CSS导致编辑器渲染出来没有工具栏、没有菜单图标像一件没穿衣服的半成品。业务代码里记得加上import wangeditor/editor/dist/css/style.css这一行没写后面排查半天都想不到是样式问题。4. 基于完整源码的典型二次开发实战4.1 注册自定义菜单给编辑器加一个自己的按钮读完源码之后第一个值得动手的实践是注册自定义菜单。WANGEDITOR v5 提供了一种非常干净的扩展方式不需要改任何源码文件只需要用Boot注册你的自定义能力。举个例子我想给工具栏加一个点击后插入特定文本的按钮import { Boot, IDomEditor, IButtonMenu } from wangeditor/editor class InsertHelloMenu implements IButtonMenu { readonly title 插入问候语 readonly tag button readonly iconSvg svg.../svg getValue(editor: IDomEditor): string { return } isActive(editor: IDomEditor): boolean { return false } isDisabled(editor: IDomEditor): boolean { return false } exec(editor: IDomEditor, value: string | boolean) { if (editor.selection) { editor.insertText(你好世界) } } } Boot.registerMenu({ key: insertHelloMenu, factory() { return new InsertHelloMenu() }, })这段代码在日常业务集成里已经可以直接用。它的核心思路是把“按钮长什么样”和“点击后干什么”封装成一个类然后交给 Boot 注册。这就是 v5 可扩展性的典型体现。为什么我不建议直接修改源码目录里的menus文件来加菜单因为一旦你改了上游源码后续官方发新版你就没法无缝升级了。要么你持续维护一整个 fork 的 diff要么你放弃升级。用Boot.registerMenu注册扩展你的代码和 WANGEDITOR 本体是分开的升级时只需要重新构建一下扩展逻辑不受影响。4.2 WANGEDITOR 怎么设置只读四个可行路径这是一个被问烂了的问题但网上的答案七零八落经常会误导新人。我只说结论只读设置要看版本而且至少有四种不同路径。第一种不渲染工具栏。工具栏都不出现用户自然没办法编辑。这是最简单粗暴的方式。第二种用 v4 的配置项。如果你还在用wangeditor这个老包配置方式是这样的const editor new WangEditor(#editor) editor.config.readOnly true editor.create()第三种用 v5 提供的实例方法。这是官方推荐的做法const editor createEditor({ selector: #editor }) editor.disable() // 进入只读 editor.enable() // 恢复编辑第四种隐藏特定工具栏按钮。通过toolbarConfig.excludeKeys排除编辑相关的菜单键。我这里给一个表格方便你做决定方案适用版本效果等价做法不传 toolbar 配置v4 / v5无工具栏纯展示手动隐藏工具栏 DOMconfig.readOnlyv4编辑器不可编辑无editor.disable()v5编辑器整体禁用editor.enable()恢复toolbarConfig.excludeKeysv5隐藏指定菜单按钮适用于部分只读场景实操时我一般推荐“使用 v5 的disable()或 v4 的config.readOnly”理由是这个 API 是官方为只读场景设计的内部还会处理焦点、光标、键盘事件等细节比手动藏按钮可靠得多。4.3 Vue/React 集成时绕过“编译地狱”二次开发时容易遇到另一个问题业务项目打包报错报错信息指到node_modules/wangeditor/editor里面的 TS 文件上。这个通常是因为有人直接把源码路径引入了业务工程而业务项目的构建器默认不会去编译 node_modules 里的 TypeScript。正确的做法是引入构建产物而不是源码。这里有两个选择一是直接用官方准备好的框架子包比如 Vue3 用wangeditor/editor-for-vueReact 用wangeditor/editor-for-react二是自己用createEditorcreateToolbar做封装。前者拿来即用后者灵活度高。以 Vue3 为例一个最简可用的集成是template div ideditor-container/div /template script setup import { onMounted } from vue import { createEditor, createToolbar } from wangeditor/editor import wangeditor/editor/dist/css/style.css onMounted(() { const editor createEditor({ selector: #editor-container, html: p初始内容/p, }) const toolbar createToolbar({ editor, selector: #toolbar-container, }) }) /script很多新手在这里会踩“DOM 还没渲染就去创建编辑器”的坑。Vue 的onMounted已经能保证当前组件 DOM 挂载完成但如果你的容器是动态渲染的比如v-if控制显示那就必须等容器真正出现在页面上再初始化。后面第五节要讲的unable to find a host window el报错本质上就和这个问题强相关。5. 高频报错排查实录uncaught (in promise) error 是怎么来的5.1 先还原现场最近群里被问得最多的一条报错是Uncaught (in promise) Error: unable to find a host window el先拆解一下这句话。“host window el”指的是编辑器宿主窗口里的容器元素。报错的含义很明确编辑器在初始化时找不到你传给它的那个 DOM 容器或者说找不到它所在的那个 window 环境。这个报错最常见的触发场景有两个一个是容器元素在你调用createEditor时根本还没渲染出来另一个是当前运行环境根本没有正常的 window 对象。下面这两段代码第一段一定会触发这个错误第二段则是修复后的写法。// 错误示范元素还没渲染就初始化 const editor createEditor({ selector: #editor, // 此时 #editor 还不存在 html: , })// 正确做法等待 DOM 渲染完成后再初始化 import { nextTick } from vue onMounted(async () { await nextTick() // 或者用一个 setTimeout 也行但 nextTick 更可靠 const editor createEditor({ selector: #editor, html: , }) })5.2 一个可以复用的排查过程我自己的定位过程基本是这几步按顺序走大部分问题五分钟内能锁死。第一步先检查selector对应的 DOM 是否存在。打开浏览器控制台执行document.querySelector(#editor)如果返回null那就不是编辑器的问题是你的页面渲染时序问题。Vue 项目里面onMounted里没渲染完的容器基本是v-if还没变成trueReact 项目则要注意useEffect的实际执行时机。第二步确认代码运行在浏览器环境而不是服务端环境。如果你用了 SSR服务端渲染比如 Next.js 或者 Nuxt页面服务端执行时是没有window对象的。这时候一定不能让编辑器初始化的代码在服务端执行。最简单的处理方式if (typeof window ! undefined) { // 只有浏览器端才初始化编辑器 }第三步检查是否处于微前端环境。如果编辑器运行在 iframe 或某个微前端子应用里window 对象的指向会比较特殊。WANGEDITOR 初始化时会在当前宿主环境里查找容器如果主应用和子应用的 window 不统一也会出现找不到容器的情况。处理办法是确认你的容器元素确实渲染在编辑器实例所在的 window 下。第四步检查 DOM 是否被重复渲染或覆盖。比如你的容器 id 在页面上出现了两次WANGEDITOR 拿到第一匹配结果可能是隐藏的那一个编辑器初始化之后看起来就是一片空白。5.3 一套可以直接拿去用的排查速查表场景报错原因解决方法Vue 中onMounted马上初始化动态容器未渲染完成加await nextTick()React 中useEffect初始化依赖项未正确触发确认容器已在 commit 阶段存在服务端渲染Node 环境没有 window用typeof window兜底微前端环境window 上下文不一致确认初始化发生在子应用容器中容器 id 重复选择器选中了隐藏节点改用唯一 id 或 ref 对象单元测试环境jsdom 模拟的 window 不完整配置测试环境或 mock 编辑器初始化这张表是我根据真实问题总结的几乎覆盖了 GitHub issue 里这个报错的绝大多数来源。还有一个细节值得多说一句不要用太通用的 id比如#editor、#container在一个复杂的后台系统里很容易撞 id。项目里所有初始化一致地、可控地传入容器元素本身尽量少依赖 id 字符串能避免超多隐藏问题。5.4 几个“初看没问题”但实际会踩的细节很多人在本地开发环境一切正常一上测试环境就复现unable to find a host window el这种情况往往不是代码时序问题而是构建后的资源加载顺序问题。比如 CSS 加载晚于 JS编辑器初始化时样式还没就位虽然不直接影响 DOM 查找但页面可能会出现闪烁。还有一个容易被忽略的是多编辑器共存。同一个页面上创建多个 WANGEDITOR 实例时记得每个实例都要有独立的selector。我见过有项目用同一个 id 创建两个编辑器后一个实例直接覆盖前一个的操作还会引发类似的宿主元素异常。另外如果你是做移动端适配要注意安卓 WebView 对 DOM API 的支持差异。某些老版本 WebView 里容器元素虽然存在但getComputedStyle等 API 的返回结果有兼容性问题编辑器初始化时拿不到正确的宽高信息表现起来也接近这个报错。这种情况建议优先升级 WebView 内核或者用官方推荐的初始化参数做降级。最后分享一个我自己的习惯。每次接入 WANGEDITOR我都会加一个很轻量的封装模块统一处理初始化时机、版本判断、容器 id 分配、以及window环境判断。这样不管业务页面是 Vue 还是 React是服务端渲染还是纯浏览器入口都是一样的。以后排查问题的时候只需要看这一个文件就够了不用翻遍整个业务代码找哪一行在初始化编辑器。这个思路也适用于任何复杂的第三方编辑器集成值得你在项目里试一试。