关于cesium:教程-在-Vue3Ts-中引入-CesiumJS-的最佳实践2023

49次阅读

共计 18327 个字符,预计需要花费 46 分钟才能阅读完成。

这篇如果 Vue 和 CesiumJS 不产生史诗级的变动,应该不会再有后文了。次要是这类文章没什么养分。

这篇次要修改上篇 https://segmentfault.com/a/1190000042385877 中一些插件的变动,并降级开发服务器的版本。

心急的敌人拉到文末,有示例工程链接下载。

1. 本篇适用范围与目标

1.1. 适用范围

  • 严格应用 Vue3 + TypeScript 的前端我的项目,包管理器默认应用 pnpm
  • 构建工具应用 Vite4
  • 应用原生 CesiumJS 依赖做利用开发
  • 客户端渲染,因为我不太熟悉 Vue 的服务端渲染,有本篇的介绍后,相熟 SSR 的读者能够本人接入
  • 单页利用,多页利用也能够参考此法

鉴于国内应用 CesiumJS 的比例大多数为利用开发(粗话即“APICaller”),而非扩大开发(基于源码作新性能封装、打包),所以我默认读者应用 CesiumJS 是通过 npmjs 网站(或镜像站)拉取的依赖,即:

pnpm add cesium@latest

有想批改源码再本人打包的读者,我感觉应该去看我的源码系列博客。

1.2. 目标

在 Vue3 工程中引入 CesiumJS 的最佳形式,并引出地图组件封装的简略教训两则。

这篇文章更偏向于给读者一些原理,而不是提供一套开箱即用的工具,有能力的读者能够依据这篇文章的原理,联合 Vite 或其它打包工具的 API,写一个专属插件。

2. 牛刀小试 – 先看到地球

如果没有疾速看到 3D 虚构地球,我感觉心急的敌人会心急(废话)。

第 2 节不须要晓得原理,原理和最佳实际请往下浏览 3、4、5 节。

2.1. 创立 Vue3 – TypeScript 工程并装置 cesium

如果你没有命令行根底,也不懂什么是 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 时指定了版本,是思考到 很多我的项目可能不太留神依赖版本治理,所以罗唆锁死固定版本。

2.2. 清理不必要的文件并创立三维地球

我移除了 src/assetssrc/components 文件夹,并删除全副 src/style.css 的代码,改写 main.tsApp.vuestyle.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 的类型
  • 引入 CesiumJS 本人的 css,供 Viewer 的各个内置界面小组件(时间轴等)提供 CSS 款式
  • 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.3. 中段解疑 – 奇怪的门路

你留神到了,2.2 大节里有两个奇怪的门路:

window.CESIUM_BASE_URL = 'node_modules/cesium/Build/CesiumUnminified/'
new TileMapServiceImageryProvider({url: 'node_modules/cesium/Build/CesiumUnminified/Assets/Textures/NaturalEarthII',})

这是因为 Vite 开发模式下(pnpm devNODE_ENVdevelopment)是间接把工程根门路(即 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.4. 打包部署

有了 2.3 大节的解释,当初要上生产环境了,生产环境兴许是 nginx,兴许是其它的 Web 服务器,这个时候就没有 node_modules 了,毕竟 Vite 的开发服务器职责曾经在 build 后实现。

这个时候就要作出以下批改:

  • 批改 CESIUM_BASE_URL 为生产环境能拜访的 CesiumJS 库文件的地址
  • 批改 TileMapServiceImageryProvider 的离线 TMS 门路

在批改之前,须要你把 CesiumJS 的四大动态资源文件夹从 node_modules 中拷贝进去,跟着做就行。

我把 node_modules/cesium/Build/CesiumUnminified/ 这个未压缩版本的文件夹下所有内容,即 AssetsWidgetsWorkersThirdParty 四个文件夹拷贝到 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 端口的服务:

运行起来和开发时无异。

2.5. 无限的优化

有人兴许对 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 插件,也能够就此为止,如果你不厌弃上述三个麻烦事儿。

3. CesiumJS 前置常识

3.1. CesiumJS 依赖包中的材料阐明

通过包管理器下载到 node_modules 下的 cesium 依赖,是 CesiumJS 打包好的“包”,它具备如下材料:

  • 不残缺的源代码,位于 node_modules/cesium/Source/ 目录下,含一个进口文件 Cesium.js 和一个 TypeScript 类型定义文件 Cesium.d.ts,进口文件导出的所有模块,也就是真正的源码均来自子包 @cesium/engine@cesium/widgets(于 1.100 版本变动,将代码宰割于子包中)
  • 打包后的库程序文件,含 IIFEES-ModuleCommonJS 三种格局,每种格局又有压缩代码版本和未压缩版本,别离寄存于 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,体积管制很不错。

3.2. 构建后的 CesiumJS 库组成 – 主库文件与四大文件夹

主库文件在 2.1 大节曾经阐明,压缩版和未压缩版均含 CommonJSIIFEES-Module 三种格局的库文件,文件名有所不同。

CesiumJS 的源代码(即 node_modules/cesium/Source/ 的进口文件,以及这个进口文件引自的 @cesium/engine@cesium/widgets 子包的代码模块)并不是残缺的 cesium 库,cesium 库还包含:

  • 一套 WebWorker,用于参数几何的生成、ktx2 纹理解码、draco 压缩数据解码等多线程工作
  • 一套 css 文件,用于 Viewer 下具备 HTML 界面的内置组件的款式表白,例如工夫线等组件
  • 一套动态资源文件,用于结构默认场景和内置组件,例如 SkyBox 背景图、图标、离线的两级 TMS 数据等
  • 一些第三方库,用于 basis 纹理和 draco 数据解码的 WebAssembly 文件以及配套的 WebWorker 文件

仅靠源代码是不能运行起 Cesium 三维地球场景的,必须应用构建版本的 CesiumJS 库。而官网构建后的 CesiumJS 库(即公布在 npm 上的 cesium 包)肯定会蕴含以上四类文件,即 node_modules/cesium/Build/ 下的压缩和未压缩版本文件夹下的 WorkersWidgetsWidgetsAssets 四大文件夹。

3.3. 链接库文件和四大文件夹的 CESIUM_BASE_URL 变量

在 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/'

不再赘述。

4. 古代前端工具的基本常识

4.1. 抉择 Vite 的理由

尤雨溪在某次 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 算是稳固了。

4.2. 为什么内部化引入(External)一个库

Vite 和 Webpack 相似,都能把一些依赖忽视,不参加打包,一旦某个依赖被配置为“内部的”,即 External 化,就不会打包它了。

社区在一般前端的实际中常常把 Vue、React、Axios 等不须要打包、能够应用高速 CDN 减速的库都内部化了。

CesiumJS 这个体积如此微小的库(压缩版 + gzip 后主库文件至多也有 900+KB)按理说也应该内部化,极大加重打包时的累赘和产物,应用 CDN 还能些许减速首屏性能。

External 化须要一些比拟繁琐的配置,如果读者认为不须要内部化,任 Vite 把 CesiumJS 再次打包那几秒钟、十几秒钟也无所谓的话,其实也能够不做这一步。

既然说了最佳化实际,那我就肯定要写这一步,万一有人须要呢?

在之后会应用 vite-plugin-externals 插件(留神,有 s 结尾)实现内部化。

4.3. TypeScript 类型提醒

没有类型提醒还得本人手动确认传值类型是否正确,TS 在动态代码编辑环境借助代码编辑器的各种性能,就能够事后查看出可能存在的谬误,最大地躲避运行时的问题。

cesium 包自带了类型文件,位于 node_modules/cesium/Source/Cesium.d.ts,你也能够在其 package.json 中找到类型字段。

咱们创立工程时,模板曾经配置好了 TypeScript,默认状况下不须要咱们额定配置什么,失常在组件或 ts 文件中导入 cesium 包的模块即可:

import {Viewer} from 'cesium'

这也是官网举荐的导入办法,这样导入是具备 TS 类型提醒的。

噢对了,如果你用的是 VSCode,偶然你会遇到 TS 类型提醒不失常的问题,大多数是这 5 个起因:

  • 如果你在用 Volar 插件来智能提醒 .vue 文件,那么你须要去 Vue 官网文档中配置下“take over”模式
  • 没有装置 typescript 到开发依赖
  • 装置了 typescript 到开发依赖然而工程没有应用开发依赖的 ts,而应用了 VSCode 本人的 ts,这个用 Ctrl + Shift + P 切换一下 ts 版本即可(搜寻“Select Typescript”或间接搜“Typescript”抉择版本即可),会写入 .vscode/settings.json 文件
  • 上述问题都排除了,兴许是 tsconfig.json 没有包含指标 d.ts 文件
  • 也有可能某个库压根就没有自带 d.ts,也没有对应的类型库

4.4. 开发服务器的门路与代码中的门路问题

这是一个老手问题,老手在开发工具(例如 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 数据文件门路在运行时也能失常读取
  • 认为 CesiumJS 会帮你解决门路问题

这点就不说怎么解决了,只求一些老手读者能理解分明什么是“源代码文件的绝对 URL”和“运行时 URL”这些根本区别。

此处塞一行防爬虫文字,原文出自 @岭南灯火,常驻知乎,其余博客社交平台根本有号,想找原文请劳烦搜寻一下~~

5. 教程(原理)注释

与其说是教程,不如说是基于第 2 节的持续优化,优化到最佳实际。

5.1. 应用 create-vite 在命令行创立工程

这个参考 2.1 和 2.2 大节即可。

5.2. 指定版本装置 cesium

指定版本装置在 2.1 大节有阐明,若不指定版本装置:

pnpm add cesium

那么在 package.json 中,cesium 依赖的版本号(首次 add 时的最新版)后面就会多一个 ^

{
  "dependencies": {"cesium": "^1.104.0"}
}

除非手动 update,即 pnpm update cesium@具体版本,否则 ^ 前面的版本号是不会扭转的。

那如果不指定版本装置 cesium 会用哪个版本呢?会用第一次 add 的版本,并且会写进对应包管理器的锁文件中。

  • pnpm 是 pnpm-lock.yaml
  • npm 是 package-lock.json
  • yarn 是 yarn.lock

5.3. 包管理工具锁文件的取舍

这大节能够与 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

5.4. 应用插件内部化 CesiumJS

原理、起因在 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 大节一起解决问题:

5.5. 应用插件主动在 index.html 引入 Cesium.js 库文件

读者能够手动把 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-html
  • vite-plugin-html-config
  • vite-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'})
      ]
    })
  ],
}

这样打包时就是相对完满的音讯了:

然而到此为止,依然有两个须要“手动”的事件待解决:

  • 四大动态文件的复制
  • CesiumJS 库文件的复制

巧的是,这些资源文件都能够从 cesium 包内拷贝,压缩版的 node_modules/cesium/Build/Cesium,非压缩版的 node_modules/cesium/Build/CesiumUnminified,请读者紧接着看 5.6 大节:

5.6. 四大动态文件夹与库文件的拷贝(CDN 或独立部署了 CesiumJS 库可省略此步)

这里须要一些插件或者 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 依赖,你都能看到这四个动态文件夹的复制步骤。

5.7. 额定优化 – 应用环境变量配置 CESIUM_BASE_URL 并适配其它配置

至此我认为工程的配置曾经满足非常灵活地运行了。它满足了:

  • 无论开发或生产环境,内部化了 CesiumJS,让 Vite 不再打包 cesium 依赖,大大减少打包工夫、缩小利用代码体积(从构建产物中剥离 cesium 库)
  • 无论开发或生产环境,都 主动复制四个动态资源文件夹、主动在 index.html 注入 CesiumJS 库文件的 script 标签以加载 CesiumJS

然而,一旦改用局域网或曾经部署好的 CesiumJS 库(这种状况请本人解决跨域),或者应用 CDN,那么装置在 node_modules 下的 cesium 其实曾经没有必要走 5.6 的动态文件复制了,而且注入 index.html 的主库文件须要批改。

我以国内 bootcdn 上的 CesiumJS 为例,既然 Vite 内置了不同环境文件的解析的函数 loadEnv(参考 Vite 官网文档 – 应用环境变量),我就分 developmentproduction 简略讲一讲。

  • 开发模式(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.jsoncompilerOptions 中增加:

{
  "compilerOptions": {"types": ["vite/client"]
  }
}

如果是旧版本的 Vite 创立的模板,你能够增加在 tsconfig.json 对应的地位中。

5.9. 额定优化 – 应用 gzip 事后压缩打包产物

在服务器上应用 gzip 能进一步晋升网络传输速度。打包时,应用适合的插件即可事后进行 gzip 打包,我选用的是 vite-plugin-compression 插件:

import compress from 'vite-plugin-compression'

// 应用见插件官网文档

在开发模式这玩意儿没起作用,就不细谈了。

5.8. 如何共享 CesiumJS 的 Viewer 对象

Vue 有 pinia 这个全局状态大杀器,能够把外围的 Viewer 对象送入全局状态中,然而要防止 Vue 的响应式劫持,响应式问题能够通过 Vue3 的 shallowRefshallowReactive 来解决:

<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 对象:

  • defineExpose
  • 层层事件冒泡至父级组件,或者应用全局事件库(如 mitt)
  • 应用全局状态 pinia 或 vuex

不再展现代码,请读者参考各种路径的官网文档来传递,留神 肯定要防止响应式劫持

6. 探索 CesiumJS 等库的前端组件封装

这里只是以 Vue 为例讲个思路,在其它前端框架中也实用。

6.1. 以 CesiumJS 等库为主的看板式工程

这种工程有一个特点,就是地图场景会占满浏览器窗口的全副尺寸,并且不可在高度和宽度上呈现滚动条。

个别这种就是“XX 零碎”的原型。这种工程有什么特点呢?那就是地图 / 三维场景简直占据绝大多数的性能,大多数时候是浮动在地图场景上的一些 UI 元素在显示数据、产生交互。也就是说,切换的其实是一些界面组件,地图组件简直不变,反过来看,界面组件大多数时候反而还要去拜访地图外围对象,像 CesiumJS 是 Viewer,OpenLayers 是 Map 等。

我的倡议是,所有业务界面组件应该作为地图组件的 子组件,在 Vue 中,就有 slot 的设计。

联合前端路由,还能追随路由切换(RouteView 也应作为 slot 编写在地图组件中)。

地图组件作为最顶层的组件,能够联合前端组件的生命周期特点,当外围对象创立实现后,才通过条件渲染把子组件关上,在 Vue 中利用 provide/inject 实现地图外围对象的下发和注入。在 React 中应用 useContext 下发也是相似的。

6.2. 后盾管理系统式工程

这种通常是表单的数据通过组件的 props 下传给地图,繁多地显示下级操作接管来的数据。这种地图组件设计就比较简单,只需设计好 props 的数据结构,在组件挂载时创立外围对象并显示接管到的数据即可。

7. 示例工程下载

留了两个版本,读者能够本人在压缩包中找本人称心的。一个是第 2 节的最简略的,让 Vite 打包 CesiumJS 的版本,做了分 chunk;另一个则是通过第 5 节残缺配置后、具备各种正文和细节,供读者本人革新学习的版本。

微云链接

正文完
 0