Cesium入门(十九)CesiumJS 1.142 部署踩坑:新版 Sandcastle2 为何必须开 8080 和 8081 两个端口
核心关键词:CesiumJS 1.142、Sandcastle2、静态部署、postMessage、origin 隔离 难度级别:中等
“环境配不对,代码全白费。”
升级到 CesiumJS 1.142 后,我按老习惯把 release 包丢进 http-server,一个端口打开 Sandcastle——结果编辑器出来了,示例却一个都跑不起来,右侧预览区一片空白。折腾了半天才搞明白:新版 Sandcastle2 已经从”一个静态页面”重构为”双页面应用”,必须同时开 8080 和 8081 两个端口,而且端口还不能随便改。
一、现象:单端口部署,示例全部白屏
老版本(1.140 及以前)的 Sandcastle 位于 Apps/Sandcastle/,是纯静态的 HTML 页面。随便一个静态服务器(http-server、Live Server、nginx),用任意端口发布,打开就能用,代码和预览都在同一个页面里。
1.142 的 release 包则完全不同:
- Sandcastle 的目录从
Apps/Sandcastle/变成了Apps/Sandcastle2/ - 只开一个静态服务器时,编辑器、示例库、设置这些 UI 都能正常显示
- 但点击运行示例,右侧的预览 iframe 白屏,控制台报错指向 8081 端口连接失败
如果只看页面结构,很难想通:UI 都在,代码也是同一份文件,为什么预览跑不起来?
快速定位的方法也很简单:按 F12 打开开发者工具,选中预览区域的 iframe,看它的 src 属性指向哪个地址。只要指向的是 http://localhost:8081/...,而你的 8081 端口并没有服务,就能立刻锁定是部署方式的问题,而不是代码问题。
二、根因:Sandcastle2 不是”一个页面”,而是”两个页面”
1.142 把 Sandcastle 从零重写,技术栈换成了 React + Vite,并且在架构上拆成了两部分:
- Sandcastle App:负责编辑器、示例库、设置等 UI(入口是
index.html) - Viewer:真正运行示例代码的页面(入口是
templates/bucket.html),被放在一个<iframe>里
App 和 Viewer 之间通过 window.postMessage 通信(源码里的 IframeBridge 封装),运行示例时的大致流程是:
// App 侧:把用户写的代码通过 iframe 发给 Viewer
iframe.contentWindow.postMessage(
{
id: "sandcastle-bridge",
message: { type: "runCode", code: code, html: html }
},
"http://localhost:8081" // 目标 origin,必须是 Viewer 所在的端口
);
关键就在这里:iframe 里的 Viewer 页面,并不是从你打开 App 的那个端口加载的,而是被硬编码指向另一个端口。
三、为什么非要跨源:这全是安全设计
看到这里你可能想问:同一个端口不行吗?非要拆两个端口多麻烦。
Cesium 官方在源码注释里写得很直白。server.js 中启动 8081 端口时注释:
// This "mirror" server runs on a separate port to create origin separation between
// the main Sandcastle app and the viewer page for security
而在 Sandcastle 的 README 中也有明确说明:App 和 Viewer 可以同源,但不推荐,因为会失去跨源安全隔离带来的好处。具体来说有三点:
- localStorage 隔离:示例代码是用户自己写的,如果与 App 同源,它就能直接读写 App 的
localStorage——包括 Sandcastle Copilot 保存的 API Key(BYOK 模式下你的 Gemini/Claude Key 都存在这里) - 防止示例代码篡改 App:同源时,iframe 里的代码可以直接操作父页面 DOM,改掉编辑器状态
- postMessage 本来就适合跨源:通信时显式校验消息来源,反而更安全
所以跨源不是设计缺陷,而是刻意为之的隔离。
四、端口为什么固定在 8080 和 8081
两个端口之所以是”写死”的,是因为 origin 在构建阶段就被编译进了 JS,而不是运行时读取的。
Sandcastle 构建脚本 buildStatic.js 中有这样一段:
config.define = {
__OUTER_ORIGIN__: JSON.stringify(outerOrigin), // App 所在的 origin
__INNER_ORIGIN__: JSON.stringify(innerOrigin), // Viewer iframe 所在的 origin
};
__OUTER_ORIGIN__ 和 __INNER_ORIGIN__ 在打包时被 Vite 直接替换成字符串常量。而 release 包在 gulpfile.makezip.js 中构建 Sandcastle2 时,传入的参数是:
buildSandcastleApp({
outerOrigin: "http://localhost:8080",
innerOrigin: "http://localhost:8081",
});
也就是说,你下载的 Cesium-1.142.zip 里,App 和 Viewer 的 origin 已经被固化。iframe 的地址按下面这个规则生成(Bucket.tsx):
const bucketUrl = new URL(
"/Apps/Sandcastle2/templates/bucket.html",
"http://localhost:8081" // __INNER_ORIGIN__
);
所以无论你在哪个端口打开 App,预览 iframe 都只会去 http://localhost:8081/Apps/Sandcastle2/templates/bucket.html 加载。8081 端口没有服务,就必然白屏。同时 postMessage 发送和接收时都会严格校验这个固化 origin——这就是为什么 8080 和 8081 两个端口一个都不能少,也不能换成别的端口。
五、解决方案:三种方式都能跑通
方案一:用官方 server.js(最省心)
release 包根目录自带 server.js,它会在启动时自动监听两个端口:主服务 8080,镜像服务 8081。
# 在 Cesium-1.142 解压目录下执行
node server.js --production
看到类似下面的输出就说明两个端口都起来了:
Cesium development server running locally. Connect to http://localhost:8080/
Sandcastle mirror server running on port 8081
浏览器访问 http://localhost:8080/,点开 Sandcastle 即可正常使用。
方案二:两个 http-server 双开(习惯静态部署就用这个)
开两个命令行窗口,在同一个 release 解压目录下分别执行:
# cmd 窗口 1:主服务,跑 App
http-server -p 8080
# cmd 窗口 2:镜像服务,跑 Viewer iframe
http-server -p 8081
两个窗口指向的是同一份文件,端口必须严格是 8080 和 8081。然后访问 http://localhost:8080/。
方案三:nginx 双端口(适合公司内网统一部署)
如果公司内网用 nginx 统一管理静态资源,配置两个 server 块分别监听 8080 和 8081,root 指向同一份 release 目录即可:
# nginx.conf 片段
server {
listen 8080;
root /data/cesium/Cesium-1.142; # release 包解压目录
location / { try_files $uri $uri/ =404; }
}
server {
listen 8081;
root /data/cesium/Cesium-1.142; # 同一份目录
location / { try_files $uri $uri/ =404; }
}
改完 nginx -s reload,访问 http://服务器IP:8080/ 即可,镜像服务由 nginx 接管,比多开一个 http-server 进程更稳。
注意事项
- 端口不要改:改成 8080/8081 以外的端口,App 虽然能打开,但 iframe 依然会去请求 8081,通信校验也会失败
- 局域网访问:
http-server默认监听localhost,局域网内其他人访问不了,加-a 0.0.0.0即可(server.js对应的是--public) - 8081 被占用:Sandcastle 会直接不可用,
server.js会报错退出并提示你释放端口
如果只是偶尔看示例、不需要本地修改,直接访问官方在线版 https://sandcastle.cesium.com 也可以,省去部署这一步。
总结
- Sandcastle2 是 React 重构的双页面应用,App 与 Viewer 通过
postMessage跨源通信 - 跨源隔离是安全设计:隔离 localStorage(保护 Copilot 的 API Key)、防止示例代码篡改 App
- release 包构建时把 origin 固化:App 固定在
localhost:8080,Viewer 固定在localhost:8081 - 单端口静态部署必然白屏,双端口(8080 + 8081)才是正确姿势
“文档要看,源码更要 Debug。”——这个坑的答案,就写在 Cesium 的源码注释里。
专业服务
奇小狐工作室 – 3D 数据处理专家
- CesiumJS 项目定制开发
- 3D Tiles 数据生产与优化
- 三维可视化方案咨询
- AI + 三维可视化技术落地
联系方式:微信
Elusive57