Encapsulating MapMip: How we distributed an Angular 4 based plugin as a pure JS component

Preface: MapMip

MapMip (github, demo) is a JavaScript plugin that encompasses three powerful map components: Cesium, Leaflet and Openlayers3.  It is a one-api for all plugin. It allows the user many features like adding markers, switching between layers, switching from 2D view to 3D virtual globe, importing geoJson and more, while using the same API and keeping consistency between the 3 mapping tools.

While MapMip was built with Angular (version 4), and can be used as an angular module, we require it to be distributed as a pure JS component that can be used in any app.

Purpose

Our goal was to create a plugin with angular-cli and enjoy its benefits, and at the same time distribute the code such that every JavaScript application with any  framework (React, Angular 1.5,  …) will be able to load MapMip and its API, either by <script> tag or by using an amd (import/require) via npm.

The Problem

We faced several issues when building – all solved quite easily with today’s powerful packaging tools:

  1. We wanted a plugin that will not pollute the global scope of the hosting app – so that a react developer wouldn’t need to install angular (or even know that it is being used).
  2. We wanted the app to be in one file to make using the library easier.
  3. We wanted to expose the MapMip API to any application (either on the global scope – usually window) or via import/require.
  4. Automate the process.

Solution

  1. Angular cli build does a great use of webpack in order to encapsulate third party code and css.  So by using angular cli build we achieve the first part.  5 files that hold the styles, the 3rd party libraries and our plugin code.
  2. We’ve added a gulp procedure that takes the 5 files and puts them together in the same MapMip.js file.  So now we have one file that holds all the CSS and JS we need to run MapMip.
  3. The way to expose MapMip API to any application was done first in the MapMip code itself, by exposing an API to a given global. Then, in order to use it in any application we used the UMD pattern (see below) in order to expose the API either in the global window, or return it as an export/define to be used by some AMD.
  4. Our plan is that the build of the application will automatically concatenate all files into one and will add the UMD and IIFE parts so we can just take the build output into any application.
    This would be done by:
    1) Replace the window.Mapmip = MapmipApi to MapmipGlobal.Mapmip = MapmipApi during the build process.
    2) Encapsulate the Mapmip inside an IIFE with the UMD pattern.

Now let’s explain detaily the idea of Modularity UMD, and IIFE.

 

Modularity in JavaScript and UMD  

Modularity

Modularity development in JavaScript was a problem for a long time. There were plenty of libraries like requireJs that helped, but there was no one spec.  Recently, ES6 released new specs regarding modularity of code, but still those specs were not commonly used. Meanwhile, less official specs by AMD and commonJS took place and became more popular.

UMD

UMD (Universal Module Definition) are patterns or definitions of module load idea, which can be found in UMD repository. Let’s see how we implemented this solution in MapMip:

(function(global, factory){

   var tmpGlobal = {};

   if (typeof exports === 'object' && typeof module !== 'undefined'){

       factory(tmpGlobal);

       return exports = module.exports = tmpGlobal.Mapmip;

   }

   if (typeof define === 'function' && define.amd) {

       factory(tmpGlobal);

       return define([], tmpGlobal.Mapmip);

   }

   factory(global);

})(this,

   function app(mapMipGlobal){

//…all MapMip code…

//...

mapMipGlobal['Mapmip'] = MapmipApi;
}));

 

This above code shows 3 options of loading the MapMip code: via ES6 exports , via AMD module or via a global variable, depends the environment. When one of the 3 load methods is chosen – the module is loaded . how? IIFE.

 

IIFE

IIFE (immediately-invoked function expression) is in fact wrapping an application with an auto invoked function. When the js file of the IIFE wrapped application is being imported, the function is being auto-invoked, so what what happens is that the whole application is running. In our case, the application was invoked and it was possible to expose the API class out, by storing it on the js window object, or on a global variable object.

In our code above, ‘this’, which represents the global object is being sent to ‘global’, and ‘function(app)’, is being sent to ‘factory’. Then the environment detection is being done and load MapMip by the desired method: exports of ES6, define of AMD or if none of them exists use the global object.

Finally, Take a look in a Plunker of angular 1.5, or JSFiddle of React, that loads MapMip and uses the API functions.

 

Leave a Reply