Miru 中文文档 下载 App

Miru 扩展系统是怎么工作的

一句话理解

Miru 的扩展系统让任何会写 JavaScript 的人都能为它添加新的媒体源。你不需要改 Miru 主程序的一行代码——只需写一个符合规范的 JS 文件,Miru 就能加载并运行它。

整体架构

┌───────────────┐     ┌──────────────────────┐     ┌────────────────┐
│  Miru 主程序    │────▶│    QuickJS 引擎       │────▶│  扩展 JS 文件    │
│  (Flutter UI)  │     │  (嵌入式 JS runtime)   │     │  (隔离沙箱)     │
└───────────────┘     └──────────────────────┘     └────────────────┘
       │                       │                           │
       │                       │                           ▼
       │                       │                    ┌───────────────┐
       │                       │                    │  this.request │
       │                       │                    │  (HTTP 代理)   │
       │                       │                    └───────────────┘
       ▼                       ▼                           │
   UI 渲染                  扩展调度                      联网请求

为什么选 QuickJS

QuickJS 是一个轻量级嵌入式 JavaScript 引擎,特点是:

Linux 平台暂时不支持——README 里明确说明 Linux 端因为 QuickJS 依赖问题无法启动引擎,以官方最新版为准。

Extension 基类与生命周期

每个 Miru 扩展必须导出一个继承自 Extension 基类的类:

export default class extends Extension {
  async load() { /* 扩展加载时调用一次 */ }
  async latest(page) { /* 返回"最近更新"列表 */ }
  async search(kw, page) { /* 搜索 */ }
  async detail(url) { /* 获取详情页 */ }
  async watch(url) { /* 获取播放/阅读内容 */ }
  async checkUpdate(url) { /* 检查是否有新集/新章节 */ }
  async unload() { /* 扩展卸载时调用 */ }
}

这些方法构成了扩展的完整生命周期。其中 latest 和 search 返回 ListItem 列表,detail 返回 Detail 对象,watch 根据扩展类型返回不同的消费数据(视频 / 漫画图片数组 / 小说文本数组)。

三种扩展类型

扩展文件头部的元数据里必须声明 @type,决定了 Miru 用哪个消费者来渲染它的 watch 返回值:

@type消费端watch 返回值类型
bangumi视频播放器`{ type: "hls" \"mp4", url }`
manga漫画阅读器{ urls: string[] }(图片 URL 数组)
fikushon小说阅读器{ content: string[], title, subtitle }

一个扩展文件同时只能是其中一种类型。想同时提供视频和漫画源?写两个扩展文件。

this.request 代理

扩展里的网络请求必须走 this.request,而不是原生 fetch 或 XMLHttpRequest。原因:

示例:

const res = await this.request({
  url: "/api/search?kw=" + encodeURIComponent(kw),
  method: "GET",
});

this.request 是 Miru 在加载扩展时注入到全局的代理函数,扩展作者不需要自己实现。