最近在作一箇中臺框架的設計開發,在作了主框架的基礎能力後,思考在框架落實真實業務需求過程當中,須要對主框架功能有很是多的定製化內容存在。若是在主體框架中作了哪怕一點業務改動,均可能會對後面的拓展性及靈活性有所限制。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