最近在作一个中台框架的设计开发,在作了主框架的基础能力后,思考在框架落实真实业务需求过程当中,须要对主框架功能有很是多的定制化内容存在。若是在主体框架中作了哪怕一点业务改动,均可能会对后面的拓展性及灵活性有所限制。javascript
因此为了让主体框架作的更加灵活、扩展性更搞,在主框架有了基础能力后,就再也不对主框架作任何非主框架能力的业务功能开发。html
要为主框架不断的"开槽"前端
其实在不少前端库中都有相似的设计,才可以让更多的开发者参与进来,完成各类各样的社区驱动开发。好比:Webpack
,Babel
,Hexo
,VuePress
等。java
那么如何为本身的项目开槽,作插件呢?git
在了解了不少插件的项目源码后,发现实现大多大同小异,主要分为如下几个步骤:github
这些框架都是在本身实现一套这样的插件工具,几乎和业务强相关,很差拆离。或者作了一个改写方法的工具类。整体比较离散,很差直接拿来即用。typescript
另外在实现方式上,大部分的插件实现是直接改写某方法,为他在运行时多加一个Wrap,来依次运行外部插件的逻辑代码。json
// main.js
const main = {
loadData:()=>{},
render:()=>{}
}
// plugin1.js
const plugin1 = {
render:()=>{}
}
// install.js
const install = (main, plugin) => {
main.render = ()=>{
plugin1.render()
main.render()
}
}
复制代码
在上述代码中的插件有几个明显的问题:框架
plugin1
没法控制 render()
的顺序main
中没法得知什么函数可能会被插件改写,什么函数不会被插件改写main.js
中的函数修改会存在风险,由于压根看不到 install.js
中的代码那么后来,为了解决这些问题,可能会变成这样:async
const component = {
hooks:{
componentWillMounted(){},
componentDidMounted(){}
},
mounte(){
this.hooks.componentWillMounted();
//... main code
this.hooks.componentDidMounted();
}
}
const plugin = {
componentWillMounted(){
//...
},
componentDidMounted(){
//...
}
}
// install.js
const install = (main, plugin) => {
// 忽略实现细节。
main.hooks.componentWillMounted = ()=>{
plugin1.componentWillMounted()
main.hook.componentWillMounted()
}
main.hooks.componentDidMounted = ()=>{
plugin1.componentDidMounted()
main.hook.componentDidMounted()
}
}
复制代码
另外,还有一种解法,会给插件中给 next
方法,以下:
// main.js
const main = {
loadData:()=>{},
render:()=>{}
}
// plugin1.js
const plugin1 = {
render:next=>{
// run some thing before
next();
// run some thing after
}
}
// install.js
const install = (main, plugin) => {
main.render = ()=>{
plugin1.render(main.render)
}
}
复制代码
如上,从调研结构来看,虽然都实现了对应功能,可是从实现过程来看,有几个比较明显的问题:
在调研了不少框架的实现方案的后,我但愿之后我本身的插件库可使用一个装饰器完成开槽,在插件类中经过一个装饰器完成注入,能够像这样使用和开发:
import { Hook, Inject, PluginTarget, Plugin } from 'plugin-decorator';
class DemoTarget extends PluginTarget {
@Hook
public method1() {
console.log('origin method');
}
}
class DemoPlugin extends Plugin {
@Inject
public method1(next) {
next();
console.log('plugin method');
}
}
const demoTarget = new DemoTarget();
demoTarget.install(new DemoPlugin());
demoTarget.method1();
// => origin method
// => plugin method
复制代码
而且能够支持对原函数的入参出参作装饰修改,以下:
import { Hook, Inject, PluginTarget, Plugin } from 'plugin-decorator';
class DemoTarget extends PluginTarget {
@Hook
public method1(name:string) {
return `origin method ${name}`;
}
}
class DemoPlugin extends Plugin {
@Inject
public method1(next, name) {
return `plugin ${next(name)}`;
}
}
const demoTarget = new DemoTarget();
demoTarget.install(new DemoPlugin());
console.log(demoTarget.method1('cool'));
// => plugin origin method cool
复制代码
固然,若是原函数是一个Promise的函数,那插件也应该支持Promise了!以下:
import { Hook, Inject, PluginTarget, Plugin } from 'plugin-decorator';
class DemoTarget extends PluginTarget {
@Hook
public methodPromise() {
return new Promise(resolve => {
setTimeout(() => resolve('origin method'), 1000);
});
}
}
class DemoPlugin extends Plugin {
@Inject
public async methodPromise(next) {
return `plugin ${await next()}`;
}
}
const demoTarget = new DemoTarget();
demoTarget.install(new DemoPlugin());
demoTarget.methodPromise().then(console.log);
// => Promise<plugin origin method>
复制代码
最终,我完成了这个库的开发:plugin-decorator
GitHub: 地址
没错,我就知道你会点Star,毕竟你这么帅气、高大、威猛、酷炫、大佬!
在该项目中,另外值得提的一点是,该项目是我在开发本身的一套中台框架中临时抽出来的一个工具库。
在工具库中采用了:
总体开发过程是先写测试用例,而后再按照测试用例进行开发,也就是传说中的 TDD(Test Drive Development)。
感受这种方式,至少在我作库的抽离过程当中,很是棒,总体开发流程很是高效,目的清晰。
在库的编译搭建中使用了 typescript-starter 这个库,为我节省了很多搭建项目的时间!
我的博客:yeee.wang