tanaka's Programming Memo

プログラミングについてのメモ。

CesiumをWebpackでビルドできるようにする

Cesiumとは、Google Earthと同様に3Dで地図をWebブラウザー上に描画するJavaScriptライブラリーです。WebpackでWebアプリとしてビルドできる様にするための手順が公式サイトにあったので、あちこち端折りながらまとめます。

目次

前提環境

  • コマンドラインJavaScript、Web開発の基礎知識
  • WebGLをサポートしたBrowser。対応状況が不明な場合は、こちらを開いて、動くか確認してください
  • コードエディター。CesiumのメンバーはWebstormを利用している。最小限のコードならSublime Textなどでも良い
  • Node.jsがインストールされていること。LTS(Long Time Support)バージョン推奨だが、Version 6以降なら動作します

ここでは、OSはmacOS、エディターはAtomで進めます。また、元のドキュメントではWebブラウザーの表示にwebpack-dev-serverを使っていますが、ライブリロードをしたいのでbrowser-syncに差し替えました。

基本的なWebpackアプリの作成

まずはWebpackで基本的なWebアプリが開発できる様にセットアップします。既知の場合は次に進んでください。

npmの初期化

任意の場所に、プロジェクトを入れておくディレクトリーを作成して、ターミナルを起動して、プロジェクトディレクトリーにcdで移動します。ディレクトリー名はcesium-webpack-appなどにしておきます。

mkdir cesium-webpack-app && cd cesium-webpack-app

以下のコマンドを実行して、npm用のpackage.jsonを生成します。

npm init -y

package.jsonが生成されたので、必要に応じて項目を変更します。今のところ、そのまま進めます。

browser-syncのインストール

他記事に手順を記載済みです。こちらをみて、[Browsersyncをインストール]と[Browsersyncの設定ファイル]を完了させてください。

アプリ用のコードを生成

Atomエディターをインストールしていたら、Atom .として、このディラクトリーをプロジェクトフォルダーとして、Atomを起動します。

Atomでプロジェクトフォルダーを右クリックして、[New Folder]を選択してsrcという名前のディレクトリーを生成してください。アプリ用のソースファイルは全てこの中に保存して、編集していきます。Webpackはsrcディレクトリー内のコードをビルドして、distというディレクトリーに出力します。

Atomsrcディレクトリーを右クリックして、[New File]を選択して、index.htmlという名前のファイルを作成してください。作成したら、以下のコードをコピー&ペーストなどで入力して、保存します。

<!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>

次に、WebアプリのエントリーポイントとなるJavaScriptファイルを作成します。Webpackは、Webアプリをビルドするのに必要なJavaScriptCSSなどのファイルを、エントリーポイントから辿って見つけていきます。

src/index.jsを新規ファイルとして作成して、まずは他のファイルは組み込まず、シンプルに1行だけのコードを書いておきます。

console.log('Hello World!');

Webpackのインストールと設定

ターミナルで以下を実行して、Webpackをはじめとするパッケージをプロジェクトに加えます。

npm i --save-dev webpack style-loader css-loader url-loader html-webpack-plugin

これで、以下の様なパッケージがインストールされました。

  • webpack
    • Webアプリをビルドするためのツール。分割されたファイルを統合したり、様々なファイルを再配置したり、ビルドに関連する処理をしてくれます
  • style-loader, css-loader, url-loader
    • CSSや各種リソースファイルを読み込んでくれるローダーです
  • html-webpack-plugin
    • HTMLファイルをバンドルするのに使うツールです

設定

プロジェクトディレクトリー直下に新しいファイルを作成して、webpack.config.jsという名前にしてください。このファイルにWebpackのビルド設定を以下の様に記載します。

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'
        })
    ]
};

おおよそ、以下の様なことを設定しています。

  • パス操作をするためにNodeのpathを読み込み
  • webpackモジュールをwebpackという定数に読み込み
  • html-webpack-pluginHtmlWebpackPluginという定数に読み込み
  • __dirnameはNodeのグローバル値で、このファイルのパスを表す。このプロジェクトのベースのパスをcontextを使って指定
  • エントリーポイントとして./src/index.jsを設定
  • 出力先はdistディレクトリーにして、ファイル名を[name].jsで指定
    • この場合、app.jsになります
  • CSSファイルはstyle-loadercss-loaderで読み込む様に指定
  • 画像などの各種リソースは、url-loaderで読み込む様に指定
  • HTMLファイルのテンプレートとして、src/index.htmlを指定
    • Webpackがsrc/index.htmlを雛形にして、必要な項目をページの本文に差し込みます

アプリをバンドルする

package.jsonに設定を追加して、npmコマンドで簡単にバンドルを実行できる様にします。testスクリプトは現状では利用しないので削除しても構いません。

package.jsonを開いて、"scripts"を以下の様にします。

  "scripts": {
    "build": "node_modules/.bin/webpack --config webpack.config.js",
    "start": "node_modules/.bin/webpack --watch --config webpack.config.js & browser-sync start --config bs-config.js"
  },

ビルドの実行

ターミナルで以下を入力することで、バンドルの実行と、開発サーバーでの動作確認ができる様になります。

npm start

これでローカルサーバーが起動して、自動的にWebブラウザーが起動して、 http://localhost:3000 が開きます。これ以降、index.htmlを変更したり、index.jsを変更して保存すると、自動的にビルドが実行されて、Webページが更新される様になります。

まだ作業が残っているので、動作中のターミナルで[Ctrl]+[C]キーを押してローカルサーバーを停止してください。

WebアプリにCesiumを組み込む

Cesiumをインストール

プロジェクトディレクトリーから、以下を実行します。

npm i --save-dev cesium

設定

Cesiumをインストールすると、ライブラリーに加えて、CSSや画像、JSONファイルといったものも組み込まれます。また、他スレッドで処理を行うWebWorkerファイルも含まれます。古いnpmモジュールと異なり、様々な組み合わせができるようにCesiumはエントリーポイントを定義しません。動かすためには、いくらかのオプションを設定する必要があります。

まずは、Cesiumがどこにあるかの指定が必要です。ソースコードを指定して、それをWebpackが利用して個々のファイルの依存関係を解決します。ビルドされたバージョンのCesiumを使う方法もあります。それらのモジュールはRequireJS Optimizerによって最適化されているため、ライブラリーを簡単に改良することが難しく、自由度は下がります。

webpack.config.jsを開いて、以下の設定を最初の方に追加します。

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

次に、以下のようなオプションを追加して、CesiumがWebpackでどのようにコンパイルされるかを指定します。

    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: ''
    • Webpackのいくつかのバージョンは、デフォルトの設定で、出力するファイルの各行の先頭にタブ文字を追加する設定になっています。Cesiumは複数行の文字列のインスタンスがあるため、この設定を無効にするために必要になります。
  • amd.toUrlUndefined: true
    • AMD版のWebpackはrequireステートメントtoUrlに対応していないことをCesiumに伝えるための設定です。
  • node.fs: 'empty'
    • いくらかのサードパーティーで使われるfsモジュールを解決するための設定です。これはNode環境ではなく、Webブラウザーでの動作時に利用されます。

次に、cesiumエイリアスwebpack.config.jsの適当な場所に追加します。これを設定しておくと、Nodeの伝統的なモジュールのように簡単にアプリのコードから参照できるようになります。

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

Cesiumの静的ファイルを管理する

最後に、Cesiumの静的なアセットやウィジェット、Web Workerファイルを提供して、読み込むようにします。

copy-webpack-pluginを使って、ビルド時にCesiumに含まれる各種ファイルをdistディレクトリーにコピーします。まずは以下をターミナルで実行して、プラグインをインストールします。

npm i --save-dev copy-webpack-plugin

それから、webpack.config.jsファイルの上の方に、以下の定義を追加します。

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

plugins配列を以下のようにコード追加します。

   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('')
        })
    ]

web workerは最適化済みのビルドされたものを使いますが、デバッグのためにオリジナルフォームが必要になる場合があります。それらはBuild/Cesium/Workersディレクトリーからコピーされます。

DefinePluginでは、webpackに組み込む静的ファイルのベースURLをCesiumに伝えています。

設定の動作確認

ここまでの設定が間違っていないかをチェックします。以下をターミナルで実行して、ビルドが成功して、Webページが表示されることを確認しましょう。

npm start

エラーが発生せず、Hello World!という文字が表示されればここまで成功です。いよいよ、Cesiumを使ってみましょう。

Hello World!

src/index.jsファイルを開いて、現在の内容は消して、以下を入力してください。

var Cesium = require('cesium/Cesium');
require('cesium/Widgets/widgets.css');

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

1行目で、CommonJSスタイルで、CesiumオブジェクトにCesiumの全ての機能を読み込みます。 2行目で、ウィジェットCSSを指定します。 最後に、Cesiumのビュアーを生成して、cesiumContainerのIDを持ったHTML要素に表示する指定をしています。

index.htmlに、cesiumContainer要素を追加する必要があります。src/index.htmlを開いて、<p>Hello World!</p>という行を消して、その場所に以下のコードを入力してください。

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

これで設定完了です。ローカルサーバーは起動済みですので、変更したファイルを保存すれば自動的にブラウザーがリロードされるはずです。Webブラウザーに切り替えて、Cesiumの画面が表示されれば成功です。切り替わらない場合は、Webブラウザーをリロードしてみてください。

f:id:am1tanaka:20171103233137p:plain

少し表示領域の周りに白い枠があったり、ブラウザーのクライアント領域全体に表示されておらず、スペースが無駄になっています。

f:id:am1tanaka:20171103233536p:plain

独自のCSSを定義して、ブラウザーいっぱいに表示してみましょう。

src/css/main.cssというファイルを新しく作成して、以下のコードを入力して保存します。

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

src/index.jsの先頭の方に、以下を追加して、css/main.cssを読み込むようにします。

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

上書き保存をすれば、自動的にリビルドされて、Webブラウザーが更新されます。変化しない時は、リロードしてみてください。枠がなくなり、ブラウザーのクライアント領域いっぱいに表示されるようになりました。

f:id:am1tanaka:20171103234002p:plain

以上で、簡単なCesium Webアプリの構築は完了です。

補足情報

各種、補足情報です。

アプリからCesiumモジュールを読み込むいくつかの方法

アプリに個別のCesiumモジュールを読み込む方法として、CommonJSの文法を使う方法と、ES6のモジュールを読み込むimportを使う方法があります。

全てのCesiumライブラリーをCesiumオブジェクトに設定する方法もあります。これはSandcastleで採用している方法です。Cesiumは巨大なライブラリーなので、基本的には必要なモジュールだけを読み込む方が良いでしょう。

CommonJSスタイルのrequire

CesiumオブジェクトにCesiumの全てのライブラリーを読み込んで利用するには、以下のようにします。

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

個別のモジュールを読み込むには、以下のようにします。

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

ES6スタイルのimport

CesiumオブジェクトにCesiumの全てのライブラリーを読み込んで利用するには、以下のようにします。

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

個別のモジュールを読み込むには、以下のようにします。

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

アセットファイルの読み込み

Webpackの考え方では、全てのファイルはモジュールのように扱うべきというものがあります。これにより、JavaScriptのモジュールと同じようにアセットを読み込むことができます。Webpackが読み込みに使うローダーはwebpack.config.jsloaders属性で設定しているので、JavaScriptファイル内でrequireで以下のように呼び出すだけです。

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

この後は・・・

Sandcastleの気になったサンプルを見ながら、実装してみると良いでしょう。

発展的なWebpackの設定

Webpackでは、機能を増やしたり、バンドルサイズを減らしたり、追加機能を入れたりと、多様なビルド方法を選択できます。いくつかの設定について触れておきます。

Cesiumを製品に最適化したWebpackの設定例はrelease.webpack.config.jsにあります。

コード分割

デフォルトのCesiumパッケージは、アプリと同じチャンク内にあり、とても巨大になります。CommonChunksPluginを使うことで、Cesiumを分離して、アプリのパフォーマンスを向上させることができます。開発するアプリを複数のチャンクに分けたら、それらすべてが共通のcesiumチャンクを参照するようにします。

webpack.config.jsファイルにプラグインを追加して、Cesiumモジュールを分割するルールを指定するだけです。

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

ソースマップを使うには

デバッグ時に、問題箇所がオリジナルファイルのどこかを示すためのマップファイルを作成できます。webpack.config.jsに以下を追加します。

   devtool: 'eval',

開発時には、この設定をしておくことを推奨します。製品版には含めるべきではありません。

pragmasの削除

開発時のエラーや警告メッセージが、Cesiumに埋め込まれています。これらは製品版には不要です。通常、これらは RequireJS Optimizer を使って、コードを縮小する時に削除します。これはWebpackの組み込み機能ではないので、以下のようにプラグインをインストールします。

npm i uglifyjs-webpack-plugin --save-dev

webpack.config.jsmodule.rulesの設定で、debugfalseにします。

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

Uglify(最適化/圧縮)とminify(圧縮)

uglifyingとminifyingで、製品コードのサイズを縮小できます。公開用ビルドのために、CesiumはuglifyでJavaScriptファイルを、minifyでCSSファイルを縮小します。

まだuglifyjs-webpack-pluginをインストールしていなければ、以下でインストールします。

npm i uglifyjs-webpack-plugin --save-dev

webpack.config.jsの先頭の方で、以下でrequireします。

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

webpack.config.jsplugins設定に以下を追加します。

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

CSSの最小化は、css-loaderoptionsminimize設定を書きます。

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

学習素材

公式のcesium-webpack-exampleリポジトリーには、最小のwebpack設定や、このチュートリアルで作成したHello Worldのコードが含まれ、コード設定の設定方法の導入として有用です。

データを追加したり、スタイリングを設定する方法など、作成するアプリで利用したい機能がCesium Workshop Tutorialで確認できるかもしれません。

Webブラウザーでコードをすぐに試せるSandcastleドキュメントも使えます。

Webpackについては、Webpack Conceptsや、Webpackのドキュメントを確認してください。

参考URL