Right now we have a bunch of competing approaches for how mapbox-gl-js plugins express their dependency on GL-JS.
Some plugins require-into mapbox-gl for utilities and thus actually rely on gl js internals.
Core questions
cc @jfirebaugh @tristen @mourner
cc @mcwhittemore as he's looking at making mapboxgl.Control it's own module.
how do we write a system that works equally well for cdn dependencies and browserify and webpack?
Thats a hard one. The first idea that comes to mind is having mapbox-gl pass in a ref to itself via the addControl function. This would let us say that all plugins shouldn't require mapbox-gl. This introduces some problems with confirming that the plugin works with the current version of mapbox-gl.
While I like the idea above, what I've been doing is moving to peerDependencies as this doesn't take a change to mapbox-gl and lowers the bundle size by removing extra refs to mapbox-gl.
I'd say let's do this:
peerDependencies.window.mapboxgl to exist, and warn about including after mapboxgl if it's not available. Not a perfect solution but it worked OK for Leaflet plugins and will work for both Browserify systems and CDN.@mcwhittemore what's the reason for making mapboxgl.Control a module? Also note that not all plugins are supposed to be controls. Some are new sources, some are additional methods, etc.
@mourner The idea with making mapbox.Control a module is that it could be required in with little bloat. That said, we went with the peerDependencies route over the making mapbox.Control a module when cleaning up mapbox-gl-draw and I think it worked well.
Some plugins require-into mapbox-gl for utilities and thus actually rely on gl js internals.
They shouldn't do this. require('mapbox-gl') is the public interface.
how do we write a system that works equally well for cdn dependencies and browserify and webpack?
Ideally, only CDN dependencies rely on a mapboxgl global, and browserify/webpack don't. I'm not aware of any turnkey solutions to this. It seems like it requires an approach like:
mapboxgl global.mapboxgl global to be set, requires the plugin base code, calls the factory function, and extends the mapboxgl global with the result.That looks like too much hassle if the only problem is not wanting to rely on a global inside browserify/webpack.
They shouldn't do this.
require('mapbox-gl')is the public interface.
Yes, but then we need to make sure everything a plugin could rely on is exposed (either publicly or through a "protected" method/property).
the only problem is not wanting to rely on a global inside browserify/webpack.
We get complaints about Mapbox.js relying on globals (https://github.com/mapbox/mapbox.js/issues/726, https://github.com/mapbox/mapbox.js/issues/1060, https://github.com/mapbox/mapbox.js/issues/253, https://github.com/mapbox/mapbox.js/issues/468). We should expect to get them about Mapbox GL JS too if we follow the same path.
@jfirebaugh most of these are specifically about browserify not working, and we can make sure it works while still relying on a global.
One problem we've run into with mapbox-gl-draw is that its easy for different version of babel to be being used. Because of this we're talking about bundling gl-draw before we ship it. My hold up with this is that is means that mapbox-gl-js will be bundled with it.
My hold up with this is that is means that mapbox-gl-js will be bundled with it.
Why do we need to bundle with mapbox-gl-js? You can bundle excluding it.
Oh. Didn't know browserify did this. Thanks
Relying on mapboxgl being a global doesn't work for webpack's async loading pattern:
define([
'mapbox-gl',
'mapbox-gl-directions'
], function(mapboxgl,directions) {
// failure, never gets here, because mapbox-gl-directions is not guaranteed to initialize
// after mapbox-gl
});
Given the current situation, I think that:
extend from mapbox.Control.mapboxgl object, it should rely on it as an argument (invert control), rather than as a global. For instance, mapboxDirections(mapbox)().addTo(map).@tmcw maybe we could recommend using a different webpack pattern instead of placing such restrictions on the plugin architecture? E.g. this:
define(function(require) {
var mapboxgl = require('mapbox-gl');
var mapboxglDirections = require('mapbox-gl-directions');
// all clear
});
@mourner The above example is an example of webpack code splitting with define syntax; unfortunately it's not just another pattern, it has a much different runtime behavior - specifically that the define style lets people asynchronously introduce new code to their application.
@tmcw yes, but it can still be achieved if we explicitly require the plugin to be loaded after mapboxgl in application code instead of fully rearchitecting plugins, right? For the code splitting, it would look like this:
require.ensure(["mapbox-gl", "mapbox-gl-directions"], function(require) {
var mapboxgl = require("mapbox-gl");
var mapboxglDirections = require('mapbox-gl-directions');
// all clear
});
I have been pushing things in an architectural direction that does not require sources or plugins to reference mapboxgl.
Map#addCustomLayer per https://github.com/mapbox/mapbox-gl-js/issues/281#issuecomment-247182491Map#addCustomSource per https://github.com/mapbox/mapbox-gl-js/projects/2 (not explicitly documented, this method is icing on the bigger refactor)Map#addControl per https://github.com/mapbox/mapbox-gl-js/issues/2118If all three of these changes go through, we should be able to sidestep all the architecture problems discussed in this ticket.
Does this direction make sense? Are there any other classes of plugins that should be considered? Is there anything I'm missing?
So the approach would be to remove the mapboxgl dependency from all plugins? I _think_ that's doable, as long as all Map methods support plain-old javascript objects on all methods - is that the case?
as long as all
Mapmethods support plain-old javascript objects on all methods - is that the case?
I believe this is the case. The only edge case I can think of is a plugin that wants to create instances of controls, Popups, and Markers before receiving a reference to the map.
As of https://github.com/mapbox/mapbox-gl-js/pull/3497 the code and API documentation is in place! But I'd like to queue up a blog post explaining this new API once a new release is rolled.
Also tracking updates for our controls:
@lucaswoj geocoder is now finished. Which branch should plugins/ directory PRs target?
@tmcw it should target mb-pages so the examples are fixed asap
I'm going to write up a blog about the plugin architecture and then we can close this issue.
Blogged, done https://www.mapbox.com/blog/build-mapbox-gl-js-plugins/
Most helpful comment
I have been pushing things in an architectural direction that does not require sources or plugins to reference
mapboxgl.Map#addCustomLayerper https://github.com/mapbox/mapbox-gl-js/issues/281#issuecomment-247182491Map#addCustomSourceper https://github.com/mapbox/mapbox-gl-js/projects/2 (not explicitly documented, this method is icing on the bigger refactor)Map#addControlper https://github.com/mapbox/mapbox-gl-js/issues/2118If all three of these changes go through, we should be able to sidestep all the architecture problems discussed in this ticket.
Does this direction make sense? Are there any other classes of plugins that should be considered? Is there anything I'm missing?