Ticket: Cesium integration into Fes and Vite
Branch: research/cesium-fes-vite
Date: 2026-04-10
Scope: decision facts only. No production cockpit code.
Add the official npm package cesium. Load it with ESM named imports from a cockpit map service (not a TvT plugin). Copy Cesium’s four static directories and set CESIUM_BASE_URL before the cesium import, using this app’s relative publicPath: './' (and qiankun’s injected public path when present). Do not use vite-plugin-cesium, vue-cesium, CDN Cesium.js, simpleGIS, geokit, 3d-tiles-renderer, or three-tile as the MapCanvas runtime.
package.json has no cesium. GIS-adjacent deps that are present: three@0.180.0, @tresjs/core@5.2.1, 3d-tiles-renderer@0.4.16, three-tile@0.11.10, @icegl/geokit@^2.0.3. (package.json)
CesiumJS Quickstart (Cesium 1.145 docs) gives two supported setups: CDN script, or npm install cesium for a module bundler. The npm recipe is:
window.CESIUM_BASE_URL = '/';
import { Cartesian3, Ion, Terrain, Viewer } from 'cesium';
import 'cesium/Build/Cesium/Widgets/widgets.css';
“The
window.CESIUM_BASE_URLglobal variable must be set before CesiumJS is imported. It must point to the URL where those four directories are served.”Directories to copy and serve as static files:
node_modules/cesium/Build/Cesium/Workersnode_modules/cesium/Build/Cesium/ThirdPartynode_modules/cesium/Build/Cesium/Assetsnode_modules/cesium/Build/Cesium/Widgets
Source: CesiumJS Quickstart — Install with NPM
The first-party bundler example is Webpack, not Vite: CesiumGS/cesium-webpack-example TUTORIAL.md. It copies Workers / Assets / Widgets (and later ThirdParty in the quickstart) and defines CESIUM_BASE_URL at compile time.
| Option | Why not |
|---|---|
CDN Cesium.js |
Second global copy; fights publicPath: './', qiankun, and federation ESM. Quickstart lists it as the non-bundler path. |
vite-plugin-cesium (nshen) |
Community convenience wrapper, not first-party. Injects HTML/scripts and copies the same four dirs. High collision risk with Fes HTML plugin, @originjs/vite-plugin-federation, and qiankun public-path injection. Acceptable only as a last-resort adapter of the official copy recipe. nshen/vite-plugin-cesium |
vue-cesium / Resium |
Vue/React wrappers. Ticket 03 needs replacing default camera in a map service; wrappers fight that. |
@cesium/engine + @cesium/widgets split |
Official quickstart uses the cesium meta-package. No reason to split. |
3d-tiles-renderer / three-tile / @icegl/geokit |
Three.js GIS, not native CesiumJS. ADR 0004 already chose Cesium as the main map. |
ES module import is hoisted. window.CESIUM_BASE_URL = x in the same file as import { Viewer } from 'cesium' is too late.
Required pattern:
window.CESIUM_BASE_URL (and has no cesium import).cesium.define: { CESIUM_BASE_URL: JSON.stringify(...) } like the Webpack example, but qiankun needs a runtime window.CESIUM_BASE_URL override.publicPath: './' → Vite base.fes.js:
publicPath: './', // './' 若在线部署用于生产环境 且 是使用qiankun微前端时,需要配置base为主应用地址
@fesjs/builder-vite getInnerCommonConfig:
const publicPath = base || api.config.publicPath || '/';
return deepmerge({
base: publicPath,
define: getDefine(api, publicPath),
...
}, otherViteOption);
@fesjs/utils resolveRuntimeEnv: env.BASE_URL = publicPath.
So in this app:
base is ./process.env.BASE_URL (Fes convention used in src/) is ./import.meta.env.BASE_URL is also ./Vite relative base: all generated URLs are relative to each file. Dynamic URLs must use import.meta.env.BASE_URL written exactly that way. Source: Vite — Relative base
./cesium/ safeFes default router.mode is "hash" (@fesjs/preset-built-in route plugin). .fes.js does not override it. Document URL stays at the HTML file (index.html); #/cockpit does not change how ./cesium/Workers/... resolves.
If someone later switches to history mode, ./cesium/ from /cockpit vs /cockpit/ would split. Do not silently change router mode without revisiting CESIUM_BASE_URL.
CESIUM_BASE_URLJoin, always with a trailing slash:
<resolved-base> + 'cesium/'
Resolution order:
qiankunWindow.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ when __POWERED_BY_QIANKUN__ (already handled in src/app.jsx beforeRender).new URL('cesium/', new URL(process.env.BASE_URL || './', document.baseURI)).href so ./ becomes an absolute URL against the HTML document, not a JS chunk.Do not hardcode CESIUM_BASE_URL = '/cesium/'. That ignores publicPath: './' and qiankun.
Workers, Assets, Widgets, ThirdParty are loaded from that base. They must not be processed by Vite’s worker plugin; copy the prebuilt files from node_modules/cesium/Build/Cesium/*.
Put them at dist/dev root cesium/{Workers,ThirdParty,Assets,Widgets}/, not under public/plugins/. Spec already assigns cockpit statics to public/cockpit. Cesium engine files are a build-time copy from node_modules, not a committed public tree.
Implement the copy as an extra plugin in .fes.js viteOption.plugins (small vite-plugin-static-copy or a 20-line copy plugin). Must run in:
| Mode | Why it matters |
|---|---|
yarn dev / yarn build |
default Vite copyPublicDir: true |
yarn pre.dev / yarn pre.build |
same host app |
yarn pre.dev.one / yarn pre.build.one |
.fes.predev.one.js sets copyPublicDir: false and uses copySelectedPublicDirsPlugin, which copies all non-plugins public dirs. A public/cesium tree would copy; a Vite plugin copy from node_modules is still required for dev and to avoid committing Cesium blobs. |
Cockpit is a src/pages route, not a plugin, so FES_APP_PLSNAME filtering does not drop it.
.fes.js federation:
federation({
name: 'tvt-host',
remotes: { home: { external: `Promise.resolve('https://dcser.icegl.cn/assets/remoteEntry.js')`, externalType: 'promise' } },
shared: { vue: {}, three: { version: '0.180.0' } }
})
Constraints:
cesium to shared. Remotes do not need it. Cesium is huge and CESIUM_BASE_URL is a process-wide singleton.build.target: 'esnext' (already set; good for Cesium ESM).cesium as its own manualChunks entry next to existing three / 3d-tiles-renderer splits.index.html in ways that break federation’s __rf_placeholder__shareScope or Fes HTML injection.src/app.jsx does app.use(Tres) globally. That only registers custom elements; it does not create a canvas.src/pages/index.vue is a TresCanvas. MapCanvas must not be a TresCanvas.Conflicts that do exist if ignored:
TresCanvas / OrbitControls (simpleGIS pattern) would steal pointer events from the Cesium camera contract.optimizeDeps.exclude currently only lists @fesjs/fes. Cesium should be explicitly optimizeDeps.include (or left to Vite) — do not exclude it in a way that double-bundles workers..fes.js enables qiankun.micro.useDevMode: true. src/app.jsx copies __INJECTED_PUBLIC_PATH_BY_QIANKUN__ onto window. Comment on publicPath says production-as-micro must set base to the host URL (example https://cos.icegl.cn/qiankun/tvt/). Cesium workers will 404 unless CESIUM_BASE_URL uses that injected path.
Cockpit is still not delivered as a TvT plugin; it is a first-class page inside this host, which itself can be a qiankun micro.
ADR 0007: development may look at src/plugins; delivery is src/pages/cockpit + src/modules/cockpit. Large plugins are not a cockpit runtime dependency. Spec: static assets in public/cockpit.
| Case | What it actually is | Why forbidden |
|---|---|---|
src/plugins/simpleGIS |
TresJS + 3d-tiles-renderer / three-tile / custom satellite map. Preview pages under /plugins/simpleGIS/*. |
TvT plugin. Three.js globe, not Cesium Viewer. |
simpleGIS/pages/cesiumIon.vue + cesiumIon3DTile.vue |
TilesRenderer + CesiumIonAuthPlugin with a hardcoded Ion JWT (assetId: '354759'). |
Name says Cesium; implementation is NASA AMMOS 3d-tiles-renderer. Ticket 21 forbids hardcoded Ion tokens. |
simpleGIS/pages/renderer3DTiles.vue |
TresCanvas + OrbitControls + tilesMesh (TilesRenderer on a CDN tileset.json). |
Three.js tiles, OrbitControls fights ticket 03. |
src/plugins/geokit + @icegl/geokit |
Marketing copy: “是否在为cesium复杂的sdk烦恼?” — a Three.js alternative to Cesium. | Explicitly not Cesium. |
src/plugins/useThreeGeospatial |
Ports Cesium math into Three.js. | Algorithm reference only. |
three-tile examples in simpleGIS |
Three.js tile map. | Wrong renderer. |
process.env.BASE_URL, Draco path './draco/' in cesiumIon3DTile.vue — same class of bug as CESIUM_BASE_URL with publicPath: './'..fes.js /tianditu.map proxy — ticket 21 says 天地图 is for basemap validation, not the production globe source.tilesMesh.vue (load-model, bounding sphere) — reimplement with Cesium3DTileset events, do not import PLS/simpleGIS.copySelectedPublicDirsPlugin.js — cockpit + cesium static dirs that live outside public/plugins survive pre.build.one.Do not import … from 'PLS/simpleGIS' (or geokit) from cockpit modules.
Official defaults on ScreenSpaceCameraController (Cesium 1.145):
| Flag / event | Default | Meaning in 3D |
|---|---|---|
enableInputs |
true |
Master switch; temporary (flights / drag-select). Docs: to keep inputs off, use the other booleans. |
enableRotate |
true |
rotateEventTypes default LEFT_DRAG |
enableZoom |
true |
zoomEventTypes default [RIGHT_DRAG, WHEEL, PINCH] |
enableTilt |
true |
middle-drag / pinch / ctrl+drag |
enableLook |
true |
shift+left-drag |
enableTranslate |
true |
2D / Columbus only |
Sources: ScreenSpaceCameraController, Control the camera
ADR 0008 / ticket 03 want: left-drag pan, right-drag orbit around pivot, wheel spring-arm length. That is the inverse of Cesium 3D defaults (left = rotate, wheel/right = zoom).
Spring-arm primitive already in official camera guide:
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
viewer.scene.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(0, -Math.PI / 4, 2900));
Then orbit with camera.rotateRight on tick, or (for the cockpit) a custom ScreenSpaceEventHandler that writes heading/pitch/range.
Implementation constraint: disable enableRotate, enableZoom, enableTilt, enableLook (and do not rely on enableInputs = false alone). Own the handler in the map service. Do not keep Viewer chrome (geocoder, home, timeline, …) that reintroduces default navigation.
/cockpit (src/pages/cockpit*), not src/plugins/** and not TresCanvas.new Viewer(container, { …widgets off }) once in src/modules/cockpit map service. Mode / expandedPanelIds must not v-if the container.viewer.resize().viewer.destroy() only when leaving /cockpit.cesium import is isolated to the map service so CESIUM_BASE_URL is set once.lookAtTransform + HeadingPitchRange + ScreenSpaceEventHandler in the same map service.OrbitControls (simpleGIS) and do not use vue-cesium mouse bindings.Cesium3DTileset.fromUrl(configUrl) (official example uses http://localhost:8002/tilesets/Seattle/tileset.json). Source: Cesium3DTilesetCesiumTerrainProvider.fromUrl(configUrl) for quantized-mesh / heightmap. No terrain → EllipsoidTerrainProvider. Source: CesiumTerrainProvider.fromUrl, Visualizing 3D TerrainTerrain.fromWorldTerrain(), CesiumTerrainProvider.fromIonAssetId, createOsmBuildingsAsync, Ion.defaultAccessToken, simpleGIS’s hardcoded Ion JWT.fromUrl try/catch + tileset.allTilesLoaded / provider errorEvent. Do not use 3d-tiles-renderer events.In .fes.js viteOption (later implementation ticket):
cesium (runtime).Build/Cesium/{Workers,ThirdParty,Assets,Widgets} dirs to cesium/.manualChunks.cesium = ['cesium'].shared.CESIUM_BASE_URL, import cesium + widgets.css, construct Viewer, disable default camera, expose resize/destroy.Cockpit page: mount <div ref> once; pass the element to the map service.
package.json — no cesium; Tres / three / 3d-tiles-renderer / three-tile / geokit present.fes.js — publicPath: './', federation shared vue+three, qiankun micro, tianditu proxy, manualChunks.fes.predev.one.js — copyPublicDir: falsesrc/common/copySelectedPublicDirsPlugin.js — copies all non-plugins public dirssrc/app.jsx — app.use(Tres), qiankun public path, plugin page globsrc/pages/index.vue — TresCanvas homepage, not cockpitsrc/plugins/simpleGIS/** — Tres GIS demos; cesiumIon3DTile.vue is 3d-tiles-renderer + Ion tokensrc/plugins/geokit/config.js — positions geokit as a Cesium alternativenode_modules/@fesjs/builder-vite/lib/common/getConfig.js — publicPath → Vite basenode_modules/@fesjs/preset-built-in — default router.mode: 'hash'docs/adr/0004, 0005, 0006, 0007, 0008.scratch/cockpit-shell-panel-layout/spec.md and issues 02, 03, 21, 23cesium, four static dirs, CESIUM_BASE_URL before import (CDN build 1.145)lookAtTransform / HeadingPitchRange / SSCCfromWorldTerrain is Ion; local uses a terrain provider URL