DSH 插件开发 · 第 1 章:第一个插件
/ 5 min read
本文对应官方教程第 1 章:你的第一个插件,示例代码在其基础上改编。
学习目标:理解 Cordis 插件的加载模型,写出第一个能被运行时执行的插件。
插件就是一个函数
Cordis 对插件的要求低到令人意外:一个导出了 apply 命名函数的模块,就是插件。运行时加载模块后,会用一个上下文对象(context,惯用变量名 ctx)调用这个函数,插件的所有行为都通过 ctx 完成。
import type { Context } from '@cordisjs/core'
export function apply(ctx: Context) { ctx.log('hello from cordis')}除了 apply,插件还可以导出 name——它不参与逻辑,只是给日志和诊断一个可读的标识:
export const name = 'hello'没有 name 时框架会退回用模块路径,功能不受影响,但排查问题时可读性差很多,建议始终导出。
三种插件形态
同一个插件可以用三种写法表达,按需选择:
1. 函数(上面的形式):最轻量,适合一段有副作用的初始化逻辑。
2. 对象:当插件需要 inject(依赖其他服务,第 3 章展开)或者想给 apply 传配置时使用:
export default { name: 'hello', inject: ['tools'], apply(ctx) { ctx.log('hello, with dependencies') },}3. Service 子类:插件本身要向别人提供具名服务时使用,第 3 章专门展开。
三种形态在框架内部会被归一化处理,所以没有优劣之分,只有场景之分。
组合根:cordis.yml
插件自己不会启动,启动它们的是组合文件 cordis.yml——它声明“这个运行时由哪些插件构成”:
- ./hello.ts- ./another-plugin.ts列表项是模块路径,也可以指向 npm 包名(如官方教程里的 @deepseek-ai/dsh-tools)。运行时的启动流程是:
- 读取
cordis.yml; - loader 逐项解析模块(支持 TS,配合
--import tsx); - 对每个模块调用
apply(ctx)。
两种失败语义
这一节是我认为官方教程里最容易被忽略、又最有实用价值的部分。同样是“出问题”,两种情况的表现完全不同:
apply执行时抛异常 → 整个运行时崩溃退出。启动阶段的错误被视为致命错误,宁可不起来也不带病运行。- 模块无法解析(路径写错、包没装)→ 只记录日志,其余插件照常启动。
这个设计值得体会:编译期/解析期的问题(文件丢了)被降级为可恢复,而用户代码在初始化时犯的错被升级为不可恢复。前者通常能通过修复组合文件解决,后者往往意味着插件有 bug,带病启动会让错误扩散到后续流程。
动手试一下两种情况,观察终端输出的差别,比读十遍文档记得牢。
踩坑提示
apply必须是命名导出(export function apply),export default一个函数是不够的;对象形态才用export default。- 路径以
./开头表示相对当前cordis.yml,不带./会被当作包名去解析。 - 忘记
--import tsx会得到一堆语法错误——Node 不认识 TS。
练习
- 写一个
hello插件,apply里用ctx.log打印一句问候,加入cordis.yml跑通。 - 故意把
cordis.yml里的路径写错,观察“只记日志不崩溃”的行为。 - 再故意在
apply里throw new Error('boom'),观察整个进程退出。