爲你的JavaScript庫提供插件能力

前言

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

因此爲了讓主體框架作的更加靈活、擴展性更搞,在主框架有了基礎能力後,就再也不對主框架作任何非主框架能力的業務功能開發。html

要爲主框架不斷的"開槽"前端

其實在不少前端庫中都有相似的設計,纔可以讓更多的開發者參與進來,完成各類各樣的社區驅動開發。好比:WebpackBabelHexoVuePress等。java

那麼如何爲本身的項目開槽,作插件呢?git

調研

在瞭解了不少插件的項目源碼後,發現實現大多大同小異,主要分爲如下幾個步驟:github

  1. 爲框架開發安裝插件能力,插件容器
  2. 暴露主要生命運行週期節點方法(開槽)
  3. 編寫注入業務插件代碼

這些框架都是在本身實現一套這樣的插件工具,幾乎和業務強相關,很差拆離。或者作了一個改寫方法的工具類。整體比較離散,很差直接拿來即用。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)
  }
}

複製代碼

如上,從調研結構來看,雖然都實現了對應功能,可是從實現過程來看,有幾個比較明顯的問題:

  • 對原函數侵入性修改過多
  • 對方法rewrite操做過多,太hack
  • 對TypeScript不友好
  • 多成員協做不友好
  • 對原函數操做不夠靈活,不能修改原函數的入參出參

開搞

在調研了不少框架的實現方案的後,我但願之後我本身的插件庫可使用一個裝飾器完成開槽,在插件類中經過一個裝飾器完成注入,能夠像這樣使用和開發:

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
複製代碼

Decorator

而且能夠支持對原函數的入參出參作裝飾修改,以下:

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的函數,那插件也應該支持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>
複製代碼

Duang!

最終,我完成了這個庫的開發:plugin-decorator

GitHub: 地址

沒錯,我就知道你會點Star,畢竟你這麼帥氣、高大、威猛、酷炫、大佬!

總結

在該項目中,另外值得提的一點是,該項目是我在開發本身的一套中臺框架中臨時抽出來的一個工具庫。

在工具庫中採用了:

  • TypeScript
  • Ava Unit Test
  • Nyc
  • Typedoc

總體開發過程是先寫測試用例,而後再按照測試用例進行開發,也就是傳說中的 TDD(Test Drive Development)。

感受這種方式,至少在我作庫的抽離過程當中,很是棒,總體開發流程很是高效,目的清晰。

在庫的編譯搭建中使用了 typescript-starter 這個庫,爲我節省了很多搭建項目的時間!

原帖地址:yeee.wang/posts/dfa4.…

我的博客:yeee.wang

相關文章
相關標籤/搜索