由于 Vue2 已经进入维护期,且 Vue2 对待组件内的 data 是无差别使用 Object.defineProperties 递归将其劫持的,对于复杂状态的对象会造成严重的 JavaScript 访问路径过长而导致的 性能问题,这个应该是老生常谈了。
Vue3 提供了 markRaw 函数,标记一个对象,令 Vue 不再将其视作 响应式 数据,所以本文基于 Vue3 来介绍如何引入 CesiumJS。
心急的朋友可以直接跳过本文的介绍,拉到文末,有示例工程 zip 下载。
除了 Vue3 和 Vue2 的响应式设计区别外,我认为还需要补充一点知识。
Cesium 是一个高度集成的重型 JavaScript 库,这是共识。它的源码虽然是 ESModule 格式的,但是并没有直接提供类似 index.js 的出口文件,也不存在子包的概念,只是在 Source 文件夹下简单分了几个大板块文件夹,例如 Source/Renderer 文件夹就是 CesiumJS 中整个渲染器的代码模块。
通常,除了二次修改 CesiumJS 源代码构建自己的分支版本,一般不会在 WebAPP 中直接使用 CesiumJS 的源码。一般使用的是 CesiumJS 的 构建版本,也就是 Build 文件夹下的压缩版或未压缩版库文件。
主库文件有三种格式,ESModule 的是 index.js,IIFE 的是 Cesium.js,CommonJS 的是 index.cjs。除了主库文件外,构成构建版本的 CesiumJS 还有 4 个文件夹下的静态资源:
Assets 文件夹,图片或 JSON 等前端运行时可能用到的资源ThirdParty 文件夹,WebAssembly 等前端运行时可能用到的第三方资源Widgets 文件夹,主要是各个 CesiumJS 自带的界面小部件的 CSS 文件Workers 文件夹,前端运行时用到的 WebWorker 的构建版本(WebWorker 由于一些原因,在前端运行时仍然用 CommonJS 格式加载)
因此,你在任何所谓的教程里面都会看到这四个静态资源文件夹的复制操作,除了 CDN 直接使用的方式。我在这里说清楚,希望你知道原因。
笔者是 Vite 1.0 的首批用户。尤雨溪第一次介绍 Vite 是在 Vue 3.0 测试版网络会议上,只是作为一个很小的“玩具”介绍了一下,当时的 Vite 还是与 Vue 强关联的,后来到了 Vite 2.0 才解耦合。简单的说,Vite 3.0 对 Vite 2.x 并不是破坏性更新,只是考虑到 NodeJS 12.x 已经 EOL 了,索性 3.0 就不再支持 NodeJS 12.x,其余特性笔者没特别了解。
简单的说,使用 Vite 作为开发服务器和打包工具,不外乎几个原因:
对于开项目,我有几点建议:
create-vue 脚手架或者安东尼小哥的 vitesse 模板工程替代 @vue/cli 即可;这条也适用于想更多自定义的项目、团队;简单起见,我将使用 create-vue 来演示。
最后说明为什么用 pnpm —— 它速度足够快,也有效缩小了 node_modules 的体积,对付 peer 依赖也很棒。你当然也可以用 npm 和 yarn。
在 1.1 小节我已经说明了 CesiumJS 库的构成,有一个库文件,以及 4 个静态资源文件夹。
由于 npm 下载的 cesium 包中已经有官方打包好的 构建版本 库了,没有必要让 Vite 再次将 CesiumJS 源代码再次打包,而应将其作为外部依赖,也就是配置 Vite 的 external 项,不打包,使用 CDN 或 public 文件夹下的库程序、资源。
当然,这是对官方库没有任何修改、直接使用的前提;如果想二次修改 CesiumJS 源代码,无论是自己打包,还是使用 npm-patch,上述方法便不再需要参考。
在 Vite 中,需要借助两个社区插件完成 CesiumJS 的外部化:
前者告诉 Vite 什么 dependencies 不参与打包,后者告诉 Vite 打包后的产物哪些 dependencies 需要在页面入口 html 文件中随 public 目录(或 CDN)引入。
具体配置过程参考 2.4 小节。
在代码中,有一些特殊的关键字、指令会被打包器识别,打包器会帮你把相关的资源打包、转译。
在 Webpack 时代,你就见过使用 import 指令引入 css 文件或图片:
import 'foo.css'
import Logo from '@/assets/logo.svg'
Webpack 本身只能处理 import 进来的 JavaScript 文件,对于其它的资源,则使用各种 Loader 完成打包处理过程。
Vite 则开箱支持了众多 Web 前端的资源的导入。但是,3D 领域的模型文件就没有支持,不能通过 import 命令导入,除非安装了处理对应文件格式的插件。像下面的导入指令,Vite 并不会帮你处理:
import CarModel from '@/assets/data/model.glb'
并且会在启动时给你报错。
另一个问题是要明白,当前工程的路径 ≠ 运行时的路径。运行时又分开发运行时、打包后的运行时。
所以,在一些 API 需要传递资源路径时,请一定要确保在运行时它是可以被浏览器正确请求到的,例如:
new Cesium3DTileset({
// vite 等打包器并不会帮你处理这个路径,Cesium 在发出请求也不会
url: '@/assets/tilesets/tileset.json'
})
又如:
new Cesium3DTileset({
// 或下面的例子,运行时的基础地址是 http://localhost:5173,
// 那么前端发起请求就会是 http://localhost:5173/data/tileset.json
url: './data/tileset.json'
})
最后,我认为 CompositionAPI 和 OptionAPI 并不是本文讨论的重点,但是我会使用 setup-script + CompositionAPI 来介绍。
顺便,既然都 Vue3 了,那 TypeScript 肯定是少不了的。
请确保你的机器安装了 NodeJS,版本最好使用 LTS(写文的时候,推荐 16+ 版本),以及 node 包管理工具能正常在你的命令行环境(Windows - powershell/cmd/gitbash,macOS 和 Linux 应该是有自带的 shell)使用。
我的包管理工具是 pnpm。
虽然但是,我不是很想说 NodeJS 于前端的关系,请读者自行了解 NodeJS 包以及其包管理工具。这里简单说明:NodeJS 是 vite 或 webpack 开发时的程序服务器(简称开发服务器,devServer)的基石,就像 jdk 于 Spring 框架一样。运行你的页面代码的仍旧是浏览器,打包器(vite 内置的是 rollup,webpack 自己就是)会把你写的 Vue 单文件组件、ts 代码合并、打包、转译成优化后的产物。
为了使用最新的全局状态管理器 pinia,我选择用 create-vue 这个能替代 @vue/cli 的新版脚手架,完成具备如下开发工具配置的工程创建:
piniatypescripteslintprettier不要再问我这些是什么,这些属于 Vue 和 Web 前端的生态。
创建命令:
pnpm create vue
确保你的网络没有问题,那么你就可以伴随着如下命令行提示创建出与我一样的初始工程:

node 包管理器安装依赖包,如果不指定版本,会默认当前版本以及以上的版本都可以运行,也就是会在 package.json 的依赖列表中的版本号前加一个 ^ 号:
{
"dependencies": {
"cesium": "^1.96.0"
}
}
但是,CesiumJS 每个月都会更新,而且时不时会有重大变动,我的建议是手动锁死版本,而不是依赖锁文件(pnpm 是 pnpm-lock.yaml,npm 是 package-lock.json,yarn 是 yarn.lock)。
pnpm add cesium@1.96.0
这样,以后安装依赖就不会安装到最新版本,以至于项目出现因重大变动导致运行不起来的问题了。
在 package.json 同级别路径下创建 .npmrc 文件,配置包管理器的行为、参数,使用如下配置即可不产生锁文件:
package-lock=false
这对于严格控制 package.json 中依赖版本的项目,而且不指定包管理器(即允许任意使用 pnpm、yarn、npm 来管理依赖)的项目来说是十分有利的。
先安装 Vite 插件:

然后,在 vite.config.ts 中修改 Vite 的配置:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import htmlConfig from 'vite-plugin-html-config'
import { viteExternalsPlugin } from 'vite-plugin-externals'
// https://vitejs.dev/config/
export default ({ mode: VITE_MODE }: { mode: string }) => {
const env = loadEnv(VITE_MODE, process.cwd())
console.log('VITE_MODE: ', VITE_MODE)
console.log('ENV: ', env)
const plugins = [vue()]
const externalConfig = viteExternalsPlugin({
cesium: 'Cesium'
})
const htmlConfigs = htmlConfig({
headScripts: [
{
src: './lib/cesium/Cesium.js'
}
],
links: [
{
rel: 'stylesheet',
href: './lib/cesium/Widgets/widgets.css'
}
]
})
plugins.push(
externalConfig,
htmlConfigs
)
return defineConfig({
root: './',
build: {
assetsDir: './',
minify: ['false'].includes(env.VITE_IS_MINIFY) ? false : true
},
plugins: plugins,
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
}
注意到导出的是一个函数,与 Vite 初始化的配置文件直接使用 import { defineConfig } from 'vite' 函数定义的是略有区别的。这个函数的参数是一个类型为 { mode: string } 的对象,参考:配置 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 的静态资源复制。
在 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 脚本和前端运行时的相对路径问题。
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 文件中:
/// <reference types="vite/client" />
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)
/* ... */
}
这一步是可选的,当然,我强烈推荐你做这一步,这对跨组件访问 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 的 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<HTMLDivElement>()
const unvisibleCreditRef = ref<HTMLDivElement>()
const sysStore = useSysStore()
onMounted(() => {
const viewer = new Viewer(containerRef.value as HTMLElement)
const rawViewer = markRaw(viewer)
sysStore.setCesiumViewer(rawViewer)
})
最后,你就可以在兄弟组件中访问到 Viewer 了:
<!-- BrotherComponent.vue -->
<template>
<button @click="onClick">控制台打印 viewer</button>
</template>
<script setup lang='ts'>
import { useSysStore } from '@/store/sys'
const sysStore = useSysStore()
const onClick = () => {
// 也可以写 getter,但我觉得这样就足够说明问题了
console.log(sysStore.$state.cesiumViewer)
}
</script>
由于篇幅原因,有些文章中的代码会省略、简化,工程的源码、配置可能与上述有细微差别,请自行了解。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123
我主要使用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
question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
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:请求方法一般是用于数据查询,
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
在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