Cesium and Webpack

Webpack is a popular and powerful tool for bundling JavaScript modules. It allows developers to structure their code and assets in an intuitive way and to load different kinds of files as needed with simple require statements. When building, it will trace code dependencies and pack these modules into one or more bundles that are loaded by the browser.

In the first half of this tutorial, we’ll build a simple web app from the ground up using webpack, and then cover the steps to integrate the Cesium npm module. This is a good place to start if you’d like to use Cesium to develop a web application, but for an even simpler example of getting started with Cesium, take a look at our Getting Started Tutorial.

In the second half, we’ll explore more advanced webpack configurations for optimizing an application using Cesium.

The completed application and tips for optimizing a Cesium and webpack application can be found in the official cesium-webpack-example repository.

Prerequisites

  • A basic understanding of the command line, JavaScript, and web development.
  • A browser that supports WebGL. If in doubt, make sure your browser is Cesium ready.
  • An IDE or code editor. Developers on the Cesium team members use Webstorm, but a minimal code editor such as Sublime Text will also work.
  • Node.js installed. The LTS version is a good place to start, but any version 6 or above will work.

Create a basic webpack app

In this first section, we’ll describe how to set up a basic web application with webpack and the development server. If you’ve already got an app set up and just want to add Cesium, skip to Add Cesium to a webpack app.

Initialize an app with npm

Create a new directory for your application. Let’s call it cesium-webpack-app. Open a console and run the following command in that new directory:

npm init

Follow the prompts and populate any details about your application. Press enter to use the defaults, all of which will work for this application. This will create our package.json file.

Create our application code

Create a new directory called src for our “source”. This is where all our app code will live, and these are the files we’ll edit. When we build, webpack will produce output or “distribution” files in the dist directory.

Let’s create src/index.html and add code for a boilerplate HTML page.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <title>Hello World!</title>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>

Now we need to create an entry point for the application. This will be where we tell webpack to start including all of our source code and dependencies for our bundle. That bundle will then be loaded in our index.html file.

For now, we’ll keep it simple. Create a new file src/index.js and add a quick line so we know everything’s working.

console.log('Hello World!');

Install and configure webpack

Let’s begin by installing webpack.

npm install --save-dev webpack
Configuration

Create a new file called webpack.config.js. This is where we’ll define our webpack configuration object, which we can then pass along to the compiler.

const path = require('path');

const webpack = require('webpack');

module.exports = {
	context: __dirname,
	entry: {
		app: './src/index.js'
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist'),
	}
};

First we’ll require path from Node, and the webpack module we just installed. In the configuration, we’ll tell webpack what our base path is with context and supply the Node global __dirname to specify the location of this file. We specify our entry to be src/index.js, and we’ll call it app. We’ll tell webpack to output the bundle (which will be named app.js since we specified using the entry [name]) to the dist folder. Then export this object so it can be used elsewhere.

Loaders

We’ll also need a way to load in our css files and other asset files. webpack lets us load everything like a module, and that’s accomplished using loaders. We’ll be using the style-loader, css-loader, and url-loader.

npm install --save-dev style-loader css-loader url-loader

Let’s go ahead and add module.rules to webpack.config.js. We’ll add two rules, one for css files and one for other static files. For each, we’ll define a test for the types of file we wanted loaded with that loader, as well as an array use to specify the list of loaders.

const path = require('path');

const webpack = require('webpack');

module.exports = {
    context: __dirname,
    entry: {
        app: './src/index.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: [ 'style-loader', 'css-loader' ]
        }, {
            test: /\.(png|gif|jpg|jpeg|svg|xml|json)$/,
            use: [ 'url-loader' ]
        }]
    }
};
Plugins

We’re creating a web application, so we’ll need to define our index.html and inject our bundle into that page. We’ll do that using a webpack plugin called the html-webpack-plugin.

npm install --save-dev html-webpack-plugin

Require the plugin at the top of the webpack.config.js file and add it to plugins. We’ll pass src/index.html as our template, and webpack will inject a reference to the bundle into the page body.

const path = require('path');

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	context: __dirname,
	entry: {
		app: './src/index.js'
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist'),
	},
    module: {
        rules: [{
            test: /\.css$/,
            use: [ 'style-loader', 'css-loader' ]
        }, {
            test: /\.(png|gif|jpg|jpeg|svg|xml|json)$/,
            use: [ 'url-loader' ]
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.html'
        })
    ]
};

Remember that the configuration file is just a JavaScript file, so we can require other Node modules and perform operations.

Bundle the app

We’ll define some scripts that will be easy to call with npm in out package.json. Feel free to remove the default "test" script as we won’t be using it here. Let’s add the build command.

  "scripts": {
    "build": "node_modules/.bin/webpack --config webpack.config.js"
  }

This script simply calls webpack and passes in the webpack.config.js configuration file we’ve created.

We’re using the local installation of webpack and the webpack-dev-server in these scripts. This allows each project to use its own individual version, and is what is recommended by the webpack documentation. If you’d prefer to use the global version, install it globally with npm install --global webpack and use the command webpack --config webpack.config.js to run.

When you run the build command,

npm run build

you should see some output from webpack starting with something like this:

npm run build

> test-app@1.0.0 build C:\workspace\test-app
> node_modules/.bin/webpack --config webpack.config.js

Hash: 2b42bff7a022b5d956a9
Version: webpack 3.6.0
Time: 2002ms
                                        Asset       Size  Chunks                    Chunk Names
     Assets/Textures/NaturalEarthII/2/0/3.jpg    10.3 kB          [emitted]
                                       app.js     162 kB       0  [emitted]         app

Our app.js bundle and index.html file will be output into the dist folder. These files are ready to be served out as a web app.

Run the development server

That’s not very exciting. Let’s use the webpack-dev-server to quickly serve up a development build and see our app in action.

First, install it as a dev dependency.

npm install --save-dev webpack-dev-server

Next, let’s add another script to our package.json. We’ll use the start command to run the development server, passing the config file via the --config flag as we did for build. Optionally, we’ll pass the --open flag top to open the application in our browser when the command is run.

  "scripts": {
    "build": "node_modules/.bin/webpack --config webpack.config.js",
    "start": "node_modules/.bin/webpack-dev-server --config webpack.config.js --open"
  }

We’ll need to tell the development server where to serve our files from. This will be the same folder where we put the output from webpack, in this case, dist. Add this at the bottom of your webpack configuration in webpack.config.js.

	// development server options
	devServer: {
		contentBase: path.join(__dirname, "dist")
	}

Finally, we can run the app!

npm start

You should see your content served at localhost:8080, and if you open up the console, you should see our “Hello World!” message printed there.

Basic webpack app output

`app.js` console output

Add Cesium to a webpack app

Now that we have a bare bones application running with webpack, let’s get to the fun part and add Cesium.

Install Cesium

Install the cesium module from npm and add it to our package.json file as a dev dependency.

npm install --save-dev cesium

Configure Cesium in webpack

Cesium is a large and complex library. In additional to JavaScript modules, it also includes static assets such as css, image, and json files. It includes web worker files to perform intensive calculations in another thread. Unlike traditional npm modules, Cesium does not define an entry point because of the diverse ways in which the library is used. We’ll need to configure some additional options to use it correctly.

First, let’s define where Cesium is. We’ll use the source code, which allows us to use the individual models and trace the dependencies by leveraging webpack. The alternative would be to use the built (minified or unminified) version of Cesium; however, these modules are already combined and optimized using the RequireJS optimizer, which poses some problems for easily integrating the library and gives us less flexibility will our own optimizations.

In webpack.config.js, we add the following above our configuration object:

// The path to the Cesium source code
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';

While we chose to use the npm module here for ease of installation, you could also use a clone of the GitHub repository or an unzipped release download. Just update cesiumSource to be the location of the Cesium Source directory relative to the location of your webpack.config.js file.

Next, we’ll add the following options to our configuration object in order to resolve some quirks with how webpack compiles Cesium.

    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),

        // Needed to compile multiline strings in Cesium
        sourcePrefix: ''
    },
    amd: {
        // Enable webpack-friendly use of require in Cesium
        toUrlUndefined: true
    },
    node: {
        // Resolve node module use of fs
        fs: 'empty'
    },

Let’s take a quick look at the reasoning for those individual configuration options and why they are included:

  • output.sourcePrefix: '' is needed because some versions of webpack default to adding a \t tab character before each line when output. Cesium has instances of multiline strings, so we need to override this default with an empty prefix ''.
  • amd.toUrlUndefined: true tells Cesium that the version of AMD webpack uses to evaluate require statements is not compliant with the standard toUrl function.
  • node.fs: 'empty' resolves some third-party usage of the fs module, which is targeted for use in a Node environment rather than the browser.

Next let’s add a cesium alias so we can easily reference it in our application code like a traditional Node module.

	resolve: {
		alias: {
			// Cesium module name
			cesium: path.resolve(__dirname, cesiumSource)
		}
	},

Manage Cesium static files

Lastly, let’s make sure the static Cesium asset, widget, and web worker files are being served and loaded correctly.

We’ll use the copy-webpack-plugin, which will allow us as part of the build process to copy the static files included in Cesium over to our dist directory so they can be served. First, install it.

npm install --save-dev copy-webpack-plugin

Then require it near the top of our webpack.config.js file.

const CopywebpackPlugin = require('copy-webpack-plugin');

Additionally, add the following to the plugins array.

	plugins: [
	    new HtmlWebpackPlugin({
	        template: 'src/index.html'
        }),
        // Copy Cesium Assets, Widgets, and Workers to a static directory
        new CopywebpackPlugin([ { from: path.join(cesiumSource, cesiumWorkers), to: 'Workers' } ]),
        new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Assets'), to: 'Assets' } ]),
        new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Widgets'), to: 'Widgets' } ])
	],

We’re copying the Assets and Widgets directories as-is. Additionally we’ll use the built web worker scripts, which have already been compiled and optimized with the RequireJS optimizer. Since the web workers are designed to run separately in their own thread, they can be loaded and run as-is. The web workers rarely, if ever, will be needed in their original form for debugging. We’ll copy these over from the Build/Cesium/Workers directory.

If you have pointed Cesium at a clone of the GitHub repo, the Build folder will not already exist. Make sure you navigate to the base Cesium directory and run npm run release to produce the build output. For more information, see the Cesium Build Guide.

Lastly, we’ll define an environment variable that tells Cesium the base URL at which to load static files using the DefinePlugin that’s built into webpack. The plugins array will now look like this:

    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.html'
        }),
        // Copy Cesium Assets, Widgets, and Workers to a static directory
        new CopywebpackPlugin([ { from: path.join(cesiumSource, cesiumWorkers), to: 'Workers' } ]),
        new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Assets'), to: 'Assets' } ]),
        new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Widgets'), to: 'Widgets' } ]),
        new webpack.DefinePlugin({
            // Define relative base path in cesium for loading assets
            CESIUM_BASE_URL: JSON.stringify('')
        })
    ],

Require Cesium modules in our app

There are a few different ways we can now require Cesium as well as individual Cesium modules within our application. There is the CommonJS syntax, as well as the new import statements that ES6 modules use.

Additionally, you can import the whole Cesium library under one Cesium object (for instance, this is what we do in Sandcastle). You can also require only the specific modules you need. Since Cesium is such a big library, this will allow you to include only the specified modules and their dependencies in your bundle, instead of the entire Cesium library.

CommonJS style require

To require all of the Cesium libraries under one object:

var Cesium = require('cesium/Cesium');
var viewer = new Cesium.Viewer('cesiumContainer');

To require an individual module:

var Color = require('cesium/Core/Color');
var color = Color.fromRandom();
ES6 style import

To require all of the Cesium library under one object:

import Cesium from 'cesium/Cesium';
var viewer = new Cesium.Viewer('cesiumContainer');

To require an individual module:

import Color from 'cesium/core/Color';
var color = Color.fromRandom();

Requiring asset files

The philosophy behind webpack is that every file is treated like a module. This makes importing assets exactly the same as including a JavaScript module. We’ve told webpack how to load in each type of file in our configuration using loaders, so we just need to call require.

require('cesium/Widgets/widgets.css');

Hello World!

Now that we have our environment set up and we know how to include Cesium files, let’s duplicate the Hello World app from the original Getting Started Tutorial.

Let’s take another look at our index.js file. Delete its contents. First, we’ll include Cesium. The following defines the Cesium object:

var Cesium = require('cesium/Cesium');

In order to use the Cesium Viewer widget, we’ll need to include its CSS:

require('cesium/Widgets/widgets.css');

In the HTML body, we’ll create a div for the viewer to live. In the index.html file, remove our <p>Hello World!</p> line and replace it with this div:

<div id="cesiumContainer"></div>

Finally, we’ll create an instance of viewer. Back in index.js add one more line:

var viewer = new Cesium.Viewer('cesiumContainer');

And when we run the app with npm start we’ll see the Cesium Viewer in our browser!

Hello World App

For both your sanity and mine, let’s get rid of that white border by including some custom css.

Create a new file, src/css/main.css, and add the following styles:

html, body, #cesiumContainer {
	width: 100%;
	height: 100%;
	margin: 0;
	padding: 0;
	overflow: hidden;
}

Require it in your index.js file, right beneath the other require statements:

require('./css/main.css');

Start the application again, and you’ll see the viewer in its full-screen glory.

Styled Hello World App

Feel free to copy and paste your favorite Sandcastle example. I think The Particle System example makes for a great conclusion.

Example App

Advanced webpack configurations

webpack can be leveraged in many more ways to increase performance, decrease your bundle size, and perform additional or complex build steps. Here we’ll discuss a few configuration options relevant to using the Cesium library.

Our configuration for an optimal production Cesium webpack build can be found in our example repo at release.webpack.config.js

Code Splitting

Webpack by default packages Cesium in the same chunk as our application, which ends up being huge. We can split Cesium out into its own bundle and increase the performance of our app by using the CommonChunksPlugin. If you end up creating multiple chunks for your own application, they can all reference one common cesium chunk.

Just add the plugin to your webpack.config.js file, and specify the rule for breaking out Cesium modules:

    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'cesium',
            minChunks: module => module.context && module.context.indexOf('cesium') !== -1
        })
    ]

Enable source maps

Source maps allow webpack to trace errors back to the original content, and there are many options. They offer more or less detailed debugging information in exchange for compiling speed. We recommend using the 'eval'option, but play around with what works best for your development.

	devtool: 'eval'

Source maps are not recommended for production.

Remove pragmas

We include developer errors and warnings in the Cesium source code that are not necessary for a production build. Traditionally, we remove them using the RequireJS Optimizer in minified release builds. Since there’s no built-in webpack way to remove these warnings, we’ll use the strip-pragma-loader.

First, install it,

npm install uglifyjs-webpack-plugin --save-dev

then include the loader in module.rules with debug set to false.

    rules: [{
        // Strip cesium pragmas
        test: /\.js$/,
        	enforce: 'pre',
        	include: path.resolve(__dirname, cesiumSource),
        	use: [{
        		loader: 'strip-pragma-loader',
        		options: {
        		    pragmas: {
        				debug: false
        			}
        		}
        	}]
    }]

Uglify and minify

Uglifying and minifying code allows for smaller file sizes in production. For a release build, Cesium will uglify the JavaScript files and minify the CSS files.

To uglify the Cesium source, we’ll use the uglifyjs-webpack-plugin,

npm install uglifyjs-webpack-plugin --save-dev

require it,

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

and include it in the list of plugins.

    plugins: [
        new webpack.optimize.UglifyJsPlugin()
    ]

To minify any css we load in, we’ll use the minimize option on the css-loader.

    module: {
        rules: [{
            test: /\.css$/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        // minify loaded css
                        minimize: true
                    }
                }
            ]
        }]
    }

Resources

The official cesium-webpack-example repo contains the minimal webpack configuration and hello world code covered in this tutorial, as well as instructions for optional code configurations.

For a tour of some Cesium feature to include in your new app, such as adding and styling data, see the Cesium Workshop Tutorial.

Play around in Sandcastle with help from the Documentation.

To learn more about webpack, take a look at webpack Concepts , or dive into the Documentation .