Quill 2.0 富文本编辑器上手:从 README 快速开始到源码级的构建产物与主题机制解析
【免费下载链接】quillQuill is a modern WYSIWYG editor built for compatibility and extensibility项目地址: https://gitcode.com/GitHub_Trending/qu/quill
本文基于 Quill 仓库根目录的 README 文档展开,覆盖其核心内容——编辑器初始化、npm 安装与 CDN 引入、两种主题与核心构建的区别,并结合packages/quill下的源码与构建配置,深入解析new Quill()背后的初始化流程、quill.js/quill.core.js两个入口的产物差异、snow/bubble 主题的默认工具栏配置,以及仓库的测试与开发约定。读完你可以独立完成 Quill 的接入,并理解每个配置项在源码中的落点。
项目定位:面向兼容性与可扩展性的现代 WYSIWYG 编辑器
README 开宗明义:Quill 是一个为兼容性与可扩展性而构建的现代富文本编辑器,由 Jason Chen 与 Byron Milligan 创建,当前由 Slab 团队积极维护。仓库关键词(见 根 package.json)包含wysiwyg、rich text、operational transformation、ot,这提示了 Quill 的核心设计取向:文档内容以 Delta(操作序列)为数据模型,编辑器 DOM 只是其呈现层。
从依赖看(packages/quill/package.json),Quill 本体只依赖四个运行时库:
parchment:DOM 与文档之间的抽象层(blot 注册表体系);quill-delta:Delta 数据模型;eventemitter3:事件系统;lodash-es:通用工具函数。
这一极简依赖结构也是其"兼容性"承诺的一部分——编辑器没有强制绑定任何 UI 框架。
快速开始:一个容器、一个工具栏、一行 new Quill
README 的 Quickstart 给出了最小可运行示例。这里完整保留原示例(CDN 引用来展示最直接的接入方式):
<!-- 引入 Quill 主题样式 --> <link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" /> <!-- 创建工具栏容器 --> <div id="toolbar"> <button class="ql-bold">Bold</button> <button class="ql-italic">Italic</button> </div> <!-- 创建编辑器容器(初始内容会由 Quill 接管) --> <div id="editor"> <p>Hello World!</p> <p>Some initial <strong>bold</strong> text</p> <p><br /></p> </div> <!-- 引入 Quill 库 --> <script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script> <!-- 初始化 Quill 编辑器 --> <script> const quill = new Quill("#editor", { theme: "snow", }); </script>示例中有三个关键点值得注意:
- 编辑器容器:
#editor内的初始 HTML 会被 Quill 读取并转换为内部 Delta 表示。从源码看(packages/quill/src/core/quill.ts),构造函数会保存容器的innerHTML,清空容器,随后通过clipboard.convert()把初始 HTML 转成内容并setContents(),同时清空历史记录(history.clear())。 - 工具栏容器:README 示例中
#toolbar在编辑器容器外部,按钮通过ql-bold、ql-italic这类 class 与工具模块关联。主题在构建按钮时会扫描ql-*class 并注入对应图标,见 BaseTheme.buildButtons。 - theme 选项:
theme: "snow"决定了 UI 形态。若不传 theme,默认值为'default'(见下文选项表),对应不带样式增强、不带默认工具栏的基础主题。
new Quill() 做了什么:初始化流程源码走读
构造函数 Quill.constructor 的执行顺序可以归纳为:
expandConfig()展开配置:解析容器选择器、按名称导入主题类、合并模块配置(见下文"配置系统");- 给容器加
ql-containerclass,内部创建ql-editor根节点并加ql-blankclass; - 通过注册表取出
ScrollBlot,实例化出scroll(编辑器根 blot)、Editor、Selection、Composition; - 实例化主题对象,并强制初始化四个核心模块:
keyboard、clipboard、history、uploader,随后再挂载input、uiNode并调用theme.init()(其余模块如 toolbar 在此阶段由主题按需创建); - 监听
SCROLL_UPDATE等事件,将 DOM 变化(MutationObserver 结果)归一化为 Delta 变更并触发TEXT_CHANGE; - 若有初始 HTML,则经 clipboard 转换后写入;若配置了
placeholder,写入data-placeholder属性;若readOnly: true,调用disable()禁用编辑。
理解这个流程的意义在于:README 中"传一个 CSS 选择器"这么简单的一行调用,背后实际完成了注册表查询、模块装配与 DOM 观察器挂载,这也是 Quill 能同时支持"自定义注册表/自定义格式"的扩展入口。
两种安装方式:npm 与 CDN
npm 安装
README 给出的标准命令:
npm install quillpackages/quill/package.json中声明了"main": "quill.js"与"type": "module",即打包后的dist目录既是 UMD 全局构建(浏览器<script>标签场景),也提供 ES 模块形态,可直接被 webpack、Vite 等打包器消费。仓库要求npm >= 8.2.3(engines字段,engineStrict: true)。
CDN 引入
README 列出了全部 CDN 产物,这里原样保留并补充每个产物的用途:
<!-- 主库:包含全部内置格式、模块与主题 --> <script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script> <!-- 主题样式(按所选 theme 选项引入其一) --> <link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.bubble.css" rel="stylesheet" /> <!-- 核心构建:无主题、无内置格式、无非必要模块 --> <link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.css" rel="stylesheet" /> <script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.js"></script>三个构建产物从哪来:webpack 入口与注册清单
这些文件名并非约定俗成,而是由 packages/quill/webpack.common.cjs 的entry字段直接决定:
entry: { quill: './src/quill.ts', 'quill.core': './src/core.ts', 'quill.core.css': './src/assets/core.styl', 'quill.bubble.css': './src/assets/bubble.styl', 'quill.snow.css': './src/assets/snow.styl', }也就是说,"完整版"与"核心版"的差异完全体现在两个 TS 入口分别注册了什么:
- packages/quill/src/core.ts(
quill.core.js):只注册基础 blot(Block/Container/Scroll/Text等)与核心模块(clipboard/history/keyboard/uploader/input/uiNode)。选择这个构建,意味着你要自己用Quill.register()注册需要的格式与模块,体积最小、可控性最高。 - packages/quill/src/quill.ts(
quill.js):在 core 之上再注册一批"开箱即用"内容,包括:- attributors 与格式类:
align、background、color、direction、font、size、indent、blockquote、code-block、header、list、bold、italic、link、script、strike、underline、formula、image、video等(src/quill.ts#L53-L116); - 模块:
syntax(代码高亮)、table、toolbar; - UI 组件:
Icons、Picker、ColorPicker、IconPicker、Tooltip; - 主题:
themes/bubble、themes/snow。
- attributors 与格式类:
注册机制本身也值得看一眼:Quill.register 以blots/、formats/、modules/、themes/为路径命名空间存储到Quill.imports;对 blot/格式还会同步注册进全局 Parchment 注册表。Quill.import(name)则按路径取出组件,主题与模块的动态加载都依赖它。
配置项解析:README 未列全的选项,源码里的完整清单
README 只演示了theme一个选项。真实的选项定义在 QuillOptions 接口 与Quill.DEFAULTS(quill.ts#L80-L92)中,整理如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
theme | string | 'default' | 主题名,按themes/<name>从注册表导入;未注册时抛错 |
debug | DebugLevel \| boolean | 未设置 | 日志级别(false、error、warn、log、true视为log) |
registry | Parchment.Registry | 全局注册表 | 自定义 blot 注册表;指定后将忽略formats选项 |
readOnly | boolean | false | 只读模式,初始化时调用disable() |
placeholder | string | '' | 编辑器为空时显示的占位文本(写入data-placeholder) |
bounds | HTMLElement \| string \| null | null | 悬浮 UI(如 tooltip)的坐标参照容器 |
modules | Record<string, unknown> | 见下 | 各模块配置;true表示启用并采用模块默认配置 |
formats | string[] \| null | null | 白名单式格式过滤;null表示允许全部格式 |
默认启用的核心模块(DEFAULTS.modules)为clipboard、keyboard、history、uploader,且均为true(即用各自默认配置)。注意toolbar不在core 默认模块里——它由主题或用户在modules中显式启用。
配置合并逻辑在 expandConfig:主题类静态DEFAULTS→ 用户options.modules逐层合并;模块配置值为true时展开为空对象再与模块自身的DEFAULTS合并;配置值为 falsy 的模块会被剔除(即可以modules: { history: false }禁用模块)。另外有一个实用捷径:modules.toolbar若传的是选择器字符串或 DOM 节点(而非普通对象),会被自动改写为{ container: 该值 }——这就是 README 示例中工具栏"自动挂载"的原因。
formats选项与registry互斥:若同时指定registry,源码会打印警告并忽略formats(quill.ts#L840-L849)。若只传formats,则通过 createRegistryWithFormats 基于全局注册表派生一个只包含白名单格式的注册表——这是做"受限编辑器"(例如只允许加粗/斜体/链接)的官方手段。
主题机制:snow 与 bubble 在源码中的真实差异
README 的 CDN 清单暗示了两个主题样式:quill.snow.css与quill.bubble.css。它们的实现分别在 packages/quill/src/themes/snow.ts 与 packages/quill/src/themes/bubble.ts,共同继承 BaseTheme。
各自的默认工具栏
两个主题在构造时若发现toolbar已启用但未指定container,会填入各自内置的工具栏布局:
snow(常驻式工具栏,置于编辑器上方):
// src/themes/snow.ts const TOOLBAR_CONFIG: ToolbarConfig = [ [{ header: ['1', '2', '3', false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered' }, { list: 'bullet' }], ['clean'], ];bubble(选中文字时浮起的悬浮工具栏):
// src/themes/bubble.ts const TOOLBAR_CONFIG: ToolbarConfig = [ ['bold', 'italic', 'link'], [{ header: 1 }, { header: 2 }, 'blockquote'], ];
snow 主题还会把工具栏容器加ql-snowclass、构建按钮/选择器图标,并为.ql-link按钮追加Ctrl/⌘+K快捷键绑定(snow.ts#L106-L122);bubble 主题的 tooltip 则在用户选中文字时出现于选区上方(BubbleTooltip 监听SELECTION_CHANGE,用getBounds()定位)。
主题默认 handler:链接、图片、公式、视频
BaseTheme.DEFAULTS 预置了三个工具栏 handler:
image:动态创建input[type=file],accept取自uploader模块的mimetypes配置,选中文件后调用quill.uploader.upload(range, files)走上传流程;formula/video:调起 tooltip 的编辑模式(tooltip.edit('formula' | 'video')),在浮层中粘贴内容或 URL 后回车插入。
snow 主题在其上覆盖了linkhandler:选中文字点链接按钮时弹出输入框,并内置了"看起来像邮箱就自动补mailto:"的判断(snow.ts#L124-L149)。bubble 的link则直接调起 tooltip 编辑。这些 handler 都可以通过modules.toolbar.handlers覆盖——这是 Quill 扩展自定义按钮行为的标准做法。
常用 API 速查(对照核心源码)
README 未展开 API,但结合 Quill 类 的公开方法,日常开发最常用的有:
const quill = new Quill("#editor", { theme: "snow" }); // 监听内容变化:change 是 Delta,oldContents 是旧内容 Delta quill.on("text-change", (delta, oldDelta, source) => { const json = quill.getContents().ops; // 序列化为 ops,便于存库/协作 }); // 读写内容 quill.getContents(); // 全量 Delta quill.getText(); // 纯文本 quill.setContents([{ insert: "Hello\n" }]); quill.updateContents(new Quill.Delta().retain(1).delete(5)); // 应用 Delta // 选区 quill.getSelection(); // { index, length } | null quill.setSelection(2, 4); // 格式化 quill.format("bold", true); // 作用于当前选区 quill.formatText(0, 5, { header: 1 }); // 指定区间 quill.formatLine(0, 1, "align", "center"); // 状态控制 quill.enable(false); // 等价 disable(),加 ql-disabled class quill.disable();这些方法的共同骨架是文件底部的 modify():记录变更前 Delta、执行变更、按变更内容移动选区(shiftRange)、最后以text-change与editor-change双事件派发,且source(user/api/silent)区分了触发来源——这是理解 Quill 事件流的钥匙。另外Quill.version、Quill.import('delta')、Quill.debug('log')等静态成员也定义在同一文件,方便调试与取用 Delta 类。
仓库工程结构:开发、测试与许可
README 尾部还交代了社区入口(Issues / Discussions)与 BSD 3-clause 许可(与 根 package.json 及 packages/quill/package.json 中的BSD-3-Clause一致)。从仓库结构看,开发侧的几个事实供继续深入者参考:
- 这是 npm workspaces monorepo:
packages/quill(编辑器本体,v2.0.3)与packages/website(文档站点与 playground)。根package.json的start会并行启动两者的 dev server(webpack 端口 9080、网站 9000)。 packages/quill的脚本:build(production 打包)、lint(ESLint + tsc)、test:unit(Vitest,配置在 test/unit/vitest.config.ts)、test:e2e(Playwright,playwright.config.ts)、test:fuzz(模糊测试,test/fuzz)。- 单元测试覆盖面与源码模块一一对应,例如
test/unit/formats/、test/unit/modules/、test/unit/blots/;e2e 用例位于test/e2e/(如 full.spec.ts)。 - 图标资源(
src/assets/icons/*.svg)经html-loader内联进 JS 构建,见 webpack.common.cjs 的 svgRules。
小结
- 接入路径很简单:一个容器 +
new Quill(selector, { theme }),样式与脚本按主题引入(quill.snow或quill.bubble)。 - 产物分
quill.js(全量:格式、模块、主题)与quill.core.js(最小核心:blot + 核心模块),由 webpack 入口 明确定义,按需选型。 - 配置面(
theme/modules/formats/registry/placeholder/readOnly/bounds/debug)以 QuillOptions 与 expandConfig 为准;formats白名单与自定义registry是受限编辑与完全定制的两条路线。 - snow/bubble 主题的默认工具栏、链接/图片/公式 handler 均可通过
modules.toolbar与 handler 覆盖进行二次开发,入口类在 themes/snow.ts 与 themes/bubble.ts。
【免费下载链接】quillQuill is a modern WYSIWYG editor built for compatibility and extensibility项目地址: https://gitcode.com/GitHub_Trending/qu/quill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考