Skip to content

MarkdownLib 前置插件

MarkdownLib 是一个把 Markdown 解析能力封装成前置插件的库,任何插件都可以通过 callPlugin 调用它,把 Markdown 文本渲染成 HTML。

  • 插件 IDtop.qomicex.markdown
  • 版本1.2.0
  • 依赖:无
  • 引入:内嵌 marked v15.0.12,无外部 CDN 依赖(不受 Tauri CSP 限制)

为什么需要它

QML 插件运行在 Tauri WebView 中,打包后 CSP 会拦截所有外部请求与外部 <script src> 加载。插件如果直接在 HTML 里引用 CDN 的 marked:

html
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

在打包环境下会被 CSP 拦截,marked 未定义,Markdown 无法渲染。

把 marked 抽成独立前置插件后:

  1. marked 代码内嵌在 MarkdownLib 插件包内,无需外部 CDN,天然绕过 CSP;
  2. 多个插件可以共享同一份渲染能力,避免各自内嵌重复代码;
  3. 通过插件依赖机制管理,版本清晰、可升级

安装

安装 MarkdownLib 前置插件后,其它插件在 manifest.json 声明依赖:

json
{
  "dependencies": [
    { "id": "top.qomicex.markdown", "version": ">=1.0.0" }
  ]
}

依赖字段说明:

字段必填说明
id被依赖插件的 manifest id
version版本范围,缺省 = 任意版本
optionaltrue 可选前置,false/缺省 必装前置

版本支持语法:>=1.2.0<=1.2.0>1.0<2.0=1.0.1、精确 1.2.0、多条件空格分隔 ">=1.0 <2.0"

接口

MarkdownLib 通过 registerMethod 暴露以下方法:

renderMarkdown(md)

将完整的 Markdown 文本渲染为 HTML 字符串。

说明
参数md: string — Markdown 文本
返回Promise<string> — HTML 字符串
用途渲染段落、标题、粗体、列表、代码块、表格、链接等

renderMarkdownInline(md)

行内 Markdown 渲染为 HTML(不包含块级元素)。

说明
参数md: string — 行内 Markdown
返回Promise<string> — HTML 字符串
用途渲染单行文本中的 **粗体**`代码`[链接](url)

stripHtml(html)

去除 HTML 标签,返回纯文本。

说明
参数html: string — HTML 字符串
返回Promise<string> — 纯文本
用途提取文本内容、预览摘要等

调用方法

通过 __PLUGIN_API__.callPlugin 调用:

js
await __PLUGIN_API__.callPlugin(插件Id, 方法名, ...参数)
js
const html = await __PLUGIN_API__.callPlugin(
  'top.qomicex.markdown',  // 插件 ID
  'renderMarkdown',        // 方法名
  '# 标题\n\n**加粗** 和 `代码`'
)

例子

基础渲染

html
<div id="out"></div>
<script>
  async function render() {
    try {
      const html = await __PLUGIN_API__.callPlugin(
        'top.qomicex.markdown',
        'renderMarkdown',
        '## 标题\n\n**加粗**、*斜体*、`行内代码`'
      )
      document.getElementById('out').innerHTML = html
    } catch (e) {
      document.getElementById('out').textContent = '渲染失败: ' + e.message
    }
  }
  render()
</script>

行内渲染 + 去标签

js
const inline = await __PLUGIN_API__.callPlugin('top.qomicex.markdown', 'renderMarkdownInline', '**你好** 世界')
// → '<strong>你好</strong> 世界'

const text = await __PLUGIN_API__.callPlugin('top.qomicex.markdown', 'stripHtml', '<p>Hello <b>World</b></p>')
// → 'Hello World'

在悬浮窗(overlay)中使用

Overlay 的 __PLUGIN_API__ 同样暴露 callPlugin,用法一致。

处理依赖缺失

js
if (await __PLUGIN_API__.callPlugin('top.qomicex.markdown', 'renderMarkdown', '**测试**')) {
  // 正常渲染
} else {
  // 依赖缺失或未激活,走降级逻辑
}

实现思路

提供方:注册方法

MarkdownLib 的 dist/index.html 在插件激活后,通过 registerMethod 把 marked 的能力暴露给插件系统:

html
<script>
  __PLUGIN_API__.registerMethod('renderMarkdown', function (md) {
    return marked.parse(md || '')
  })
</script>

要点:

  • registerMethod(methodName, fn)fn 的返回值可以是普通值或 Promise(异步方法同样支持);
  • 一个插件可注册多个方法;
  • 插件停用时所有方法自动注销;
  • 方法名建议语义化,避免与其他插件冲突。

调用方:调用方法

依赖方插件通过 callPlugin 调用:

js
await __PLUGIN_API__.callPlugin('top.qomicex.markdown', 'renderMarkdown', md)

调用流程:

调用方插件 ──callPlugin──▶ 主窗口 __pluginRegistry 中转


                         MarkdownLib(已激活)执行 renderMarkdown


                    返回渲染结果(Promise 值)

为什么内嵌而非 CDN

MarkdownLib 把 marked.min.js 内嵌在插件包内,而非通过 <script src="CDN"> 引用:

  • 打包后 CSP 安全:外部脚本被拦截,内嵌脚本不受影响;
  • 离线可用:不依赖网络;
  • 版本锁定:打包进插件,版本确定可复现。

定义原理

插件依赖机制

依赖通过 manifest.jsondependencies 数组声明,由后端在安装/启动时校验:

安装时:检查必装前置是否已安装且版本符合 → 否则拒绝安装(PLUGIN_MISSING_DEPENDENCY)
启动时:按依赖拓扑排序激活(先激活被依赖方,再激活依赖方)
运行中:可选前置缺失不影响启用,调用其方法时才报错

依赖方插件在 __PLUGIN_API__ 上调用 registerMethod 注册方法,方法存于主窗口的 __pluginRegistry。停用插件时对应方法自动移除。

方法注册与调用原理

registerMethod(name, fn)
  ──▶ 主窗口 __pluginRegistry[name] = fn

callPlugin(pluginId, name, ...args)
  ──▶ 查找 pluginId 注册的 name 方法
       ├── 找到 → 执行 fn(...args),返回结果(值或 Promise)
       └── 未找到 → reject(插件未安装 / 未激活 / 方法未注册)

错误处理

callPlugin 失败会 reject,建议调用方始终 try/catch:

场景错误信息
目标插件未安装插件 xxx 未提供方法 yyy(可能未安装或未激活)
目标插件未激活同上
方法未注册同上
方法内部抛错插件 xxx 方法 yyy 执行失败: <具体错误>

修订记录

日期版本修改内容修改人
2026-08-02v1.0初版创建AI Agent