Using mapbox-gl and webpack together

For those who might have missed it, Mapbox has been doing some very cool work to update the age old slippy-map to brand new world of WebGL. The library they have released to do this is mapbox-gl.

Webpack is a module bundler that reads the imports of your Javascript files and creates a bundled version by walking the dependency graph. Part of its appeal is the fact that it can do “code splitting”; creating bundles for specific pages as well as bundles for code shared across pages (Of course there’s more to it). Pete Hunt gives a great overview of it here.

So the big question is, what happens when you try to use this two awesome projects together?

ERROR in ./~/mapbox-gl/js/render/shaders.js
Module not found: Error: Cannot resolve module 'fs' in /home/mike/projects/usesthis/node_modules/mapbox-gl/js/render
 @ ./~/mapbox-gl/js/render/shaders.js 3:9-22

ERROR in ./~/mapbox-gl-style-spec/reference/v8.json
Module parse failed: /home/mike/projects/usesthis/node_modules/mapbox-gl-style-spec/reference/v8.json Line 2: Unexpected token :
You may need an appropriate loader to handle this file type.
| {
|   "$version": 8,
|   "$root": {
|     "version": {
 @ ./~/mapbox-gl-style-spec/reference/latest.js 1:17-37

ERROR in ./~/mapbox-gl-style-spec/reference/v8.min.json
Module parse failed: /home/mike/projects/usesthis/node_modules/mapbox-gl-style-spec/reference/v8.min.json Line 1: Unexpected token :
You may need an appropriate loader to handle this file type.

With a bunch flailing around and a little google-fu you also run into other fun errors like the “Uncaught TypeError: fs.readFileSync is not a function” or the dreaded “can’t read property ‘call’ of undefined” when you try to run this in your browser.

After playing around with loaders and config options before finding useful github issues, I thought I would for the benefit of my future self compile a simple working example, so I don’t have to figure this out again.

The goal here is to get Mapbox’s most basic example up and running with webpack.

Screenshot from 2016-02-24 14-43-47
The basic Mapbox-gl example.

Let create a directory to work in:

mkdir webpack-mapboxgl && cd webpack-mapboxgl

To do this we will divide the code from the example into two basic files; app.js for the javascript and index.html for the HTML.

First here’s index.html. Note that we are removing all the Javascript and in it’s place we are including the bundle.js that will be generated by webpack:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title></title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.14.2/mapbox-gl.css' rel='stylesheet' />
    <style>
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
</head>
<body>

<div id='map'></div>
<script src="bundle.js"></script>
</body>
</html>

Next, app.js:

import mapboxgl from 'mapbox-gl'

mapboxgl.accessToken = 'pk.eyJ1IjoibWlrZXdpbGxpYW1zb24iLCJhIjoibzRCYUlGSSJ9.QGvlt6Opm5futGhE5i-1kw';
var map = new mapboxgl.Map({
    container: 'map', // container id
    style: 'mapbox://styles/mapbox/streets-v8', //stylesheet location
    center: [-74.50, 40], // starting position
    zoom: 9 // starting zoom
});

No real changes, just using the new ES2015 import syntax to pull in mapboxgl.
It’s probably a good time to install webpack globally:

npm install -g webpack

This is where is gets a little hairy. Obviously mapboxgl and webpack need to be installed, as well as babel and a mess of loaders and transpiler presets. That’s life in the big city, right?

I set up npm in the directory with npm init, and then the fun begins:

npm install --save-dev webworkify-webpack transform-loader json-loader babel-loader babel-preset-es2015 babel-preset-stage-0 babel-core mapbox-gl

Next is the secret sauce that knits it all together, the webpack.config.js file:

    var webpack = require('webpack')
    var path = require('path')

    module.exports = {
      entry: './app.js',
      output: { path: __dirname, filename: 'bundle.js' },
      resolve: {
        extensions: ['', '.js'],
        alias: {
          webworkify: 'webworkify-webpack',
          'mapbox-gl': path.resolve('./node_modules/mapbox-gl/dist/mapbox-gl.js')
        }
      },
      module: {
        loaders: [
          {
            test: /\.jsx?$/,
            loader: 'babel',
            exclude: /node_modules/,
            query: {
              presets: ['es2015', 'stage-0']
            }
          },
          {
            test: /\.json$/,
            loader: 'json-loader'
          },
          {
            test: /\.js$/,
            include: path.resolve(__dirname, 'node_modules/webworkify/index.js'),
            loader: 'worker'
          },
          {
            test: /mapbox-gl.+\.js$/,
            loader: 'transform/cacheable?brfs'
          }
        ]
      },
    }; }
    ]
  },
};

With that you should be able to run the webpack command and it will produce the bundle we referenced earlier in our HTML. Open index.html in your browser and you should have a working WebGl map.

If you want to just clone this example, I’ve put it up on Github.

Advertisements

22 thoughts on “Using mapbox-gl and webpack together”

  1. Nice post!

    I’m using Angular 2 and Typescript and Webpack. I can’t get the following line to work:

    import mapboxgl from ‘mapbox-gl’

    I just get errors saying “Cannot find module ‘mapbox-gl'”

    I’ve installed mapbox-gl via NPM and followed your guide to get rid of some of the errors I was getting.

    I also tried:

    let mapboxgl = require(‘mapbox-gl’);
    require(‘../node_modules/mapbox-gl/js/mapbox-gl.js’);
    import * as mapboxgl from ‘mapbox-gl’;

    Do you know what I might be doing wrong? I’m still trying to get my head around this whole concept of ES5 vs ES6, CommonJS, Typescript, etc…etc…

    Cheers!

  2. +1 Romain’s comment, I noticed that too with the newest MapboxGL library. You can remove the following from your webpack config:

    {
    test: /\.js$/,
    include: path.resolve(__dirname, ‘node_modules/mapbox-gl/js/render/shaders.js’),
    loader: ‘transform/cacheable?brfs’
    }

  3. Found another required Webpack loader for “use_program.js” when using MapboxGL 0.18.0.

    {
    test: /\.js$/,
    include: path.resolve(__dirname, ‘node_modules/mapbox-gl/js/render/painter/use_program.js’),
    loader: ‘transform/cacheable?brfs’
    }

  4. Tried everything suggested but still getting the Cannot find module ‘mapbox-gl’ error. Could anyone please help?

    import * as mapboxgl from ‘mapbox-gl’;

    webpack.config.js

    var path = require(‘path’);
    var webpack = require(‘webpack’);
    var merge = require(‘extendify’)({
    isDeep: true,
    arrays: ‘concat’
    });
    var ExtractTextPlugin = require(‘extract-text-webpack-plugin’);
    var extractCSS = new ExtractTextPlugin(‘styles.css’);
    var devConfig = require(‘./webpack.config.dev’);
    var prodConfig = require(‘./webpack.config.prod’);
    var isDevelopment = process.env.ASPNET_ENV === ‘Development’;

    module.exports = merge({
    resolve: {
    extensions: [”, ‘.js’, ‘.ts’],
    alias: {
    webworkify: ‘webworkify-webpack’
    }
    },
    module: {
    loaders: [{
    test: /\.ts$/,
    include: /ClientApp/,
    loader: ‘ts-loader’
    }, {
    test: /\.html$/,
    loader: ‘raw-loader’
    }, {
    test: /\.css/,
    loader: extractCSS.extract([‘css’])
    }, {
    test: /\.json$/,
    loader: ‘json-loader’
    }, {
    test: /\.js$/,
    include: path.resolve(__dirname, ‘node_modules/mapbox-gl/js/render/painter/use_program.js’),
    loader: ‘transform/cacheable?brfs’
    }, {
    test: /\.js$/,
    include: path.resolve(__dirname, ‘node_modules/webworkify/index.js’),
    loader: ‘worker’
    }],
    postLoaders: [{
    include: /node_modules\/mapbox-gl/,
    loader: ‘transform’,
    query: ‘brfs’
    }]
    },
    entry: {
    main: [‘./ClientApp/boot-client.ts’]
    },
    output: {
    path: path.join(__dirname, ‘wwwroot’, ‘dist’),
    filename: ‘[name].js’,
    publicPath: ‘/dist/’
    },
    plugins: [
    extractCSS,
    new webpack.DllReferencePlugin({
    context: __dirname,
    manifest: require(‘./wwwroot/dist/vendor-manifest.json’)
    })
    ]
    }, isDevelopment ? devConfig : prodConfig);

    1. I would start by replacing “import * as mapboxgl from ‘mapbox-gl'” with “import mapboxgl from ‘mapbox-gl'”.
      I updated the example repo to use mapbox-gl-js 18 and the latest webpack. Take a look, hopefully it will help.

      1. Thanks Mike. I’ve figured out the problem. It’s that I’m using Typescript and mapbox-gl doesn’t support it…

    1. Same here. The configuration worked but since an update of my other libs (angular2 from beta to RC-1) its broken with the same error message.
      I already did an update of web pack etc. und some reverts in the dependencies but could not really figure out whats causing the problem until now.

      Did you find any solution?

      1. I could get the build working again (by digging through mapboxgl-js issues on git). In https://github.com/mapbox/mapbox-gl-js/issues/1649 primozs points out that he had to downgrade “webworkify-webpack”: “1.1.0” to “webworkify-webpack”: “1.0.6”. This resolved the issue for me, (i did it by fixing the version to 1.0.6, in my build it was ^1.0.6 which resolved to 1.1.0)

    2. I’m having the exact same issue – three “Uncaught TypeError: Cannot read property ‘call’ of undefined” errors coming from the Webpack loading process in what I think is Mapbox GL’s WebWorkers. It started happening after I “npm install”-d the project dependencies after a fresh git clone. I thought it I had just forgotten to save a dependency but I can’t for the life of me figure out which one it is! Any help would be much appreciated. My list of dependencies right now is:

      “babel-core”: “^6.8.0”,
      “babel-loader”: “^6.2.4”,
      “babel-preset-react”: “^6.5.0”,
      “brfs”: “^1.4.3”,
      “browserify”: “^13.0.1”,
      “css-loader”: “^0.23.1”,
      “es6-promise”: “^3.2.1”,
      “file-loader”: “^0.8.5”,
      “json-loader”: “^0.5.4”,
      “mapbox-gl”: “^0.18.0”,
      “react”: “^15.0.2”,
      “react-dom”: “^15.0.2”,
      “style-loader”: “^0.13.1”,
      “transform-loader”: “^0.2.3”,
      “url-loader”: “^0.5.7”,
      “webfontloader”: “^1.6.24”,
      “webpack”: “^1.13.0”,
      “webworkify-webpack”: “^1.0.6”

  5. Thanks. This helped me a lot. I need to add this line in resolve to complete without errors for using with react-mapgl .’mapbox-gl/js/geo/transform’: path.resolve(‘./node_modules/mapbox-gl/js/geo/transform’),

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s