Skip to content

Using Tangram with Bundlers & Frameworks

Brett Camper edited this page Oct 3, 2018 · 1 revision

This page documents how to bundle Tangram with require() or ES6 import statements (using a combination of Babel + Browserify/Webpack, or similar), as well as a basic example of React integration.

As of Tangram v0.12.0, Tangram is bundle-able. To use older versions of Tangram with JavaScript bundling workflows, see the appendix below.

Browserify with Tangram

With Browserify, you can require('tangram') as you would any other module.

var L = require('leaflet');
var Tangram = require('tangram');

Here is a minimal boilerplate example of using Tangram with Browserify.

Using ES6 / ES2015

If you write ES6 (aka ES2015), the import statement is fundamentally similar to the require() statement.

import L from 'leaflet';
import Tangram from 'tangram';

Using React

You can wrap a <div> element for Leaflet inside of a React component if you wish. In the spirit of Tangram's simple-demo repository, here is the simplest React component you can write to initialize a Leaflet map with Tangram (using ES2015):

import React from 'react';
import L from 'leaflet';
import Tangram from 'tangram';

class LeafletMap extends React.Component {
  componentDidMount () {
    const map = L.map(this.mapEl);
    const layer = Tangram.leafletLayer({
       scene: 'https://mapzen.com/carto/bubble-wrap-style/bubble-wrap.yaml',
       attribution: '<a href="https://mapzen.com/tangram">Tangram</a> | &copy; OSM contributors | <a href="https://mapzen.com/">Mapzen</a>'
    });
    layer.addTo(map);

    map.setView([40.70531887544228, -74.00976419448853], 15);
  }

  render () {
    return (
      <div ref={(ref) => {this.mapEl = ref }} />
    );
  }
}

export default LeafletMap;

This component can be incorporated into your React application normally. (Make sure you've also added Leaflet's stylesheet and styles for giving your map a size and position somewhere in your application.)

The problem about this is that it's merely a wrapper around Leaflet and it may not seem very React-like. And it's not. Leaflet will continue to manage its own DOM and state, so if other parts of your application need Leaflet to respond to a state change, you will need to add additional code to handle this. Similarly, interacting with Leaflet UI will not report state changes in your map back to your application unless you add your own handlers for this as well. You can check out react-leaflet for one solution to this problem.

react-leaflet

At the time of this writing this is still a very new library. It has recently been upgraded to work with Leaflet v1.

Here's the simplest way to add Tangram to a map initialized with react-leaflet:

import React from 'react';
import { Map } from 'react-leaflet';
import Tangram from 'tangram';

class LeafletMap extends React.Component {
  componentDidMount () {
    const layer = Tangram.leafletLayer({
      scene: {
        import: [
          'https://www.nextzen.org/carto/bubble-wrap-style/8/bubble-wrap-style.zip'
        ],
        sources: {
          mapzen: {
            url: 'https://tile.nextzen.org/tilezen/vector/v1/256/all/{z}/{x}/{y}.mvt',
            url_params: {
                api_key: 'YOUR-API-KEY'
            }
          }
        }
      }
    });

    layer.addTo(this.map.leafletElement);
  }

  render () {
    return (
      <Map center={[40.70532, -74.00976]} zoom={15} ref={(ref) => { this.map = ref }} />
    );
  }
}

export default LeafletMap;

Note: by itself, the above code will not display a map because the resulting DOM element will not have the appropriate styles applied, as seen in the boilerplate.

Things to test:

  1. Because map state like position and zoom are props, you should be able to control the map view using a React data flow. How does re-rendering happen? Does it rebuild Leaflet or does it call the appropriate Leaflet methods for adjusting state?
  2. Props on <Map> do not update dynamically when you interact with the map directly (like panning or zooming). So is the best way to read from map state using the reference to the Leaflet instance?

Using Webpack

To bundle Tangram in Webpack, one workaround is required in webpack.config.js. You must tell Webpack not to parse the Tangram library using the noParse on Tangram. Example

For versions of Tangram from 0.12.0 to 0.12.5, a second workaround is required for production bundling (when you optimize/minify). You must create a separate UglifyJS plugin configuration and turn off the comparisons option. Example

For a fully working boilerplate setup with React and Webpack, look at this repository: https://github.com/tangrams/react-webpack-tangram-boilerplate/

For a simpler bare-bones Webpack setup, look at this repository: https://github.com/tangrams/webpack-tangram-boilerplate/

Appendix A: Tangram versions prior to v0.12.0

Prior to v0.12.0, Tangram is not a bundle-able module because of the way it sends a copy of its own code to web workers. Tangram must be reachable at a URL to do this, but inside of a JavaScript bundle (such as one produced via Browserify or Webpack) Tangram cannot access that copy of itself. You must include Tangram as a separate <script> tag inside of your HTML, before running any other bundled scripts that depend on Tangram.

<script src="https://mapzen.com/tangram/tangram.min.js"></script>
<script src="./your-bundled-app.js"></script>

Your bundled application code will expect Tangram to be in the browser's global scope:

/* global Tangram */
var L = require('leaflet');
var layer = Tangram.leafletLayer({ scene: url });

Shimming Tangram for Browserify

A common practice is to "shim" non-CommonJS-compatible modules so that all dependencies are declared in the same way. Doing so can make your code easier to understand, since you make it clear what libraries your module depends on. This can also prepare you for the future: when you upgrade to Tangram v0.12.0, you can remove the shim.

var L = require('leaflet');
var Tangram = require('tangram'); // It looks like a local require(), but Tangram is still a global.

Next, you will need browserify-shim, which will "shim" the require() statement such that the local reference will correctly point to the global reference for Tangram. (See documentation. Include this entry in your application's package.json file:

"browserify-shim": {
  "tangram": "global:Tangram"
}

Finally, you will need to include browserify-shim in Browserify's transform options. This is usually also done inside package.json (documentation), but can vary based on what flavor of build process you prefer, so I will not go into further details here.

Shimming Tangram for Webpack

If your bundling environment is based on Webpack, you will also need to specify a shim if you choose to import Tangram. (TODO: how to do this?)