@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
JavaScript
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'
}
});
}
}