Files
api/web/vite.config.ts
Eli Bosley af5ca11860 Feat/vue (#1655)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Introduced Docker management UI components: Overview, Logs, Console,
Preview, and Edit.
- Added responsive Card/Detail layouts with grouping, bulk actions, and
tabs.
  - New UnraidToaster component and global toaster configuration.
- Component auto-mounting improved with async loading and multi-selector
support.
- UI/UX
- Overhauled theme system (light/dark tokens, primary/orange accents)
and added theme variants.
  - Header OS version now includes integrated changelog modal.
- Registration displays warning states; multiple visual polish updates.
- API
  - CPU load now includes percentGuest and percentSteal metrics.
- Chores
  - Migrated web app to Vite; updated artifacts and manifests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
Co-authored-by: Michael Datelle <mdatelle@icloud.com>
2025-09-08 10:04:49 -04:00

185 lines
5.3 KiB
TypeScript

import path from 'node:path';
import { fileURLToPath, URL } from 'node:url';
import ui from '@nuxt/ui/vite';
import tailwindcss from '@tailwindcss/vite';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import removeConsole from 'vite-plugin-remove-console';
import { serveStaticHtml } from './vite-plugin-serve-static';
const dropConsole = process.env.VITE_ALLOW_CONSOLE_LOGS !== 'true';
console.log(dropConsole ? 'WARN: Console logs are disabled' : 'INFO: Console logs are enabled');
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const assetsDir = path.join(__dirname, '../api/dev/webGui/');
/**
* Used to avoid redeclaring variables in the webgui codebase.
*/
function terserReservations(inputStr: string) {
const combinations = ['ace'];
// Add 1-character combinations
for (let i = 0; i < inputStr.length; i++) {
combinations.push(inputStr[i]);
}
// Add 2-character combinations
for (let i = 0; i < inputStr.length; i++) {
for (let j = 0; j < inputStr.length; j++) {
combinations.push(inputStr[i] + inputStr[j]);
}
}
return combinations;
}
const charsToReserve = '_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
/**
* Shared terser options for consistent minification
*/
const sharedTerserOptions = {
mangle: {
reserved: terserReservations(charsToReserve),
toplevel: true,
},
};
/**
* Shared define configuration
*/
const sharedDefine = {
'globalThis.__DEV__': JSON.stringify(process.env.NODE_ENV === 'development'),
__VUE_PROD_DEVTOOLS__: JSON.stringify(false),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
'process.env': JSON.stringify({}),
};
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
tailwindcss(),
vue({
template: {
compilerOptions: {
// Treat all unraid-* components as custom elements
isCustomElement: (tag) => tag.startsWith('unraid-'),
},
},
}),
ui(),
serveStaticHtml(), // Serve static test pages
// Remove console logs in production
...(dropConsole
? [
removeConsole({
includes: ['log', 'info', 'debug'],
}),
]
: []),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'~': fileURLToPath(new URL('./src', import.meta.url)),
'~~': fileURLToPath(new URL('./', import.meta.url)),
'~/': fileURLToPath(new URL('./src/', import.meta.url)),
},
},
define: {
...sharedDefine,
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
},
publicDir: false, // Don't copy public files to dist
build: {
outDir: 'dist',
emptyOutDir: true,
manifest: false, // Disable Vite's manifest since we generate our own
lib: {
entry: fileURLToPath(new URL('./src/components/Wrapper/auto-mount.ts', import.meta.url)),
name: 'UnraidStandaloneApps',
fileName: 'standalone-apps',
formats: ['es'],
},
rollupOptions: {
output: {
format: 'es',
entryFileNames: 'standalone-apps.js',
chunkFileNames: '[name]-[hash].js',
assetFileNames: (assetInfo) => {
// Keep CSS files with predictable names
if (assetInfo.name?.endsWith('.css')) {
return 'standalone-apps.css';
}
return '[name]-[hash][extname]';
},
inlineDynamicImports: false,
},
},
cssCodeSplit: false, // Bundle all CSS together
minify: 'terser',
terserOptions: sharedTerserOptions,
},
server: {
port: 3000,
proxy: {
'/graphql': {
target: 'http://localhost:3001',
changeOrigin: true,
ws: true,
secure: false,
// Important: preserve the host header
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq) => {
proxyReq.setHeader('X-Forwarded-Host', 'localhost:3000');
proxyReq.setHeader('X-Forwarded-Proto', 'http');
proxyReq.setHeader('X-Forwarded-For', '127.0.0.1');
});
// Handle connection errors gracefully
proxy.on('error', (err: Error, _req: unknown, res: unknown) => {
console.warn('[Vite] GraphQL proxy error (API server may not be running):', err.message);
// Check if res has writeHead method (it's an HTTP response, not a socket)
const httpRes = res as {
writeHead?: (statusCode: number, headers: Record<string, string>) => void;
end?: (data: string) => void;
};
if (
httpRes &&
typeof httpRes.writeHead === 'function' &&
typeof httpRes.end === 'function'
) {
httpRes.writeHead(503, {
'Content-Type': 'application/json',
});
httpRes.end(
JSON.stringify({
error: 'GraphQL API server not available',
message: 'Please start the API server on port 3001',
})
);
}
});
},
},
'/webGui': {
target: `file://${assetsDir}`,
changeOrigin: true,
},
},
// Configure static file serving
fs: {
strict: false,
allow: ['..'], // Allow serving files outside of root
},
},
});