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.

Plugin System is weird

See original GitHub issue

I want to preface all of this with a big thank you to all maintainers of this wonderful project. Please don’t take anything of the following as negativity, but constructive criticism or questions, because of lacking documentation, which I am happy to PR, once I understand it.

Maybe it’s a matter of taste, but I find that the plugin system is quite weird. I guess there is some history (backwards compatibility) to it that I am missing, but here are my points.

Constructor Signature

The constructor signature for plugins is very weird.

Mutating the global config

First of all, I think this is a bug:

https://github.com/verdaccio/verdaccio/blob/4f87750c180abcc000f23c3873919b6dc682b9f5/src/lib/plugin-loader.ts#L23-L25

The original appConfig object is mutated with every plugin that is loaded, since merge mutates the first parameter.

This is a contrived example, but this config would thrash the middlewares:

auth:
  my-auth-plugin:
    middlewares: []
    enabled: true

middlewares:
  a-very-important-middleware-plugin:
    enabled: true

Since auth plugins are loaded first and since their config is merged into the global config, middlewares get overridden with an empty array [] and a-very-important-middleware-plugin is never loaded.

Assuming we need to keep this weird behavior of merging the plugin config with the global config, it should actually look like this IMO:

function mergeConfig(appConfig, pluginConfig): Config { 
  return _.merge({}, appConfig, pluginConfig); 
} 
Different signature for ES module plugins

The next weird thing is that this only occurs for ES module based plugins:

https://github.com/verdaccio/verdaccio/blob/4f87750c180abcc000f23c3873919b6dc682b9f5/src/lib/plugin-loader.ts#L111

“Old school” plugins only receive the plugin’s config:

esModuleBased = new Plugin(mergeConfig(config, pluginConfigs[pluginId]), params);
oldSchool     =     plugin(pluginConfigs[pluginId], params);

Is this intentional?

Inconsistent params / options type

Different plugin types are invoked with different shapes of params / options.

Auth & Middleware Plugins

Receive { config, logger }.

https://github.com/verdaccio/verdaccio/blob/66f4197236d9d71af149314aae15102b336f45e1/src/lib/auth.ts#L44-L59

https://github.com/verdaccio/verdaccio/blob/66f4197236d9d71af149314aae15102b336f45e1/src/api/index.ts#L49-L57

Theme Plugins

Receive an empty object {}.

https://github.com/verdaccio/verdaccio/blob/66f4197236d9d71af149314aae15102b336f45e1/src/api/web/index.ts#L22-L30

TypeScript Types

The relevant TypeScript types look like this:

	class Plugin<T> {
		constructor(config: T, options: PluginOptions<T> );
	}

	interface IPlugin<T> {
		version?: string;
		// In case a plugin needs to be cleaned up/removed
		close?(): void;
	}

	type PluginOptions<T> = {
		config: T & Config;
		logger: Logger
	}

So according to this, I would expect to only receive my plugin’s config as the first parameter (config) and always receive { config: GlobalAppConfig & PluginConfig, logger }, as the second parameter.

Proposed Solution

Personally, I would prefer, if we’d remove the & PluginConfig from this and remove the merging for the first parameter.

This way I can consume a clean plugin config, that will not accidentally get clobbered during the boot process, as the first parameter. And then as the second parameter I could access a clean global app config, that is not weirdly mutated.

So kinda like this:

	class Plugin<T> {
		constructor(config: T, options: PluginOptions );
	}

	interface IPlugin<T> {
		version?: string;
		// In case a plugin needs to be cleaned up/removed
		close?(): void;
	}

	type PluginOptions = {
		config: Config;
		logger: Logger
	}

I guess that this wasn’t done like this, because of backwards compatibility. So maybe we can fix this with a major release?

Multiple Plugin Instances per Type

My second major issue is that, when a plugin intends to implement multiple plugin types, e.g. IPluginAuth and IPluginMiddleware, it will be instantiated twice.

This was not at all obvious from the documentation and I personally would prefer, if the instance was shared. I can workaround this by implementing a singleton pattern, but it needlessly complicated things.

FWIW here's my singleton plugin pattern.

Again, thanks a lot for this great project! I love working with it and hope that we can make this even better! 🎉

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
juanpicadocommented, Apr 23, 2021

Hi @buschtoens thanks for the detailed report, that’s is how should be done ☕️ .

The answer you are looking for is, yes, legacy. The story of this project started did not start with the best way, old Node.js supported, many PR without enough context and so on, object mutability, coercion and etc etc. We had to onboard many legacy users and developers from Sinopia in the last 2 major releases. I think nowadays if someone still using Sinopia, hard to believe, but still happens (https://twitter.com/jsunderhood/status/1151564931134894080), Verdaccio 4 is the last step for safe on-boarding. Verdaccio ~5~ 6 will have breaking changes in terms of plugins, for sure.

Some of the issues you have mentioned were already reported, https://github.com/verdaccio/verdaccio/issues/928 https://github.com/verdaccio/verdaccio/issues/1357 and I’m aware of them, but I just noticed other things that you pointed out.

So, that being said, as you mention, this only can be done in a major release, since v4 was just released, the next one will happen until next year https://github.com/verdaccio/contributing/blob/master/RELEASES.md but usually we start with alphas in October, so, we might work in a branch a new approach for plugins.

I did not have plans to start any development any time soon, I have https://github.com/verdaccio/verdaccio/issues/541 and https://github.com/verdaccio/verdaccio/issues/1334 in my priority list before go with this.

But I will take your suggestions very seriously and perhaps we can contribute on this on Verdaccio ~5~ 6, keep in touch 🤙 .

0reactions
juanpicadocommented, Sep 16, 2022
Read more comments on GitHub >

github_iconTop Results From Across the Web

Plugin systems - when & why? - DEV Community ‍ ‍
Plugin systems offer a very simple solution to this problem by stating that not everything has to belong to the core.
Read more >
I am looking for resources on plugin architectures, can you help?
I want to extend my knowledge about plugins. I'm looking for information about plugin architectures and design patterns as a foundation of plugin...
Read more >
Strange framework in some plugins - Cubase
After upgrading to the latest version of Cubase, Plugin Alliance SSL Plugins appear with this strange frame. is this going to be like ......
Read more >
Weird symbol appearing in plugin configuration (CR) | SpigotMC
A weird symbol appears when I use System#lineSeperator which you can see at the end of string one. I know it has something...
Read more >
Plugins in Rust: The Technologies | NullDeref
A more in-depth look at Rust plugin systems. ... This is possibly the least weird way to implement a Plugin Development Kit, i.e.,...
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