教程 - 深度探讨在 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 / inject
API,穿透传递 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 的markRaw
API,将 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