一、先搞清楚我们要解决什么麻烦

如果你用 Vite 做项目,想加一个三维地球,往页面里一放,Cesium 是个很容易想到的选择。但问题来了:Cesium 这个库很老,内部用了大量动态导入和 WebAssembly,而 Vite 打包时追求极致速度,会把静态资源重新整理、路径重写,结果两者一碰,就会出现各种奇奇怪怪的报错。比如加载地球时黑屏、控制台飘红、找不到 Worker 文件、WASM 文件被当成了普通二进制等内容。说白了,就是 Cesium 自己的一套“找文件”方式,和 Vite 的“按需编解码”逻辑产生了冲突。这篇文章不扯太多底层原理,就专门给你捋一捋怎么用配置解决这些问题,以及碰到了常见错误该怎么改。

1.1 应用场景:用 Vite 写 Cesium 三维地图

最常见的场景是做一个内部项目,比如物联网可视化大屏、智慧园区、城市数字孪生。技术栈是 Vue 或 React,但底层构建工具换成了 Vite。Cesium 负责渲染 3D 地球,叠加建筑白模、轨迹回放、天气特效等。开发阶段,Vite 启动快,热更新爽,但一引入 Cesium,可能就遇到开发环境正常、一打包就挂掉的尴尬。还有一个场景是 Cesium 的模型分解、3D Tiles 顶点压缩、Draco 解压等功能,需要加载 WASM 文件,如果配置不对,Chrome 会直接提示 MIME type 不支持,导致功能失效。所以我们需要一套完整的实践方案,让 Cesium 和 Vite 从“互不认识”变成“配合默契”。

1.2 技术优缺点:Vite 快但封装多,Cesium 老牌但依赖重

Vite 的好处是快,它用原生 ES Module 开发,打包交给 Rollup,启动速度比 Webpack 快好几倍。但代价是它对 CommonJS、AMD、动态 require 的支持比较“挑剔”。Cesium 则是一个历史悠久的三维库,内部代码从上世纪 Web Worker 出现时就开始积累,里面既有 import() 动态加载,也有 new Worker 创建线程,还有 .wasm 二进制模块。这些特性在传统 Webpack 下能自动配置好,但 Vite 默认并不认识 Cesium 内部的那些加载器。所以我们需要主动告诉 Vite:哪些文件是 Cesium 的内置资源,哪些需要复制到最终目录,哪些模块要排除预构建。理解了这一点,后面所有配置就有方向了。

二、准备工作:建项目装依赖

2.1 创建 Vite 项目

先打开你的终端,创建一个最基础的 Vite JavaScript 项目,不需要任何框架模板,这样方便我们专注研究 Cesium 的接入。如果你已经在用 Vue 或 React,后面配置思路也是一样的。

# 创建一个 vanilla 模板项目
npm create vite@latest cesium-vite-demo -- --template vanilla

# 进入项目目录
cd cesium-vite-demo

# 安装依赖
npm install

装好后,你会看到一个很干净的目录,包含 index.htmlmain.jsstyle.css。我们先跑起来看看:

npm run dev

浏览器打开终端提示的地址,就能看见一个空页面。接下来我们要把 Cesium 塞进去。

2.2 安装 Cesium

Cesium 官方发布包很大,里面既有源码,也有编译好的构建文件。我们用 npm 直接安装最新版本即可,别担心大小,后面打包可以优化。

npm install cesium

安装完成后,注意看一下 node_modules/cesium/Build 目录,里面有几个子目录,我们常用的是 Cesium(压缩版)和 CesiumUnminified(未压缩方便调试)。Cesium 的静态资源(比如 WorkersAssetsThirdParty)都在这个目录底下。Vite 打包时,默认不会去复制这些文件,这就是问题所在。我们需要让它们能出现在最终的 dist 目录中,或者让 Vite 能正确引用它们。

三、配置 Vite 让 Cesium 顺利跑起来

3.1 最省事的做法:使用 vite-plugin-cesium

如果你不想跟各种底层细节搏斗,直接用一个专门为 Cesium 设计的 Vite 插件,它叫 vite-plugin-cesium。这个插件做三件事:复制静态资源、设置 CESIUM_BASE_URL、关闭 Vite 对 Cesium 的预构建干扰。安装它:

npm install vite-plugin-cesium -D

然后在项目根目录创建或修改 vite.config.js,写上以下内容:

// vite.config.js
// 技术栈:Vite 4 + JavaScript + Cesium + vite-plugin-cesium
import { defineConfig } from 'vite';
// 导入 Cesium 专用插件
import cesium from 'vite-plugin-cesium';

export default defineConfig({
  plugins: [
    // 插件会自动处理 Cesium 的 Worker 和 WASM 路径
    cesium()
  ]
});

就这几行,大功告成。现在你可以直接在 main.js 里写 import * as Cesium from 'cesium',然后初始化一个地球。很多之前遇到的诡异报错,比如 Error: Could not load the worker,基本上不会再出现。这个插件适合大多数项目,但如果你想手动控制所有细节,或者插件版本与你的 Cesium 版本有兼容性问题,那就需要理解下一步的手动配置。

3.2 手动配置核心选项

手动配置不复杂,关键是明白 Cesium 需要一个全局变量 CESIUM_BASE_URL,用来告诉它自己的 WorkersWasm 文件放在哪里。通常我们把这些文件复制到 Vite 的 public 目录下,然后通过绝对路径访问。操作如下:

# 在项目根目录创建 public/Cesium 文件夹
mkdir -p public/Cesium

# 把 Cesium 编译好的全部资源复制进去
cp -r node_modules/cesium/Build/Cesium/* public/Cesium/

复制过程可能需要几秒钟,如果文件太多,你甚至可以选择只复制 WorkersThirdPartyAssets 这三个子目录。但稳妥起见,全部复制就行。

接着打开 index.html,在 <head> 里加上一段脚本,设置 CESIUM_BASE_URL

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Cesium + Vite 手动配置</title>
    <script>
      // 这是 Cesium 寻找 Worker 和 WASM 的根地址
      // 必须指向 public 下的 Cesium 目录
      window.CESIUM_BASE_URL = '/Cesium/';
    </script>
    <style>
      html, body, #app { margin: 0; height: 100%; }
    </style>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/main.js"></script>
  </body>
</html>

然后还需要在 Vite 配置里做一个小手脚。Cesium 内部使用了 Node.js 的 global 变量,浏览器里没有这个变量,会报 global is not defined。我们在 Vite 里把它替换成 window

// vite.config.js
// 技术栈:Vite + JavaScript 手动配置
import { defineConfig } from 'vite';

export default defineConfig({
  // 将打包后的路径设置为相对路径,避免子目录部署时白屏
  base: './',
  define: {
    // Cesium 里有的地方引用了 global,这里给它换成 window
    global: 'window'
  }
});

注意,define 里的 global 只影响构建后的代码。如果你在开发模式下也遇到这个问题,可以同时加上 optimizeDeps.esbuildOptions.define。不过大多数情况下,只要用了上面这种方式,开发和生产都能正常跑。

3.3 处理动态导入的坑

Cesium 内部大量使用动态导入来按需加载 Web Worker。举个例子,当你加载 3D Tiles 时,Cesium 会创建一个 Worker 线程来处理瓦片数据;当你使用模型拖拽时,又会有另一个 Worker。这些 Worker 文件是以固定路径形式存在的,如果你没有提前把它们放在指定目录,Cesium 在运行时就会根据 CESIUM_BASE_URL 去请求,请求不到就报错。

Vite 本身支持动态导入,但它的自动解析是基于代码里能静态分析的字符串。Cesium 内部是用拼接字符串的方式去拼 Worker 路径的,Vite 猜不到,所以无法帮忙重写。这就是为什么我们不能直接依赖 Vite 去处理 Cesium 的 Worker,而必须把整个 Build/Cesium 目录原封不动地放入 public 下。这样运行时路径是固定的,不经过 Vite 的打包流程,自然就不会被搞乱。

下面是一个正常使用 Cesium 的示例,你不需要手工去触发 Worker,但你会看到 Cesium 的 API 会自动利用动态导入:

// main.js
// 技术栈:JavaScript + Vite + Cesium
import * as Cesium from 'cesium';
import './style.css';

// 创建 Viewer,里面已经包含了 Globe、SkyBox 等组件
const viewer = new Cesium.Viewer('app', {
  // 关闭一些用不到的功能,让页面看起来干净
  baseLayerPicker: false,
  geocoder: false,
  homeButton: false,
  infoBox: false,
  sceneModePicker: false,
  selectionIndicator: false,
  timeline: false,
  animation: false
});

// 让相机飞到北京市中心
viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(116.391, 39.907, 10000)
});

// 你可以在控制台观察一下
// 当加载 3D Tiles 时,Cesium 会动态创建 Worker,加载速度很快

如果要加载一个 3D Tiles 数据源,可以这样写:

// 动态加载 3D Tiles 数据(示例)
try {
  const tileset = await Cesium.Cesium3DTileset.fromUrl(
    'http://your-server.com/tileset.json'
  );
  viewer.scene.primitives.add(tileset);
} catch (error) {
  console.error('加载 3D Tiles 失败,很可能是 Worker 或 WASM 路径配置不对', error);
}

这段代码本身不复杂,但它背后就涉及了 Cesium 内部的动态导入和可能的 WASM 解码。只要前面静态资源和 CESIUM_BASE_URL 配置正确,这些“隐形的动态导入”就能正常工作。

3.4 处理 WebAssembly 加载的坑

WebAssembly(简称 WASM)是什么?你可以把它理解成一种“更小的字节码”,浏览器可以直接执行,速度接近原生。Cesium 在处理 3D Tiles 压缩、Draco 网格压缩、凹凸贴图等特性时,会加载对应的 .wasm 文件。浏览器对 .wasm 文件有严格的 MIME 类型要求,必须是 application/wasm,否则拒绝执行。

Vite 开发服务器默认支持 WASM,但打包后如果你用静态服务器托管,需要确保服务器响应头正确。大部分 Nginx、Apache 默认可能不认识 .wasm,你可以在部署时加上配置。不过更重要的是,.wasm 文件的路径要从 CESIUM_BASE_URL 下访问。使用 vite-plugin-cesium 时,它会把 WASM 放到正确位置;手动配置的话,因为我们已经复制了整个 Build/Cesium 目录,所以 WASM 文件也在其中。

为了验证你的打包产物里有没有 WASM,可以运行打包命令:

npm run build

打包完成后查看 dist 目录:

find dist -name "*.wasm" | head -20

正常情况下,你应该能看到一些 .wasm 文件,比如 basis_transcoder.wasmdraco_decoder.wasm 等。如果看不到,说明你的复制或插件配置漏了。此时请检查 public/Cesium 里是否真的存在这些文件。

四、常见错误与修正

4.1 报错 Could not load the worker

这个报错可能是 Cesium 找不到 Workers/createGeometry.js 之类的文件。原因往往是你没设置 CESIUM_BASE_URL,或者设置成了错误的路径。修正方法是:

# 确保你的 public/Cesium/Workers 目录存在
ls public/Cesium/Workers | head

如果不存在,就重新复制。如果存在,请检查 index.html 里的 window.CESIUM_BASE_URL 是否以 / 结尾,例如 /Cesium/。另外,如果你使用的是 vite-plugin-cesium,不要自己再手动设置这个变量,插件会覆盖掉它,可能导致冲突。

4.2 报错 MIME type 'application/octet-stream' 不支持

这个错误一般在加载 .wasm 时出现。浏览器看到 application/octet-stream 就会拒绝执行。解决办法有两个层面:

第一,开发环境:Vite 内置了对 WASM 的处理,理论上不会报这个错。如果报了,可以尝试重启开发服务器,或者检查 vite.config.js 里有没有配置 build.target 过于陈旧。建议将目标设置为 es2020 或更高:

// vite.config.js
export default defineConfig({
  build: {
    // 确保构建目标支持 WASM 模块
    target: 'es2020'
  }
});

第二,生产环境:你需要让服务器正确识别 .wasm 类型。以 Nginx 为例,在 nginx.conf 或站点配置里加上:

location /Cesium/ {
  add_header Content-Type application/wasm;
}

4.3 开发环境正常,打包后白屏

这种情况一般是 base 路径错误。如果你把打包好的 dist 目录放到了服务器子目录,比如 http://example.com/map/,而 Vite 默认 base/,导致所有资源都请求到了根路径,当然找不到。手动配置时,我们已经加了 base: './'。如果你使用的是 vite-plugin-cesium,插件可能会覆盖 base,需要检查一下。最简单的办法是在 vite.config.js 里显式设置为 base: './',并放在插件后面:

// vite.config.js
import { defineConfig } from 'vite';
import cesium from 'vite-plugin-cesium';

export default defineConfig({
  plugins: [cesium()],
  base: './'
});

五、完整示例工程

前面讲了不少分散的配置,现在我们组合一个可以直接跑起来的最小工程。技术栈还是 Vite 4 + JavaScript + Cesium。

5.1 项目结构

cesium-vite-demo/
├─ public/
│  └─ Cesium/          # Cesium 静态资源,使用插件或手动复制后生成
├─ index.html
├─ main.js
├─ style.css
└─ vite.config.js

5.2 package.json

{
  "name": "cesium-vite-demo",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "cesium": "^1.119.0"
  },
  "devDependencies": {
    "vite": "^4.5.0",
    "vite-plugin-cesium": "^1.2.22"
  }
}

5.3 vite.config.js

// vite.config.js
// 技术栈:Vite 4 + JavaScript + Cesium + vite-plugin-cesium
import { defineConfig } from 'vite';
import cesium from 'vite-plugin-cesium';

export default defineConfig({
  // 插件会自动复制 Cesium 静态资源并设置 CESIUM_BASE_URL
  plugins: [cesium()],
  /**
   * 这两项是为了保证打包产物在任意子目录都能跑
   * 同时解决 Cesium 内部 global 变量缺失的问题
   */
  base: './',
  define: {
    global: 'window'
  }
});

5.4 main.js

// main.js
// 技术栈:JavaScript + Vite + Cesium
import * as Cesium from 'cesium';
import './style.css';

// 初始化一个 Viewer,挂载到 id 为 app 的 DOM 上
const viewer = new Cesium.Viewer('app', {
  // 关闭基础组件,让画面更纯粹
  animation: false,
  baseLayerPicker: false,
  fullscreenButton: false,
  geocoder: false,
  homeButton: false,
  infoBox: false,
  sceneModePicker: false,
  selectionIndicator: false,
  timeline: false,
  navigationHelpButton: false
});

// 添加一张在线影像图,这里用 OpenStreetMap
viewer.imageryLayers.addImageryProvider(
  new Cesium.OpenStreetMapImageryProvider({
    url: 'https://tile.openstreetmap.org/'
  })
);

// 可选:动态导入 Cesium 里的一个模块,看看是否正常
async function checkDynamicImport() {
  // 这个 Esri 模块内部用到了动态导入和 WASM
  const { ArcGisMapServerImageryProvider } = await import('cesium');
  console.log('动态导入成功:', ArcGisMapServerImageryProvider);
  // 实际用不用看你自己,这里只是测试一下
}
checkDynamicImport();

// 把相机飞到香港,顺便看下三维地形
viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(114.16, 22.28, 15000)
});

// 如果你想看地球旋转起来,可以打开这个开关
// viewer.clock.shouldAnimate = true;

5.5 style.css

/* style.css */
/* 让整个页面铺满窗口,不留白边 */
html,
body,
#app {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

运行 npm run dev,你会看到一个干净的地球。运行 npm run build 后,打开 dist/index.html,同样应该正常工作。这就是一个最基础的兼容性验证。

六、注意事项与总结

6.1 注意事项

第一,Cesium 版本和 Vite 插件的兼容性要看清楚。虽然 vite-plugin-cesium 一直在更新,但新版本 Cesium 的结构可能会有变化,建议锁定一个你验证过可用的版本组合。比如 Cesium 1.119 + Vite 4 + 插件 1.2.22 是个非常稳的组合。

第二,不要随意修改 CESIUM_BASE_URL。如果你的项目需要把 dist 部署到 CDN 上,那么 CESIUM_BASE_URL 可能得改成 CDN 的绝对地址。这会导致 Cesium 的 Worker 和 WASM 从 CDN 加载,而你的页面却能正常显示。这个场景下,你需要把 Cesium 的静态资源单独上传到 CDN 的对应路径。

第三,如果你使用了 Vue 或 React,vite.config.js 中的配置依然有效,但要注意 main.js 的挂载方式。确保 Cesium 容器元素是真实存在且具有高度和宽度,否则地球会一片空白。

第四,开发环境下默认的 localhost 一般没问题,但如果你使用了 HTTPS 域名,而 Cesium 加载的是 HTTP 的在线影像图层,浏览器会拦截混合内容,导致图层不显示。建议统一使用 HTTPS 或者 http

第五,optimizeDeps 可选配置。某些情况下,Cesium 在开发模式会被 Vite 预构建,你可以用 optimizeDeps.exclude: ['cesium'] 来避免预构建。不过使用插件后通常不需要手动设置,但如果你遇到开发模式启动慢或者内存溢出,可以试试调优。

6.2 总结

Cesium 与 Vite 的兼容性问题,本质上是“古老的资源加载方式和现代构建工具之间的路径战争”。解决方案的核心有两点:一是让 Cesium 的静态资源以原貌保留到最终目录,二是让 Cesium 能通过一个全局根路径准确找到这些资源。vite-plugin-cesium 为你省掉了复制文件的麻烦,手动配置则让你理解背后的原理。无论哪种方式,都要注意 global 变量替换、base 路径、WASM 的 MIME 类型这三个关键点。希望这篇指南能帮你少走弯路,顺利把三维地球跑起来。