Plugins and middleware approach
See original GitHub issueI’m investigating patterns for a plugin / extension approach for matter.js. Ultimately I’d like to maintain a collection of small module packages named matter-*
that follow a common pattern (e.g. like the grunt / gulp ecosystem) rather than a monolithic library.
This should hopefully:
- make the core lighter and simpler
- allow new features without feature creep worries
- encourage community contributions without needing pull requests
Some goals for a plugin approach are:
- a common pattern
- simple to define
- simple to use
- forwards and backwards compatibility
- compatibility with other plugins
- promote low coupling
To start we can say that plugins should be:
- CommonJS module format
- Built with browserify / webpack for browser use
One of the more difficult concerns for plugins is the need to extend existing methods like Engine.update
or Body.create
with new features. To do this plugins will need to hook these module methods so they can apply their own operations at the right moment. We could use events for this, but this requires definitions of lots of new events and there is little control over order of execution.
My proposal is that plugins can patch any module method through function composition. It could work like this:
MyPlugin.js
var MyPlugin = module.exports = {};
MyPlugin.patch = function(base) {
// check dependencies
assert(base.Engine);
assert(Matter.Plugin.has(base, 'matter-another-plugin'));
// or we can load our own plugin dependencies directly
Matter.Plugin.use(base, MyPluginDep); // or 'matter-my-plugin-dep'
// use function composition to patch and extend
var engineUpdate = base.Engine.update;
base.Engine.update = function(engine, delta, correction) {
MyPlugin.Engine.update(engine, delta, correction);
return engineUpdate(engine, delta, correction);
}
// more patched methods here
}
MyPlugin.somethingNew = function(things) {
// doing something new has no need for patching
}
MyPlugin.Engine = {};
MyPlugin.Engine.update = function(engine, delta, correction) {
// do plugin stuff on the engine
}
App.js
var Matter = require('matter-js');
var MyPlugin = require('matter-my-plugin');
var AnotherPlugin = require('matter-another-plugin');
Matter.use(MyPlugin);
Matter.use(AnotherPlugin);
// Matter.use is sugar for Matter.Plugin.use(Matter, MyPlugin) e.g.
// (base, plugin) => { if (!Matter.Plugin.has(base, plugin)) { plugin.patch(base); base.plugins.push('matter-my-plugin'); } }
// we can now use Matter.* as normal, with additional features :)
var newThings = MyPlugin.somethingNew([Matter.Body.Rectangle(...)]);
I realise that monkey-patching like this might make some people wince. But it’s extremely powerful.
It’s also optional! As it’s entirely possible to use plugins defined like this without using Matter.use
(obviously takes more work on the user’s part). Documentation of plugins might be a little tricky…
Candidates for plugins so far:
- Matter.Render
- Matter.RenderPixi
- Matter.Runner
- Matter.Svg
If anybody has any comments, ideas or examples of other good approaches I’d be interested to hear.
Edit: Looks like Vue.js is using a very similar approach.
Issue Analytics
- State:
- Created 8 years ago
- Comments:13 (9 by maintainers)
Top GitHub Comments
Just an update on this, I’m very close to having finalised the plugins implementation and it will be published in a branch soon!
The plugins branch is now pushed! Check it out!
Take a look at the code for Matter.Plugin to see the implementation.
There are also two new wiki pages on Using plugins and Creating plugins that go in to detail on the approach and the design of the plugin system.
Here are three simple plugins you can try:
You can see them in action by running the attractors example.
I’d appreciate it if you guys got back to me with your thoughts!