认识插件
我们对 Cordis 的介绍从插件开始。
还记得我们在 起步 中创建的第一个插件吗?让我们回顾一下它的内容:
ts
import { Context } from 'cordis'
export const name = 'example'
export interface Config {}
export function apply(ctx: Context, config: Config) {
ctx.logger.success('Hello World')
}
接下来,我们将介绍这个文件每一部分的含义。
插件的基本形式
一个插件需要是以下三种基本形式之一:
- 一个接受两个参数的函数,第一个参数是所在的上下文,第二个参数是传入的配置;
- 一个接受两个参数的类,第一个参数是所在的上下文,第二个参数是传入的配置;
- 一个对象,其中的
apply
方法是第一种形式中所说的函数。
可以使用 ctx.plugin()
来加载插件。这相当于将 ctx
传入插件的函数或类中。因此,下面的四种写法是基本等价的:
ts
// 直接对 ctx 进行操作
ctx.logger.success('Hello World')
// 使用函数形式的插件
ctx.plugin(ctx => ctx.logger.success('Hello World'))
// 使用对象形式的插件
ctx.plugin({
apply: ctx => ctx.logger.success('Hello World'),
})
// 使用类形式的插件
ctx.plugin(class {
constructor(ctx: Context) {
ctx.logger.success('Hello World')
}
})
模块化的插件
插件化最大的好处就是可以把不同的功能写在不同的模块中。此时插件将作为模块的导出,它可以是 默认导出 或 导出整体。
对于对象形式的插件,你还可以额外提供一个 name
属性作为插件的名称。对于函数和类形式的插件来说,插件名称便是函数名或类名。具名插件有助于更好地描述插件的功能,并被用于插件关系可视化中,实际上不会影响任何运行时的行为。
foo.ts
// 整体导出对象形式的插件
import { Context } from 'cordis'
export interface Config {}
export const name = 'Foo'
export function apply(ctx: Context, config?: Config) {}
bar.ts
// 默认导出类形式的插件
import { Context } from 'cordis'
class Bar {
constructor(ctx: Context, config?: Bar.Config) {}
}
namespace Bar {
export interface Config {}
}
export default Bar
TIP
上述两种写法中,默认导出的优先级更高。只要插件模块提供了默认导出,Cordis 加载器就只会加载默认导出,而非导出整体。在开发中请务必注意这一点。
嵌套的插件
Cordis 的插件也是可以嵌套的。你可以将你编写的插件解耦成多个独立的子模块,再将调用这些子模块的一个新插件作为入口模块,就像这样:
index.ts
// 入口文件,从上述模块分别加载插件
import { Context } from 'cordis'
import * as Foo from './foo'
import Bar from './bar'
export function apply(ctx: Context) {
ctx.plugin(Foo)
ctx.plugin(Bar)
}
这样当你加载上述模块时,就相当于同时加载了 foo
和 bar
两个模块。这样的做法不仅能够减轻心智负担,解耦出的模块还享受独立的热重载,你可以在不影响一个模块运行的情况下修改另一个的代码!
当你在开发较为复杂的功能时,可以将插件分解成多个独立的子插件,并在入口文件中依次加载这些子插件。许多大型插件都采用了这种写法。
在配置文件中加载
在配置文件中,你可以使用插件的 npm 包名 (也就是 package.json
中的 name
字段) 来添加插件。打开 cordis.yml
,并在底部加入一行:
cordis.yml
- name: cordis-plugin-example