Skip to content

认识插件

我们对 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')
}

接下来,我们将介绍这个文件每一部分的含义。

插件的基本形式

一个插件需要是以下三种基本形式之一:

  1. 一个接受两个参数的函数,第一个参数是所在的上下文,第二个参数是传入的配置;
  2. 一个接受两个参数的类,第一个参数是所在的上下文,第二个参数是传入的配置;
  3. 一个对象,其中的 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
)
}

这样当你加载上述模块时,就相当于同时加载了 foobar 两个模块。这样的做法不仅能够减轻心智负担,解耦出的模块还享受独立的热重载,你可以在不影响一个模块运行的情况下修改另一个的代码!

当你在开发较为复杂的功能时,可以将插件分解成多个独立的子插件,并在入口文件中依次加载这些子插件。许多大型插件都采用了这种写法。

在配置文件中加载

在配置文件中,你可以使用插件的 npm 包名 (也就是 package.json 中的 name 字段) 来添加插件。打开 cordis.yml,并在底部加入一行:

cordis.yml
- name: cordis-plugin-example