Using CesiumJS with Svelte

Svelte is a component framework for the web that works at build time, as opposed to frameworks like React that work at runtime. This post will demonstrate how to set up a basic CesiumJS application built on Svelte.

Setup

First, we’ll clone the Svelte startup template. We will be enabling Typescript in the project to take advantage of Cesium’s TypeScript support.

# Set up svelte template
npx degit sveltejs/template cesium-svelte-app
cd cesium-svelte-app
# Enable TypeScript
node scripts/setupTypeScript.js
# Install Svelte dependencies
npm install
# Add cesium as a dependency
npm install cesium

Configuring Rollup

This Svelte template uses Rollup, so we’ll need to perform some additional configuration to allow Cesium’s static assets to be imported. So, first, we’ll install the required Rollup dependencies:

# Install Rollup plugins
npm install --save-dev rollup-plugin-copy
npm install --save-dev rollup-plugin-postcss

Then, let’s make sure the static assets are copied in the build process in rollup.config.js

import postcss from 'rollup-plugin-postcss';
import copy from 'rollup-plugin-copy';
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';

import path from 'path';

const production = !process.env.ROLLUP_WATCH;
const cesiumBuildPath = 'node_modules/cesium/Build/Cesium'

export default {
	input: 'src/main.ts',
	output: {
		sourcemap: true,
		format: 'iife',
		name: 'app',
		file: 'public/build/bundle.js'
	},
	plugins: [
		svelte({
			dev: !production,
			css: css => {
				css.write('public/build/bundle.css');
			},
			preprocess: sveltePreprocess(),
		}),
		resolve({
			browser: true,
			dedupe: ['svelte']
		}),
		postcss({
			extensions: [ '.css' ]
		}),
		copy({
			targets: [
				{ src: path.join(cesiumBuildPath, 'Assets'), dest: 'public/build/' },
				{ src: path.join(cesiumBuildPath, 'ThirdParty'), dest: 'public/build/' },
				{ src: path.join(cesiumBuildPath, 'Widgets'), dest: 'public/build/' },
				{ src: path.join(cesiumBuildPath, 'Workers'), dest: 'public/build/' },
			]
		}),
		commonjs(),
		typescript({ sourceMap: !production }),
		!production && serve(),
		!production && livereload('public'),
		production && terser()
	],
	watch: {
		clearScreen: false
	}
};

function serve() {
	let started = false;

	return {
		writeBundle() {
			if (!started) {
				started = true;

				require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
					stdio: ['ignore', 'inherit', 'inherit'],
					shell: true
				});
			}
		}
	};
}

Adding the Cesium Viewer

Inside App.svelte, let’s add the Cesium viewer:

<script lang="ts">
	import { onMount } from 'svelte';
	import { Viewer } from 'cesium';
	import '../node_modules/cesium/Build/Cesium/Widgets/widgets.css'

	window.CESIUM_BASE_URL = './build';

    let viewer: Viewer;
	onMount(async () => {
		viewer = new Viewer('cesiumContainer');
	});
	
</script>

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

To ensure that the Cesium container takes up the full screen, add the following CSS snippet to global.css:

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

Now, we’re ready to run:

npm run dev

And we should have a Cesium Viewer up and running with Svelte!

Creating a simple Infobox component

Finally, let’s add a simple Infobox component to display information about the currently selected entity. Create a new file called Infobox.svelte in the src directory.

<script lang="ts">
    import type { Entity } from "cesium";
    export let entity: Entity = undefined;
</script>

<style>
    #cesiumInfobox {
        position: absolute;
        z-index: 2;
        right: 10px;
        top: 10px;
        color: white;
        background-color: gray;
        width: 10%;
        height: 5%;
        font-family: 'Courier New', Courier, monospace;
        font-size: large;
        border-radius: 5px;
        display: flex;
        align-items: center;
        text-align: center;
    }
    #entityNameLabel {
        width: 100%;
        flex: 1;
    }
</style>

<div id="cesiumInfobox">
    <label id="entityNameLabel">{entity.name}</label>
</div>

To display this new component, we need to add it to our main App.svelte component:

<script lang="ts">
	window.CESIUM_BASE_URL = './build';

	import { onMount }from 'svelte';
	import { Viewer, defined, Cartesian3, Color, Entity } from 'cesium';
	import Infobox from './Infobox.svelte';
	import '../node_modules/cesium/Build/Cesium/Widgets/widgets.css'

	let viewer: Viewer;
	let entity: Entity;
	onMount(async () => {
		viewer = new Viewer('cesiumContainer', {
    		baseLayerPicker : false,
			infoBox: false,
			geocoder: false,
			homeButton: false,
			navigationHelpButton: false,
			sceneModePicker: false
		});
        // Add sample entity to select.
		var blueBox = viewer.entities.add({
			name: "Blue box",
			position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
			box: {
				dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
				material: Color.BLUE,
			},
		});
		// Update entity object on selection/deselection.
		viewer.selectedEntityChanged.addEventListener(() => {
			entity = viewer.selectedEntity;
		});
	});
	
</script>

<div id="cesiumContainer">
    <!--Only show Infobox, if entity is defined-->
	{#if defined(entity)}
		<Infobox entity={entity} />
	{/if}
</div>

Now, when we click on our entity, we should have an infobox appear with its name!

2 Likes

Very nice! I used Svelte to create our space catalog viewer. It is really a great match for Cesium.

2 Likes

Try this with Rollup.