解决 Astro + Vite + Cesium 打包问题

Table of Contents

问题描述

在使用 Astro + Vite + Cesium 构建博客项目时,发现了一个奇怪的问题:

  • 生产构建时:Cesium 没有正确打包进 dist 文件夹
  • 开发运行时:一切正常,没有任何问题

这导致生产环境可能无法正常运行,但开发环境却完全正常。

问题分析

1. vite-plugin-cesium 的构建行为

通过查看 vite-plugin-cesium 的源码,发现了问题的根源:

js
// vite-plugin-cesium 在构建时的行为
if (rebuildCesium) {
// 重新构建 Cesium,打包进 bundle
userConfig.build = {
assetsInlineLimit: 0,
chunkSizeWarningLimit: 5000,
rollupOptions: {
output: {
intro: `window.CESIUM_BASE_URL = ${JSON.stringify(CESIUM_BASE_URL)};`
}
}
}
} else {
// 默认行为:将 Cesium 设为外部依赖
userConfig.build = {
rollupOptions: {
external: ["cesium"],
plugins: [externalGlobals({ cesium: "Cesium" })]
}
}
}

问题所在

  • 默认情况下(rebuildCesium: false),Cesium 被配置为外部依赖
  • 这意味着 Cesium 不会被打包,而是期望从外部加载(如 CDN 或全局 script)
  • 但是 HTML 中没有引入 Cesium.js 的 script 标签
  • 静态资源(Assets、Workers、Widgets 等)也没有被复制到 dist 目录

2. Astro 构建流程的差异

Astro 的构建流程与纯 Vite 项目不同:

  • vite-plugin-cesiumtransformIndexHtml hook 可能没有被正确执行
  • closeBundle hook 中的资源复制逻辑可能没有运行

3. 开发模式为什么正常?

开发模式下正常是因为:

  • Vite 的 dev server 会从 node_modules 直接加载 Cesium
  • vite-plugin-cesiumconfigureServer hook 会设置中间件来服务 Cesium 资源
  • 所以开发时一切正常,但构建后缺少这些资源

解决方案

步骤一:创建自定义 Astro 集成

创建一个自定义的 Astro 集成来处理 Cesium 资源的复制和配置:

tssrc/integrations/cesium.ts
import type { AstroIntegration } from 'astro'
import fs from 'fs/promises'
import path from 'path'
export function cesiumIntegration(): AstroIntegration {
return {
name: 'cesium-integration',
hooks: {
'astro:build:done': async ({ dir }) => {
const cesiumBuildPath = path.join(
process.cwd(),
'node_modules/cesium/Build/Cesium'
)
const cesiumDistPath = path.join(dir.pathname, 'cesium')
// 确保目标目录存在
await fs.mkdir(cesiumDistPath, { recursive: true })
// 复制 Cesium 的静态资源
const resources = ['Assets', 'ThirdParty', 'Workers', 'Widgets', 'Cesium.js']
const copyRecursive = async (src: string, dest: string) => {
const stat = await fs.stat(src)
if (stat.isDirectory()) {
await fs.mkdir(dest, { recursive: true })
const entries = await fs.readdir(src)
for (const entry of entries) {
await copyRecursive(path.join(src, entry), path.join(dest, entry))
}
} else {
await fs.copyFile(src, dest)
}
}
for (const resource of resources) {
const src = path.join(cesiumBuildPath, resource)
const dest = path.join(cesiumDistPath, resource)
try {
await fs.access(src)
await copyRecursive(src, dest)
console.log(`✓ Copied Cesium ${resource} to ${dest}`)
} catch (err) {
console.warn(
`⚠ Skipped Cesium ${resource}: ${err instanceof Error ? err.message : 'unknown error'}`
)
}
}
},
'astro:config:setup': ({ injectScript }) => {
// 注入 Cesium 的全局配置脚本
injectScript('head-inline', `
window.CESIUM_BASE_URL = '/cesium/';
`)
},
},
}
}

步骤二:创建 CesiumLoader 组件

创建一个组件来动态加载 Cesium.js:

astrosrc/components/CesiumLoader.astro
---
// CesiumLoader 组件:在需要 Cesium 的页面加载 Cesium.js
// 只在客户端加载,避免 SSR 问题
---
<script>
// 检查是否已经加载了 Cesium
if (typeof window !== 'undefined' && !window.Cesium) {
// 动态加载 Cesium.js
const script = document.createElement('script')
script.src = '/cesium/Cesium.js'
script.async = true
script.onload = () => {
console.log('Cesium loaded successfully')
}
script.onerror = () => {
console.error('Failed to load Cesium.js')
}
document.head.appendChild(script)
}
</script>

步骤三:更新配置文件

astro.config.mjs 中添加集成:

js
import { cesiumIntegration } from './src/integrations/cesium'
export default defineConfig({
// ... 其他配置
integrations: [
// ... 其他集成
cesiumIntegration(),
],
vite: {
plugins: [tailwindcss(), cesium()],
// ... 其他配置
},
})

在需要 Cesium 的页面中使用:

mdx
---
title: 'init cesium with react'
---
import CesiumLoader from '~/components/CesiumLoader.astro'
import InitMapDemo from '~/components/mdx/InitMapDemo'
<CesiumLoader />
<InitMapDemo client:load id={'fs'}/>

开发模式问题修复

在解决打包问题后,还遇到了开发模式下的问题:

问题

  • Vite 依赖优化缓存过期(504 Outdated Optimize Dep)
  • React 运行在生产模式但死代码消除未应用

解决方案

  1. 更新 optimizeDeps 配置
js
vite: {
optimizeDeps: {
include: ['cesium', 'clsx', 'react-syntax-highlighter'],
exclude: []
},
}
  1. 添加清理缓存的脚本
json
{
"scripts": {
"dev:clean": "rm -rf node_modules/.vite && astro dev --host"
}
}
  1. 使用清理后的开发命令
bashTerminal window
npm run dev:clean

最终效果

  • Cesium 资源正确复制到 dist/cesium/
  • HTML 中正确注入 CesiumLoader 的 script 标签
  • CESIUM_BASE_URL 正确设置为 /cesium/
  • 开发模式依赖优化正常工作
  • 生产构建和开发运行都正常

总结

这个问题的核心在于:

  1. 插件适配问题vite-plugin-cesium 主要针对纯 Vite 项目设计,在 Astro 中需要额外处理
  2. 构建流程差异:Astro 的构建流程与 Vite 不同,某些 hook 可能不会按预期执行
  3. 开发/生产差异:开发模式和生产模式的资源加载方式不同

通过创建自定义集成和组件,我们成功解决了这些问题,确保了 Cesium 在 Astro 项目中能够正常工作。

后记

My avatar

感谢你读到这里。

这座小站更像一份持续维护的“终端笔记”:记录我解决问题的过程,也记录走过的弯路。

如果这篇内容对你有一点点帮助:

  • 点个赞 / 收藏一下,方便你下次回来继续翻
  • 欢迎在评论区补充你的做法(或者指出我的疏漏)
  • 想持续收到更新:可以订阅 RSS(在页面底部)

我们下篇见。


More Posts

评论

评论 (0)

请先登录后再发表评论

加载中...