痛点:CesiumJS 与 Vite 集成的 "四步仪式"
如果你曾经尝试在 Vite 项目中使用 CesiumJS,一定对那套繁琐的初始化流程印象深刻。在看到地球渲染出来之前,你必须依次完成:
- 手动拷贝 WASM Worker 与静态资源到构建输出目录
- 在任何 Cesium 模块加载之前 设置
window.CESIUM_BASE_URL - 手动注入
CesiumWidget.css的<link>标签 - 想方设法把 Ion 访问令牌塞进打包产物
几乎每个项目的开局都如出一辙:从 StackOverflow 复制一段配置,改到开发环境能跑,一上线生产环境又崩了,然后陷入无限循环的调试。
本文要介绍的 vite-plugin-cesium-engine,就是为了让这一切彻底消失。
定位差异:专为 @cesium/engine 而生
社区里其实已经有几个 Cesium 的 Vite 插件,但它们无一例外都面向完整的 cesium 包 ------ 也就是自带整套 Viewer UI 的那个版本。
但如果你想要的是更轻量的 @cesium/engine 核心库(不包含任何 Widget 组件,完全由你自己掌控 UI 层),此前几乎没有现成方案,只能自己动手折腾。
vite-plugin-cesium-engine 的定位非常明确:只为 @cesium/engine 服务,不做全能型选手。
安装与基础使用
安装依赖
bash
bash
# 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
最小配置
在 vite.config.ts 中引入插件即可:
typescript
运行
javascript
import { defineConfig } from "vite";
import { cesiumEngine } from "vite-plugin-cesium-engine";
export default defineConfig({
plugins: [cesiumEngine()],
});
这就是全部配置。不需要设置 CESIUM_BASE_URL,不需要写资源拷贝脚本,不需要手动导入 CSS。
然后在业务代码中直接使用:
typescript
运行
javascript
import { CesiumWidget, Terrain } from "@cesium/engine";
const widget = new CesiumWidget(document.getElementById("app")!, {
terrain: Terrain.fromWorldTerrain(),
});
插件自动完成的工作
这个插件在背后默默帮你搞定了四件事:
- 构建时 :自动将 WASM Worker、编译产物和
CesiumWidget.css拷贝到输出目录 - HTML 注入 :在任何模块加载之前,将
window.CESIUM_BASE_URL注入到页面中 - CSS 注入 :自动添加
CesiumWidget.css的<link>标签 - 开发服务器 :
vite dev时直接从node_modules提供静态资源,无需拷贝
主流框架集成示例
插件的配置在所有框架中完全一致,差异仅在于组件的生命周期钩子。
React
tsx
javascript
import { useEffect, useRef } from "react";
import { CesiumWidget, Terrain } from "@cesium/engine";
export default function App() {
const containerRef = useRef<HTMLDivElement>(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 3
vue
xml
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue";
import { CesiumWidget, Terrain } from "@cesium/engine";
const container = ref<HTMLDivElement>();
let widget: CesiumWidget;
onMounted(() => {
widget = new CesiumWidget(container.value!, {
terrain: Terrain.fromWorldTerrain(),
});
});
onBeforeUnmount(() => widget?.destroy());
</script>
<template>
<div ref="container" style="width: 100%; height: 100%" />
</template>
Svelte
svelte
xml
<script lang="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();
});
</script>
<div bind:this={container} style="width: 100%; height: 100%" />
原生 TypeScript
typescript
运行
javascript
import { CesiumWidget, Terrain } from "@cesium/engine";
const widget = new CesiumWidget(document.getElementById("app")!, {
terrain: Terrain.fromWorldTerrain(),
});
// 支持 HMR 热更新时的资源清理
if (import.meta.hot) {
import.meta.hot.dispose(() => widget.destroy());
}
仓库的 examples/ 目录下提供了以上四种框架的完整可运行脚手架。
进阶配置
Ion 令牌:构建时注入
通过插件选项传入 Cesium Ion 访问令牌,它会在构建阶段被注入,无需在业务代码中写 Ion.defaultAccessToken = ...,也不需要运行时读取环境变量:
typescript
运行
scss
cesiumEngine({
ionToken: process.env.CESIUM_ION_TOKEN,
});
按环境区分令牌
传入一个 { [mode]: token } 的映射表,可以针对不同 Vite 模式使用不同令牌:
typescript
运行
php
cesiumEngine({
ionToken: {
development: process.env.CESIUM_ION_TOKEN_DEV,
production: process.env.CESIUM_ION_TOKEN_PROD,
},
});
vite dev→ 注入CESIUM_ION_TOKEN_DEVvite build→ 注入CESIUM_ION_TOKEN_PROD
插件还会在启动时校验令牌格式是否为合法 JWT,如果 .env 变量替换失败会立即给出警告,避免把问题带到生产环境才暴露。
虚拟模块:类型安全的运行时常量
插件暴露了一个 virtual:cesium 模块,让你可以在不触碰 window 全局变量的情况下读取运行时常量:
json
json
// tsconfig.json
{
"compilerOptions": {
"types": ["vite-plugin-cesium-engine/virtual"]
}
}
typescript
运行
javascript
import { CESIUM_BASE_URL, ION_TOKEN } from "virtual:cesium";
console.log(CESIUM_BASE_URL); // "/cesium/"
console.log(ION_TOKEN); // 你的令牌,或 null
这两个值都在构建时解析,如果未使用会被 tree-shaking 移除,不会增加产物体积。
自定义资源路径
对于 CDN 部署或有自己 public 目录约定的框架(如 Laravel、Rails),可以精确控制资源的输出位置和访问路径:
typescript
运行
arduino
export default defineConfig({
base: "/app/",
plugins: [
cesiumEngine({
assetsPath: "vendor/cesium", // 输出到 dist/vendor/cesium/
cesiumBaseUrl: "/app/vendor/cesium", // 从此路径提供服务
}),
],
});
如果 cesiumBaseUrl 不以 Vite 的 base 开头,插件会在启动时发出警告,把配置错误扼杀在开发阶段。
调试模式
如果资源注入不符合预期,开启 debug: true 会在开发服务器启动时打印完整的配置摘要:
typescript
运行
lua
cesiumEngine({ debug: true });
输出示例:
plaintext
csharp
[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
已知问题:protobufjs 的 eval 警告
构建时你可能会看到这样一条 Rollup 警告:
plaintext
bash
[EVAL] Use of direct eval function is strongly discouraged
node_modules/protobufjs/dist/minimal/protobuf.js
这来自 @cesium/engine 的传递依赖 protobufjs。该库中的 eval 是有意为之,不存在安全问题。可以在 vite.config.ts 中静默掉这条警告:
typescript
运行
ini
build: {
rollupOptions: {
onwarn(warning, defaultHandler) {
if (warning.code === "EVAL" && warning.id?.includes("protobufjs")) return;
defaultHandler(warning);
},
},
},
零运行时依赖
这个插件本身没有任何运行时依赖 ------ 只使用了 Node 内置模块(node:fs、node:path)和 Vite 自身的插件 API。安装它不会给你的项目带来任何额外的第三方包。
总结
vite-plugin-cesium-engine 用一个极简的插件接口,解决了 CesiumJS 与 Vite 集成中长期存在的配置碎片化问题。它的设计哲学很清晰:
- 专注 :只做
@cesium/engine,不追求大而全 - 零配置:默认行为覆盖 90% 的使用场景
- 类型安全:通过虚拟模块提供带类型的运行时常量
- 错误前置:令牌校验、路径校验都在启动时完成
如果你正在用 Vite 构建三维地球应用,并且希望摆脱繁琐的资源配置,这个插件值得一试。
相关链接:
- npm: vite-plugin-cesium-engine
- GitHub: jfayot/vite-plugin-cesium-engine
- 示例项目:React・Vue・Svelte・Vanilla TS