Vditor 生命周期(运行时 runtime)设计构想

本贴最后更新于 1052 天前,其中的信息可能已经时移世改

写在前面的

Vditor 目前来说是没有生命周期(或称运行时这一概念,下称为 runtime)。好处是可以以最少的 API 实现编辑器的渲染,但也导致了代码整体解耦仍然不够的问题。虽然可以通过配置项实现自定义渲染器的功能,但仍然没有统一的插件开发工程和独立插件发布。因此,我开始尝试对 Vditor 进行 runtime 设计。

抽象化插件开发

在设计 Vdok 之时,为了实现对标 VuePress、docsify、VitePress 等文档工具的语法效果,Vdok 尝试对 Vditor 进行第三方的语法拓展。(Vdok 目前是搁置了,因为没有太多持续的时间进行迭代,自行修改是可以跑起来的)在 Vdok 的开发之时,逐渐意识到样式隔离和插件标准化的意义。所以对于 Vditor 的 runtime 进行设计,首先就聚焦于使 Vditor 可以使用标准化的插件。

插件标准化的内容仍在探索之中,会随着版本的更新逐渐添加更多的开放特性。目前可用的类型设计如下:

/**
 * Vditor 注册插件类型
 * TODO: ILuteRenderCallback 类型重写, 现有类型不利于插件开发
 */
export interface IVditorPlugin {
    id: string
    renderers?: Map<keyof ILuteRender, ILuteRenderCallback>
    styles?: Map<string, string>
}

插件的命名规范为: /vditor-plugin-([a-zA-Z0-9_]+)/

插件示例如下:

const plugin_example: IVditorPlugin = {
  id: "vditor-plugin-test",
  renderers: new Map([
    [
      "renderDocument",
      function (node, entering) {
        return ["", Lute.WalkContinue]
      },
    ],
  ]),
}

需要解决的问题

Vditor 目前的类型定义对于插件开发仍然是不够用的。使用 TypeScript 进行部分自定义渲染器开发,无法兼容现有的 Vditor 类型定义。这个问题在 Vdok 开发的时候已经体会到了,可以参考 vdok/renderers.ts at main · HerbertHe/vdok (github.com) 的代码体会。

似乎 Vditor 现有的类型定义不能完全 cover 掉 Lute 支持的所有 API 情况,有些 API 对于部分的渲染器也是完全不可用的。所以重写此部分的类型定义是很有必要的。之前给 Lute PR,也在链滴里面回答了这部分的提问。其实,Lute 支持的自定义渲染器有很多、也很复杂,这部分的拓展需要阅读 Lute 的源码。

这部分的类型定义需要慢慢随着版本的更新,慢慢进行重写迭代。

链式调用使用插件

目前 Vditor 对象实例化的过程是高耦合的,一环环相扣,很难将插件进行注入。在参考主流前端框架之后,我还是觉得对象构造函数实例化和 DOM 挂载渲染的过程应该分离。

比如 React:

import React from "react";
import ReactDOM from "react-dom";

function Hello(props) {
  return <h1>Hello World!</h1>;
}

ReactDOM.render(<Hello />, document.getElementById("root"));

Vue:

const Counter = {
  data() {
    return {
      counter: 0
    }
  }
}

Vue.createApp(Counter).mount('#counter')

Vditor(options).render(HTMLDivElement)

因此,对于 Vditor 的方法可以进一步设计实现 runtime。

类似的语法如下:

const vditor = new Vditor(options).render(HTMLDivElement)

Vditor(options).use(plugins): IVditor

此 API 方法是先前提出的。在综合现有 Vditor 的代码之后,还是决定先修改 Vditor 的现有入口代码,以实现实例化和挂载渲染分离,提出了上面的 Vditor(options).render(HTMLDivElement) 方法进行手动挂载渲染。

通过返回 Vditor 实例,可以链式调用注册标准化插件,并且在实例不同的 runtime 阶段实现插件对应内容的注册。

重构 Vditor 的样式表

现有的 Vditor 样式表基于 Sass 进行开发,但是 node-sass 依赖的环境非常的复杂,在 Windows 平台太容易报错了。因此,使用 Less 进行重构 Vditor 样式表有进一步深远的意义。

除了上述依赖环境的问题之外,面向现代化的样式表在开发中越来越流行了。诸如 Tailwind CSS、Windi CSS、UnoCSS 等,在提高浏览器兼容性、减小样式表体积等前端优化上有很大的实用意义。迁移到 Less,可以快速引入上述第三方工具,减小开发复杂度、提升性能。

将在 runtime 设计迭代稳定之后,着手修改现有的构建工具配置实现样式表的重构。为支持标准化插件进行样式自定义,将在长远计划中提供 vditor-plugin-template 插件模板项目,实现无需关注进行底层构建工具配置。

Vditor 重构之后的样式表也同时准备支持统一化原有的布局风格,开放渲染特定位置的 API 给插件开发。

Vditor Plugin

目前 Vditor Plugin 设计的数据结构尚未稳定,将随着设计深入进行迭代。

在上面 抽象化插件开发 部分直观展示了目前的设计进度,下面是字段解释和更长期的设计计划。

字段解释

/**
 * Vditor 注册插件类型
 * TODO: ILuteRenderCallback 类型重写, 现有类型不利于插件开发
 */
export interface IVditorPlugin {
    id: string
    renderers?: Map<keyof ILuteRender, ILuteRenderCallback>
    styles?: Map<string, string>
}

id:即插件的标识符。规范已经在上述的内容中提及。

renderers:Vditor 自定义渲染器。采用了 Map 的数据结构,保证插件内自定义渲染器的唯一性。对于多插件的自定义渲染器冲突的问题,Vditor Plugin 将根据注册顺序进行倒序优先级排序。

styles:插件自定义样式表。采用约定的方式,支持插件开发者自行开发自定义样式表。

长期计划

Vditor Plugin 将上述特性实现之后,计划在设计特定生命周期阶段进行代码注入。

  • setup() 方法

在 render 阶段之前进行触发。

  • after() 方法

在 render 完成之后进行触发。

粗略的生命周期设计图

graph TD Vditor实例化 --> 插件Setup 插件Setup --> 实例Render 实例Render --> 插件After 插件After --> 实例挂载渲染结束

Vditor 设计长期规划

因为 runtime 的实际需要,无法避免带来 BREAK CHANGES,但同时带来了更多拓展的可能性。自己由于志愿服务工作个人时间比较少,迭代开发 PR 可能会需要很长的时间。

在长期计划中,随着 runtime 的加持,也同时打算重构 Vditor 的文档。

对于 Lute 体积过大的问题,emmm 我也有尝试在修 Golang 编译到 wasm 的报错,这算是实验性的尝试,比较麻烦。。。

关于 Vditor 的探索学习

从第一次接触 Vditor 到如今似乎已有两年了,从一名用户随着自己开发水平的提升开始慢慢贡献。我经历了基于 Sym 的 Nucode 中北大学大数据协会论坛,到黑客派改名链滴,不变的还是开源精神。

Vditor 整体的代码难度还是不高的,有能力的同学可以阅读阅读,然后写点文章分析分析啥的,就跟他们喜欢学习 React 的代码结构一样 ;)

现在自己的想法太多,写代码的速度跟不上 idea 的产生速度,实在是一件痛苦的事情 😔

  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    357 引用 • 1827 回帖 • 2 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    26 引用 • 196 回帖 • 18 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • CheGuevara

    不过话说回来,之前想对 vditor 进行一些拓展的,但是模块内部的耦合是很高的,只好戴着脚铐跳舞,算是勉勉强强完成需求,大改的话工作量太大了 😂

    1 回复
  • 其他回帖
  • CheGuevara

    这个坑就挖的大了,我看到你前几天在 github 上有建了几个相关的仓库,还以为你时间挺多的呢trollface

    1 回复
  • 不错,插件这块我没经验,大佬加油!

    1 回复
  • HerbertHe

    基于 Vditor 周围的生态项目我做了不少,碰到过很多“开发不舒服”的地方,因为各种原因先暂停了更新。如果 Vditor 能作为一个独立项目运营的话,我愿意把现有的所有项目全部捐献给社区。

    之前 PR 里面的贡献是把 Sass 和 Lint 工具全部改掉了,先解决最基本的开发问题。核心 API Vditor.render()Vditor.use() 实现再迭代大改底层加载逻辑。如果顺利的话,Vditor 的自身代码体积会减小很多,拓展性会增强很多,开源影响力也会更大~

  • 查看全部回帖