Reputation: 10792
I built a plugin for leaflet (npm package), and I want it to be available either as an ES6 import, or as a script in the HTML header.
For example, I want both of these to be possible:
// As an npm package
import 'leaflet-arrowheads'
// From your HTML header
<script type="text/javascript" src="./leaflet.arrowheads.js"></sript>
My plugin has a dependency, leaflet geometryutil. So the code looks something like this:
import 'leaflet.geometryutil'
L.Polyline.include({
...my plugin code here
})
L.LayerGroup.include({
... a bit more code here
})
This works when using ES6 modules, where leaflet.geometryutil
is installed as a dependency of my package. But when being used in the header of an HTML file, this obviously won't work. (Of course anyone trying to do this will have to have the leaflet.geometryutil script in their header somewhere before my plugin - this is explained clearly in my github readme.)
My current strategy has been to just have 2 github branches - one with the import statement, and one without. But I am trying to understand what I can do to merge these two branches and have a single piece of code that works in both situations. My 2 ideas so far have been these:
Only import the geometryutil
module if it's not already present in the Leaflet namespace:
if(!L.GeometryUtil){
import('leaflet-geometryutil')
.then( GeometryUtil => {
console.log('all good')
})
.catch( err => throw(err) )
}
L.Polyline.include({ ..plugin code... })
This doesn't work because the import
promise doesn't return until after my L.Polyline.include
code runs, so any use of GeometryUtil
inside my code returns a GeometryUtil
is undefined. This works in the HTML header scenario where the author has a GeometryUtil
script tag prior to my plugin tag, because the if
statement return false and the import promise is not executed.
Is there another way to write a dynamic import statement so that the plugin code runs only after the import promise is complete, and is then properly imported into another module that the plugin user is authoring?
It seems I might be able to accomplish my goal with webpack. Based on the webpack docs about authoring libraries, I could do something like
output: {
path: './dist',
filename: 'leaflet.arrowheads.js',
library: 'arrowheads',
libraryTarget:'umd'
}
But I am not really sure how to configure the output exactly. The docs explain that this will expose a global variable arrowheads
, but that's not what's needed. The plugin I'm building doesn't really expose a new variable, but rather adds to the existing leaflet library.
How can I configure a webpack.config to build the plugin so that it 1: is available as an import (which itself imports its dependency leaflet.geometryutil), and also 2: is available as a script tag from an HTML header, which automatically alters the leaflet object L
as intended?
Is there a third option I'm not thinking of?
Thanks for reading.
Upvotes: 3
Views: 6325
Reputation: 53350
This is a common puzzle for frontend libraries.
It is true that webpack can offer some solutions, same for Rollup (which is the build engine used by Leaflet).
As for dynamic import, it works fine when you build an application and have the dynamic import managed by your build engine, but I would not rely on it when writing a library, for which you do not know if the environment / build engine used by the consumer project will support it.
In your case, you may even save the hassle of using a build engine and having to play trying to configure it properly.
First of all, you properly noticed (and followed the Leaflet convention) that most Leaflet plugins do not export anything to global scope or to an importer, but have the side effect of adding some behaviour within Leaflet's L
namespace. As such, when using a build engine importer, we just have to do:
import "leaflet";
import "leaflet-arrowheads"; // nothing in curly braces
Therefore your library does not even need to export anything (i.e. you can get rid of the export default
before your L.Polyline.include
).
You might now start seeing a possible solution: have a common file for both import and HTML script, containing only your L.Polyline.include
code; have a separate import entry file (i.e. what you reference in your package.json/main
that imports your dependencies AND your common file. BTW you could even import Leaflet in that file!
package.json
{
"main": "index.js" // npm convention, not even needed if your file is named "index.js"
}
index.js
import "leaflet";
import "leaflet.geometryutil";
import "./src/leaflet-arrowheads.js"; // common file
src/leaflet-arrowheads.js
L.Polyline.include() // etc.
Then you have a single common file to maintain, no need to use a separate branch, everything is published to npm, i.e. free CDN's (like unpkg, cdnjs and jsDelivr) will be able to directly serve your common file, ready for HTML script consumption!
The last thing to worry is your dependency: in the case of node style import, it is fine since your package lists it as dependency, therefore it will be installed with your package, and you properly import it in the index file.
But for HTML script, you still have to import the dependency "manually", i.e. you still have to tell the users of your library to include the dependency in their HTML scripts.
This can be avoided by bundling the dependency with your plugin, typically by introducing now a build engine. In that case, the usual practice is to build several versions: 1 with all dependencies already bundled, so that your library user needs only 1 import; 1 with only your code, letting your library user managing the import of the dependencies (typically so that they have control of version, can use them direclty as well without duplicating code, etc.). For this, the above index file is also what is used as the build entry point. Then you have to specify what not to include in the bundle: exclude Leaflet, and possibly Leaflet-geometryUtil.
Upvotes: 4
Reputation: 17
I do the following for my site...
After the end 'body' tag include the following line
<?php include_once("files_java/_java_loader.php");?>
The _java_loader.php file is like this...
<?php
$script_Array = explode("/", $_SERVER['SCRIPT_FILENAME']);
$script_Name = $script_Array[count($script_Array)-1];
// Load module specific scripts for each module
switch ($script_Name) {
case 'maps.php':
case 'map_all.php':
case 'neighbors.php':
case 'view_large_map.php':
?><script type="text/javascript" src="<?php echo SERVER_PATH; ?>files_java/mapper.js"></script><?php echo PHP_EOL;
?><script type="text/javascript" src="<?php echo SERVER_PATH; ?>files_java/initMaps.js"></script><?php echo PHP_EOL;
?><script type="text/javascript" src="<?php echo SERVER_PATH; ?>files_java/downloadXML.js"></script><?php echo PHP_EOL;
?><script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAXPvUkWaeVfdwQjtlBZwlZND0-0bcYpZs" defer></script><?php echo PHP_EOL;
break;
}?>
Thus, you can set specific files to load for specific modules. Above the $script_array statement include all the javascript links that are needed for all modules.
I use a similar structure for CSS loads also.
jdadwilson
Upvotes: 0