[CesiumJS] Cesium Intermediate Tutorial 10 – CesiumJS and webpack

Cesium Chinese Website: http://cesiumcn.org/ | Fast Access in China: http://cesium.coinidea.com/

webpack is a popular and powerful tool for bundling JavaScript modules. It allows developers to structure code and assets in an intuitive way, and load different types of files as needed using simple require statements. During build time, it tracks code dependencies and bundles these modules into one or more bundles for the browser to load.

In the first half of this tutorial, we set up a simple web application using webpack from scratch, then cover the subsequent steps to integrate the Cesium npm module. If you like developing web applications with CesiumJS, webpack is a great choice. If you are new to Cesium and looking to learn how to build your first sample application, please check the Getting Started Tutorial.

In the second part, we will explore more advanced webpack configurations to optimize applications using CesiumJS.

The complete code and tips for optimizing a CesiumJS webpack application can be found in the official cesium-webpack-example.

Prerequisites

  • Basic understanding of the command line, JavaScript, and web development.
  • An IDE or code editor. Cesium team developers use Visual Studio Code, but a minimal code editor such as Sublime Text works perfectly fine as well.
  • Node.js installed. We recommend using the latest LTS version.

Create a basic webpack app

In this section, we will cover how to set up a basic web application with webpack and a development server. If you already have an application set up and just want to add CesiumJS, skip to Add CesiumJS to a webpack app.

Initialize an app with npm

Create a new cesium-webpack directory for your app. Open the console, navigate to the new directory, and run the following command:

1
npm init

Follow the prompts and fill in all the details about your application. Press enter to use the default values. This will create package.json.

Create the app code

Create a src directory for our application code. When we build the application, webpack will generate distribution files in the dist directory.

Create src/index.html and add the boilerplate HTML page code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!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>

Next, create an entry point for the application. This is the entry point where webpack looks for all JavaScript source code and dependencies to bundle.

Create src/index.js and add the following code:

1
console.log('Hello World!');

Install and configure webpack

Start by installing webpack:

1
npm install --save-dev webpack

Configuration

Create webpack.config.js to define the webpack configuration object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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'),
    }
};

context specifies the base path for the files. entry is used to specify the bundles. In this case, the entry point for the app bundle is src/index.js. webpack will output the bundled app.js to the dist folder.

Loaders

Webpack loads everything like a module. Use loaders to load CSS and other asset files. Install style-loader, css-loader, and url-loader.

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

Add two module.rules in webpack.config.js: one for CSS files and another for other static files. For each rule, test defines the file types to load, and use specifies the list of loaders.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

Define index.html and inject the bundle into this page using a webpack plugin called html-webpack-plugin.

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

Reference the plugin in webpack.config.js, then inject it into plugins. Pass src/index.html as our template.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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'
        })
    ]
};

The configuration file is a JavaScript file, so we can require other Node modules and perform operations.

Bundle the app

Use package.json to define scripts that can be called with npm. Add a build command.

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

This script calls webpack and passes in the webpack.config.js configuration file.

We use local installations of webpack and webpack-dev-server in these scripts. This allows each project to use its own separate version, which is recommended by the webpack documentation. If you prefer to use a global version, install it globally with npm install –global webpack, and run it with the command webpack –config webpack.config.js.

When you run the build command: npm run build

You should see some output from webpack, starting with the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

The app.js bundle and index.html file will be output to the dist folder.

Run the development server

Use webpack-dev-server to serve the development build so we can see our app in action:

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

Add a start script in package.json to run the development server. Set the configuration file via the –config flag. Use the –open flag to open the application in the browser when the command is executed.

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

Tell the development server to serve files from the dist folder. Add this to the bottom of webpack.config.js.

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

Finally, run the app:

1
npm start

You should see your content rendered at localhost:8080, and see the “Hello World!” message when you open the browser console.

Add CesiumJS to a webpack app

Install CesiumJS

Install the cesium module from npm and add it to package.json.

Configure CesiumJS in webpack

CesiumJS is a large and complex library. In addition to JavaScript modules, it also includes static assets such as CSS, image, and JSON files. It includes web worker files to perform intensive computations in separate threads. Unlike traditional npm modules, CesiumJS does not define an entry point because the library can be used in many different ways. We need to configure some additional options for use with webpack.

First, define the location of CesiumJS. This tutorial is based on the source code, so webpack can include individual models and track dependencies. Alternatively, the built (minified or unminified) versions of CesiumJS can be used. However, these modules have already been combined and optimized, which gives us less flexibility.

Add the following code to the top of webpack.config.js:

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

This tutorial uses the npm module for easy installation, but you can also clone the GitHub repository or download and extract a release version.

Add the following options to the configuration object to resolve some issues with how webpack compiles CesiumJS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    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'
    },
  • output.sourcePrefix: Adds a \t tab before each line to override the webpack default. CesiumJS has some multiline strings, so we need to override this default with an empty prefix ’’.
  • amd.toUrlUndefined: true tells CesiumJS that the AMD webpack version used to evaluate require statements does not conform to the standard toUrl function.
  • node.fs: ’empty’ resolves some third-party usage issues with the fs module, which is intended for use in a Node environment, not in a browser.

Add a cesium alias so we can reference it in our application code.

1
2
3
4
5
6
    resolve: {
        alias: {
            // CesiumJS module name
            cesium: path.resolve(__dirname, cesiumSource)
        }
    },

Manage CesiumJS static files

Finally, ensure that static CesiumJS assets, widgets, and web worker files are properly served and loaded.

As part of the build process, use copy-webpack-plugin to copy static files to the dist directory.

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

Require it at the top of the webpack.config.js file:

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

And add it to the plugins array:

1
2
3
4
5
6
7
8
9
    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' } ])
    ],

This copies the Assets and Widgets directories as well as the built web worker scripts.

If you are using a fork of the CesiumJS repository, the Build folder will not exist. Run npm run release to generate the output folder. For more details, see the Cesium Build Guide.

Define an environment variable that tells CesiumJS the base URL for loading static files using the webpack DefinePlugin. The plugins array will now look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    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 CesiumJS modules in our app

There are several ways to reference CesiumJS modules in our application. You can use CommonJS syntax or ES6 import statements.

You can import the entire CesiumJS library, or require only the specific modules you need. Importing individual modules will cause webpack to only compile those modules and their dependencies in the bundle, rather than the entire library.

CommonJS style require

Require all of CesiumJS:

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

Require individual modules:

1
2
var Color = require('cesium/Core/Color');
var color = Color.fromRandom();

ES6 style import

Import all of CesiumJS:

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

Import individual modules:

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

Requiring asset files

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

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

Hello World!

Create a new file, src/css/main.css, to style our application:

1
2
3
4
5
6
7
html, body, #cesiumContainer {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
}

Create a div for the CesiumJS Viewer in the index.html body. Replace

Hello World!

with the following div:

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

Remove the contents of index.js and include Cesium and our CSS files:

1
2
3
var Cesium = require('cesium/Cesium');
require('./css/main.css');
require('cesium/Widgets/widgets.css');

Add a line of code to create the Viewer:

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

Run the app with npm start to view the Viewer in the browser.

Copy and paste your favorite Sandcastle example. For instance, the Particle System Fireworks example makes for a great conclusion.

Advanced webpack configurations

webpack can further improve performance, reduce bundle size, and perform additional or complex build steps. Here, we will discuss some configuration options related to using the CesiumJS library.

A configuration for optimizing production Cesium webpack builds can be found in the repository example webpack.release.config.js.

Code splitting

By default, webpack bundles CesiumJS in the same chunk as our application, resulting in a large file. We can split CesiumJS into its own bundle and improve our application performance using the CommonChunksPlugin. If you end up creating multiple chunks for your application, they can all reference a common cesium chunk.

Add the plugin to the webpack.config.js file and specify the rules for splitting CesiumJS modules:

1
2
3
4
5
6
    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. They provide more or less detailed debugging information in exchange for compilation speed. We recommend using the ’eval’ option.

1
    devtool: 'eval'

Source maps are not recommended for production builds.

Remove pragmas

Developer errors and warnings exist in the CesiumJS source code that have been removed from our minified release builds. Since there is no built-in webpack method to remove these warnings, we will use strip-pragma-loader.

Install the package:

1
npm install strip-pragma-loader --save-dev

In module.rules, set debug to false and include the loader:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    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, CesiumJS uglifies JavaScript files and minifies CSS files.

Use uglifyjs-webpack-plugin to uglify the CesiumJS source code.

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

Require it in the configuration file:

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

Include it in the plugins list:

1
2
3
    plugins: [
        new webpack.optimize.UglifyJsPlugin()
    ]

Use the minimize option on css-loader to optimize CSS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        // minify loaded css
                        minimize: true
                    }
                }
            ]
        }]
    }

Resources

The official cesium-webpack-example repository contains a minimal webpack configuration, the Hello World code from this tutorial, and instructions for optional code configurations.

For tutorials on CesiumJS features to include in a new application, see the Cesium Workshop Tutorial.

Explore examples in [Sandcastle] and check the CesiumJS Documentation.

To learn more about webpack, check out the webpack concepts, or read the documentation in depth.

Cesium Chinese Website QQ Group: 807482793

Cesium Chinese Website: http://cesiumcn.org/ | Fast Access in China: http://cesium.coinidea.com/