UNPKG

hyde-vite-plugin

Version:

HydePHP Vite plugin for realtime compiler integration

190 lines (169 loc) 5.52 kB
import { Plugin, ResolvedConfig } from 'vite'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // The interprocess communication signal file for the web server const HOT_FILE_PATH = 'app/storage/framework/runtime/vite.hot'; export interface HydePluginOptions { /** * Asset entry points to process * * @default ['resources/assets/app.css', 'resources/assets/app.js'] */ input?: string[]; /** * Enable hot reloading for content files * * @default true */ refresh?: boolean; /** * Content directories to watch for changes * * @default ['_pages', '_posts', '_docs'] */ watch?: string[]; } /** * Resolve the path to a resource, ensuring that the path works when used as an ESM package. */ function resolveResource(resource: string): string { // In ESM context, __dirname is not available, so we use fileURLToPath try { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); return path.resolve(__dirname, '../resources', resource); } catch (error) { // Fallback for CommonJS return path.resolve(__dirname, '../resources', resource); } } /** * Check if a file exists and is a file */ function fileExists(file: string): boolean { try { return fs.statSync(file).isFile(); } catch { return false; } } /** * Check if the JavaScript file has actual content to prevent empty app.js files from being compiled */ function hasJavaScriptContent(filePath: string): boolean { try { if (!fs.existsSync(filePath)) return false; const content = fs.readFileSync(filePath, 'utf-8'); // Remove comments and check if there's any actual code return content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '').trim().length > 0; } catch (error) { return false; } } /** * HydePHP Vite plugin for realtime compiler integration */ export default function hydePlugin(options: HydePluginOptions = {}): Plugin { const { input = ['resources/assets/app.css', 'resources/assets/app.js'], watch = ['_pages', '_posts', '_docs'], refresh = true, } = options; let config: ResolvedConfig; let hotFilePath: string; return { name: 'hyde-vite-plugin', config(config, { command }) { // Only modify build configuration if (command === 'build') { // Process input files - use named keys to prevent filename collisions const resolvedInput: Record<string, string> = {}; for (const entry of input) { const resolvedPath = path.resolve(process.cwd(), entry); // Only include app.js if it has actual content if (entry.endsWith('app.js')) { if (hasJavaScriptContent(resolvedPath) && fileExists(resolvedPath)) { resolvedInput.js = resolvedPath; } } else if (entry.endsWith('app.css') && fileExists(resolvedPath)) { resolvedInput.css = resolvedPath; } else if (fileExists(resolvedPath)) { const basename = path.basename(entry, path.extname(entry)); resolvedInput[basename] = resolvedPath; } } return { build: { outDir: '_media', emptyOutDir: false, rollupOptions: { input: resolvedInput, output: { entryFileNames: (chunkInfo) => { // Use app.js for the JS entry point if (chunkInfo.name === 'js') { return 'app.js'; } return '[name].js'; }, chunkFileNames: '[name].js', assetFileNames: (assetInfo) => { // Use app.css for CSS assets if (assetInfo.name && assetInfo.name.endsWith('.css')) { return 'app.css'; } return '[name].[ext]'; } } } } }; } }, configResolved(resolvedConfig) { config = resolvedConfig; hotFilePath = path.resolve(process.cwd(), HOT_FILE_PATH); }, configureServer(server) { // Create hot file when Vite server starts fs.mkdirSync(path.dirname(hotFilePath), { recursive: true }); fs.writeFileSync(hotFilePath, ''); // Remove hot file when Vite server closes ['SIGINT', 'SIGTERM'].forEach(signal => { process.on(signal, () => { try { fs.rmSync(hotFilePath); } catch (error) { // Ignore errors when removing hot file } process.exit(); }); }); // Render the Vite index page when the root URL is requested server.middlewares.use((req, res, next) => { if (req.url === '/') { try { const indexPath = resolveResource('index.html'); const content = fs.readFileSync(indexPath, 'utf-8'); res.end(content); } catch (error) { next(); } } else { next(); } }); // Add additional watch paths for content files if refresh option is enabled if (refresh) { watch.forEach(dir => { const contentPath = path.resolve(process.cwd(), dir); if (fs.existsSync(contentPath)) { server.watcher.add(path.join(contentPath, '**')); server.watcher.add(path.join(contentPath, '**/**')); } }); } } }; }