Zero-config Cesium.js in Vite — introducing vite-plugin-cesium-engine

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Zero-config Cesium.js in Vite — introducing vite-plugin-cesium-engine

    If you've ever tried to use CesiumJS with Vite, you know the ritual. Before you can render a globe you have to:
    • Copy WASM workers and assets to your output directory
    • Set window.CESIUM_BASE_URL before any Cesium module loads
    • Inject a tag for CesiumWidget.css
    • Somehow get your Ion access token into the bundle


    Every project starts the same way: copy-paste from a StackOverflow answer, tweak until it works in dev, discover it breaks in prod, repeat.


    I built vite-plugin-cesium-engine to make all of that disappear.





    What it targets

    There are already a couple of Cesium Vite plugins out there, but they all target the full cesium package — the one that comes with the entire Viewer UI. If you want the lean @cesium/engine core (no widgets, full control over your own UI), you were on your own.


    This plugin is purpose-built for @cesium/engine only.





    Installation





    # npm
    npm i -D @cesium/engine vite-plugin-cesium-engine

    # pnpm
    pnpm add -D @cesium/engine vite-plugin-cesium-engine

    # yarn
    yarn add -D @cesium/engine vite-plugin-cesium-engine










    Basic usage

    Add it to your Vite config:






    // vite.config.ts
    import { defineConfig } from "vite";
    import { cesiumEngine } from "vite-plugin-cesium-engine";

    export default defineConfig({
    plugins: [cesiumEngine()],
    });







    That's the entire setup. No CESIUM_BASE_URL, no asset copy scripts, no CSS imports. Then use Cesium directly:






    import { CesiumWidget, Terrain } from "@cesium/engine";

    const widget = new CesiumWidget(document.getElementById("app")!, {
    terrain: Terrain.fromWorldTerrain(),
    });







    The plugin automatically:
    • Copies WASM workers, built files, and CesiumWidget.css to your output directory
    • Injects window.CESIUM_BASE_URL into your HTML before any module loads
    • Injects the CesiumWidget.css tag
    • Serves assets directly from node_modules during vite dev (no copying needed)





    Framework examples

    The setup is identical across frameworks — only the lifecycle hooks differ.


    React





    // src/main.tsx
    import { useEffect, useRef } from "react";
    import { CesiumWidget, Terrain } from "@cesium/engine";

    export default function App() {
    const containerRef = useRefHTMLDivElement>(null);

    useEffect(() => {
    if (!containerRef.current) return;
    const widget = new CesiumWidget(containerRef.current, {
    terrain: Terrain.fromWorldTerrain(),
    });
    return () => widget.destroy();
    }, []);

    return div ref={containerRef} style={{ width: "100%", height: "100vh" }} />;
    }







    Vue





    script setup lang="ts">
    import { ref, onMounted, onBeforeUnmount } from "vue";
    import { CesiumWidget, Terrain } from "@cesium/engine";

    const container = refHTMLDivElement>();
    let widget: CesiumWidget;

    onMounted(() => {
    widget = new CesiumWidget(container.value!, {
    terrain: Terrain.fromWorldTerrain(),
    });
    });

    onBeforeUnmount(() => widget?.destroy());
    script>

    template>
    ref="container" style="width: 100%; height: 100%" />
    template>







    Svelte





    "ts">
    import { onMount } from "svelte";
    import { CesiumWidget, Terrain } from "@cesium/engine";

    let container: HTMLDivElement;

    onMount(() => {
    const widget = new CesiumWidget(container, {
    terrain: Terrain.fromWorldTerrain(),
    });
    return () => widget.destroy();
    });


    bind:this={container} style="width: 100%; height: 100%" />







    Vanilla TypeScript





    // src/main.ts
    import { CesiumWidget, Terrain } from "@cesium/engine";

    const widget = new CesiumWidget(document.getElementById("app")!, {
    terrain: Terrain.fromWorldTerrain(),
    });

    if (import.meta.hot) {
    import.meta.hot.dispose(() => widget.destroy());
    }







    Complete, ready-to-run starter projects for all four frameworks are available in the examples/ directory of the repository.





    Ion token — baked in at build time

    Set your Cesium Ion access token through the plugin options and it gets injected at build time — no Ion.defaultAccessToken = ... in your app code, no runtime env variable reading:






    cesiumEngine({
    ionToken: process.env.CESIUM_ION_TOKEN,
    })







    Per-environment tokens

    Pass a { [mode]: token } map to use different tokens per Vite mode:






    cesiumEngine({
    ionToken: {
    development: process.env.CESIUM_ION_TOKEN_DEV,
    production: process.env.CESIUM_ION_TOKEN_PROD,
    },
    })











    vite dev # injects CESIUM_ION_TOKEN_DEV
    vite build # injects CESIUM_ION_TOKEN_PROD







    The plugin also validates that the token looks like a valid JWT at startup and warns you if it doesn't — catching .env substitution failures before they become runtime surprises.





    Virtual module — typed runtime constants

    The plugin exposes a virtual:cesium module so your app code can read CESIUM_BASE_URL and ION_TOKEN without touching window globals:






    // tsconfig.json
    {
    "compilerOptions": {
    "types": ["vite-plugin-cesium-engine/virtual"]
    }
    }











    import { CESIUM_BASE_URL, ION_TOKEN } from "virtual:cesium";

    console.log(CESIUM_BASE_URL); // "/cesium/"
    console.log(ION_TOKEN); // your token, or null







    Both values are resolved at build time and tree-shaken if unused.





    Custom asset paths

    For CDN deployments or frameworks with opinionated public directories (Laravel, Rails, etc.), you can control exactly where assets land and where they're served from:






    // vite.config.ts
    export default defineConfig({
    base: "/app/",
    plugins: [
    cesiumEngine({
    assetsPath: "vendor/cesium", // dist/vendor/cesium/
    cesiumBaseUrl: "/app/vendor/cesium", // served from here
    }),
    ],
    });







    The plugin warns at startup if cesiumBaseUrl doesn't start with Vite's base, so misconfigurations are caught immediately rather than at runtime in production.





    Debug mode

    If something isn't wiring up as expected, debug: true prints a full summary at dev-server startup:






    cesiumEngine({ debug: true })











    [cesium-engine] mode : development
    [cesium-engine] vite base : ""
    [cesium-engine] cesiumBaseUrl: "/cesium"
    [cesium-engine] assetsPath : "cesium"
    [cesium-engine] ionToken : eyJhbGciOiJ… (mode: development)
    [cesium-engine] copying assets:
    ThirdParty/*.wasm → cesium/ThirdParty
    Build/* → cesium
    Source/Assets/ → cesium
    Widget/*.css → cesium/Widget










    Known warning: protobufjs eval

    You may see this Rollup warning in your build output:






    [EVAL] Use of direct eval function is strongly discouraged
    node_modules/protobufjs/dist/minimal/protobuf.js







    This comes from protobufjs, a transitive dependency of @cesium/engine. The eval is intentional in that library and not a security issue. Suppress it in your vite.config.ts:






    build: {
    rollupOptions: {
    onwarn(warning, defaultHandler) {
    if (warning.code === "EVAL" && warning.id?.includes("protobufjs")) return;
    defaultHandler(warning);
    },
    },
    },










    Zero runtime dependencies

    The plugin has no runtime dependencies — it uses only Node built-ins (node:fs, nodeath) and Vite's own plugin API. What gets installed alongside it is exactly nothing beyond what you already have.





    Links



    Feedback, issues, and PRs are very welcome.




    More...

Working...