Composing Software: Spatial for the JAMstack generation.

Dennis Bauszus
7 min readAug 23, 2022

--

At GEOLYTIX we are currently putting finishing touches to the v4 release of XYZ/Mapp.

XYZ is a node.js application to provide serverless cloud interfaces for spatial data. Mapp is a library to build web applications which interface with the XYZ API.

Working with a small team of dedicated engineers we must keep the library as lean as possible. With dozens of v3 instances already in production, our clients we are satisfied with the available features and performance. Developer experience and integration within the open source community are the priorities for the next release. A first step in this direction was the decision to split the Mapp bundle in two by porting any UI methods into a separate library.

In this article I want to highlight some Javascript (ES2020) techniques which we use to extend and customise the core functionality of the Mapp library and UI within a browser context (or JAMStack for clickbait).

Fortunately we never supported Internet Explorer to begin with. This allows to use the best of available techniques such as function composition, object proxies, and dynamic imports.

What is software composition.

In the words of Eric Elliot; All software development is composition: The act of breaking a complex problem down to smaller parts, and then composing those smaller solutions together to form your application.

Composing Software by Eric Elliot

What is the JAMstack.

Coined by Netlify CEO and co-founder Mathias Biilmann to describe modern web development architecture, a JAMstack consist of JavaScript, APIs, and Markup. In Jamstack websites, the application logic typically resides on the client side, without being tightly coupled to the back end server.

Image credit www.cygnismedia.com

This means that we are able to import the Mapp library into any hosted markup and start building map-centric applications with vanilla Javascript. There is no need to learn any particular web framework though you are free to choose one.

Image by Andres Reales

Composition without compilation.

There are multiple ways to bring script into the context of a web application.

The basic usage of the <script> tag allows import from a source.

Script may also be nested inline as a HTMLScriptElement.

Then there is the evil eval(). There isn’t much to be said but;

Never use eval().

Within Mapp script we may also use static imports for locally available modules. We use esbuild to bundle the Mapp and UI libraries.

Proposed at TC39, dynamic imports are finally available in all modern browser.

As of yesterday IE is already gone from the MDN support matrix.

The static form may be preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking. However we are able to use dynamic imports as a fallback for missing script dependencies and to extend our application via plugins.

It is important to note that these imports come from Skypack which provides load optimized npm packages with no install and no build tools.

Function Composition

Function composition is an approach where the result of one function is passed on to the next function, which is passed to another until the final function is executed for the final result.

Multiple utility libraries such as Ramda and Lodash provide methods to compose functions. Within the mapp.utils we provide a basic compose() method where an array of functions is reduced to provide a final results from the reducer callbacks.

Callback arrays

We provide callback arrays where it is not required to execute methods in a synchronous manner.

The Openlayers draw interaction for example can be configured with a condition to confirm whether the event should be handled. With a callback array we can execute multiple asynchronous function calls from a single event.

Initiated with a callback array the Mapp API draw interaction allows for multiple calls to different routing APIs such as Mapbox Directions or Here Routing with the draw geometry as input.

DOM events and proxies

The separation of user interface and mapview means that the Mapp library cannot know about any listview element created by the UI library. List objects must be able to handle insert, delete, and update methods. These changes should be reflected in the display of the listview element. Once imported, UI library methods may listen to change events on the mapview target or the document body itself. This is not recommended unless the event is in regard to a view change of the mapview element.

A more elegant solution will create a Proxy from the mapview.locations list object. Methods to add or remove location views from listview element can be bound to the set and deleteProperty methods of the proxy.

Dictionaries

In order to provide user interfaces for our global clients it must be possible to extend Mapp dictionaries by merging new entries and languages into the dictionaries object.

The mapp.dictionary itself is a proxy which will return the English text for the requested key value as fallback.

Dynamic Imports

The UI and Mapp bundle must only include dependencies without which no mapview could be initialised. Furthermore, any script included must be open source and not violate our licence.

Openlayers allows for 3rd party mapping libraries such as mapbox-gl or maplibre-gl to override the default render of the layer object.

Within a Mapp module we can check whether a library has already been loaded from a script tag and is available as global object. If unavailable we will create a promise to await the import of the required library from Skypack. Subsequent and concurrent calls must wait for the promise assigned to a variable outside the closure to be fulfilled.

In a similar fashion are we able to provide fallback imports for libraries such chart-js and tabulator which are referenced in the mapp.ui library. This allows us to provide core interface methods for data visualisations but only load the libraries once required. Not every Mapp instance adds charts and/or tables for spatial data represented in a mapview. And not every user will open a location which supports such a visualisation. But once they do, the necessary script will either be imported from Skypack if not already cached in the browser.

A client can change the behaviour of the layer style function to create custom icons which represent the distribution of feature properties within a cluster and display their location on hover.

Mapp Plugins

Prior to the general availability of dynamic imports we would use promises to to create a script tag element which we append to the document head of an application view. Once loaded the script element and associated event listener would be removed.

This process required the plugin script to be nested in a CustomEvent dispatched to the document. The Mapp library could be passed as event detail thus allowing to modify or append the library within the global (window) context.

This rather unelegant process has now been superseded by the v4 Mapp loadPlugins utility. Here we only need to provide an array of source locator to be dynamically imported within a Promise.all().

The plugin script as shown in the codepen above is hosted on Geolytix /public Github repository published via Github Pages.

We added a plugin to create and cache SVG icons that represent a feature’s sentiment property to the Bristol locale in the XYZ/Mapp dev workspace.

https://geolytix.dev/latest/view/blog?layers=mapbox_base,sentiment_style&locale=Bristol&plugins=true

SEE YOU SPATIAL COWBOY…

This article is in support of my lightning talk at the ’22 FOSS4G conference in Firenze. I hope to meet you there and talk anything Mapp and Javascript for Spatial in more detail.

GEOLYTIX are proud to sponsor FOSS4G in the UK (17th November ‘22). Please mark the date and join us either in London or Leeds. I am planning to organise and record a workshop prior to the UK event to provide some hands-on Mapp v4 experience for front-end developers.

Please subscribe to the GEOLYTIX newsletter and follow us on twitter for forthcoming details.

--

--

Dennis Bauszus

I am doing some web and map stuff with @GEOLYTIX. Mostly maps on the web.