Wednesday 20 April 2016

MapGuide Open Source 3.1 pre-flight check

Okay, all systems should be go to start the release cycle for the first beta of MapGuide Open Source 3.1 real soon. A lot of time was wasted spent on refactoring the vagrant-based build system on Linux to ensure that making this and future releases of MapGuide and FDO a more simpler affair:

  • The bulk of the shell provisioning logic from our current Vagrantfiles have been offloaded to their own shell scripts, making the Vagrantfiles now a super-thin script that calls the main provisioning script with the right MapGuide/FDO branch names and version numbers
  • As a result, in terms of making future releases, we just have to update branch names and version numbers in one central location (the Vagrantfile) instead of what we currently do, which is updating the branch names and version numbers in all our shell scripts
These changes were made not only for the current build system, but also back-ported for use in producing future 3.0 and 2.6 point releases. Meaning once 3.1 drops, you should see 3.0.1 and 2.6.2 point releases following real soon.

So before we kick the build system into gear, I think I'll give a good week or two to pore through the current MapGuide/FDO/Fusion bug lists to see what stuff we can easily knock off before I hit the big red button.

Thursday 14 April 2016

Making Immutable.JS objects easier to work with in TypeScript

UPDATE 4/4/2018: Want to see how this is done in TypeScript 2.8? Click here.

I've been playing with React.JS a lot lately (personally and at work), in combination with TypeScript, Redux and Immutable.JS.

In my adventures with this particular front-end stack (starting from this excellent React/TypeScript/Redux starter template), I've been finding the use of Immutable.JS greatly helps in reasoning about code and how state changes flow through various React components.

One of the problems however, is that the Immutable.JS APIs are very dictionary-like (ie. Magic strings galore!) when dealing with immutable objects. The TypeScript definition for this library is also somewhat restrictive with regards to immutable objects, insisting that your immutable objects (ie. Maps) have keys and values of a single certain type.

Which means, if you had a TypeScript object modeled like this:


interface IGeographicCoordinate {
    lat: number;
    lng: number;
}

interface IPlacemark {
    id: number;
    coordinate: IGeographicCoordinate;
    name: string;
}

It's immutable form would look like this:


import { fromJS, Map } from 'immutable';

...

let pm: IPlacemark = {
    id: 1,
    coordinate: {
        lat: 0.0,
        lng: 0.0
    },
    name: "Null Island"
};
//First way: using fromJS
let imPlacemark = fromJS(pm);
//Second way: using Map()
let imPlacemark2 = Map(pm);

Now if we were to have a peek at what the inferred types are in any editor that supports TypeScript (for example, Visual Studio Code), we see that the default tooling experience is ... not very helpful



The first immutable object is inferred to be of type 'any'. The second one is inferred to be of type 'Map<{}, {}>', which is close enough to 'any'. If you're working with objects of type 'any', you're pretty much back in vanilla JS land with none of the benefits that TypeScript offers. If an object's type or shape can be described in TypeScript, it should!

Sadly, the typings in Immutable.JS demand our Map instances use homogeneous key and value types for all possible properties. Having keys and values as of type 'any' doesn't really help us in the strongly-typed land of TypeScript. 'any' is pretty much an escape hatch type to allow for interoperability with existing JavaScript code/libraries, it offers zero value in terms of type-safety and tooling experience.

Not to mention, accessing values of such an immutable object is magic strings galore.


let id = imPlacemark.get('id');

And because we're dealing with 'any', that propagates down to everything.



When you have nested objects, you're pretty much back in JavaScript land where any object can be anything. TypeScript can't help us here. You have to know up-front what types you're expecting because the TypeScript tooling and language services won't be able to help you.


//Returns Immutable.Map and not IGeographicCoordinate
let coord = imPlacemark.get('coordinate');
//These will be any and you have to know to get on 'lat' and 'lng' keys
let lat = coord.get('lat');
let lng = coord.get('lng');

So is there anything in TypeScript that allows us to work with APIs such as Immutable.JS Maps in a more rigid and robust manner? Fortunately, I've found a useful little pattern that I've been using that makes working with immutable objects more TypeScript friendly and it involves leveraging 2 language features of TypeScript:
With these two features, we can define a complementary (and mostly type-safe) immutable version of any interface that will return different types based on the name of the key we pass in. So if we return to our placemark example.


interface IGeographicCoordinate {
    lat: number;
    lng: number;
}

interface IPlacemark {
    id: number;
    coordinate: IGeographicCoordinate;
    name: string;
}

We can define complementary immutable versions of the above interfaces like so:


type GeographicCoordinateProperties = "lat" | "lng";
type PlacemarkProperties = "id" | "coordinate" | "name";

interface IGeographicCoordinateImmutable {
    get(key: GeographicCoordinateProperties): any;
    get(key: "lat"): number;
    get(key: "lng"): number;
}

interface IPlacemarkImmutable {
    get(key: PlacemarkProperties): any;
    get(key: "id"): number;
    get(key: "coordinate"): IGeographicCoordinateImmutable;
    get(key: "name"): string;
}

How this works is as follows:
  • We define string literal types for each interface that contains the names of all the properties of that interface.
  • In each complementary immutable interface, the get() method that returns 'any' is just a signature that closely matches the get() method of the original Immutable.JS Map. Because interfaces in TypeScript are just a compile-time means of enforcing and validating an object's "shape", it doesn't have to precisely match 1:1 with get() in Map. It just has to take a key and return some value, this interface type information disappears once everything is transpiled to JavaScript. This method signature is just the proverbial "foot in the door" to allow us to define specialized overload signatures off of it, we don't actually use this signature. By having the key be of a string literal type, it constrains our specialized overload signatures to only property names of the actual interface.
  • Then it is just a case of filling out each specialized get() with the expected return type for the given property name. If a property is another nested object, you basically return its "immutable" equivalent interface.
With this pattern, you can safely "cast" your Immutable.JS maps into type-safe immutable versions of your original interfaces with full TypeScript tooling assistance.



And you will be prevented from accessing values with unknown keys due to the string literal type constraint.



Now, unfortunately there is still one problem with this approach. We're still using magic strings for property access (albeit, constrained to a specific set of string values), meaning this is not resilient against rename or other structural reorganisation refactorings (you'll have to manually update any changed property names in the immutable interfaces), which is why I am so hoping that TypeScript gets something like a nameof operator in a future release. Having a nameof-like language construct should mean the death of most magic strings in your codebase, and would make this immutable interface approach completely type safe and refactoring-friendly. 

Still, the above pattern in its current form has greatly simplified my usage of Immutable.JS objects in TypeScript, which itself has already simplified the building of React applications and components.

Hope this is useful to you as it was for me.

Friday 8 April 2016

Announcing: MapGuide Maestro 6.0M5

Here's a long overdue milestone of MapGuide Maestro 6.0

This fixes a long annoying issue where I forgot to include NetTopologySuite.dll in the installer, resulting in certain functionality being broken. Until now, you had to source this dll from the Maestro SDK.

Other changes include:

  • Now requires .net Framework 4.5
  • The Provider Template tool has been updated to use the Roslyn compiler to build the LocalNative provider
  • MgCooker: Don't start script generation or a tiling run if no actual maps or tilesets have been selected
  • The Local connection mode now uses mg-desktop 3.0
  • Fix: Feature Source XML does not properly update when switching editor to unmanaged file mode
  • Fix: Cannot make v3.0.0 untiled Map Definitions

Thursday 7 April 2016

Wouldn't it be nice?

I've been playing around with a lot of React.JS lately (personally and at work)

Wouldn't it be nice if the default developer "getting started" experience in MapGuide was a little snippet of ES6 or TypeScript like this?


 import * as React from "react";  
 import * as ReactDOM from "react-dom";  
 import { WebLayout } from "mapguide-viewer";  
 
 let agentUrl = "https://mymapguideserver/mapguide/mapagent/mapagent.fcgi";
 let resourceId = "Library://Samples/Sheboygan/Layouts/React.WebLayout";
  
 ReactDOM.render(<WebLayout agentUrl={agentUrl} 
                            resourceId={resourceId} />, document.body);  
   

Or if you're more inclined towards Fusion

 import * as React from "react";  
 import * as ReactDOM from "react-dom";  
 import { FlexibleLayout } from "mapguide-viewer";  
 
 let agentUrl = "https://mymapguideserver/mapguide/mapagent/mapagent.fcgi";
 let resourceId = "Library://Samples/Sheboygan/Layouts/React.ApplicationDefinition";
  
 ReactDOM.render(<FlexibleLayout agentUrl={agentUrl} 
                                 template="Slate"
                                 resourceId={resourceId} />, document.body);  
   

Sadly, this is just a pipe dream at the moment. I don't know what effort it would take to turn this into reality.