question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Wrapper function for creating plugins

See original GitHub issue

Another v5 feature we should consider is tweaking the docisfy plugin architecture to be more resilient. This warrants further discussion, but here are two ideas worth considering:

  1. Provide a docisfy plugin “starter” or a sample repo to use as a starting point.

    This would make life much easier for the community and help enforce acceptance criters (below).

  2. Define acceptance criteria for all docsify plugins.

    This is critical for docsify because unlike static sites, a docsify-powered site requires Javascript to render content. If a docsify plugin throws an error, it is almost certain that site functionality will be broken.

    Here are a few idea for acceptance criteria:

    • Specify the version of ECMAScript plugin developers should target. For docsify v4.x, ES5 is the required target to ensure IE11 support. This means dev must either author their plugin with ES5 syntax or transpile their code from ES6+ to ES5. For docsify v5, we’ll have to look at our supported browser list to determine the appropriate ECMAScript version to target.
    • Plugin code must be wrapped in a try { ... } catch(err) { ...} statement to prevent a plugin error from breaking a site.
    • Plugins must make their version number accessible to other plugins for interoperability. Docsify addons like docsify-themeable and docsify-tabs already do this by adding an object to the global $doscify object ($docsify.tabs and $docsify.themeable) with a value key and value. The goal is to allow both docsify and other plugins to determine which plugins are active and, if necessary, modify their behavior as needed.
    • Plugins must specify a compatible docsify version to prevent breaking sites when a new version of docsify is released. For example, consider a v4 site that is configured to load the latest version of docsify and multiple plugins. If docsify v5 is released using the same URLs, the v4 site will automatically be “upgraded” to v5 but will likely break as a result of trying to load v4 plugins. This safeguard would allow docsify (or the plugin itself) to determine if it is safe to run the plugin before doing so, which would at least allow the site to load and function properly (albeit without incompatible plugin functionality). This wouldn’t be necessary if users locked their docsify, theme, and plugin CDN URLs to a major version (see #780), but since they don’t a safeguard like this is necessary.

Just some ideas to get started.

_Originally posted by @jhildenbiddle in https://github.com/docsifyjs/docsify/issues/1061#issuecomment-641731543_

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:1
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
trusktrcommented, Jun 25, 2020

simplicity of how data is shared between hook methods in the first example.

I think using this is as simple:

{
  version: "4", // required
  docsifyVersion: "4.11.0", // required
  onInit() {
    this.myVar = 1;
  },
  onBeforeEach(content) {
    // Do something with this.myVar...
  },
  async onAfterEach(html) {
    // Do something with this.myVar...
  }
};

(Sidenote, the example uses async functions instead of callbacks (instead of next))

Using this, the object can be more easily extended, and we can use super to call parent methods, and read parent state, etc, which I think is a nice benefit:

class SomePlugin {
  version = "4" // required
  docsifyVersion = "4.11.0" // required
  onInit() {
    this.myVar = 1;
  }
  onBeforeEach(content) {
    // Do something with this.myVar...
  }
  onAfterEach(html, next) {
    // Do something with this.myVar...
  }
};

class SomePlugin2 extends SomePlugin {
  onInit() {
    super.onInit()
    this.otherVar = 1
  }
  onBeforeEach(content) {
    const md = super.onBeforeEach(content)
    // Do something with this.otherVar...
  },
  async onAfterEach(html) {
    const html = await super.onAfterEach(html)
    // Do something with this.otherVar...
  }
}

If we want private variables (like you get with function-scoped variables, then we have private fields:

class SomePlugin {
  version = "4" // required
  docsifyVersion = "4.11.0" // required
  #myVar
  onInit() {
    this.#myVar = 1;
  }
  onBeforeEach(content) {
    // Do something with this.#myVar...
  }
  onAfterEach(html, next) {
    // Do something with this.#myVar...
  }
};

class SomePlugin2 extends SomePlugin {
  onInit() {
    super.onInit()
    console.log(this.#myVar) // error, not possible to access parent private property.
  }
}

(Private properties work in Chrome and Edge out of the box today.)

For comparison, here’s the equivalent code to achieve variable sharing and extensibility if we go the single-function approach, which I think is less ideal:

class SomePlugin {
  version = "4" // required
  docsifyVersion = "4.11.0" // required
  definition(hook) {
    hook.init((...args) => this.onInit(...args));
    hook.beforeEach((...args) => this.beforeEach(...args));
    hook.afterEach((...args) => await this.afterEach(...args));
  }
  async onInit() {
    this.myVar = 1;
  }
  async onBeforeEach(content) {
    // Do something with this.myVar...
  }
  async onAfterEach(html) {
    // Do something with this.myVar...
  }
};

class SomePlugin2 extends SomePlugin {
  onInit() {
    super.onInit()
    this.otherVar = 1
  }
  onBeforeEach(content) {
    const md = super.onBeforeEach(content)
    // Do something with this.otherVar...
  },
  async onAfterEach(html) {
    const html = await super.onAfterEach(html)
    // Do something with this.otherVar...
  }
}
0reactions
jhildenbiddlecommented, Jun 26, 2020

No worries, nothing wrong with expressing opinions, in my opinion. 😃

Agreed. Didn’t mean the “respectfully” to sound snarky.

As for the other comments, I think there is some confusion about where I’m discussing the use of JavaScript classes as opposed to single vs. multiple methods. For example, my comments on the pitfalls of relying on this are related to the single vs multiple method debate, not classes (since the same issues with this exist with both classes and objects). Class-related comments are intended to demonstrate how requiring docsify plugin authors to adopt them would be (IMHO) a mistake, not a criticism of those who find them useful. If JavaScript classes butter your biscuit, grab some biscuits and have at it.

I’ve read those arguments against classes before. I still believe they are great for certain situations and not for others. It’s not an all-or-nothing type of thing.

Sure. People have preferences and things can usually be done multiple ways. The discussion changes when preferences have repercussions that impact others, as is the case with requiring others to use JavaScript classes. See: React.

What I proposed isn’t forcing a class-based plugin system

Perfect. I incorrectly assumed it was when you spoke of extending classes, calling parent methods, and reading parent state (i.e. having plugins extend from a docsify parent class of some sort, similar to React.Component).

this is something people need to learn to be effective with JavaScript. it is a “novice” mistake that with a little experience can be avoided

And yet it remain an issue after 25 years of JavaScript. You may get it, but I don’t think assuming others do (and if they don’t, they should) is a good way to approach designing software for a wide range of people. We should be removing stumbling blocks, not introducing them or leaving them in place because we prefer organizing our code a certain way.

const makeSomePlugin = () => {
  const myVar = 123

  return {
    version: "4",
    docsifyVersion: "4.11.0",
    init() { /* ... uses myVar ... */ },
    beforeEach(content) { /* ... uses myVar ... */ },
    afterEach(content) { /* ... uses myVar ... */ },
  }
}

window.$docsify = {
  plugins: [ makeSomePlugin() ]
}

^ That is cleaner than passing callbacks into hook functions, in my opinion. Wdyt?

👍 Yep (fixed method shorthand syntax usage to fat arrow function).

Someone could also choose to make a special type of “factory function” that must be called with new

They could. I hope they wouldn’t to avoid the inevitable bugs from this:

window.$docsify = {
  plugins: [ 
    myPlugin1,      // object
    myPlugin2(),    // function
    new myPlugin3() // class
  ]
}

Knowing that the recommendations we make and tools we provide will heavily influence how people write docsify-related plugin code, my hope would be that we would favor something like this:

window.$docsify = {
  plugins: [ 
    myPlugin1(), 
    myPlugin2(), 
    myPlugin3() 
  ]
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Class or function wrapper for plugin code
When developing plugins, to avoid using a prefix for all functions and variables, it is common to use a wrapper for the plugin...
Read more >
Using Classes as Code Wrappers for WordPress Plugins
Many tutorials that explain how to write a WordPress plugin (including ours) show the use of global functions for implementing action and ...
Read more >
Settings wrappers for plugins and themes - Justin Tadlock
Creating wrappers for individual settings. Here's how I create an array of default settings: function jt_get_default_settings() { return array( ...
Read more >
Why do we wrap plugin source code with function($)??
By wrapping a jQuery plugin in a self-executing anonymous function like this, and passing in jQuery as an argument, the plugin author can...
Read more >
Addon Wrapper Example - Elementor Developers
Let's create a wrapper for Elementor addons that incorporates everything ... function elementor_test_addon() { // Load plugin file require_once( __DIR__ .
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found