Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Leaflet renders Windy map when used via CDN but not via npm pakcage

I am using Leaflet with Windy in React via CDN which works fine:

in index.js:

<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://api.windy.com/assets/map-forecast/libBoot.js"></script>

React component:

export const renderMap = (): void => {
    const options = {
        key: 'xyz',
        lat: 41.3,
        lon: 2.1,
        zoom: 10,
    };
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (window as any).windyInit(options, (windyAPI: any) => {
        const { map } = windyAPI;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (window as any).L.popup()
            .setLatLng([41.3, 2.1])
            .setContent(':)')
            .openOn(map);
    });
};

However I want to be able to use the leaflet npm package, not the CDN. When I import the package, it is defined but Windy throws the error:

libBoot.js:3 Leaflet library is missing

in index.js:

<script src="https://api.windy.com/assets/map-forecast/libBoot.js"></script>

React component:

import L from 'leaflet';

export const renderMap = (): void => {
    console.log('L', L); // defined - object is present
    const options = {
        key: 'xyz',
        lat: 41.3,
        lon: 2.1,
        zoom: 10,
    };
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (window as any).windyInit(options, (windyAPI: any) => {
        const { map } = windyAPI;
        L.popup()
            .setLatLng([41.3, 2.1])
            .setContent(':)')
            .openOn(map);
    });
};

There is very little information on setting this up online because Windy defers to Leaflet, and Leaflet defers to Windy. There is no information in the Leaflet quick start guide or in their github.

What is the correct way to make this work using the Leaflet npm package

like image 971
Mr. Robot Avatar asked Nov 07 '22 03:11

Mr. Robot


1 Answers

The "Leaflet library is missing" error is simply due to the loading order of these JS libraries:

https://github.com/windycom/API/tree/master/hello-world#hello-world

Load the Leaflet library at the beginning of your script and after that the Windy API library from URL https://api.windy.com/assets/map-forecast/libBoot.js.

  • when you write the <script> tags yourself explicitly in the HTML page (to load them via CDN), you correctly load Leaflet first, then Windy
  • but when you import Leaflet, webpack (which is the default build engine when you use React) bundles it with your app code, and by default it inserts the <script> tag last in the <body> of your HTML page, therefore making it be loaded after Windy (which then throws the error message)
  • when you reintroduce your <script> tag to load Leaflet from your node_modules folder, I guess you put it back first (i.e. before the tag for Windy); however this will work only in development, because that node_modules folder will not be available in production.

The easiest solution for you would have been if Windy library could be loaded from a local file (or npm package) instead of from its URL (/ CDN): in that case, you would simply import it after Leaflet, and webpack will bundle them in that order.

However it is likely that Windy performs some magic itself based on its location, e.g. to load CSS:

The Leaflet CSS is loaded automatically.

While it would be possible to workaround this behaviour for CSS, it is likely it also does some magic for other stuff (e.g. access to its data and layers).

So if we assume we must load Windy from CDN, but (for whatever reason) we are absolutely ready to still get the headache of loading Leaflet from npm (instead of from CDN as well, which we know works fine and easily), the we have to find a way to force webpack load order somehow.

We are now clearly stepping outside of webpack main use case, because in this specific situation, we still want Leaflet to be bundled, but load Windy from URL and after Leaflet, but obviously before our app code.

I see at least 2 possible (obviously hacky) solutions:

  • generate the <script> tag for Windy dynamically, within our app code; this ensures that it loads after Leaflet; but we have to delay the execution of the part of our app that depends on Windy (e.g. by wrapping it in a setTimeout, ideally checking that Windy is now available, since its loading delay may vary)
  • use some webpack plugins to import Windy in our code, but have webpack actually still load it from URL: e.g. using dynamic-cdn with specific configuration to specify the URL; not sure it would be easy to still have correct load order, as it may not recognize the dependency to Leaflet.

All that being said, we may still question the need to load Leaflet from npm in this case (since loading it from CDN before Windy is a breeze).

Given the casts to any you have to do for Windy, I see that you actually use TypeScript. Therefore you may want to have Leaflet typings available, which come almost naturally when you import it (and have installed @types/leaflet), giving you the impression that it must be loaded from npm for these typings to work. In that case, we can leverage the fact that the types and the actual runtime code are 2 separate things. In this case, you can still import Leaflet in your code and benefit from its type, but instruct webpack that it should not be bundled; that you will make it available externally, typically because you load from CDN. See the externals option of webpack. That way, you get the best of both worlds: you have the liberty of loading Leaflet your way, here explicitly before Windy, and still have Leaflet typings.

like image 59
ghybs Avatar answered Nov 12 '22 16:11

ghybs