教程 - 深度探讨在 Vue3 中引入 CesiumJS 的最佳方式
- 配置 Vite | Vite 官方中文文档
之后在 2.6 会详细说明这个
mode有什么用,这里先略过。这小节主要是对这两个插件的配置:
const plugins = [vue()] const externalConfig = viteExternalsPlugin({/* ... */}) const htmlConfigs = htmlConfig({/* ... */}) plugins.push( externalConfig, htmlConfigs ) return defineConfig({ /* ... */ plugins: plugins, })这两个插件的用法和用途,就不详细说明了,简单说明:
vite-plugin-external插件的 key 是 dependencies 的名称,value 是打包后代码全局访问的变量名称(作为 Namespace),即cesium依赖在打包后在window.Cesium上访问。vite-plugin-html-config插件中,如果像我一样是从node_modules中复制的 CesiumJS 库文件,而不是填写的 CDN 外链,那么打包后页面运行时,静态库文件的相对路径是从defineConfig中的root起算的。在 2.5 小节会讲到 CesiumJS 的静态资源复制。
2.5. 静态资源复制脚本
在 1.1 小节中已详细说明了 CesiumJS 的静态资源的 4 个文件夹。由于此示例工程使用
node_modules下的 CesiumJS,也即node_modules/cesium/Build/Cesium或未压缩版的node_modules/cesium/Build/CesiumUnminified,并且 Vite 构建时会把public文件夹下的资源原封不动复制到发布文件夹下,所以需要借助 NodeJS 文件操作 API 复制这些资源到 public 文件夹下。如果你使用 CDN 上的 CesiumJS,而不是
node_modules下的 CesiumJS 依赖,就不需要这一步,但是还是得配置CESIUM_BASE_URL,告诉前端运行时的 CesiumJS 相对路径起源于哪里(参考 2.6 小节)。这个脚本可以放置于
scripts/目录下,方便起见,我放在了项目根目录。复制我使用
recursive-copy包,删除文件我使用del包,都作为 devDependencies 安装。import copy from 'recursive-copy' import { deleteSync } from 'del' const baseDir = `node_modules/cesium/Build/CesiumUnminified` const targets = [ 'Assets/**/*', 'ThirdParty/**/*', 'Widgets/**/*', 'Workers/**/*', 'Cesium.js', ] deleteSync(targets.map((src) => `public/lib/cesium/${src}`)) copy(baseDir, `public/lib/cesium`, { expand: true, overwrite: true, filter: targets })然后,我在
package.json的 scripts 中添加了两个命令:{ "scripts": { "postinstall": "node static-copy.js", "static-copy": "node static-copy.js" } }postinstall会在pnpm install后自动执行静态资源复制,static-copy则允许手动升级cesium包后更新public文件夹下 CesiumJS 的静态文件。注意
deleteSync和copy函数的目标文件夹路径,我设为了public/lib/cesium,与 2.4 小节中htmlConfig的配置是一样的。为了简单起见,
vite.config.ts中配置的build.assetsDir我改为了./;否则,deleteSync和copy的目标路径就要手动加上build.assetsDir了。例如,默认的 assetsDir 是assets,那么目标路径就从public/lib/cesium变成了public/assets/lib/cesium。请十分仔细地注意这些路径问题,分清楚
public文件夹、build.assetsDir的意义,static-copy.js文件的 cwd 等,分清楚 NodeJS 脚本和前端运行时的相对路径问题。2.6. 使用环境变量配置 CESIUM_BASE_URL
CESIUM_BASE_URL告诉 CesiumJS 在前端运行时相对哪个路径访问那 4 个文件夹下的静态资源,与 2.4、2.5 小节中的路径配置十分相关,请务必读懂 2.4、2.5 小节中的路径配置。当然,如果你使用的是 CDN 上的 CesiumJS 库,那么这个环境变量配置就要配置成 CDN 的基础路径。例如,
https://unpkg.com/cesium@1.96.0/Build/Cesium/Cesium.js对应的 CESIUM_BASE_URL 就是https://unpkg.com/cesium@1.96.0/Build/Cesium考虑到我使用的是
node_modules下的包,复制到public文件夹下,所以我在环境变量文件.env中指定的CESIUM_BASE_URL是一个相对于工程运行时的地址:VITE_CESIUM_BASE_URL = './lib/cesium'随 Vite 启动工程后,在入口文件
src/main.ts中将 CesiumJS 的前端运行时基路径挂在至全局:import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import './main.css' Object.defineProperty(globalThis, 'CESIUM_BASE_URL', { value: import.meta.env.VITE_CESIUM_BASE_URL }) createApp(App) .use(createPinia()) .mount('#app')为了便于类型提示,我将
VITE_CESIUM_BASE_URL的类型写在了工程根目录下的env.d.ts文件中:///interface ImportMetaEnv { VITE_CESIUM_BASE_URL: string } 这是使用 TypeScript 的 interface 补全
import.meta.env的类型定义。为了让 TypeScript 识别这个类型声明文件,还得在
tsconfig.json中配置类型文件路径,把env.d.ts添加进来:{ "include": [ "env.d.ts", "src/**/*", "./vite.config.*" ] }环境变量是 Vite 的功能,参考:环境变量和模式 | Vite 官方中文文档
在 2.4 小节有完整的
vite.config.ts配置文件,其中默认导出的是一个函数,函数参数的意义已经在 2.4 中有官方参考资料。下面这几行代码就是在启动工程时,让 Vite 加载与
vite.config.ts同路径下的环境变量文件,并读取里面的环境变量:export default ({ mode: VITE_MODE }: { mode: string }) => { // 根据当前 mode 读取对应文件中的环境变量 const env = loadEnv(VITE_MODE, process.cwd()) // 在控制台打印出来 console.log('VITE_MODE: ', VITE_MODE) console.log('ENV: ', env) /* ... */ }2.7. 使用全局状态库跨组件共享 Viewer 对象
这一步是可选的,当然,我强烈推荐你做这一步,这对跨组件访问 Viewer 很有帮助。
作为替代方案,你可以使用 Vue 的
provide / injectAPI,穿透传递 Viewer 给所有子组件,对兄弟组件就无能为力了(可以借助 EventBus,略麻烦,不再赘述)。首先,是在
src/main.ts中让 Vue 实例安装pinia状态管理库:import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import './main.css' /* ... */ createApp(App) .use(createPinia()) .mount('#app')然后,是创建状态存储器,位于
src/store/sys.ts:import { defineStore } from 'pinia' import { Viewer } from 'cesium' export interface SysStore { cesiumViewer: Viewer | null } export const useSysStore = defineStore({ id: 'sys', state: (): SysStore => ({ cesiumViewer: null }), actions: { setCesiumViewer(viewer: Viewer) { this.cesiumViewer = viewer } } })紧接着,是在
App.vue中使用 Vue 的markRawAPI,将 Viewer 对象标记为非响应式,避免 Vue 响应式劫持产生的访问性能问题,并调用 store 对应的 set 方法:import { ref, onMounted, markRaw } from 'vue' import { ArcGisMapServerImageryProvider, Camera, Viewer, Rectangle } from 'cesium' import { useSysStore } from '@/store/sys' const containerRef = ref() const unvisibleCreditRef = ref () const sysStore = useSysStore() onMounted(() => { const viewer = new Viewer(containerRef.value as HTMLElement) const rawViewer = markRaw(viewer) sysStore.setCesiumViewer(rawViewer) }) 最后,你就可以在兄弟组件中访问到 Viewer 了:
3. 伸手的看过来 - 工程下载
由于篇幅原因,有些文章中的代码会省略、简化,工程的源码、配置可能与上述有细微差别,请自行了解。
https://share.weiyun.com/ndkxAeIv