cesium-fes-vite.md 17 KB

Research: native CesiumJS in TvT.js / Fes.js / Vite

Ticket: Cesium integration into Fes and Vite Branch: research/cesium-fes-vite Date: 2026-04-10 Scope: decision facts only. No production cockpit code.

Decision

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.


1. Package and load method

Repo fact

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)

Official load method

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_URL global 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/Workers
  • node_modules/cesium/Build/Cesium/ThirdParty
  • node_modules/cesium/Build/Cesium/Assets
  • node_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.

What not to use as the primary integration

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.

ESM import-order constraint

ES module import is hoisted. window.CESIUM_BASE_URL = x in the same file as import { Viewer } from 'cesium' is too late.

Required pattern:

  1. A tiny module that only sets window.CESIUM_BASE_URL (and has no cesium import).
  2. Map service imports that module first, then cesium.
  3. Optionally also define: { CESIUM_BASE_URL: JSON.stringify(...) } like the Webpack example, but qiankun needs a runtime window.CESIUM_BASE_URL override.

2. How this repo’s Vite / Fes stack maps onto Cesium

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:

  • Vite 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

Hash router makes ./cesium/ safe

Fes 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.

Recommended CESIUM_BASE_URL

Join, always with a trailing slash:

<resolved-base> + 'cesium/'

Resolution order:

  1. qiankunWindow.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ when __POWERED_BY_QIANKUN__ (already handled in src/app.jsx beforeRender).
  2. Else 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/*.

Where to copy static files

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.

Federation

.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:

  • Do not add cesium to shared. Remotes do not need it. Cesium is huge and CESIUM_BASE_URL is a process-wide singleton.
  • Keep build.target: 'esnext' (already set; good for Cesium ESM).
  • Add cesium as its own manualChunks entry next to existing three / 3d-tiles-renderer splits.
  • Do not let a Cesium Vite plugin rewrite index.html in ways that break federation’s __rf_placeholder__shareScope or Fes HTML injection.

TresJS / Three.js

  • src/app.jsx does app.use(Tres) globally. That only registers custom elements; it does not create a canvas.
  • Default src/pages/index.vue is a TresCanvas. MapCanvas must not be a TresCanvas.
  • ADR 0004 / ticket 23: Cesium is the geographic main map; Three.js is a separate enhancement for selected objects and must not take over the Cesium scene. They share domain state, not a WebGL context.
  • No need to put Three.js inside Cesium’s renderer for tickets 02/03/21.

Conflicts that do exist if ignored:

  • Wrapping MapCanvas in TresCanvas / OrbitControls (simpleGIS pattern) would steal pointer events from the Cesium camera contract.
  • Two live WebGL contexts at once (Cesium globe + Tres overlay) is allowed by browsers but expensive; ticket 23 already says close the enhancement and return to the map.
  • 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.

qiankun

.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.


3. Existing plugin cases: reference vs forbidden runtime

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.

Must NOT be MapCanvas runtime

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.

Useful as reference only (copy ideas, not imports)

  • Relative asset habit: 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.
  • 3D Tiles loading / empty / error UX from 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.


4. Camera: replacing Cesium defaults

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.


5. Constraints for implementation tickets (conclusions only)

02 — MapCanvas persistent mount

  • Host is a plain DOM container in /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.
  • On panel expand/collapse the container size changes → viewer.resize().
  • viewer.destroy() only when leaving /cockpit.
  • cesium import is isolated to the map service so CESIUM_BASE_URL is set once.

03 — Camera control contract

  • After Viewer construct, turn off default SSCC rotate/zoom/tilt/look.
  • Implement pan / orbit / spring-arm on lookAtTransform + HeadingPitchRange + ScreenSpaceEventHandler in the same map service.
  • Do not use Tres OrbitControls (simpleGIS) and do not use vue-cesium mouse bindings.

21 — 3D Tiles and terrain baseline

  • Load tiles with Cesium3DTileset.fromUrl(configUrl) (official example uses http://localhost:8002/tilesets/Seattle/tileset.json). Source: Cesium3DTileset
  • Load terrain with CesiumTerrainProvider.fromUrl(configUrl) for quantized-mesh / heightmap. No terrain → EllipsoidTerrainProvider. Source: CesiumTerrainProvider.fromUrl, Visualizing 3D Terrain
  • Forbidden: Terrain.fromWorldTerrain(), CesiumTerrainProvider.fromIonAssetId, createOsmBuildingsAsync, Ion.defaultAccessToken, simpleGIS’s hardcoded Ion JWT.
  • URLs and any future token come from ticket 04 config injection, not source.
  • Loading / success / fail / empty: fromUrl try/catch + tileset.allTilesLoaded / provider errorEvent. Do not use 3d-tiles-renderer events.
  • Ground vs pipeline view is camera + data layers on this same Viewer, not a second engine.

6. Suggested wiring (not implemented)

In .fes.js viteOption (later implementation ticket):

  1. Dependency: cesium (runtime).
  2. Plugin: copy the four Build/Cesium/{Workers,ThirdParty,Assets,Widgets} dirs to cesium/.
  3. manualChunks.cesium = ['cesium'].
  4. Do not add Cesium to federation shared.
  5. Map service: set 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.


Sources

High-trust, this repo

  • 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: false
  • src/common/copySelectedPublicDirsPlugin.js — copies all non-plugins public dirs
  • src/app.jsx — app.use(Tres), qiankun public path, plugin page glob
  • src/pages/index.vue — TresCanvas homepage, not cockpit
  • src/plugins/simpleGIS/** — Tres GIS demos; cesiumIon3DTile.vue is 3d-tiles-renderer + Ion token
  • src/plugins/geokit/config.js — positions geokit as a Cesium alternative
  • node_modules/@fesjs/builder-vite/lib/common/getConfig.js — publicPath → Vite base
  • node_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, 23

High-trust, official docs

Medium-trust (community, cited only as a rejected option)