Raruto / leaflet-elevation

Leaflet plugin that allows to add elevation profiles using d3js
GNU General Public License v3.0
253 stars 82 forks source link

GDPR compliance - usage without unpkg.com #266

Closed FabianSchmick closed 1 year ago

FabianSchmick commented 1 year ago

Example usage with webpack

I can't find a working example or documentation on how to use this plugin with webpack and imports. I have also found #229 which does not help me.

Your environment

Steps to reproduce

import * as L from 'leaflet';
import '@raruto/leaflet-elevation/dist/leaflet-elevation';

let map = L.map('map', {
    center: [41.4583, 12.7059],
    zoom: 5,
});

L.tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
    maxZoom: 18,
    attribution: 'Data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);

let controlElevation = L.control.elevation().addTo(map);
controlElevation.load("https://raruto.github.io/leaflet-elevation/examples/via-emilia.gpx");

Results in an error

Uncaught (in promise) Error: Cannot find module 'https://unpkg.com/@tmcw/togeojson@5.6.2/dist/togeojson.umd.js' Uncaught (in promise) Error: Cannot find module 'http://localhost/src/handlers/distance.js'


What I tried so far

Raruto commented 1 year ago

Hi Fabian,

in this regard I did a quick test in the past.

You can found it listed within the FAQs:

3. How can I import this library as ES module?

Usually, when working with a js bundler like Vite or Webpack, you need to provide to this library the full path to some dynamically imported files from the srcFolder:

import './your-custom-style.css';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import '@raruto/leaflet-elevation/src/index.js';
import '@raruto/leaflet-elevation/src/index.css';

const map = L.map('map', {
    center: [41.4583, 12.7059]
    zoom: 5,
});

const controlElevation = L.control.elevation({
    // CHANGE ME: with your own http server folder (eg. "http://custom-server/public/path/to/leaflet-elevation/src/")
    srcFolder: 'http://unpkg.com/@raruto/leaflet-elevation/src/'
}).addTo(map);

// Load track from url (allowed data types: "*.geojson", "*.gpx", "*.tcx")
controlElevation.load("https://raruto.github.io/leaflet-elevation/examples/via-emilia.gpx");

All external dependencies are lazy loaded (by default from unpkg.com) if they are not found within the HTML page before running the leaflet-elevation script (e.g. togoejson, d3js, ..)

πŸ‘‹ Raruto

FabianSchmick commented 1 year ago

Hi @Raruto

Thank you for your quick reply. I didn't see the FAQs. But with your answer, I could resolve the first error:

Uncaught (in promise) Error: Cannot find module 'https://unpkg.com/@tmcw/togeojson@5.6.2/dist/togeojson.umd.js'

The solution was:

import * as L from 'leaflet';
import * as d3 from 'd3';
global.d3 = d3;
import * as toGeoJSON from '@tmcw/togeojson';
global.toGeoJSON = toGeoJSON;
import 'leaflet-geometryutil';
import 'leaflet-almostover';

But I still have the other error

Uncaught (in promise) Error: Cannot find module 'http://localhost/src/handlers/distance.js'

The file is also public, available at http://localhost/src/handlers/distance.js Can I import the handlers by myself and don't use the lazy loading? Or do I need to configure my webpack for this?


If I import the handlers by myself like:

import '@raruto/leaflet-elevation/src/handlers/distance.js';
import '@raruto/leaflet-elevation/src/handlers/time.js';
import '@raruto/leaflet-elevation/src/handlers/altitude.js';
import '@raruto/leaflet-elevation/src/handlers/slope.js';
import '@raruto/leaflet-elevation/src/handlers/speed.js';
import '@raruto/leaflet-elevation/src/handlers/acceleration.js';

It also results in the same error And if I empty the handlers:

const controlElevation = L.control.elevation({
    handlers: []
}).addTo(map);

I get the error

Uncaught (in promise) Error: Cannot find module 'http://localhost/src/components/summary.js'

hupe13 commented 1 year ago

Maybe this syntax helps: https://github.com/Raruto/leaflet-elevation/blob/ea155564475c44941cb81a9df3eaf8d633ed90fc/examples/leaflet-elevation_chart_labels.html#L77-L79

FabianSchmick commented 1 year ago

If I add the following comment /* webpackIgnore: true */ in here:

return condition !== false
  ? import(/* webpackIgnore: true */ _.resolveURL(src, this.options.srcFolder))
  : Promise.resolve();

Everything works with the lazy loading.


Got the comment from https://github.com/webpack/webpack/issues/8341#issuecomment-436550381 maybe this should be added in a PR?

Explanation from https://stackoverflow.com/a/56998596/5947371:

Webpack reinterprets the standard ES import statement. Within a standard webpack config, webpack uses this statement as a split point for bundles, so even modules that are dynamically imported are expected to be there at webpack build time (and the build process will fail if they are not available)

Raruto commented 1 year ago

Do I need to configure my webpack?

I'd say it shouldn't be necessary, if you're able to disable lazy loading completely.

Probably the sample code mentioned in the FAQ was tested with Vite (which is overall easier to set up).


Can I import the handlers by myself?

Just prevent the lazy loading logic by providing your own handlers, it should be something like following:

import { Distance } from "@raruto/leaflet-elevation/src/handlers/distance.js";
import { Time } from "@raruto/leaflet-elevation/src/handlers/time.js"
...
  handlers: [
    Distance,
    Time,
    ...
  ]

Original source: https://github.com/Raruto/leaflet-elevation/issues/229#issuecomment-1386659536


If you still have problems, try to expose them globally as well:

import { Distance } from "@raruto/leaflet-elevation/src/handlers/distance.js";
import { Time } from "@raruto/leaflet-elevation/src/handlers/time.js"
import '@raruto/leaflet-elevation/src/index.js';

...

L.Control.Elevation.Distance = Distance;
L.Control.Elevation.Time = Time;

...

const controlElevation = L.control.elevation({ ... }).addTo(map);

Related source:

https://github.com/Raruto/leaflet-elevation/blob/ea155564475c44941cb81a9df3eaf8d633ed90fc/src/control.js#L750-L764


If I add the following comment /* webpackIgnore: true */ in here, everything works with the lazy loading.

Maybe this should be added in a PR?

Within your local code (to apply some patch or quick test), you can always monkey patch any native functionality through the L.Control.Elevation.include().

For more info: old issues

πŸ‘‹ Raruto

FabianSchmick commented 1 year ago

@Raruto thank you for your support and help.

I try to prevent the lazy loading logic by providing my own handlers as you suggested. This works for the files in the handlers directory. For the files in the components directory, it will not work, as they still try to get lazy loaded.

But I will stick to the second approach and monkey patch the import function with the specific webpack comment πŸ‘πŸΌ thank you again for the useful tips :)

Raruto commented 1 year ago

Ok @FabianSchmick.

When you're done, just to facilitate the searching job for those who will come later, remember to post your final code snippet as well.

πŸ˜‰ Raruto

FabianSchmick commented 1 year ago

Final code:

import * as L from 'leaflet';
import * as d3 from 'd3';
global.d3 = d3;
import * as toGeoJSON from '@tmcw/togeojson';
global.toGeoJSON = toGeoJSON;
import 'leaflet-geometryutil';
import 'leaflet-almostover';
import '@raruto/leaflet-gesture-handling/dist/leaflet-gesture-handling';
import '@raruto/leaflet-elevation/libs/leaflet-edgescale.min.js';
import * as _ from '@raruto/leaflet-elevation/src/utils';
import '@raruto/leaflet-elevation/dist/leaflet-elevation';

let map = L.map('map', {
    center: [41.4583, 12.7059],
    zoom: 5,
});

L.tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
    maxZoom: 18,
    attribution: 'Data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);

// Monkey patch the import function -> https://leafletjs.com/examples/extending/extending-1-classes.html#lclassinclude
L.Control.Elevation.include({

    import: function(src, condition) {
        if (Array.isArray(src)) {
            return Promise.all(src.map(m => this.import(m)));
        }
        switch(src) {
            case this.__D3:          condition = typeof d3 !== 'object'; break;
            case this.__TOGEOJSON:   condition = typeof toGeoJSON !== 'object'; break;
            case this.__LGEOMUTIL:   condition = typeof L.GeometryUtil !== 'object'; break;
            case this.__LALMOSTOVER: condition = typeof L.Handler.AlmostOver  !== 'function'; break;
            case this.__LDISTANCEM:  condition = typeof L.DistanceMarkers  !== 'function'; break;
            case this.__LEDGESCALE:  condition = typeof L.Control.EdgeScale !== 'function'; break;
            case this.__LHOTLINE:    condition = typeof L.Hotline  !== 'function'; break;
        }

        // Add the ignore comment for webpack
        return condition !== false ? import(/* webpackIgnore: true */ _.resolveURL(src, this.options.srcFolder)) : Promise.resolve();
    }

});

const controlElevation = L.control.elevation().addTo(map);
controlElevation.load('https://raruto.github.io/leaflet-elevation/examples/via-emilia.gpx');

Last step, configure webpack to copy the node_modules/@raruto/leaflet-elevation/src directory to your public project directory, so the lazy loading can find the requested files.