Table of Contents
问题描述
在使用 Astro + Vite + Cesium 构建博客项目时,发现了一个奇怪的问题:
- 生产构建时:Cesium 没有正确打包进
dist文件夹 - 开发运行时:一切正常,没有任何问题
这导致生产环境可能无法正常运行,但开发环境却完全正常。
问题分析
1. vite-plugin-cesium 的构建行为
通过查看 vite-plugin-cesium 的源码,发现了问题的根源:
// 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-cesium的transformIndexHtmlhook 可能没有被正确执行closeBundlehook 中的资源复制逻辑可能没有运行
3. 开发模式为什么正常?
开发模式下正常是因为:
- Vite 的 dev server 会从
node_modules直接加载 Cesium vite-plugin-cesium的configureServerhook 会设置中间件来服务 Cesium 资源- 所以开发时一切正常,但构建后缺少这些资源
解决方案
步骤一:创建自定义 Astro 集成
创建一个自定义的 Astro 集成来处理 Cesium 资源的复制和配置:
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:
---// 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 中添加集成:
import { cesiumIntegration } from './src/integrations/cesium'
export default defineConfig({ // ... 其他配置 integrations: [ // ... 其他集成 cesiumIntegration(), ], vite: { plugins: [tailwindcss(), cesium()], // ... 其他配置 },})在需要 Cesium 的页面中使用:
---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 运行在生产模式但死代码消除未应用
解决方案
- 更新
optimizeDeps配置:
vite: { optimizeDeps: { include: ['cesium', 'clsx', 'react-syntax-highlighter'], exclude: [] },}- 添加清理缓存的脚本:
{ "scripts": { "dev:clean": "rm -rf node_modules/.vite && astro dev --host" }}- 使用清理后的开发命令:
npm run dev:clean最终效果
- ✅ Cesium 资源正确复制到
dist/cesium/ - ✅ HTML 中正确注入 CesiumLoader 的 script 标签
- ✅
CESIUM_BASE_URL正确设置为/cesium/ - ✅ 开发模式依赖优化正常工作
- ✅ 生产构建和开发运行都正常
总结
这个问题的核心在于:
- 插件适配问题:
vite-plugin-cesium主要针对纯 Vite 项目设计,在 Astro 中需要额外处理 - 构建流程差异:Astro 的构建流程与 Vite 不同,某些 hook 可能不会按预期执行
- 开发/生产差异:开发模式和生产模式的资源加载方式不同
通过创建自定义集成和组件,我们成功解决了这些问题,确保了 Cesium 在 Astro 项目中能够正常工作。