UNPKG

@edgeone/nuxt-pages

Version:

A professional deployment package that seamlessly deploys your Nuxt 3/4 applications to Tencent Cloud EdgeOne platform with optimized performance and intelligent caching.

244 lines (211 loc) 7.7 kB
import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; import { readFileSync, existsSync, statSync } from 'fs'; import { extname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Static assets directory const ASSET_DIR = resolve(__dirname, '../assets'); // MIME type mapping const MIME_TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.txt': 'text/plain', '.xml': 'application/xml' }; /** * Get the MIME type of a file */ function getMimeType(filePath) { const ext = extname(filePath).toLowerCase(); return MIME_TYPES[ext] || 'application/octet-stream'; } /** * Handle static file requests */ function handleStaticFile(url) { try { // Remove query parameters let cleanUrl = url.split('?')[0]; // Handle IPX image processing paths from @nuxt/image // Convert /_ipx/s_800x600/hero.png to /hero.png // Also handles nested paths: /_ipx/s_800x600/images/hero.png -> /images/hero.png if (cleanUrl.startsWith('/_ipx/')) { let newUrl = ''; // Remove /_ipx/ prefix const ipxPath = cleanUrl.slice(6); // Remove '/_ipx/' // IPX format: /_ipx/[params]/[original_path] // Parameters are typically the first segment(s) and contain underscores/commas // The original file path starts after the params const pathSegments = ipxPath.split('/').filter(s => s); // Remove empty segments if (pathSegments.length === 0) { // Empty path after /_ipx/, skip return null; } // Check each segment for file extension for (let i = 0; i < pathSegments.length; i++) { const segment = pathSegments[i]; if(segment.startsWith('s_') || segment.startsWith('w_') || segment.startsWith('h_') || segment.startsWith('q_') || segment.startsWith('f_') || segment.startsWith('c_') || segment.startsWith('bg_') || segment.startsWith('blur_') || segment.startsWith('_')) { continue; } // Check if segment ends with a known image extension newUrl += '/' + segment; } if(cleanUrl.includes('http://') || cleanUrl.includes('https://')) { newUrl = cleanUrl.includes('http://') ? cleanUrl.slice(cleanUrl.indexOf('http://')) : cleanUrl.slice(cleanUrl.indexOf('https://')); } return { statusCode: 302, headers: { 'from-server': 'true', location: newUrl } } } if(cleanUrl.includes('-ssr-functions/_ipx/')) { let newUrl = ''; const pathSegments = cleanUrl.split('/') // Remove empty segments if(pathSegments.length === 0) { return null; } // Check each segment for file extension newUrl = pathSegments.slice(4).join('/'); if(newUrl.includes('http:/') && !newUrl.includes('http://')) { newUrl = newUrl.replace('http:/', 'http://'); } else if(newUrl.includes('https:/') && !newUrl.includes('https://')) { newUrl = newUrl.replace('https:/', 'https://'); } return { statusCode: 302, headers: { 'from-server': 'true', location: newUrl } } } // 本地调试寻找文件路径 const possiblePaths = []; // Direct file path const directPath = resolve(ASSET_DIR, cleanUrl.startsWith('/') ? cleanUrl.slice(1) : cleanUrl); possiblePaths.push(directPath); // Try each possible path for (const filePath of possiblePaths) { // Security check: ensure file is within asset directory if (!filePath.startsWith(ASSET_DIR)) { continue; } if (existsSync(filePath) && statSync(filePath).isFile()) { const content = readFileSync(filePath); const mimeType = getMimeType(filePath); return { statusCode: 200, headers: { 'Content-Type': mimeType, 'Content-Length': content.length.toString(), 'Cache-Control': 'public, max-age=31536000' // 1 year cache }, body: content }; } } } catch (error) { console.error('Static file error:', error); } return null; } /** * Lazy load Nitro application */ let nitroApp = null; async function getNitroApp() { if (!nitroApp) { // Set environment variables to prevent automatic server startup process.env.NITRO_PORT = ''; process.env.PORT = ''; // Set correct static assets path process.env.NITRO_PUBLIC_DIR = ASSET_DIR; const { {{USE_NITRO_APP_SYMBOL}}: useNitroApp } = await import('./chunks/nitro/nitro.mjs'); nitroApp = useNitroApp(); } return nitroApp; } async function getBody(req) { if (req.method !== 'GET' && req.method !== 'HEAD') { const chunks = []; for await (const chunk of req) { chunks.push(chunk); } const body = Buffer.concat(chunks).toString(); return body; } return ''; } /** * EdgeOne function handler */ export default async function handler(req, res, context) { try { const url = req.url || '/'; const method = req.method || 'GET'; const headers = req.headers || new Headers(); const body = await getBody(req); // First try to handle static assets if (method === 'GET') { const staticResponse = handleStaticFile(url); if (staticResponse) { return new Response(staticResponse.body, { status: staticResponse.statusCode, headers: staticResponse.headers }); } } // Handle dynamic requests const app = await getNitroApp(); try { const response = await app.localCall({ url, method, headers, body }); return new Response(response.body, { status: response.status || response.statusCode, headers: response.headers }); } catch (nitroError) { // Handle Nitro static file read errors (prerender files not found) // Check error and its cause property (H3Error may wrap actual error in cause) const actualError = nitroError?.cause || nitroError; const errorPath = actualError?.path || nitroError?.path; const errorCode = actualError?.code || nitroError?.code; // If error is due to prerender static file not found, try dynamic rendering if (errorCode === 'ENOENT' && errorPath && (errorPath.includes('/assets/') || errorPath.includes('assets/')) && (errorPath.includes('/index.html') || errorPath.includes('index.html'))) { console.warn(`Prerender file not found: ${errorPath}, falling back to dynamic rendering for ${url}`); // If static file handling has been tried but file not found, should perform dynamic rendering // Nitro should be able to handle dynamic routes, but if it still tries to read static files, // it may be due to configuration issues. We throw an error directly to let user know to build or check configuration throw new Error(`Prerender route ${url} not found. Make sure to run build first or configure routeRules correctly. Original error: ${actualError?.message || nitroError?.message}`); } // Other errors are thrown directly throw nitroError; } } catch (error) { return new Response(`Server Error: ${error.message}`, { status: 500, headers: { 'Content-Type': 'text/plain' } }); } }