正確に言えば、あ〜SSR環境で動作するアプリケーション内でもCesiumJSを利用したいね〜です。
使用環境
Svelte v5.0.0
SvelteKit v2.0.0
pnpm v9.14.4
TL;DR
ビルド時にvite-plugin-static-copy
を用いて、クライアントサイドで使用するパッケージを静的ファイルにコピーする。
試してみたいくらいだったらCDN使えば良い。
はじめに
CesiumJSはロード時にパッケージ内の静的ファイル(AssetsやWorkersなど)をクライアントサイド(ブラウザ)で動的に読み込む必要がある。このため、CesiumJS自体はサーバーサイドレンダリング(SSR)には適さず、多くの場合アプリケーション全体もSSG(静的サイト生成)またはCSR(クライアントサイドレンダリング)環境で動作させることが一般的となる。
これは、CesiumJSがWebGLベースで動作し、大量の静的リソースを効率よく配信する設計だからとも言える。
一方で現代のウェブ開発要件に応じてSSRやハイブリッドレンダリングへの移行も検討されるようになった。例えばAWS Amplifyなどのモダンなホスティング環境では、Lambda@Edgeを活用したSSRによる初期表示速度向上とエッジコンピューティングによる効率的な配信が容易に実現可能となる。このような仕組みを活用することで、CesiumJSの3D描画機能と最新ウェブ技術の利点を両立することができる。
この記事では、アプリケーション全体をSSR環境で動作させつつ、クライアントサイドでのロード・動作する必要があるCesiumJSとの適合方法について解説する。
やらないこと
- AWS Amplifyへの詳細なデプロイ手順
準備
Svelte5のプロジェクトを作成し、ローカル環境でCesiumJSが動作するところまで
プロジェクト作成
npx sv create svelte5-cesium
選択したオプション
┌ Welcome to the Svelte CLI! (v0.6.11)
│
◇ Which template would you like?
│ SvelteKit minimal
│
◇ Add type checking with Typescript?
│ Yes, using Typescript syntax
│
◆ Project created
│
◇ What would you like to add to your project? (use arrow keys / space bar)
│ tailwindcss
│
◇ Which plugins would you like to add?
│ none
│
◇ Which package manager do you want to install dependencies with?
│ pnpm
パッケージマネージャにはpnpmを採用、tailwindcssも入れた。
pnpm run dev
で起動すると↓が表示できる
CesiumJSの導入
src/routes/+page.svelte
に以下を記載する。
<script lang="ts">
import { onMount } from "svelte";
import { UrlTemplateImageryProvider, Viewer, Cartesian3 } from "cesium";
import("cesium/Build/Cesium/Widgets/widgets.css");
// CesiumJSの静的リソース(AssetsやWorkers)をロードするためのベースURL
const baseUrl = "/node_modules/cesium/Build/Cesium/";
onMount(() => {
// CesiumJSの静的リソースのベースURLをグローバル変数に設定
(window as any).CESIUM_BASE_URL = baseUrl;
// Cesium Viewerインスタンスの初期化(地図コンテナを指定)
const viewer = new Viewer("cesiumContainer", {});
// 地理院地図の衛星画像プロバイダー
const gsiSeamless = new UrlTemplateImageryProvider({
url: "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg",
});
// 作成した画像プロバイダーをCesium Viewerに追加
viewer.imageryLayers.addImageryProvider(gsiSeamless);
// カメラの初期位置
viewer.camera.setView({
destination: Cartesian3.fromDegrees(139.767052, 35.681167, 10000),
});
});
</script>
<div id="cesiumContainer" class="cesiumContainer h-screen w-screen"></div>
地形は適用していないのでのっぺら
AWS Amplifyにホスティング
AWS Amplify用にビルドしてとりあえずホスティングしてみる。
ビルド設定
AWS Amplify用にビルドする必要があるので、svelte.config.js
にてadapterをamplify-adapter
に変更する。
import amplifyAdapter from 'amplify-adapter';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: amplifyAdapter()
}
};
export default config;
pnpm run dev
でビルドするとPJルートのbuild
配下にビルドファイルが生成されているはず(output先を他に設定していたら別)
デプロイ
GitHubにリポジトリを作成してデプロイしてみる。
リポジトリを作成後、AWS Amplifyのマネコンからデプロイの設定を行う。
リポジトリ/ブランチを選択後の「アプリケーションの設定」にて
出力ディレクトリをビルドにはビルド設定でoutputが出力されたbuild
ディレクトリを指定する。
当然ビルドに失敗する。
pnpmがないので、pnpm install
を実行する前にインストールする必要がある。
最終的なコード
version: 1
frontend:
phases:
preBuild:
commands:
- npm install -g pnpm@9.14.4
- pnpm install
build:
commands:
- pnpm run build
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- 'node_modules/**/*'
- Cesium ionを使用するため環境変数にトークンを埋め込みたい場合は、ホスティング > 環境変数に設定後、ビルド設定にでも含めてあげる必要がある
- キャッシュのパスに
.pnpm-store/**/*
を含めることで、pnpmらしい更なるビルド時間短縮と安定性向上が期待できる
デプロイ結果
デプロイ済みのドメインに接続してみると…
エラーが出ている。
コンソールにはCeisumJSのライブラリに含まれている静的ファイルが取得できなかった旨のエラーが表示されている。
対処法
その1
上記のエラーはクライアントサイドでCeiumJSが静的ファイルを読み込もうとしたものの、該当のパッケージの静的ファイルがクライアントサイドに存在しないことに起因している。
そこでビルド時にvite-plugin-static-copy
を用いて、クライアントサイドで使用するパッケージを静的ファイルにコピーする。
https://www.npmjs.com/package/vite-plugin-static-copy
vite.config.ts
にて以下のように設定することで、ビルドファイルstatic
に指定したファイル(今回はcesiumパッケージの各ファイル)を含めることができる。
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
export default defineConfig({
plugins: [
sveltekit(),
viteStaticCopy({
targets: [
{
src: 'node_modules/cesium/Build/Cesium/Workers/**/*',
dest: 'Cesium/Workers'
},
{
src: 'node_modules/cesium/Build/Cesium/Assets/**/*',
dest: 'Cesium/Assets'
},
{
src: 'node_modules/cesium/Build/Cesium/Widgets/**/*',
dest: 'Cesium/Widgets'
}
]
})
],
});
pnpm run build
した結果、build > static
配下にCesium
が存在することがわかる。
クライアントサイドの静的ファイルにCesiumJSのパッケージが含まれても、CESIUM_BASE_URL
がそのままだと参照できないことに変わりはない。
そこで環境ごとにCESIUM_BASE_URL
を切り替える必要がある。
// CesiumJSの静的リソース(AssetsやWorkers)をロードするためのベースURL
const baseUrl = import.meta.env.DEV
? "/node_modules/cesium/Build/Cesium/"
: "/Cesium/";
ここではvite-plugin-static-copy
で指定したビルドファイルに含めるディレクトリ名を指定する。
すると開発環境ではインストールしたnode_module
を、検証・本番環境ではビルド時に含められていたパッケージを使用することができる。
デプロイすると無事見られるようになる。
その2
CESIUM_BASE_URL
にCDN URLを指定する。
const baseUrl = "https://cdn.jsdelivr.net/npm/cesium@1.125.0/Build/Cesium/";
まとめ
SSR環境で動作するアプリケーションにおいて、クライアントサイドでCesiumJSのパッケージを読み込むとき、CDNを直で読み込めば終わりだが以下の観点から、私はvite-plugin-static-copy
の使用を推したい。
- 開発環境と検証・本番環境でのCesiumJS動作環境のバージョン差異が防げる
- CDN依存性によるリスク(プロバイダー障害やキャッシュ問題)への対策
一方で、CDNの特性上パフォーマンスの観点やビルド成果物の軽量化においてはCDNのメリットは挙げられる。
ちょっと検証するくらいだったら正直CDNでいいかなとも思う。