respira/vite.config.mts
Jan-Henrik Bruhn ded3423a19 feature: Add application version to page title
- Read version from package.json at build time using Vite define
- Create global __APP_VERSION__ constant injected by Vite
- Update document title in App component to include version (e.g., "Respira v0.0.0")
- Works reliably in both web and Electron builds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 23:48:05 +01:00

166 lines
4.4 KiB
TypeScript

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteStaticCopy } from 'vite-plugin-static-copy'
import tailwindcss from '@tailwindcss/vite'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import { readFileSync } from 'fs'
import type { Plugin } from 'vite'
// Read version from package.json
const packageJson = JSON.parse(readFileSync('./package.json', 'utf-8'))
const appVersion = packageJson.version
const PYODIDE_EXCLUDE = [
'!**/*.{md,html}',
'!**/*.d.ts',
'!**/node_modules',
]
export function viteStaticCopyPyodide() {
const pyodideDir = dirname(fileURLToPath(import.meta.resolve('pyodide')))
return viteStaticCopy({
targets: [
{
src: [join(pyodideDir, '*').replace(/\\/g, '/')].concat(PYODIDE_EXCLUDE),
dest: 'assets',
},
],
})
}
interface PyPIPackage {
package: string
version: string
}
interface WheelData {
filename: string
buffer: Buffer
}
async function getPyPIWheelUrl(packageName: string, version: string): Promise<{ url: string; filename: string }> {
const pypiUrl = `https://pypi.org/pypi/${packageName}/${version}/json`
const response = await fetch(pypiUrl)
if (!response.ok) {
throw new Error(`Failed to fetch PyPI metadata: ${response.statusText}`)
}
const data = await response.json() as {
urls: Array<{
packagetype: string
filename: string
url: string
}>
}
// Find the wheel file (.whl) for py3-none-any
const wheelFile = data.urls.find((file) =>
file.packagetype === 'bdist_wheel' &&
file.filename.endsWith('-py3-none-any.whl')
)
if (!wheelFile) {
throw new Error(`No py3-none-any wheel found for ${packageName} ${version}`)
}
return {
url: wheelFile.url,
filename: wheelFile.filename,
}
}
export function downloadPyPIWheels(packages: PyPIPackage[]): Plugin {
const wheels: WheelData[] = []
return {
name: 'download-pypi-wheels',
async buildStart() {
try {
// Download all wheels in parallel
const downloadPromises = packages.map(async ({ package: packageName, version }) => {
console.log(`[download-pypi-wheels] Downloading ${packageName}@${version} from PyPI...`)
// Get wheel info from PyPI
const { url, filename } = await getPyPIWheelUrl(packageName, version)
// Download the wheel file
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Failed to download ${filename}: ${response.statusText}`)
}
const arrayBuffer = await response.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
console.log(`[download-pypi-wheels] Successfully downloaded ${filename}`)
return { filename, buffer }
})
const downloadedWheels = await Promise.all(downloadPromises)
wheels.push(...downloadedWheels)
} catch (error) {
console.error(`[download-pypi-wheels] Error downloading wheels:`, error)
throw error
}
},
configureServer(server) {
// Serve wheel files during dev mode
server.middlewares.use((req, res, next) => {
const wheel = wheels.find((w) => req.url === `/${w.filename}`)
if (wheel) {
res.setHeader('Content-Type', 'application/octet-stream')
res.setHeader('Content-Length', wheel.buffer.length)
res.end(wheel.buffer)
return
}
next()
})
},
async generateBundle() {
if (wheels.length === 0) {
throw new Error('No wheel files were downloaded')
}
// Add all wheel files as assets to the bundle
for (const wheel of wheels) {
this.emitFile({
type: 'asset',
fileName: wheel.filename,
source: wheel.buffer,
})
}
},
}
}
// https://vite.dev/config/
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify(appVersion),
},
plugins: [
react(),
tailwindcss(),
downloadPyPIWheels([
{ package: 'pystitch', version: '1.0.0' },
]),
viteStaticCopyPyodide(),
],
optimizeDeps: {
exclude: ['pyodide'],
},
worker: {
format: 'es',
},
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
// Mark all dev server resources as same-origin
'Cross-Origin-Resource-Policy': 'same-origin',
},
},
})