博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
为你的JavaScript库提供插件能力
阅读量:6767 次
发布时间:2019-06-26

本文共 3816 字,大约阅读时间需要 12 分钟。

前言

最近在做一个中台框架的设计开发,在做了主框架的基础能力后,思考在框架落实真实业务需求过程中,需要对主框架功能有非常多的定制化内容存在。如果在主体框架中做了哪怕一点业务改动,都可能会对后面的拓展性及灵活性有所限制。

所以为了让主体框架做的更加灵活、扩展性更搞,在主框架有了基础能力后,就不再对主框架做任何非主框架能力的业务功能开发。

要为主框架不断的"开槽"

其实在很多前端库中都有类似的设计,才能够让更多的开发者参与进来,完成各种各样的社区驱动开发。比如:WebpackBabelHexoVuePress等。

那么如何为自己的项目开槽,做插件呢?

调研

在了解了很多插件的项目源码后,发现实现大多大同小异,主要分为以下几个步骤:

  1. 为框架开发安装插件能力,插件容器
  2. 暴露主要生命运行周期节点方法(开槽)
  3. 编写注入业务插件代码

这些框架都是在自己实现一套这样的插件工具,几乎和业务强相关,不好拆离。或者做了一个改写方法的工具类。总体比较离散,不好直接拿来即用。

另外在实现方式上,大部分的插件实现是直接改写某方法,为他在运行时多加一个Wrap,来依次运行外部插件的逻辑代码。

// main.jsconst main = {  loadData:()=>{},  render:()=>{}}// plugin1.jsconst plugin1 = {  render:()=>{}}// install.jsconst install = (main, plugin) => {  main.render = ()=>{    plugin1.render()    main.render()  }}复制代码

在上述代码中的插件有几个明显的问题:

  • plugin1 无法控制 render() 的顺序
  • main 中无法得知什么函数可能会被插件改写,什么函数不会被插件改写
  • 如果按照模块文件拆分,团队成员中根本不知道 main.js 中的函数修改会存在风险,因为压根看不到 install.js 中的代码

那么后来,为了解决这些问题,可能会变成这样:

const component = {  hooks:{    componentWillMounted(){},    componentDidMounted(){}  },  mounte(){    this.hooks.componentWillMounted();    //... main code    this.hooks.componentDidMounted();  }}const plugin = {  componentWillMounted(){    //...  },  componentDidMounted(){    //...  }}// install.jsconst install = (main, plugin) => {  // 忽略实现细节。  main.hooks.componentWillMounted = ()=>{    plugin1.componentWillMounted()    main.hook.componentWillMounted()  }  main.hooks.componentDidMounted = ()=>{    plugin1.componentDidMounted()    main.hook.componentDidMounted()  }}复制代码

另外,还有一种解法,会给插件中给 next 方法,如下:

// main.jsconst main = {  loadData:()=>{},  render:()=>{}}// plugin1.jsconst plugin1 = {  render:next=>{    // run some thing before    next();    // run some thing after  }}// install.jsconst 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
复制代码

Duang!

最终,我完成了这个库的开发:plugin-decorator

GitHub:

没错,我就知道你会点Star,毕竟你这么帅气、高大、威猛、酷炫、大佬!

总结

在该项目中,另外值得提的一点是,该项目是我在开发自己的一套中台框架中临时抽出来的一个工具库。

在工具库中采用了:

  • TypeScript
  • Ava Unit Test
  • Nyc
  • Typedoc

整体开发过程是先写测试用例,然后再按照测试用例进行开发,也就是传说中的 TDD(Test Drive Development)。

感觉这种方式,至少在我做库的抽离过程中,非常棒,整体开发流程非常高效,目的清晰。

在库的编译搭建中使用了 这个库,为我节省了不少搭建项目的时间!

原帖地址:

个人博客:

转载于:https://juejin.im/post/5d0096e5f265da1bb003b729

你可能感兴趣的文章