这篇如果 Vue 和 CesiumJS 不发生史诗级的变动,应该不会再有后文了。主要是这类文章没什么营养。
这篇主要修正上篇 https://www.cnblogs.com/onsummer/p/16629036.html 中一些插件的变化,并升级开发服务器的版本。
心急的朋友拉到文末,有示例工程链接下载。
严格使用 Vue3 + TypeScript 的前端项目,包管理器默认使用 pnpm
构建工具使用 Vite4
使用原生 CesiumJS 依赖做应用开发
客户端渲染,因为我不太熟悉 Vue 的服务端渲染,有本篇的介绍后,熟悉 SSR 的读者可以自己接入
单页应用,多页应用也可以参考此法
鉴于国内使用 CesiumJS 的比例大多数为应用开发(粗话即“APICaller”),而非扩展开发(基于源码作新功能封装、打包),所以我默认读者使用 CesiumJS 是通过 npmjs 网站(或镜像站)拉取的依赖,即:
pnpm add cesium@latest
有想修改源码再自己打包的读者,我觉得应该去看我的源码系列博客。
在 Vue3 工程中引入 CesiumJS 的最佳方式,并引出地图组件封装的简单经验两则。
这篇文章更倾向于给读者一些原理,而不是提供一套开箱即用的工具,有能力的读者可以根据这篇文章的原理,结合 Vite 或其它打包工具的 API,写一个专属插件。
如果没有快速看到 3D 虚拟地球,我觉得心急的朋友会心急(废话)。
第 2 节不需要知道原理,原理和最佳实践请往下阅读 3、4、5 节。
如果你没有命令行基础,也不懂什么是 NodeJS、npm,不知道 node-package 是什么东西,建议先补补 NodeJS 为基础的前端工具链知识。
直接上命令行(要联网,配好你的 npm 源),请在任意你方便的地方运行:
pnpm create vite
输入你想要的手动选择 Vue、TypeScript 的模板即可,然后进入工程文件夹,我的工程文件夹叫作 v3ts-cesium-2023,所以我接下来要安装 CesiumJS:
cd ./v3ts-cesium-2023
pnpm add cesium@1.104
pnpm add 会一并把模板的其它依赖下载下来,所以就不用再执行 pnpm install 了。
我在安装 cesium 时指定了版本,是考虑到 很多项目可能不太注意依赖版本管理,所以干脆锁死固定版本。
我移除了 src/assets 和 src/components 文件夹,并删除全部 src/style.css 的代码,改写 main.ts、App.vue、style.css 如下:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
declare global {
interface Window {
CESIUM_BASE_URL: string
}
}
createApp(App).mount('#app')
你注意到了,我在 main.ts 中为全局声明了 CESIUM_BASE_URL 变量的类型为 string,这在 App.vue 中就会用到:
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { TileMapServiceImageryProvider, Viewer, buildModuleUrl } from 'cesium'
import 'cesium/Build/CesiumUnminified/Widgets/widgets.css'
const viewerDivRef = ref<HTMLDivElement>()
window.CESIUM_BASE_URL = 'node_modules/cesium/Build/CesiumUnminified/'
onMounted(() => {
new Viewer(viewerDivRef.value as HTMLElement, {
imageryProvider: new TileMapServiceImageryProvider({
url: 'node_modules/cesium/Build/CesiumUnminified/Assets/Textures/NaturalEarthII',
})
})
})
</script>
<template>
<div id="cesium-viewer" ref="viewerDivRef"></div>
</template>
<style scoped>
#cesium-viewer {
width: 100%;
height: 100%;
}
</style>
我在 App.vue 组件的 mounted hook 中轻松地创建了 Viewer,语法不再赘述。我做了如下几个点让地球显示出来:
Viewer 构造参数传递了 div#cesium-viewer 元素的 ref 值,并将其类型 as HTMLElement,以满足 CesiumJS 的类型Viewer 创建了一个 CesiumJS 自带的离线 TMS 瓦片服务,你可能很奇怪为什么路径是 node_modules 起头的,待会解释,这个 TMS 瓦片服务只有 2 级CESIUM_BASE_URL带着好奇心,先别急,等我讲完,最后是 style.css,是一些简单的样式:
/* style.css */
html, body {
padding: 0;
margin: 0;
}
#app {
height: 100vh;
width: 100vw;
}
随后,命令行启动开发服务器:
pnpm dev
在 Vite4 的强大性能加持下,很快就起起来了,这个时候就可以在浏览器看到一个具有两级离线 TMS 瓦片服务的三维地球:

你注意到了,2.2 小节里有两个奇怪的路径:
window.CESIUM_BASE_URL = 'node_modules/cesium/Build/CesiumUnminified/'
new TileMapServiceImageryProvider({
url: 'node_modules/cesium/Build/CesiumUnminified/Assets/Textures/NaturalEarthII',
})
这是因为 Vite 开发模式下(pnpm dev,NODE_ENV 是 development)是直接把工程根路径(即 vite.config.ts 所在的文件夹)映射到 http://localhost:5173/ 这个 URL 上的,所以理所当然填写 CesiumJS 库文件的路径就要从 node_modules 开始写起。
我这里选用的是 CesiumUnminified 版本(未压缩版本)。
CESIUM_BASE_URL 的含义是,项目运行的根网络路径(这里就是指 Vite 开发服务器的默认地址 http://localhost:5173/),加上 CESIUM_BASE_URL 后,在这个拼成的路径就能访问到 CesiumJS 的入口文件,即完整版:
http://localhost:5173/node_modules/cesium/Build/CesiumUnminified/Cesium.js(这个指向的是未压缩版的 IIFE 库文件)
你可以把这个完整地址在启动后粘贴到浏览器的地址栏,然后回车,就能看到 CesiumJS 打包后的库文件源码了。
同理,自带的 TMS 瓦片数据就存放在 http://localhost:5173/node_modules/cesium/Build/CesiumUnminified/Assets/Textures/NaturalEarthII 地址下,TMS 服务的识别方法就是观察网络请求有无一个 tilemapresource.xml 文件:

有了 2.3 小节的解释,现在要上生产环境了,生产环境也许是 nginx,也许是其它的 Web 服务器,这个时候就没有 node_modules 了,毕竟 Vite 的开发服务器职责已经在 build 后完成。
这个时候就要作出以下修改:
CESIUM_BASE_URL 为生产环境能访问的 CesiumJS 库文件的地址TileMapServiceImageryProvider 的离线 TMS 路径在修改之前,需要你把 CesiumJS 的四大静态资源文件夹从 node_modules 中拷贝出来,跟着做就行。
我把 node_modules/cesium/Build/CesiumUnminified/ 这个未压缩版本的文件夹下所有内容,即 Assets、Widgets、Workers、ThirdParty 四个文件夹拷贝到 public/libs/cesium/ 下(没有就自己创建一下):

CesiumJS 的正常运行需要这些静态文件,原因在第 3 节会详细说明,先照做。
然后修改 CESIUM_BASE_URL 和离线 TMS 的地址:
window.CESIUM_BASE_URL = 'libs/cesium/'
new TileMapServiceImageryProvider({
url: 'libs/cesium/Assets/Textures/NaturalEarthII',
})
此时运行 pnpm dev,依旧是正常的,只不过静态文件资源已经从 node_modules/cesium/Build/CesiumUnminified/ 改到了 public/libs/cesium/ 下。
顺带一提,Vite 开发服务器的根路径,除了挂载了工程的根目录,还挂载了工程根目录下的
public目录,public 目录的作用请自己查阅 Vite 文档。
这个时候就可以使出 pnpm build 然后 pnpm preview 组合了,打包并使用 http 服务预览构建后的产物:
pnpm build && pnpm preview
我的 CPU 是 i5 13600K,在 7 秒多的打包后紧接着就启动了 4173 端口的服务:

运行起来和开发时无异。
有人也许对 Vite 等打包工具比较熟悉,可以配置分包(修改 vite.config.ts 中的配置参数)来辨别打包后的产物各自的体积:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), splitVendorChunkPlugin()],
build: {
rollupOptions: {
output: {
manualChunks: {
cesium: ['cesium']
}
}
}
}
})
这样之后打包的产物就略有不同:


似乎
splitVendorChunkPlugin()不添加到plugins数组中也可以生效,但是为了尽可能优化打包产物,还是加上了
但是,即便这样,也只是把 cesium 依赖分拆到一个块文件中,并没有实质性改变如下事实:
Vite 仍需对毫无修改的 cesium 依赖包打包一次,CesiumJS 已经在发布 npm 包时进行了构建,其虽然有 ESModule 格式的产物,但是并不支持 Tree-Shaking 减小大小,事实上也没有必要,CesiumJS 的内部是高度耦合的三维渲染器、各种算法,这种高度集成的算法产物保持一致是比较好的(或许官方未来可能有改变,但是至少现在没有),所以在我这里这 “7秒多” 的打包时间毫无必要,在其它打包工具也是一样的(Webpack等)
我需要手动复制 node_modules/cesium/Build/CesiumUnminified/ 下的四个静态资源文件夹
对多个发布环境仍需要手动修改 CESIUM_BASE_URL,如果切换到 CDN 或内网已有 CesiumJS 在线库资源,这个改起来就麻烦许多
考虑到真正的项目大概率不会使用自带的离线二级 TMS 瓦片服务,所以不算作可优化的点。
所以,我将费点篇幅,先介绍 CesiumJS 包 的基本知识,再介绍一些现代前端工具的常识,最后再介绍我认为最合理最灵活的引入方式。
授人以渔,你可以根据这篇文章的内容自己写一个方便的 Vite 插件,也可以就此为止,如果你不嫌弃上述三个麻烦事儿。

通过包管理器下载到 node_modules 下的 cesium 依赖,是 CesiumJS 打包好的“包”,它具备如下资料:
不完整的源代码,位于 node_modules/cesium/Source/ 目录下,含一个出口文件 Cesium.js 和一个 TypeScript 类型定义文件 Cesium.d.ts,出口文件导出的所有模块,也就是真正的源码均来自子包 @cesium/engine 和 @cesium/widgets(于 1.100 版本变动,将代码分割于子包中)
打包后的库程序文件,含 IIFE、ES-Module、CommonJS 三种格式,每种格式又有压缩代码版本和未压缩版本,分别存放于 node_modules/cesium/Build/Cesium/、node_modules/cesium/Build/CesiumUnminified/ 目录下,各种格式各有用途,如果是 CommonJS 环境下,会引用 index.cjs,而如果是 ES-Module 环境下,会引用 index.js;剩下的 Cesium.js 则用在 IIFE 环境下。
无论是不完整的源码,还是打包后的库程序文件,都会附带所需的静态资源文件
应用级别的开发,只需要用到打包后的库程序文件以及 TypeScript 类型定义文件就好了。
我一般选用的是 IIFE 格式里的压缩版本,即 node_modules/cesium/Build/Cesium/Cesium.js,这个库文件只有 3.7 MB,gzip 压缩后可小于 1 MB,体积控制很不错。
主库文件在 2.1 小节已经说明,压缩版和未压缩版均含 CommonJS、IIFE、ES-Module 三种格式的库文件,文件名有所不同。

CesiumJS 的源代码(即 node_modules/cesium/Source/ 的出口文件,以及这个出口文件引自的 @cesium/engine 和 @cesium/widgets 子包的代码模块)并不是完整的 cesium 库,cesium 库还包括:
WebWorker,用于参数几何的生成、ktx2 纹理解码、draco 压缩数据解码等多线程任务Viewer 下具有 HTML 界面的内置组件的样式表达,例如时间线等组件仅靠源代码是不能运行起 Cesium 三维地球场景的,必须使用构建版本的 CesiumJS 库。而官方构建后的 CesiumJS 库(即发布在 npm 上的 cesium 包)一定会包含以上四类文件,即 node_modules/cesium/Build/ 下的压缩和未压缩版本文件夹下的 Workers、Widgets、Widgets、Assets 四大文件夹。

在 2.2 和 2.3 小节中已经比较完备地解释了 CESIUM_BASE_URL 的作用,它就是告诉已经运行的 CesiumJS 上哪去找四类静态资源。
当然,可以设置私有部署的 CesiumJS 库或者免费的 CDN:
window.CESIUM_BASE_URL = 'http://localhost:8888/cesium/1.103.0/'
window.CESIUM_BASE_URL = 'https://cdn.bootcdn.net/ajax/libs/cesium/1.103.0/'
不再赘述。
尤雨溪在某次 B 站直播介绍 Vue3 测试版(似乎是2020年)时,在介绍完新的 setup 函数后,带了个货,即 Vite 的最初始版本,应该是 1.0 时代的东西了,那时还和 Vue 是强依赖的,在 Vite2 时才与具体前端框架解耦。
我在第一时间就去体验了 Vite1.0,说实话没什么特别的感觉,还以为是做了一个什么模板。没想到经过 2.0 的积累更新、3.0、4.0 的快速迭代后,现在的 Vite 已经是我替代 Webpack 的主力前端开发工具了(说实话我很少用 Webpack 为底子的各种脚手架、框架)。
Vite 真的很快,上一篇还是 Vite3,现在已经到 Vite4 了,这更新速度...虽然在 API 和配置上基本没什么变化,应该在 4.x 算是稳定了。
Vite 和 Webpack 类似,都能把一些依赖无视,不参与打包,一旦某个依赖被配置为“外部的”,即 External 化,就不会打包它了。
社区在普通前端的实践中经常把 Vue、React、Axios 等不需要打包、可以使用高速 CDN 加速的库都外部化了。
CesiumJS 这个体积如此巨大的库(压缩版 + gzip 后主库文件至少也有900+KB)按理说也应该外部化,极大减轻打包时的负担和产物,使用 CDN 还能些许加速首屏性能。
External 化需要一些比较繁琐的配置,如果读者认为不需要外部化,任 Vite 把 CesiumJS 再次打包那几秒钟、十几秒钟也无所谓的话,其实也可以不做这一步。
既然说了最佳化实践,那我就一定要写这一步,万一有人需要呢?
在之后会使用 vite-plugin-externals 插件(注意,有 s 结尾)完成外部化。
没有类型提示还得自己手动确认传值类型是否正确,TS 在静态代码编辑环境借助代码编辑器的各种功能,就可以预先检查出可能存在的错误,最大地规避运行时的问题。
cesium 包自带了类型文件,位于 node_modules/cesium/Source/Cesium.d.ts,你也可以在其 package.json 中找到类型字段。
我们创建工程时,模板已经配置好了 TypeScript,默认情况下不需要我们额外配置什么,正常在组件或 ts 文件中导入 cesium 包的模块即可:
import { Viewer } from 'cesium'
这也是官方推荐的导入方法,这样导入是具备 TS 类型提示的。
噢对了,如果你用的是 VSCode,偶尔你会遇到 TS 类型提示不正常的问题,大多数是这 5 个原因:
.vue 文件,那么你需要去 Vue 官方文档中配置下 “take over” 模式typescript 到开发依赖Ctrl + Shift + P 切换一下 ts 版本即可(搜索“Select Typescript” 或直接搜 “Typescript” 选择版本即可),会写入 .vscode/settings.json 文件tsconfig.json 没有包括目标 d.ts 文件d.ts,也没有对应的类型库这是一个新手问题,新手在开发工具(例如 Webpack、Vite)的滋润下能非常熟练地从各种地方 import 各种各样的资源,例如 ts、js、json、图片图标、less/css/sass 等资源模块。
例如:
import Logo from '@/assets/svg/logo.svg'
这样的路径大概率是配置好 @ 指向工程下的 src 目录。
或者裸模块导入:
import { ref } from 'vue'
这些看似“不就是这样的吗”的导入实际上是开发工具做的努力。
然而,在 GIS、三维这些小众的领域,开发工具就不一定有适配了。例如,你不能把相对目录或配置好的目录下的 glTF 模型导入:
import Duck from './data/duck.gltf'
import Ball from '@/assets/model/duck.glb'
幸运的是对 glTF 模型已经有了 vite 插件,但是我仍然不推荐你这样引入。
同理,CesiumJS 的 3DTiles 数据集也不要这么做,虽然它的入口文件是一个 json 文件,但是瓦片文件打包器并不会帮你处理。
理清楚导入问题后,还有一个新手常犯的问题是把“源码相对路径”当作“运行时的路径”,假设有这么一个代码文件 src/views/home.vue 中创建了一个 3DTiles 数据集对象:
// src/views/home.vue
Cesium3DTileset.fromUrl({
url: '../assets/tileset.json'
})
有的新手把数据就放在了上一级的 src/assets/tileset.json 路径下。这犯了 2 个低级错误:
../assets/tileset.json 数据文件路径在运行时也能正常读取这点就不说怎么解决了,只求一些新手读者能了解清楚什么是 “源代码文件的相对 URL” 和 “运行时 URL” 这些基本区别。
此处塞一行防爬虫文字,原文出自 @岭南灯火,常驻知乎博客园,其余博客社交平台基本有号,想找原文请劳烦搜索一下~~
与其说是教程,不如说是基于第 2 节的继续优化,优化到最佳实践。
这个参考 2.1 和 2.2 小节即可。
指定版本安装在 2.1 小节有说明,若不指定版本安装:
pnpm add cesium
那么在 package.json 中,cesium 依赖的版本号(首次 add 时的最新版)前面就会多一个 ^:
{
"dependencies": {
"cesium": "^1.104.0"
}
}
除非手动 update,即 pnpm update cesium@具体版本,否则 ^ 后面的版本号是不会改变的。
那如果不指定版本安装 cesium 会用哪个版本呢?会用第一次 add 的版本,并且会写进对应包管理器的锁文件中。
pnpm-lock.yamlpackage-lock.jsonyarn.lock这小节可以与 5.2 一起看。锁文件的作用是把各个依赖包的具体版本锁死。
有锁文件的 package 会从锁文件中找版本,否则会按 package.json 中的 “版本要求” 来获取特定版本。
如果 package.json 中各个依赖包的版本都是确定的,项目负责人也能管理起依赖的版本控制,那么其实可以不需要锁文件。
我在本文就配置了 不需要锁文件,且我在安装依赖时明确指定了具体版本(主要是 cesium)。
对于 pnpm 和 npm,只需在工程根目录下创建一个(如果不存在).npmrc 文件,并写入此配置:
package-lock=false
对于 yarn,则是创建 .yarnrc 文件并写入:
--install.no-lockfile true
如果项目有要求,或者版本管理比较差,我建议还是把锁文件留着并提交到 git 记录中,但是 cesium 的版本,我还是强烈建议确定版本安装:
pnpm add cesium@1.104
原理、原因在 4.2 小节,这里主要讲配置。
rollup-plugin-external-globals外部化依赖有很多插件都可以实现,既然 Vite4 打包时用的是 rollup,用 rollup-plugin-external-globals 插件就可以完成打包时外部化:
pnpm add rollup-plugin-external-globals -D
然后是用法:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vue from '@vitejs/plugin-vue'
import externalGlobals from 'rollup-plugin-external-globals'
export default defineConfig({
plugins: [vue(), splitVendorChunkPlugin()],
build: {
rollupOptions: {
externalGlobals({
cesium: 'Cesium'
}),
},
},
})
也可以用 Vite 插件:
vite-plugin-externals(注意有个 s 结尾)pnpm add vite-plugin-externals -D
用法:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteExternalsPlugin } from 'vite-plugin-externals'
export default defineConfig({
plugins: [
vue(),
splitVendorChunkPlugin(),
vitePluginExternals({
// key 是要外部化的依赖名,value 是全局访问的名称,这里填写的是 'Cesium'
// 意味着外部化后的 cesium 依赖可以通过 window['Cesium'] 访问;
// 支持链式访问,参考此插件的文档
cesium: 'Cesium',
})
],
})
上面两个插件任选一个均可,只不过 vite-plugin-externals 在开发模式也会起作用,而 rollup-plugin-external-globals 只会在生产模式(NODE_ENV = production 条件,即构建打包时)对 rollup 起作用。
我选用 vite-plugin-externals 插件,因为它两种模式都能起作用。再次启动 pnpm dev,打开浏览器发现找不到模块:

这是因为在开发模式也把 CesiumJS 外部化了,找不到很正常。
Vite 启动后会有一个依赖预构建的过程,打开
node_modules/.vite/deps目录,这里就是预构建的各种代码中导入的依赖包
在开发模式只需配置一下即可避免外部化,而让 Vite 把 cesium 依赖预构建:
vitePluginExternals({
cesium: 'Cesium',
}, {
disableInServe: true, // 开发模式时不外部化
})
执行 pnpm build 后,提升显著:

但是 pnpm preview 时,依然会找不到从 cesium 依赖导入的类(注意端口,是 preview 默认的 4173):

这是因为外部化 CesiumJS 后,便不再打包 cesium 依赖,所以打包后的应用找不到 CesiumJS 的类和 API 了。
怎么办呢?
总结一下现在的进度:
创建了 Vue3 + TypeScript 项目,并已经在第 2 节通过手动拷贝的方式把四个静态资源文件夹拷贝到 public/libs/cesium/ 目录下,配置好了 CESIUM_BASE_URL 让 CesiumJS 能访问到这些静态资源,并成功看到了具有离线 TMS 瓦片的三维地球
使用插件完成了打包外部化 CesiumJS,极大提高了打包速度、极大减小了构建产物的体积
那么现在遇到了什么问题?
cesium 找不到 CesiumJS 库如何解决问题?
只需打包时把 CesiumJS 的主库文件导入 index.html 不就行了吗?请紧接着 5.5 小节一起解决问题:
读者可以手动把 node_modules/cesium/Build/Cesium/Cesium.js 这个压缩版的 IIFE 格式库程序文件复制到 public/libs/cesium/ 下,然后在工程入口文件 index.html 中添加一行 script 标签引入库文件:
<head>
<script src="libs/cesium/Cesium.js"></script>
</head>
但是,如果是自己手动写这个标签,执行打包时会收到 Vite 的一句警告:

为了解决这个问题,最好的办法就是在 Vite 的配置文件中,用插件的办法自动插入这个 script 标签。
有很多插件可以修改 index.html:
vite-plugin-htmlvite-plugin-html-configvite-plugin-insert-html等等,上述三个插件我都有用过,各有特色,按需选择。
我这里以 vite-plugin-insert-html 插件为例,在 index.html 的 <head> 标签下插入这个 script 标签:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vue from '@vitejs/plugin-vue'
import { insertHtml, h } from 'vite-plugin-insert-html'
export default defineConfig({
plugins: [
vue(),
splitVendorChunkPlugin(),
viteExternalsPlugin({
cesium: 'Cesium',
}),
insertHtml({
head: [
h('script', {
src: 'libs/cesium/Cesium.js'
})
]
})
],
}
这样打包时就是绝对完美的消息了:

但是到此为止,仍然有两个需要 “手动” 的事情待解决:
巧的是,这些资源文件都可以从 cesium 包内拷贝,压缩版的 node_modules/cesium/Build/Cesium,非压缩版的 node_modules/cesium/Build/CesiumUnminified,请读者紧接着看 5.6 小节:
这里需要一些插件或者 nodejs 脚本来做文件的静态复制。简单起见,就拿 Vite 的静态文件复制插件完成这个目的。
有很多可选插件,静态文件复制的插件在 Webpack 也有,叫作 CopyWebpackPlugin,在 Vite 中我选用 vite-plugin-static-copy 插件:
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
vue(),
splitVendorChunkPlugin(),
viteExternalsPlugin({
cesium: 'Cesium',
}),
viteStaticCopy({
targets: [
{
src: 'node_modules/cesium/Build/CesiumUnminified/Cesium.js',
dest: 'libs/cesium/'
},
{
src: 'node_modules/cesium/Build/CesiumUnminified/Assets/*',
dest: 'libs/cesium/Assets/'
},
{
src: 'node_modules/cesium/Build/CesiumUnminified/ThirdParty/*',
dest: 'libs/cesium/ThirdParty/'
},
{
src: 'node_modules/cesium/Build/CesiumUnminified/Workers/*',
dest: 'libs/cesium/Workers/'
},
{
src: 'node_modules/cesium/Build/CesiumUnminified/Widgets/*',
dest: 'libs/cesium/Widgets/'
},
]
}),
insertHtml({
head: [
h('script', {
src: 'libs/cesium/Cesium.js'
})
]
}),
], // End of plugins
}
这个 target 中很多路径都是相同的,可以通过数组计算完成,这里就留给读者自己改进了。dest 是打包后的根路径的相对路径。
无论你见到的哪个教程,只要用的是 node_modules 下的 cesium 依赖,你都能看到这四个静态文件夹的复制步骤。
至此我认为工程的配置已经满足非常灵活地运行了。它满足了:
无论开发或生产环境,外部化了 CesiumJS,让 Vite 不再打包 cesium 依赖,大大减少打包时间、减少应用代码体积(从构建产物中剥离 cesium 库)
无论开发或生产环境,都 自动复制四个静态资源文件夹、自动在 index.html 注入 CesiumJS 库文件的 script 标签以加载 CesiumJS
但是,一旦改用局域网或已经部署好的 CesiumJS 库(这种情况请自己解决跨域),或者使用 CDN,那么安装在 node_modules 下的 cesium 其实已经没有必要走 5.6 的静态文件复制了,而且注入 index.html 的主库文件需要修改。
我以国内 bootcdn 上的 CesiumJS 为例,既然 Vite 内置了不同环境文件的解析的函数 loadEnv(参考 Vite 官方文档 - 使用环境变量),我就分 development 和 production 简单讲一讲。
NODE_ENV = development),使用 node_modules 下的 cesium 依赖,复制四个静态文件和库文件NODE_ENV = production),使用 bootcdn 上的 CDN 链接给出最终的 vite.config.ts(注意,默认导出改成了函数):
import { defineConfig, type PluginOption, splitVendorChunkPlugin, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteExternalsPlugin } from 'vite-plugin-externals'
import { insertHtml, h } from 'vite-plugin-insert-html'
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig((context) => {
const mode = context.mode
const envDir = 'env' // 环境变量文件的文件夹,相对于项目的路径,也可以用 nodejs 函数拼接绝对路径
const isProd = mode === 'production'
const env = loadEnv(mode, envDir)
const cesiumBaseUrl = env['VITE_CESIUM_BASE_URL']
// 默认 base 是 '/'
const base = '/'
const plugins: PluginOption[] = [
vue(),
splitVendorChunkPlugin(),
viteExternalsPlugin({
cesium: 'Cesium', // 外部化 cesium 依赖,之后全局访问形式是 window['Cesium']
}),
insertHtml({
head: [
// 生产模式使用 CDN 或已部署的 CesiumJS 在线库链接,开发模式用拷贝的库文件,根据 VITE_CESIUM_BASE_URL 自动拼接
h('script', {
// 因为涉及前端路径访问,所以开发模式最好显式拼接 base 路径,适配不同 base 路径的情况
src: isProd ? `${cesiumBaseUrl}Cesium.js` : `${base}${cesiumBaseUrl}Cesium.js`
})
]
})
]
if (!isProd) {
// 开发模式,复制 node_modules 下的 cesium 依赖
const cesiumLibraryRoot = 'node_modules/cesium/Build/CesiumUnminified/'
const cesiumLibraryCopyToRootPath = 'libs/cesium/' // 相对于打包后的路径
const cesiumStaticSourceCopyOptions = ['Assets', 'ThirdParty', 'Workers', 'Widgets'].map((dirName) => {
return {
src: `${cesiumLibraryRoot}${dirName}/*`, // 注意后面的 * 字符,文件夹全量复制
dest: `${cesiumLibraryCopyToRootPath}${dirName}`
}
})
plugins.push(
viteStaticCopy({
targets: [
// 主库文件,开发时选用非压缩版的 IIFE 格式主库文件
{
src: `${cesiumLibraryRoot}Cesium.js`,
dest: cesiumLibraryCopyToRootPath
},
// 四大静态文件夹
...cesiumStaticSourceCopyOptions
]
}),
)
}
return {
base,
envDir,
mode,
plugins,
}
})
为了 ts 能提示 import.meta.env.MODE,需要在 src/vite-env.d.ts 中补充类型定义(参考 Vite 文档):
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
并且告诉 TypeScript 要用由 vite/client 提供的 import.meta 类型,在 tsconfig.node.json 的 compilerOptions 中添加:
{
"compilerOptions": {
"types": ["vite/client"]
}
}
如果是旧版本的 Vite 创建的模板,你可以添加在 tsconfig.json 对应的位置中。
在服务器上使用 gzip 能进一步提升网络传输速度。打包时,使用合适的插件即可预先进行 gzip 打包,我选用的是 vite-plugin-compression 插件:
import compress from 'vite-plugin-compression'
// 使用见插件官方文档
在开发模式这玩意儿没起作用,就不细谈了。
Vue 有 pinia 这个全局状态大杀器,可以把核心的 Viewer 对象送入全局状态中,但是要避免 Vue 的响应式劫持,响应式问题可以通过 Vue3 的 shallowRef 或 shallowReactive 来解决:
<script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue'
import { Viewer } from 'cesium'
const viewerDivRef = ref<HTMLDivElement>()
const viewerRef = shallowRef<Viewer>()
onMounted(() => {
viewerRef.value = new Viewer(viewerDivRef.value as HTMLElement, /* ... */)
})
</script>
或者用 shallowReactive:
<script lang="ts" setup>
import { onMounted, shallowReactive, ref } from 'vue'
import { Viewer } from 'cesium'
const viewerDivRef = ref<HTMLDivElement>()
const viewerRef = shallowReactive<{
viewer: Viewer | null
}>({
viewer: null
})
onMounted(() => {
viewerRef.viewer = new Viewer(viewerDivRef.value as HTMLElement, /* ... */)
})
</script>
甚至可以更简单一些:
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { Viewer } from 'cesium'
const viewerDivRef = ref<HTMLDivElement>()
let viewer: Viewer | null = null
onMounted(() => {
viewer = new Viewer(viewerDivRef.value as HTMLElement, /* ... */)
})
</script>
当然也可以用 Vue 的 provide/inject 函数来下发、注入子组件,仅适用于地图组件在最顶层的情况:
<!-- 顶层组件下发 Viewer -->
<script lang="ts" setup>
import { onMounted, ref, provide } from 'vue'
import { Viewer } from 'cesium'
import { CESIUM_VIEWER } from '@/symbol'
const viewerDivRef = ref<HTMLDivElement>()
let viewer: Viewer | null = null
onMounted(() => {
viewer = new Viewer(viewerDivRef.value as HTMLElement, /* ... */)
provide(CESIUM_VIEWER, viewer)
})
</script>
<!-- 下面是子组件调用 -->
<script lang="ts" setup>
import { inject } from 'vue'
import type { Viewer } from 'cesium'
import { CESIUM_VIEWER } from '@/symbol'
const viewer = inject<Viewer>(CESIUM_VIEWER)
</script>
这个 CESIUM_VIEWER 是一个 Symbol,来自 src/symbol/index.ts:
export const CESIUM_VIEWER = Symbol('CESIUM_VIEWER')
如果业务界面组件与地图组件是兄弟组件或父子,那只能用三种方式传递 Viewer 对象:
不再展示代码,请读者参考各种途径的官方文档来传递,注意一定要避免响应式劫持。
这里只是以 Vue 为例讲个思路,在其它前端框架中也适用。
这种工程有一个特点,就是地图场景会占满浏览器窗口的全部尺寸,并且不可在高度和宽度上出现滚动条。
一般这种就是“XX系统”的原型。这种工程有什么特点呢?那就是地图/三维场景几乎占据绝大多数的功能,大多数时候是浮动在地图场景上的一些 UI 元素在显示数据、发生交互。也就是说,切换的其实是一些界面组件,地图组件几乎不变,反过来看,界面组件大多数时候反而还要去访问地图核心对象,像 CesiumJS 是 Viewer,OpenLayers 是 Map 等。
我的建议是,所有业务界面组件应该作为地图组件的 子组件,在 Vue 中,就有 slot 的设计。
结合前端路由,还能跟随路由切换(RouteView 也应作为 slot 编写在地图组件中) 。
地图组件作为最顶层的组件,可以结合前端组件的生命周期特点,当核心对象创建完成后,才通过条件渲染把子组件打开,在 Vue 中利用 provide/inject 实现地图核心对象的下发和注入。在 React 中使用 useContext 下发也是类似的。
这种通常是表单的数据通过组件的 props 下传给地图,单一地显示上级操作接收来的数据。这种地图组件设计就比较简单,只需设计好 props 的数据结构,在组件挂载时创建核心对象并显示接收到的数据即可。
留了两个版本,读者可以自己在压缩包中找自己满意的。一个是第 2 节的最简单的,让 Vite 打包 CesiumJS 的版本,做了分 chunk;另一个则是经过第 5 节完整配置后、具备各种注释和细节,供读者自己改造学习的版本。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
1.1.1 YARN的介绍 为克服Hadoop1.0中HDFS和MapReduce存在的各种问题⽽提出的,针对Hadoop1.0中的MapReduce在扩展性和多框架⽀持⽅⾯的不⾜,提出了全新的资源管理框架YARN. ApacheYARN(YetanotherResourceNegotiator的缩写)是Hadoop集群的资源管理系统,负责为计算程序提供服务器计算资源,相当于⼀个分布式的操作系统平台,⽽MapReduce等计算程序则相当于运⾏于操作系统之上的应⽤程序。 YARN被引⼊Hadoop2,最初是为了改善MapReduce的实现,但是因为具有⾜够的通⽤性,同样可以⽀持其他的分布式计算模
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来