原文地址:https://cesiumjs.org/tutorials/cesium-and-webpack/
Cesium 和 Webpack
Webpack是非常強大非常流行的JavaScript 模塊打包工具。它可以讓開發(fā)人員以一種簡單直觀的 require 方式去加載各種頁面需要的文件,極大的方便了開源人員對代碼和資源文件進行結構化設計。當編譯的時候,它會跟蹤代碼依賴性,把所有的模型打包到瀏覽器可以直接加載的一個或者多個bundles中。
在這個教程的前一半,我們創(chuàng)建一個簡單的web項目,學會使用webpack,然后再去集成 Cesium npm模塊。這是基于Cesium開發(fā)正式web項目的很好開端,但是它不是學習Cesium的最簡單示例,可以看一下我們的新手入門。
在教程的后半部分,我們將討論更多高級的webpack 配置參數(shù),去優(yōu)化使用Cesium的項目。
這個優(yōu)化Cesium和Webpack集成的項目示例,可以查看官網(wǎng)webpack示例 代碼庫。
先決條件
- 對命令行,JavaScript語言和web開發(fā)需要有一個基本了解。
- 一個代碼編輯器(IDE)。Cesium團隊的開發(fā)人員都用 Webstorm, 但是 Sublime Text 等工具也可以。
- 安裝了Node.js 。LTS版本最好,但是只要是v6以上都可以的。
創(chuàng)建一個基本的 webpack 程序
第一部分,描述如何建立一個簡本的webpack程序以及一個開發(fā)測試服務器。如果你已經(jīng)創(chuàng)建了程序,就想添加Cesium,直接看后面和Cesium繼承部分。
使用npm初始化程序
創(chuàng)建一個程序目錄,命名為cesium-webpack-app。打開控制臺,定位到該目錄下,運行下面的命令:
npm init
根據(jù)提示信息,填寫你的項目里的詳細信息。一路按enter使用默認配置也是可以的。完成后,這個目錄下創(chuàng)建了一個 package.json文件。
編寫自己的程序代碼
在這個目錄下,創(chuàng)建一個源碼目錄src。這個目錄是我們項目源碼所在位置,我們編輯的文件都放這下面。當我們編譯后,webpack會自動生成編譯后文件放在 dist目錄下。
我們創(chuàng)建一個 src/index.html文件,用一個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>
現(xiàn)在我們需要添加我們程序的入口 。這個入口會告訴webpack去把所有依賴的源碼文件打包。打包后的文件才會在 index.html文件里引用。
現(xiàn)在,為了簡單,我們新建 src/index.js文件,只添加下面這句確認環(huán)境配置好了。
console.log('Hello World!');
安裝和配置webpack
下來我們安裝webpack。
npm install --save-dev webpack

配置參數(shù)
創(chuàng)建一個名為webpack.config.js的文件。這是定義webpack的配置參數(shù) 的地方,它會傳給webpack的編譯器。
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'),
}
};
首先,我們需要從Nodejs中加載 path和我們剛剛安裝的webpack模塊。我們告訴webpack,context的及處理路徑是Node的全局變量__dirname,這個全局變量指的這個文件所在的目錄位置。我們設定了我們的程序入口是 src/index.js,而且把它命名為app。我們還告訴webpack把打包后的文件(這個文件的名稱叫app.js,因為我們設置了程序入口的[name])輸出到dist目錄下。最后export 把這個配置對象導出,其他文件才能用到這個配置。
加載器(Loaders)
我們還需要加載我們的css以及其他資源文件的方法。webpack允許我們像加載js模塊一樣,載入這些文件。我們需要使用 loaders。我們需要使用 style-loader, css-loader, 和 url-loader。
npm install --save-dev style-loader css-loader url-loader

繼續(xù)往 webpack.config.js里添加 module.rules。我們添加兩個規(guī)則,一個是為css文件,一個是為其他靜態(tài)文件。每個一個配置,我們定義 test過濾器來篩選這種類似的文件,還定義了一個use數(shù)組,指定這種類型用到的loader。
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' ]
}]
}
};
插件
項目已經(jīng)創(chuàng)建好了,下來需要修改index.html ,讓它把打包文件引到頁面上。使用webpack的 插件(plugins) 中的 html-webpack-plugin。
npm install --save-dev html-webpack-plugin

在webpack.config.js 文件的最開始加載這個插件,并添加到 plugins配置里去。把 src/index.html 做為一個template設置,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'
})
]
};
注意,這個配置文件也是一個普通的JavaScript文件,所以我們可以引用任何Nodejs的模塊,做任何操作。
打包程序
在 package.json文件里添加一點腳本配置,我們就能很方便的調用npm。把里面默認的"test"腳本刪除掉,我們用不上它。添加一個build命令。
"scripts": {
"build": "node_modules/.bin/webpack --config webpack.config.js"
}
這個腳本就是簡單的調用了webpack命令,并把我們剛才新建的配置文件webpack.config.js 當作參數(shù)傳遞過去。
因為我們的webpack剛才是本地安裝。這樣每個項目都使用獨立的版本,這也是webpack的推薦安裝方式。如果你選擇全局安裝方式,使用 npm install --global webpack全局安裝webpack,然后這里的build命令修改為 webpack --config webpack.config.js。
我們運行下編譯命令。
npm run build
你應該可以看到webpack開始輸出了一些信息:
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

我們的打包文件
app.js 和index.html 輸出到了 dist目錄。這些文件已經(jīng)是可以使用的程序了。
啟動開發(fā)服務器
這個還不算什么。我們使用 webpack-dev-server 快速的搭建一個開發(fā)者服務器,我們的程序就可以實時查看了。
首先安裝webpack-dev-server
npm install --save-dev webpack-dev-server
下來,在 package.json文件中增加另外的腳本。使用start命令去啟動服務,通過 --config參數(shù)傳遞參數(shù)。還傳一個 --open去自動在瀏覽器打開我們的頁面。
"scripts": {
"build": "node_modules/.bin/webpack --config webpack.config.js",
"start": "node_modules/.bin/webpack-dev-server --config webpack.config.js --open"
}
我們需要告訴服務器去哪里尋找文件。這個需要和我們的webpack輸出目錄一致,也就是 dist目錄。所以在 webpack.config.js文件中增加一些配置。
// 開發(fā)服務器配置
devServer: {
contentBase: path.join(__dirname, "dist")
}
最后,我們運行程序
npm start
你應該能看見程序已經(jīng)在 localhost:8080運行了。如果你打開瀏覽器控制臺,因會看見“Hello World!” 已經(jīng)輸出了。


webpack 程序里集成 Cesium
我們的webpack程序已經(jīng)搭好了框架,下來我們干點有意思的,增加Cesium。
安裝Cesium
從npm中安裝 cesium 模塊,在 package.json 中增加開發(fā)依賴選項。
npm install --save-dev cesium
webpack中配置Cesium
Cesium是一個龐大的復雜的庫。除了JavaScript模塊之外,它還包含靜態(tài)資源css,圖片和json文件。它也包含為了多線程進行大量計算的web worker。和傳統(tǒng)npm模塊不同,Cesium也沒有定義一個入口,因為這個庫可以各種方式應用。為了正確使用,我們需要配置一些額外選項。
首先,我們定義Cesium所在位置。我們使用源碼,這樣我們就可以使用獨立模塊,依靠webpack可以跟蹤依賴性。其他方式是使用編譯好的(最小化壓縮或者未壓縮)版本的Cesium;可是,那些模塊已經(jīng)被RequireJS optimizer 組合和優(yōu)化,這種方式集成起來簡單避免犯錯,但是對于我們的優(yōu)化靈活度不夠好。
在webpack.config.js,我們增加如下配置
// Cesium源碼所在目錄
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';
我們選擇使用了npm 中的模塊,安裝容易一些,當然你也可以用 GitHub 庫 上的版本,或者解壓已發(fā)布版本下載。這時候僅僅需要更改cesiumSource 指向Cesium的 Source 目錄,相對 webpack.config.js 文件的相對路徑。
接著再增加一些配置項,為了解決使用webpack編譯Cesium的一些問題。
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
//需要編譯Cesium中的多行字符串
sourcePrefix: ''
},
amd: {
//允許Cesium兼容 webpack的require方式
toUrlUndefined: true
},
node: {
// 解決fs模塊的問題(Resolve node module use of fs)
fs: 'empty'
},
快速過下,為什么這些配置選項需要配置。
-
output.sourcePrefix: ''因為某些版本的webpack默認會在輸出的每一行的開始增加一個\t字符。Cesium有很多多行字符串,所以我們需要使用空字符串''來覆蓋這個選項。 -
amd.toUrlUndefined: true告訴Cesium,webpack中計算require聲明的AMD 模塊里的toUrl函數(shù)和標準的不兼容。 -
node.fs: 'empty'解決一些第三方庫使用的fs模塊,它一般是用在NodeJS的環(huán)境里,而不能在瀏覽器環(huán)境里使用。
下來我們增加一個cesium別名(alias) ,我們就很容易的在項目里引用,就像一個傳統(tǒng)的Node 模塊。
resolve: {
alias: {
// Cesium模塊名稱
cesium: path.resolve(__dirname, cesiumSource)
}
},
</figure>
管理Cesium 靜態(tài)文件
最后,我們確認一下Cesium的靜態(tài)資源,控件,web worker文件能被服務正確加載。
我們使用 copy-webpack-plugin,它能在編譯階段,把Cesium里靜態(tài)文件整個拷貝到 dist 目錄下,確保我們的服務能訪問它。首先,安裝它。
npm install --save-dev copy-webpack-plugin
下來在webpack.config.js文件最開始require它。
const CopywebpackPlugin = require('copy-webpack-plugin');
此外,在plugins 目錄下增加下述配置:
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
// 拷貝Cesium 資源、控價、web worker到靜態(tài)目錄
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' } ])
],
我們直接拷貝了Assets和Widgets目錄。也拷貝了編譯好的 web worker腳本,他們已經(jīng)使用 RequireJS optimizer編譯和優(yōu)化過了。因為web woker設計就是運行在他們自己的線程里,所以他們可以直接載入和運行。web worker很少需要調試他們原來的代碼。所以直接整個從 Build/Cesium/Workers目錄拷貝過去。
如果你用的GitHub庫的源碼,那么這個Build目錄不存在。確認你定位到了Cesium的根目錄下,然后運行 npm run release去編譯輸出。更多信息可以查看Cesium編譯指導。
最后,我們使用DefinePlugin 定義了一個環(huán)境變量,這個告訴Cesium加載靜態(tài)文件的URL根路徑,并把它編譯到webpack里去。最后plugins 的配置數(shù)組應該是這樣的:
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
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({
//Cesium載入靜態(tài)的資源的相對路徑
CESIUM_BASE_URL: JSON.stringify('')
})
],
在我們的程序里引用Cesium
現(xiàn)在我們的程序里引用Cesium的獨立模塊有幾種方式。使用CommonJS的語法,以及最新的ES6的 import模塊聲明。
另外,你也可以通過一個 Cesium 引入整個Cesium庫(例如,我們在 Sandcastle中所用的方式)。你也可以只請求某個你需要的特定模塊。因為Cesium是一個巨大的庫,這種方式讓你只引用你用的特定模塊,而不是整個Cesium庫。
CommonJS 形式的 require
引用Cesium整個庫:
var Cesium = require('cesium/Cesium');
var viewer = new Cesium.Viewer('cesiumContainer');
引用某個Cesium庫:
var Color = require('cesium/Core/Color');
var color = Color.fromRandom();
ES6 形式的 import
引用Cesium整個庫:
import Cesium from 'cesium/Cesium';
var viewer = new Cesium.Viewer('cesiumContainer');
引用某個Cesium庫:
import Color from 'cesium/core/Color';
var color = Color.fromRandom();
請求靜態(tài)資源文件
webpack的設計理念就是每個文件都當作一個模塊。所以導入靜態(tài)資源和導入js模塊完全相同。我們已經(jīng)在loaders里面配置了,告訴webpack每種類型的文件使用哪個loader,所以我們只需要調用 require:
require('cesium/Widgets/widgets.css');
Hello World! 示例
有了基本框架也學習如何引入Cesium文件,我們來創(chuàng)建一個簡單的Hello World程序。
再看下index.js文件。刪除它的內容,首先我們引入 Cesium,然后定義 Cesium的對象:
var Cesium = require('cesium/Cesium');
為了使用Cesium Viewer里的控件,我們還需要引入Cesium的CSS:
require('cesium/Widgets/widgets.css');
在HTML的body部分,我們創(chuàng)建了一個viewer需要的div。在 index.html 文件里,刪除 <p>Hello World!</p> 這一行,替換成下面:
<div id="cesiumContainer"></div>
最后,我們創(chuàng)建一個viewer的實例。返回到 index.js 文件,添加下面代碼:
var viewer = new Cesium.Viewer('cesiumContainer');
當我們運行 npm start ,我們將看見瀏覽器里的Cesium控件了。

為了美觀,我們用一些自定義css去掉頁面上的白色邊界。
創(chuàng)建一個新的文件src/css/main.css,增加下面的樣式:
html, body, #cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
在index.js里require它,在其他require的后面。
require('./css/main.css');
重新npm start,Cesium的控件完美全屏了。
隨便拷貝粘貼 Sandcastle 中的示例。 我認為 粒子系統(tǒng)的示例 最能說明問題。

webpack 高級配置
webpack有很多其他方法增加性能、減小打包大小、執(zhí)行額外的或者復雜的編譯過程。對于使用Cesium庫,我們將討論一些配置選項。
我們的最佳配置選項示例放在github的配置庫里 webpack.release.config.js
代碼切片
webpack默認會把Cesium和整個程序代碼打進一個 chunk中,最終這個庫非常龐大。我們也可以用CommonChunksPlugin把Cesium打到它自己的包,提升程序的性能。如果最終你的程序創(chuàng)建了多個chunks,他們可以引用一個通用的cesiumchunk。
只需要在 webpack.config.js添加這個插件,然后設置打斷Cesium模塊的規(guī)則。
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'cesium',
minChunks: module => module.context && module.context.indexOf('cesium') !== -1
})
]
這個插件最新版webpack上已經(jīng)不能這么配置了,使用config.optimization.splitChunks去設置,詳情點擊webpack-splitChunks
啟用 source maps
Source maps 允許webpack跟蹤原來代碼中的錯誤,它有很多配置項。它用編譯速度來換取或多或少的調試詳情信息。我們推薦使用'eval'選項,但是盡量使用你自己開發(fā)中最好的方式。
devtool: 'eval'
不推薦在最終產(chǎn)品代碼里添加Source maps(容易被破解唄)。
刪除 pragmas
Cesium源碼里包含了一些開發(fā)錯誤和警告信息,但是產(chǎn)品中是不需要的。通常我們使用 RequireJS Optimizer在release編譯下壓縮它。因為沒有webpack內置的方式去刪除這些警告,我們將使用 strip-pragma-loader。
首先,安裝。
npm install strip-pragma-loader --save-dev
然后在loader的 module.rules增加規(guī)則,并且 把 debug 設置為 false.
rules: [{
// 刪除 cesium pragmas
test: /\.js$/,
enforce: 'pre',
include: path.resolve(__dirname, cesiumSource),
use: [{
loader: 'strip-pragma-loader',
options: {
pragmas: {
debug: false
}
}
}]
}]
混淆和壓縮
混淆和壓縮代碼使最終產(chǎn)品的代碼變得更小。對于一個release編譯,Cesium會JavaScript文件,并且壓縮CSS文件。為了混淆Cesium源碼,我們使用 uglifyjs-webpack-plugin插件。
npm install uglifyjs-webpack-plugin --save-dev
引入這個插件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
在插件里引用它
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
這個插件最新版webpack上已經(jīng)不能這么配置了,使用config.optimization.minimize去設置,詳情點擊webpack-optimization
為了壓縮css代碼,在css-loader中使用minimize選項。
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
// minify loaded css
minimize: true
}
}
]
}]
}
相關資源
官方 cesium-webpack-示例 的git庫。包含最小的webpack配置,以及此篇教程的hello word代碼,還有部分可選的代碼配置選項。
學習如何基于Cesium開發(fā)項目實例,比如如何添加數(shù)據(jù)和配置樣式,那么去學習 Cesium Workshop 教程。
玩下 Sandcastle以及官方用戶手冊。
深入學習webpack,可以看下webpack 概念 ,或者看下webpack的配置手冊 。
