preface

Recently, IN the design and development of a mid-platform framework, after making the basic capabilities of the main framework, I thought that in the process of implementing the real business requirements of the framework, there need to be a lot of customized content for the functions of the main framework. If even a small business change is made in the main framework, it may limit the extensibility and flexibility of the future.

Therefore, in order to make the main framework do more flexible, more expansibility, after the main framework has the basic ability, it will not do any business function development on the main framework that is not the main framework ability.

Keep slotting the main frame.

In fact, there are many front-end libraries that have similar designs, allowing more developers to participate in a variety of community-driven development. Examples: Webpack, Babel, Hexo, VuePress, etc.

So how to slot for their own projects, make plug-ins?

research

After understanding the project source code of many plug-ins, I found that the implementation is mostly similar, mainly divided into the following steps:

  1. Install plugin capability for framework development, plugin container
  2. Method of exposing major life cycle nodes (slotting)
  3. Write code to inject business plug-ins

These frameworks are implementing their own set of plug-in tools that are almost business relevant and hard to detach. Or a utility class that overwrites methods. The overall comparison of discrete, not directly used.

In addition, in terms of implementation, most plug-in implementations directly rewrite a method, add a Wrap to it at runtime, to run the external plug-in logic code in turn.

// main.js
const main = {
  loadData:(a)= >{},
  render:(a)= >{}}// plugin1.js
const plugin1 = {
  render:(a)= >{}}// install.js
const install = (main, plugin) = > {
  main.render = (a)= >{
    plugin1.render()
    main.render()
  }
}


Copy the code

There are several obvious problems with the plug-in in the above code:

  • plugin1Unable to controlrender()The order of
  • mainWhat functions may and may not be overwritten by plug-ins
  • If you split it by module file, team members don’t even know about itmain.jsThe function modification in is risky because it is not seen at allinstall.jsThe code in

So later, in order to solve these problems, it might look like this:

const component = {
  hooks:{
    componentWillMounted(){},
    componentDidMounted(){}
  },
  mounte(){
    this.hooks.componentWillMounted();
    / /... main code
    this.hooks.componentDidMounted(); }}const plugin = {
  componentWillMounted(){
    / /...
  },
  componentDidMounted(){
    / /...}}// install.js
const install = (main, plugin) = > {
  // Ignore implementation details.
  main.hooks.componentWillMounted = (a)= >{
    plugin1.componentWillMounted()
    main.hook.componentWillMounted()
  }
  main.hooks.componentDidMounted = (a)= >{
    plugin1.componentDidMounted()
    main.hook.componentDidMounted()
  }
}
Copy the code

In addition, there is another solution, which gives the next method to the plug-in, as follows:

// main.js
const main = {
  loadData:(a)= >{},
  render:(a)= >{}}// plugin1.js
const plugin1 = {
  render:next= >{
    // run some thing before
    next();
    // run some thing after}}// install.js
const install = (main, plugin) = > {
  main.render = (a)= >{
    plugin1.render(main.render)
  }
}

Copy the code

As mentioned above, from the perspective of the research structure, although corresponding functions have been realized, there are several obvious problems in the implementation process:

  • Too many intrusive changes to the original function
  • Rewriting is too much of a hack
  • Not TypeScript friendly
  • Multi-member collaboration is not friendly
  • The operation of the original function is not flexible enough to modify the input and output parameters of the original function

Open to

After investigating the implementation of many frameworks, I hope that in the future my plugin library can be slotted with a decorator, and the plugin class can be injected with a decorator, which can be used and developed like this:

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
Copy the code

Decorator

In addition, it can support the decoration modification of the input and output parameters of the original function, as follows:

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
Copy the code

Promise

Of course, if the original function is a Promise function, then the plug-in should also support Promises! As follows:

import { Hook, Inject, PluginTarget, Plugin } from 'plugin-decorator';

class DemoTarget extends PluginTarget {
  @Hook
  public methodPromise() {
    return new Promise(resolve= > {
      setTimeout((a)= > 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>
Copy the code

Duang!

Finally, I finished developing the library: plugin-decorator

Address of making:

That’s right, I knew you’d get a Star, because you’re so handsome, tall, powerful, cool, and big!

conclusion

Another point worth mentioning in this project is that it was a tool library that I pulled out of the middle of developing my own mid-platform framework.

Used in the tool library:

  • TypeScript
  • Ava Unit Test
  • Nyc
  • Typedoc

The overall Development process is to write Test cases first, and then develop according to the Test cases, also known as TDD(Test Drive Development).

It felt like this approach, at least during my library withdrawal, was great, and the overall development process was very efficient and purposeful.

Using the typescript-starter library to build the library has saved me a lot of time building projects!

Posts: Yeee. wang/posts/dfa4…

Personal blog: Yeee.Wang