Plugins contributing plugins

I’m writing a plugin that wants to add an input rule, and I’m considering using the PluginSpec.view callback to construct a new InputRule and InputRules plugin, and then use EditorView.reconfigure to contribute it to the editor.

Is this a reasonable pattern? What I have in mind is something like this:

const pluginSpec = {
  view(editorView: EditorView) {
    const inputRulePlugin = InputRule.inputRules({
      rules: [
        new InputRule(/* … */)
      ]
    });
    editorView.reconfigure({
      plugins: editorView.state.plugins.concat([
        inputRulePlugin
      ])
    });
    return {};
  }
}

Uhh, that seems scary, and I’m not really sure how it works – the code seems to only run at initialization, and immediately reconfigures the editor once. Is there something missing? If not, why not just pass the input rule plugin instead of this plugin?

A concrete example is a “user mention” plugin, that has an input rule for @, but also brings along other behaviour (e.g. keyboard shortcuts). The plugin wants to compose a mix of other behaviour — some entirely bespoke, some based on other plugins.

Perhaps there’s a better pattern than to try and compose plugins, and instead have lower-level building blocks?

Ah, I see what you mean.

At one point I had the concept of plugin dependencies, where they could specify an array of other plugins that should be enabled when they were active, but resolving duplicate dependencies and such got a little scary and obscure, so I went back to an approach were the user directly controls the array of active plugins. Can you maybe make your plugin constructor return an array of plugins, and concatenate those with other plugins (like the example setup does)?

I did export a helper from the keymap module that makes it possible to mix it into another plugin, but with inputrules, the way it keeps state and handles undoing a rule is somewhat subtle, and I feel trying to export a way to mix it in would get complex and error-prone.

I think that should work. I suspect the syntax will be a little strange to consumers (they may expect a plugin to encapsulate all the logic, as opposed to needing an array of plugins) but that’s just aesthetics. I don’t think plugin dependencies is the direction I’d go (due to the complexity), and instead go with a composition approach — which the current API does fine (albeit it may require refactoring some of the plugins).

One scenario where this gets messy is adding/removing plugins. No longer would a single plugin key be adequate, instead you’d need an array of plugin keys. Having a single container that encapsulates all the logic of a unit is very attractive.

I’ve never actually needed to dynamically add or remove plugins, but I guess it can be one way to handle features that can be enabled/disabled dynamically. Still, removing all elements of an array from another array isn’t very complicated either.