Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document GL JS plugin architecture #1835

Closed
tmcw opened this issue Dec 14, 2015 · 24 comments
Closed

Document GL JS plugin architecture #1835

tmcw opened this issue Dec 14, 2015 · 24 comments

Comments

@tmcw
Copy link
Contributor

tmcw commented Dec 14, 2015

Right now we have a bunch of competing approaches for how mapbox-gl-js plugins express their dependency on GL-JS.

  • peerDependencies
  • expecting a global to exist or window.mapboxgl
  • dependency with a require

Some plugins require-into mapbox-gl for utilities and thus actually rely on gl js internals.

Core questions

  • when should dependencies get a reference to gl-js?
  • how do we write a system that works equally well for cdn dependencies and browserify and webpack?

cc @jfirebaugh @tristen @mourner

@tristen
Copy link
Member

tristen commented Dec 14, 2015

cc @mcwhittemore as he's looking at making mapboxgl.Control it's own module.

@mcwhittemore
Copy link
Contributor

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.

@mourner
Copy link
Member

mourner commented Dec 14, 2015

I'd say let's do this:

  1. Specify Mapbox GL as peerDependencies.
  2. Rely on 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.

@mcwhittemore
Copy link
Contributor

@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.

@jfirebaugh
Copy link
Contributor

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:

  • The base code of Mapbox GL JS does not set up a global.
  • The base code of a plugin exports a factory function that takes a mapboxgl module as input and returns the plugin module as output.
  • There's a special "CDN wrapper" for Mapbox GL JS that requires the base code and sets a mapboxgl global.
  • There's a special "CDN wrapper" for each plugin that expects the mapboxgl global to be set, requires the plugin base code, calls the factory function, and extends the mapboxgl global with the result.

@mourner
Copy link
Member

mourner commented Dec 14, 2015

That looks like too much hassle if the only problem is not wanting to rely on a global inside browserify/webpack.

@mourner
Copy link
Member

mourner commented Dec 14, 2015

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).

@jfirebaugh
Copy link
Contributor

the only problem is not wanting to rely on a global inside browserify/webpack.

We get complaints about Mapbox.js relying on globals (mapbox/mapbox.js#726, mapbox/mapbox.js#1060, mapbox/mapbox.js#253, mapbox/mapbox.js#468). We should expect to get them about Mapbox GL JS too if we follow the same path.

@mourner
Copy link
Member

mourner commented Dec 14, 2015

@jfirebaugh most of these are specifically about browserify not working, and we can make sure it works while still relying on a global.

@mcwhittemore
Copy link
Contributor

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.

@mourner
Copy link
Member

mourner commented Dec 21, 2015

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.

@mcwhittemore
Copy link
Contributor

Oh. Didn't know browserify did this. Thanks

@tmcw
Copy link
Contributor Author

tmcw commented Aug 16, 2016

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:

  • Third-party controls should never attach themselves to mapboxgl. If we have an official directions plugin, it should be mapboxDirectionsControl, not mapboxgl.Directions.
  • We should not rely on mapboxgl until runtime. Relying on the global is not tenable with the variety of build systems right now.
  • mapbox.Control should be most useful as an interface, like IControl, not as a class. Plugins should not try to extend from mapbox.Control.
  • If a Control depends on the mapboxgl object, it should rely on it as an argument (invert control), rather than as a global. For instance, mapboxDirections(mapbox)().addTo(map).

@mourner
Copy link
Member

mourner commented Aug 16, 2016

@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
});

@tmcw
Copy link
Contributor Author

tmcw commented Aug 16, 2016

@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.

@mourner
Copy link
Member

mourner commented Aug 16, 2016

@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
});

@lucaswoj
Copy link
Contributor

I have been pushing things in an architectural direction that does not require sources or plugins to reference mapboxgl.

If 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?

@tmcw
Copy link
Contributor Author

tmcw commented Oct 25, 2016

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?

@lucaswoj
Copy link
Contributor

as long as all Map methods 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.

tmcw added a commit that referenced this issue Oct 31, 2016
This implements part of #1835

- mapboxgl.Control: deleted
- Controls need to implement addTo -> controls need to implement onAdd
- Navigation & attribution controls are no longer evented
tmcw added a commit that referenced this issue Nov 7, 2016
This implements part of #1835

- mapboxgl.Control: deleted
- Controls need to implement addTo -> controls need to implement onAdd
- Navigation & attribution controls are no longer evented
tmcw added a commit that referenced this issue Nov 7, 2016
This implements part of #1835

- mapboxgl.Control: deleted
- Controls need to implement addTo -> controls need to implement onAdd
- Navigation & attribution controls are no longer evented
- Fix container style, clean up naming and scoping of scale and geocoder control
- Move position argument to addControl method
- Move onRemove responsibility to controls
- Don't pass a boolean to attributionControl. Fixes #3561
tmcw added a commit that referenced this issue Nov 9, 2016
…apboxgl (#3497)

* Remove Control class, add removeControl method.

This implements part of #1835

- mapboxgl.Control: deleted
- Controls need to implement addTo -> controls need to implement onAdd
- Navigation & attribution controls are no longer evented
- Fix container style, clean up naming and scoping of scale and geocoder control
- Move position argument to addControl method
- Move onRemove responsibility to controls
- Don't pass a boolean to attributionControl. Fixes #3561

* Unsubscribe from events on onRemove. Fixes #3562

* Fix attributionControl tests

* Avoid more usage of bind

* Remove inherited super

* Remove single-use constant

* Document IControl

* Update debug.html, improve documentation

* Fix lint

* Fix default, let documentation infer it

* Fix addControl removeControl tests

* Use bindAll instead of arrow functions

* Don't use parameter default syntax

* Remove duplicated doc

* Add getDefaultPosition
@tmcw
Copy link
Contributor Author

tmcw commented Nov 10, 2016

As of #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:

@tmcw
Copy link
Contributor Author

tmcw commented Nov 15, 2016

@lucaswoj geocoder is now finished. Which branch should plugins/ directory PRs target?

@mollymerp
Copy link
Contributor

@tmcw it should target mb-pages so the examples are fixed asap

@tmcw
Copy link
Contributor Author

tmcw commented Dec 5, 2016

I'm going to write up a blog about the plugin architecture and then we can close this issue.

@tmcw
Copy link
Contributor Author

tmcw commented Dec 6, 2016

Blogged, done https://www.mapbox.com/blog/build-mapbox-gl-js-plugins/

@tmcw tmcw closed this as completed Dec 6, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants