import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const cesiumSource = path.join(__dirname, 'node_modules', 'cesium', 'Build', 'Cesium') const modelSource = path.join(__dirname, 'model') const adminiveFrontend = path.join(__dirname, '..', 'Adminive', 'frontend') const amisSdkSource = path.join(adminiveFrontend, 'node_modules', 'amis', 'sdk') const amisPackage = JSON.parse(fs.readFileSync(path.join(adminiveFrontend, 'node_modules', 'amis', 'package.json'), 'utf8')) const amisSdkUrlPrefix = `/ui/vendor/amis/${amisPackage.version}/` function amisContentType(fileName) { const extension = path.extname(fileName).toLowerCase() if (extension === '.css') return 'text/css; charset=utf-8' if (extension === '.js') return 'text/javascript; charset=utf-8' if (extension === '.json') return 'application/json; charset=utf-8' if (extension === '.svg') return 'image/svg+xml' if (extension === '.woff') return 'font/woff' if (extension === '.woff2') return 'font/woff2' if (extension === '.ttf') return 'font/ttf' return 'application/octet-stream' } function serveAmisSdk(server) { server.middlewares.use((request, response, next) => { const pathname = new URL(request.url || '/', 'http://localhost').pathname if (!pathname.startsWith(amisSdkUrlPrefix)) { next() return } const relativePath = decodeURIComponent(pathname.slice(amisSdkUrlPrefix.length)) const sourcePath = path.resolve(amisSdkSource, relativePath) if (sourcePath !== amisSdkSource && !sourcePath.startsWith(`${amisSdkSource}${path.sep}`)) { next() return } try { response.statusCode = 200 response.setHeader('Content-Type', amisContentType(sourcePath)) response.end(fs.readFileSync(sourcePath)) } catch { next() } }) } function amisSdkPlugin() { return { name: 'ecap-adminive-amis-sdk', enforce: 'pre', resolveId(source) { if (source === 'amis' || source.startsWith('amis/')) { this.error(`Do not import ${source} into the Vite module graph; use the AMIS SDK runtime`) } return null }, buildStart() { const emitDirectory = (directory, prefix) => { for (const entry of fs.readdirSync(directory)) { const sourcePath = path.join(directory, entry) const relativePath = `${prefix}${entry}` if (fs.statSync(sourcePath).isDirectory()) { emitDirectory(sourcePath, `${relativePath}/`) } else { this.emitFile({type: 'asset', fileName: `vendor/amis/${amisPackage.version}/${relativePath}`, source: fs.readFileSync(sourcePath)}) } } } emitDirectory(amisSdkSource, '') }, transformIndexHtml: { order: 'post', handler() { return [ {tag: 'link', attrs: {rel: 'stylesheet', href: `${amisSdkUrlPrefix}sdk.css`}, injectTo: 'head-prepend'}, {tag: 'link', attrs: {rel: 'stylesheet', href: `${amisSdkUrlPrefix}helper.css`}, injectTo: 'head-prepend'}, {tag: 'link', attrs: {rel: 'stylesheet', href: `${amisSdkUrlPrefix}iconfont.css`}, injectTo: 'head-prepend'}, {tag: 'script', attrs: {src: `${amisSdkUrlPrefix}sdk.js`}, injectTo: 'head-prepend'} ] } }, configureServer(server) { serveAmisSdk(server) }, configurePreviewServer(server) { serveAmisSdk(server) } } } function copyDir(source, target) { fs.mkdirSync(target, { recursive: true }) for (const entry of fs.readdirSync(source, { withFileTypes: true })) { const sourcePath = path.join(source, entry.name) const targetPath = path.join(target, entry.name) if (entry.isDirectory()) { copyDir(sourcePath, targetPath) } else { fs.copyFileSync(sourcePath, targetPath) } } } function cesiumAssetsPlugin() { let outDir = path.join(__dirname, 'wwwroot') const serveStatic = (root, req, res, next) => { const urlPath = decodeURIComponent((req.url || '').split('?')[0]).replace(/^\/+/, '') const filePath = path.normalize(path.join(root, urlPath)) if (!filePath.startsWith(root) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { next() return } const contentType = filePath.endsWith('.js') ? 'application/javascript' : filePath.endsWith('.css') ? 'text/css' : filePath.endsWith('.json') ? 'application/json' : filePath.endsWith('.glb') ? 'model/gltf-binary' : 'application/octet-stream' res.setHeader('Content-Type', contentType) fs.createReadStream(filePath).pipe(res) } return { name: 'ecap-cesium-assets', configResolved(config) { outDir = path.isAbsolute(config.build.outDir) ? config.build.outDir : path.join(config.root, config.build.outDir) }, configureServer(server) { server.middlewares.use('/ui/cesium', (req, res, next) => { serveStatic(cesiumSource, req, res, next) }) server.middlewares.use('/ui/model', (req, res, next) => { serveStatic(modelSource, req, res, next) }) }, closeBundle() { copyDir(cesiumSource, path.join(outDir, 'cesium')) copyDir(modelSource, path.join(outDir, 'model')) } } } const spaAliasPaths = new Set([ '/map', '/map3d', '/aircraftlist', '/settings', '/ui/map', '/ui/map3d', '/ui/aircraftlist', '/ui/settings' ]) function spaAliasesPlugin() { return { name: 'ecap-spa-aliases', configureServer(server) { server.middlewares.use(async (req, res, next) => { const urlPath = decodeURIComponent((req.url || '').split('?')[0]) if (!spaAliasPaths.has(urlPath)) { next() return } try { const indexPath = path.join(__dirname, 'index.html') let html = fs.readFileSync(indexPath, 'utf-8') html = await server.transformIndexHtml('/ui/index.html', html) res.statusCode = 200 res.setHeader('Content-Type', 'text/html') res.end(html) } catch (error) { next(error) } }) } } } // https://vite.dev/config/ export default defineConfig({ base: '/ui/', publicDir: 'public', // 默认就是 public define: { CESIUM_BASE_URL: JSON.stringify('/ui/cesium/') }, plugins: [react(), amisSdkPlugin(), cesiumAssetsPlugin(), spaAliasesPlugin()], server: { proxy: { '/map/imagery': { target: 'http://127.0.0.1', changeOrigin: true, }, '/map/terrain': { target: 'http://127.0.0.1', changeOrigin: true, }, '/map/resources': { target: 'http://127.0.0.1', changeOrigin: true, }, '/map/graphics': { target: 'http://127.0.0.1', changeOrigin: true, }, '/map/view': { target: 'http://127.0.0.1', changeOrigin: true, }, '/map/models': { target: 'http://127.0.0.1', changeOrigin: true, }, '/tiles': { target: 'http://127.0.0.1', changeOrigin: true, }, '/api': { target: 'http://127.0.0.1', changeOrigin: true, }, '/ws': { target: 'http://127.0.0.1', // drogon 的端口!! ws: true, // ☆ 必须 ☆ changeOrigin: true }, '/aircraftlist.json': { target: 'http://127.0.0.1' }, }, fs:{ strict: false } }, build: { outDir: 'wwwroot' } })