第一个插件
认识插件:函数式插件
最简单的 IPE 的插件,就是一个接受依赖注入的普通函数。
/**
* 我们的第一个插件!
* @param {import('@inpageedit/core').InPageEdit} ctx - 插件所属的上下文
* @param {any} [config] - 可能传入的配置项
*/
function myPlugin(ctx, config) {
console.log(ctx, `hello, ${config.name || 'world'}`) // hello, IPE
}
// 然后加载它
ipe.plugin(myPlugin, { name: 'IPE' })你没看错,就这么一个简单的函数,便是合法的 IPE 插件。它的第一个参数 ctx 是插件所属的上下文,你也可以接受第二个参数 config,它是可能传入的配置项。
TIP
对于简单的插件来说,我们更推荐直接传入一个箭头函数,除了了更简洁之外,还能自动获得关于 ctx 的类型推导!
ipe.plugin((ctx) => {
console.log(ctx, 'hello, world')
})你可以亲自尝试一下:打开你的浏览器控制台(F12),粘贴上面的代码,按下回车键。
就是这么简单,当你加载这个插件后,立刻就能在控制台看到 hello, world 字样。
让我们做些什么
为了更好的理解 IPE 的插件机制,我们让这个插件做点事情:
ipe.plugin((ctx) => {
ctx.toolbox.addButton({
id: 'my-first-plugin',
icon: '👋',
tooltip: 'My First Plugin',
onClick: () => {
alert('hello, world')
},
})
})看看页面右下角的工具盒,是不是多了一个按钮?点击它,你会看到一个弹窗,上面写着 hello, world。
真不赖,我们的插件看上去离“快速编辑”也不远嘛!
但是控制台中有一些警告:
Error: property toolbox is not registered, declare it as
injectto suppress this warning
别紧张,这是因为我们没有在插件中注入 toolbox 服务。
依赖注入
为了解决这个问题,我们需要在插件中注入 toolbox 服务。
ipe.plugin((ctx) => {
ctx.inject(['toolbox'], (ctx) => {
ctx.toolbox.addButton({
id: 'my-first-plugin',
icon: '👋',
tooltip: 'My First Plugin',
onClick: () => {
alert('hello, world')
},
})
})
})现在没有报错了!但是,为什么要这样做?
因为 ctx.toolbox 其实是由另外一个插件提供的服务,它的加载时机是不确定的。人总有运气差的时候,万一咱们的myButtonPlugin安装时toolbox还没有加载,那你就只能望 undefined 而兴叹了。
而通过 ctx.inject,IPE会确保你在插件中拿到的上下文,一定包含了 toolbox 服务。
更清晰的定义归纳:对象式插件
不过总是ctx.inject有时候会导致难以阅读,所以我们可以换用对象式插件:
ipe.plugin({
inject: ['toolbox'],
apply(ctx) {
ctx.toolbox.addButton({
id: 'my-first-plugin',
icon: '👋',
tooltip: 'My First Plugin',
onClick: () => {
alert('hello, world')
},
})
},
})现在你学会了第二种插件的写法:IPE 插件可以是一个包含 apply 方法的对象。
apply完全等同于函数式插件中的函数
可选的属性:
name属性来传递插件的名称。inject属性来传递服务列表。
OOP 爱好者:类式插件
IPE的插件可以是一个普通的类,它的 constructor 的第一个参数是所在的上下文,第二个参数是可能传入的配置项。
import { InPageEdit } from '@inpageedit/core'
class MyButtonPlugin {
static inject = ['toolbox']
constructor(
public ctx: InPageEdit,
config?: any
) {
ctx.toolbox.addButton({
id: 'my-first-plugin',
icon: '👋',
tooltip: 'My First Plugin',
onClick: () => {
alert('hello, world')
},
})
}
}
ipe.plugin(MyButtonPlugin)一般来说,TypeScript 的爱好者会很喜欢这种写法,因为它更符合面向对象编程的习惯,你可以把插件的状态保存在类的实例中,也可以把一些逻辑归纳在类的方法中。
如果你感兴趣的话,你甚至可以继承 BasePlugin 来简化一些样板代码,或者使用修饰器来声明依赖注入之类的:
import { InPageEdit, BasePlugin, Inject } from '@inpageedit/core'
import type {} from '@inpageedit/core/plugins/toolbox/index'
@Inject(['toolbox'])
export default class MyButtonPlugin extends BasePlugin {
constructor(
public ctx: InPageEdit,
config?: any
) {
super(ctx, config, 'MyButtonPlugin')
}
start() {
this.ctx.toolbox.addButton({
id: 'my-first-plugin',
icon: '👋',
tooltip: 'My First Plugin',
onClick: () => {
alert('hello, world')
},
})
}
stop() {
this.ctx.toolbox.removeButton('my-first-plugin')
}
}import PluginMyButton from './PluginMyButton.js'
ipe.plugin(PluginMyButton)总结:插件的基本形式
一个插件需要是以下三种基本形式之一:
- 一个接受两个参数的函数,第一个参数是插件所属的上下文,第二个参数是可能传入的配置项
- 一个对象,其中的
apply方法是第一种形式中所说的函数,包括可选的inject和name等属性 - 一个接受两个参数的类,
constructor的第一个参数是插件所属的上下文,第二个参数是可能传入的配置项
ctx.plugin((ctx, config) => {
// ...
})
ctx.plugin({
inject: [],
apply: (ctx, config) => {
// ...
},
})
ctx.plugin(
class MyPlugin {
constructor(
public ctx: InPageEdit,
public configs?: any
) {
// ...
}
}
)