Leaflet: How are we feeling about ES6 module support these days?

Created on 28 Mar 2020  路  7Comments  路  Source: Leaflet/Leaflet

Hi folks,

I've been digging around today trying to figure out why Webpack's tree shaking magic did not appear to be working on Leaflet's code. Digging back through the history here, I see the following historic PRs:

  • February 2018: #6021 adds ES6 Module support, and unlocking all the goodies that come alongside
  • March 2018: Leaflet/Leaflet.markercluster#874 is broken due to the lack of a global L
  • July 2018: #6239 was a hotfix removing support for ES6 modules due to the above

In the removal hotfix, there was a comment that "people who want to take advantage of tree shaking can opt-in by adding custom rules resolving Leaflet to dist/leaflet-src.esm.js." I'll freely admit that I'm struggling to figure out how to do that (it may be pretty clear to be fair; I'm really not up to date with the modern Javascript ecosystem.)

Ever since that hotfix, there's been a few PRs attempting to reintroduce module support but they've all been closed with a link back to the removal hotfix, and no further discussion.

The Leaflet/Leaflet.markercluster#874 issue remains open, with recent (January this year) comments floating the ideas of:

  • A new major version release of Leaflet to enforce the removal of the L global variable
  • Updating plugins to automatically resolve L to the leaflet module, rather than requiring Leaflet to expose L as a global variable.

Beyond this chatter on the Markercluster repo, I haven't seen any other discussion on how the Leaflet community is feeling about the lack of ES6 module support and the widespread (?) dependence on a global L. If I may, I'd like to be the one to bring these up for discussion.

Do we know what the remaining blockers are for:

  • Removing the global L
  • Bringing back ES6 module support?

And is there anything we can do make progress on these?

Thanks all,
Rob

Most helpful comment

I have a setup that works pretty well using Parcel v2. I suppose it would work for any bundler.

What I do is have my own module myLeaflet.js where I import only what is required by my own code and the plugins I use. As an example,

/* myLeaflet.js */  
import {
  Control,
  DomEvent,
  DomUtil,
  Evented,
  LatLng,
  LatLngBounds,
  Layer,
  Map
} from "../../node_modules/leaflet/dist/leaflet-src.esm.js"

export {
  Control,
  DomEvent,
  DomUtil,
  Evented,
  LatLng,
  LatLngBounds,
  Layer,
  Map
}

/*
 * The global namespace L is required by a few plugins.  We provide one,
 * with the bare minimum content that they require.
 */
window.L = {
  Control,
  DomEvent,
  DomUtil
}

In my own code I import what I need from myLeaflet.js rather than leaflet. For the plugins that require the L namespace, a minimal one is there for them. Some leaflet plugins modify an existing Leaflet class and some create their own. As an example of two that are in node_modules I do something like

import { Map } from "./myLeaflet.js"
import "thePlugin"
import "theOtherPlugin"

const L = window.L
const thePlugin = L.thePlugin()
const theOtherPlugin = new L.Control.theOtherPlugin(options)

const map = new Map()
theOtherPlugin.addTo(map)
...

One peculiarity is that, at least for Parcel, it does not work to import the plugins inside myLeaflet.js and export them from there. myLeaflet.js must be imported first, and separately. The act of importing it populates the L namespace.

So there is some pollution of the global namespace with L but not much, and full tree-shaking is enabled.

All 7 comments

It would be great to just add the "module" field to package.json

Hey everyone, I'm new to the project. Working with a team, considering migrating from Mapbox to Leaflet to save on performance. Would really love the ability to import at an ES6 module. Threw me for a loop this morning that I could not.

*Edit: if it's a useful data point, we primarily build with Vue.js

You can import from /dist/leaflet-src.esm.js, but if your toolchain relies on the module field in package.json, as most tools do at the moment, you will be out of luck.

your options are to rewrite the imports at build/run time, or to patch the package.json under node_modules/leaflet to include the line

"module": "dist/leaflet-src.esm.js"

Hopefully our good maintainers will merge my PR soon and we won't have to resort to patch-package shenanigans anymore

In the mean time, put this in scripts/fix-leaflet.mjs

// @ts-check
import pkgJson from '../node_modules/leaflet/package.json';

import { writeFileSync } from 'fs';

writeFileSync(
  './node_modules/leaflet/package.json',
  JSON.stringify({
    ...pkgJson,
    module: 'dist/leaflet-src.esm.js',
  }, null, 2));

then add this to your postinstall:

"postinstall": "node --experimental-json-modules scripts/fix-leaflet.mjs"

I added a default index.js to the root of the Leaflet package. With this in place, I can just add the raw Leaflet repos to a project (where the dist folder contains only css files). Node automatically falls back to the main source, since the main entry point from package.json does not exist.

valdese-net/Leaflet@ade00ec59dbc494ea9f6b48cba403299d8415971

I have a setup that works pretty well using Parcel v2. I suppose it would work for any bundler.

What I do is have my own module myLeaflet.js where I import only what is required by my own code and the plugins I use. As an example,

/* myLeaflet.js */  
import {
  Control,
  DomEvent,
  DomUtil,
  Evented,
  LatLng,
  LatLngBounds,
  Layer,
  Map
} from "../../node_modules/leaflet/dist/leaflet-src.esm.js"

export {
  Control,
  DomEvent,
  DomUtil,
  Evented,
  LatLng,
  LatLngBounds,
  Layer,
  Map
}

/*
 * The global namespace L is required by a few plugins.  We provide one,
 * with the bare minimum content that they require.
 */
window.L = {
  Control,
  DomEvent,
  DomUtil
}

In my own code I import what I need from myLeaflet.js rather than leaflet. For the plugins that require the L namespace, a minimal one is there for them. Some leaflet plugins modify an existing Leaflet class and some create their own. As an example of two that are in node_modules I do something like

import { Map } from "./myLeaflet.js"
import "thePlugin"
import "theOtherPlugin"

const L = window.L
const thePlugin = L.thePlugin()
const theOtherPlugin = new L.Control.theOtherPlugin(options)

const map = new Map()
theOtherPlugin.addTo(map)
...

One peculiarity is that, at least for Parcel, it does not work to import the plugins inside myLeaflet.js and export them from there. myLeaflet.js must be imported first, and separately. The act of importing it populates the L namespace.

So there is some pollution of the global namespace with L but not much, and full tree-shaking is enabled.

I think @ebrensi has a really good approach. If Leaflet doesn't pursue ES6 module support in the future and someone finds this thread looking for a good solution, I just want to endorse that idea. Despite relying too much on the global L, it still does a great job of encapsulating the Leaflet code and managing it. This kind of interface between 3rd party libraries and your own code is a responsible way to do things (and ES6 modules kind of solve the problem for us, but without them, a custom solution like this is as good as anything).

Was this page helpful?
0 / 5 - 0 ratings