教程 - 在 Vue3+Ts 中引入 CesiumJS 的最佳实践@2023
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/assets
和 src/components
文件夹,并删除全部 src/style.css
的代码,改写 main.ts
、App.vue
、style.css
如下:
// main.tsimport { 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 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.4. 打包部署
有了 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 端口的服务:
运行起来和开发时无异。
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 版本变动,将代码分割于子包中) -
打包后的库程序文件,含
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,体积控制很不错。
3.2. 构建后的 CesiumJS 库组成 - 主库文件与四大文件夹
主库文件在 2.1 小节已经说明,压缩版和未压缩版均含 CommonJS
、IIFE
、ES-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/
下的压缩和未压缩版本文件夹下的 Workers
、Widgets
、Widgets
、Assets
四大文件夹。
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 官方文档 - 使用环境变量),我就分 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.modeconst 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
对应的位置中。
5.9. 额外优化 - 使用 gzip 预先压缩打包产物
在服务器上使用 gzip 能进一步提升网络传输速度。打包时,使用合适的插件即可预先进行 gzip 打包,我选用的是 vite-plugin-compression
插件:
import compress from 'vite-plugin-compression'// 使用见插件官方文档
在开发模式这玩意儿没起作用,就不细谈了。
5.8. 如何共享 CesiumJS 的 Viewer 对象
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 对象:
- 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 节完整配置后、具备各种注释和细节,供读者自己改造学习的版本。
微云链接
相关文章:
![](https://img-blog.csdnimg.cn/img_convert/8fde6ad07321c0078f5e29af71bcb73f.jpeg)
教程 - 在 Vue3+Ts 中引入 CesiumJS 的最佳实践@2023
1. 本篇适用范围与目的 1.1. 适用范围 严格使用 Vue3 TypeScript 的前端项目,包管理器默认使用 pnpm 构建工具使用 Vite4 使用原生 CesiumJS 依赖做应用开发 客户端渲染,因为我不太熟悉 Vue 的服务端渲染,有本篇的介绍后,熟悉…...
![](https://img-blog.csdnimg.cn/f4411797922f45d0a519ff2b48a5d6c7.png)
最优化方法
一. 图论 1.最小生成树 图的生成树是它的一颗含有其所有顶点的无环连通子图,一 幅加权图的最小生成树(MST)是它的一颗权值(树中的所有边的权值之和) 最小的生成树 • 适用场景:道路规划、通讯网络规划、管道铺设、电线布设等 题目数据 kruskal算法 稀疏图&#x…...
![](https://www.ngui.cc/images/no-images.jpg)
Mongodb 多文档聚合操作处理方法二(Map-reduce 函数)
聚合 聚合操作处理多个文档并返回计算结果。您可以使用聚合操作来: 将多个文档中的值分组在一起。 对分组数据执行操作以返回单个结果。 分析数据随时间的变化。 要执行聚合操作,您可以使用: 聚合管道 单一目的聚合方法 Map-reduce 函…...
![](https://www.ngui.cc/images/no-images.jpg)
ant design vue j-modal 修改高度
问题描述 今天在项目中遇到关于j-modal组件修改弹窗大小问题,我尝试使用直接使用:height"300",没用效果,弹窗大小依然和没改之前一样,后来找到了这种方式可以去修改j-modal弹窗大小,下面来看下代码实现&…...
![](https://www.ngui.cc/images/no-images.jpg)
spring学习笔记七
一、自动装配 1.1、BookDao接口和实现类 public interface BookDao {void save(); } public class BookDaoImpl implements BookDao {public void save(){System.out.println("book dao save......");} } 1.2、BookService接口和实现类 public interface BookSer…...
![](https://img-blog.csdnimg.cn/42dd301cef3d47539c7879e725fae2d5.png)
hw技战法整理参考
目录 IP溯源反制 账户安全策略及预警 蜜罐部署联动方案...
![](https://img-blog.csdnimg.cn/f860ba4cdba644f5bcf53a67444e31a4.png)
uniapp 全局数据(globalData)的设置,获取,更改
globalData,这是一种简单的全局变量机制。这套机制在uni-app里也可以使用,并且全端通用 因为uniapp基本上都是将页面,或者页面中相同的部分,进行组件化,所以会存在父,子,(子…...
![](https://img-blog.csdnimg.cn/img_convert/a2cf728ffa252a3ca5e8d0bc751462bf.png)
Profinet转EtherNet/IP网关连接AB PLC的应用案例
西门子S7-1500 PLC(profinet)与AB PLC以太网通讯(EtherNet/IP)。本文主要介绍捷米特JM-EIP-PN的Profinet转EtherNet/IP网关,连接西门子S7-1500 PLC与AB PLC 通讯的配置过程,供大家参考。 1, 新建工程&…...
![](https://www.ngui.cc/images/no-images.jpg)
Python组合模式介绍、使用方法
一、Python组合模式介绍 概念: 组合模式(Composite Pattern)是一种结构型设计模式,它通过将对象组合成树状结构来表示“整体/部分”层次结构,让客户端可以以相同的方式处理单个对象和组合对象。 功能: 统一对待组合对象和叶子对…...
![](https://img-blog.csdnimg.cn/img_convert/2ff625181903466c01dd9a7b0b5842ea.jpeg)
生成模型和判别模型工作原理介绍
您解决的大多数机器学习和深度学习问题都是从生成模型和判别模型中概念化的。在机器学习中,人们可以清楚地区分两种建模类型: 将图像分类为狗或猫属于判别性建模生成逼真的狗或猫图像是一个生成建模问题神经网络被采用得越多,生成域和判别域就增长得越多。要理解基于这些模型…...
![](https://www.ngui.cc/images/no-images.jpg)
shardingsphere读写分离配置
注: 如果是升级之前的单库单表,要将之前的 数据库接池 druid-spring-boot-starter 注释掉,换成 druid,否则无法连接数据库。 原因: 因为数据连接池的starter(比如druid)可能会先加载并且其创…...
![](https://img-blog.csdnimg.cn/a87ba4141930475b88714b9646f57d19.png)
登录报错 “msg“:“Request method ‘GET‘ not supported“,“code“:500
1. 登录失败 2. 排查原因, 把 PostMapping请求注释掉, 或改成GetMapping请求就不会报错 3. 找到SecurityConfig.java , 新增 .antMatchers("/**/**").permitAll() //匹配允许所有路径 4. 登录成功...
![](https://www.ngui.cc/images/no-images.jpg)
Python 日期和时间
Python 日期和时间 Python 程序能用很多方式处理日期和时间,转换日期格式是一个常见的功能。 Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间。 时间间隔是以秒为单位的浮点小数。 每个时间戳都以自从1970年1月1日午夜(历元&…...
![](https://img-blog.csdnimg.cn/be572553eed64f1c8d314f9b9d6e81c0.png)
pytorch的发展历史,与其他框架的联系
我一直是这样以为的:pytorch的底层实现是c(这一点没有问题,见下边的pytorch结构图),然后这个部分顺理成章的被命名为torch,并提供c接口,我们在python中常用的是带有python接口的,所以被称为pytorch。昨天无意中看到Torch是由lua语言写的&…...
![](https://www.ngui.cc/images/no-images.jpg)
Kibana-elastic--Elastic Stack--ELK Stack
Kibana 是什么? | Elastic 将数据转变为结果、响应和解决方案 使用 Kibana 针对大规模数据快速运行数据分析,以实现可观测性、安全和搜索。对来自任何来源的任何数据进行全面透彻的分析,从威胁情报到搜索分析,从日志到应用程序监测…...
![](https://img-blog.csdnimg.cn/f1430167c30d4bb2bfd4b9de94418e23.png)
Docker复杂命令便捷操作
启动所有状态为Created的容器 要启动所有状态为"created"的Docker容器,可以使用以下命令: docker container start $(docker container ls -aq --filter "statuscreated")上述命令执行了以下步骤: docker container l…...
![](https://img-blog.csdnimg.cn/f4b535d9417d4c75816ccd66104e2cb8.png)
Python中的datetime模块
time模块用于取得UNIX纪元时间戳,并加以处理。但是,如果以方便的格式显示日期,或对日期进行算数运算,就应该使用datetime模块。 目录 1. datetime数据类型 1) datetime.datetime.now()表示特定时刻 2)da…...
![](https://img-blog.csdnimg.cn/e85f35f45ac1413487005c9a4683b65c.gif)
Flutter - 微信朋友圈、十字滑动效果(微博/抖音个人中心效果)
demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新,请前往github查看最新代码 前言 一般APP都有类似微博/抖音个人中心的效果,支持上下拉刷新,并且顶部有个图片可以下拉放大,图片底部是几个tab,可…...
![](https://img-blog.csdnimg.cn/00b4f309ed0c427085095ee42b65f358.png)
MySQL检索数据和排序数据
目录 一、select语句 1.检索单个列(SELECT 列名 FROM 表名;) 2.检索多个列(SELECT 列名1,列名2,列名3 FROM 表名;) 3.检索所有的列(SELECT * FROM 表名;) 4.检索不同的行&#x…...
![](https://img-blog.csdnimg.cn/e9334af0560043b08931680cc48b5e60.png)
通过STM32内部ADC将烟雾传感器发送的信号值显示在OLED上
一.CubeMX配置 首先我们在CubeMX配置ADC1, 设置一个定时器TIM2定时1s采样一次以及刷新一次OLED, 打开IIC用于驱动OLED显示屏。 二.程序 在Keil5中添加好oled的显示库,以及用来显示的函数、初始化函数、清屏函数等。在主程序中初始化oled,并将其清屏。…...
![](https://www.ngui.cc/images/no-images.jpg)
ZEPHYR 快速开发指南
简介 国内小伙伴在学习zephyr的时候,有以下几个痛点: 学习门槛过高github访问不畅,下载起来比较费劲。 这篇文章将我自己踩的坑介绍一下,顺便给大家优化一些地方,避免掉所有的坑。 首先用virtualbox 来安装一个ubu…...
![](https://www.ngui.cc/images/no-images.jpg)
【FPGA + 串口】功能完备的串口测试模块,三种模式:自发自收、交叉收发、内源
【FPGA 串口】功能完备的串口测试模块,三种模式:自发自收、交叉收发、内源 VIO 控制单元 wire [1:0] mode;vio_uart UART_VIO (.clk(ad9361_l_clk), // input wire clk.probe_out0(mode) // output wire [1 : 0] probe_out0 );将 mod…...
![](https://img-blog.csdnimg.cn/img_convert/b6fd4e9382ddecbbf52ba540f18e8459.png)
初步了解预训练语言模型BERT
本文字数::4024字 预计阅读时间:12分钟 BERT是由Google提出的预训练语言模型,它基于transformer架构,被广泛应用于自然语言处理领域,是当前自然语言处理领域最流行的预训练模型之一。而了解BERT需要先了解注…...
![](https://www.ngui.cc/images/no-images.jpg)
Android Hook系统 Handler 消息实现
前言 主线程的Handler 主要依赖于 ActivityThread,Android是消息驱动,比如view的刷新,activity的创建等,如果能打印系统层Handler消息日志,就需要对于系统层的Handler 进行Hook 原理 ActivityThread中 mH对象主要负责…...
![](https://www.ngui.cc/images/no-images.jpg)
R语言从入门到精通之【R语言的使用】
系列文章目录 1.R语言从入门到精通之【R语言介绍】 2.R语言从入门到精通之【R语言下载与安装】 3.R语言从入门到精通之【R语言的使用】 文章目录 系列文章目录一、新手上路1.R语句构成2.获取帮助3.工作空间二、包1.包的安装2.实践应用总结一、新手上路 1.R语句构成 R语句由函…...
![](https://www.ngui.cc/images/no-images.jpg)
WPF实战学习笔记29-登录数据绑定,编写登录服务
添加登录绑定字段、命令、方法 修改对象:Mytodo.ViewModels.ViewModels using Mytodo.Service; using Prism.Commands; using Prism.Events; using Prism.Mvvm; using Prism.Services.Dialogs; using System; using System.CodeDom.Compiler; using System.Collec…...
![](https://www.ngui.cc/images/no-images.jpg)
c++函数式编程:统计文件字符串,文件流
头文件 #include <iostream> #include <fstream> #include <string> #include <sstream> #include <algorithm> #include <vector>统计方法 int count_lines(const std::string &filename) {std::ifstream in{filename};return std:…...
![](https://www.ngui.cc/images/no-images.jpg)
scp命令----跨服务器传输文件
scp命令 Linux scp 命令用于 Linux 之间复制文件和目录。 scp 是 secure copy 的缩写, scp 是 linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令。 scp 是加密的,rcp 是不加密的,scp 是 rcp 的加强版。 一、Linux scp 命令 以下是scp命令常用的…...
![](https://img-blog.csdnimg.cn/d890c858afd64ba595a81c831defdb55.png)
React Dva项目中模仿网络请求数据方法
我们都已经选择react了 那么自然是一个前后端分离的开发形式 至少我在公司中 大部分时候是前后端同时开发的 一般你在开发界面没有接口直接给你 但你可以和后端约定数据格式 然后在前端模拟数据 我们在自己的Dva项目中 在根目录下的 mock 目录下创建一个js文件 我这里叫 filmDa…...
![](https://img-blog.csdnimg.cn/522c8805ffe3470ea357a242882fb55c.png)
【云原生】Docker容器命令监控+Prometheus监控平台
目录 1.常用命令监控 docker ps docker top docker stats 2.weave scope 1.下载 2.安装 3.访问查询即可 3.Prometheus监控平台 1.部署数据收集器cadvisor 2.部署Prometheus 3.部署可视化平台Gragana 4.进入后台控制台 1.常用命令监控 docker ps [rootlocalhost ~…...
![](http://upload-images.jianshu.io/upload_images/2085791-a3593714c18c3b1b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
做自己看视频的网站/免费域名服务器
2019独角兽企业重金招聘Python工程师标准>>> Fiori里的busy dialog有两种表现形式,一种是下图里的花朵形状,由5个不断旋转的花瓣组成。另一种是下图的3/4个圆环不断旋转的效果。 关于前者的效果,可以看我制作的这个视频。这个视频…...
![](https://img-blog.csdnimg.cn/03679b3dd2c8498aa5d93d7313f9fd05.png)
avada 做的网站/软文写作平台发稿
简述 CentOS8 引入新的module软件包管理机制,下面将介绍如何部署搭建本地module源。 部署流程: 思路: 先制作nginx-1.14的module.yaml(需要微调),然后同法制作nginx-1.16的module.yaml(需要微调)。 然后将nginx-1.1…...
![](https://img-blog.csdnimg.cn/20181102133453484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xvbmdrb3Vzb25n,size_16,color_FFFFFF,t_70)
医院网站建设招标说明/韶关新闻最新今日头条
最近,小米充电宝突然不能正常输出也不能充电了。具体现象是充电时四个灯同时闪烁,平时既也不能输出供电,也充不进电,但是电池电量显示正常。 小米充电宝很好拆,无聊拆开看看也行哦。中午花了半小时把充电宝修好了。 …...
![](/images/no-images.jpg)
未备案网站 赚钱/中国科技新闻网
编译原理,用Reactjs用自制编译器 此人的教程非常好...
![](/images/no-images.jpg)
电子商务网站建设下载/百度搜索排名优化
有名信号量 semphore如果说信号是外部事件和进程的关联的机制的话,那么信号量就是进程、线程之间通信的机制。根据是支持不同进程之间的通信还是同一个进程内不同线程的通信,信号量可以分为有名信号量和无名信号量。顾名思义,有名信号量在建立…...
![](/images/no-images.jpg)
手机上的html编辑器/天津搜索引擎seo
android apk文件的反编译现在有两种形式,一种是转换成smali的字节码格式,一种是专为jar格式的class文件. 先讲第一种. 首先去下一个apktool.它能将apk转译成smali文件并解析出正确的xm格式l的布局文件. 使用时将需要反编译到apk(如weibo.apk)放到解压后的apktool目录下,运行…...