@edgeone/nuxt-pages 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +275 -0
  2. package/dist/build/content/server.js +18 -0
  3. package/dist/build/content/static.js +17 -0
  4. package/dist/build/functions/server.js +19 -0
  5. package/dist/build/plugin-context.js +18 -0
  6. package/dist/build/routes.js +18 -0
  7. package/dist/build/templates/nuxt-handler-backup.js +305 -0
  8. package/dist/build/templates/nuxt-handler-monorepo.tmpl-ipx_backup.js +511 -0
  9. package/dist/build/templates/nuxt-handler-monorepo.tmpl.js +243 -0
  10. package/dist/build/templates/nuxt-handler.tmpl.js +212 -0
  11. package/dist/esm-chunks/chunk-5YBUNNZ4.js +81 -0
  12. package/dist/esm-chunks/chunk-6BT4RYQJ.js +43 -0
  13. package/dist/esm-chunks/chunk-6YERJDAJ.js +208 -0
  14. package/dist/esm-chunks/chunk-GX4Z7KQX.js +15065 -0
  15. package/dist/esm-chunks/chunk-HBXUWFGE.js +19 -0
  16. package/dist/esm-chunks/chunk-HY3HNABZ.js +87 -0
  17. package/dist/esm-chunks/chunk-KGYBHZC3.js +1467 -0
  18. package/dist/esm-chunks/chunk-MMMRMLH2.js +132 -0
  19. package/dist/esm-chunks/chunk-NJ4SUJNF.js +5635 -0
  20. package/dist/esm-chunks/chunk-QG7JLDXY.js +127 -0
  21. package/dist/esm-chunks/chunk-RPSYO4VM.js +562 -0
  22. package/dist/esm-chunks/chunk-UOPC2N5A.js +69 -0
  23. package/dist/esm-chunks/chunk-V2LFVP3C.js +838 -0
  24. package/dist/index.js +61 -0
  25. package/dist/run/config.js +17 -0
  26. package/dist/run/constants.js +17 -0
  27. package/dist/run/handlers/cache.cjs +1410 -0
  28. package/dist/run/handlers/nuxt-cache.cjs +200 -0
  29. package/dist/run/handlers/nuxt-server.js +156 -0
  30. package/dist/run/handlers/request-context.cjs +148 -0
  31. package/dist/run/handlers/server.js +77 -0
  32. package/dist/run/handlers/tags-handler.cjs +177 -0
  33. package/dist/run/handlers/tracer.cjs +1004 -0
  34. package/dist/run/handlers/use-cache-handler.js +220 -0
  35. package/dist/run/handlers/wait-until.cjs +123 -0
  36. package/dist/run/headers.js +17 -0
  37. package/dist/run/revalidate.js +34 -0
  38. package/dist/run/storage/regional-blob-store.cjs +64 -0
  39. package/dist/run/storage/request-scoped-in-memory-cache.cjs +1582 -0
  40. package/dist/run/storage/storage.cjs +191 -0
  41. package/dist/shared/blob-types.cjs +37 -0
  42. package/dist/shared/blobkey.js +25 -0
  43. package/dist/shared/cache-types.cjs +33 -0
  44. package/dist/shared/nuxt-cache-types.cjs +18 -0
  45. package/dist/types/options.js +6 -0
  46. package/dist/utils.js +25 -0
  47. package/package.json +58 -0
@@ -0,0 +1,511 @@
1
+ import { resolve, dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { readFileSync, existsSync, statSync } from 'fs';
4
+ import { extname } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ // Static assets directory
10
+ const ASSET_DIR = resolve(__dirname, '../assets');
11
+
12
+ // MIME type mapping
13
+ const MIME_TYPES = {
14
+ '.html': 'text/html; charset=utf-8',
15
+ '.js': 'application/javascript',
16
+ '.css': 'text/css',
17
+ '.json': 'application/json',
18
+ '.png': 'image/png',
19
+ '.jpg': 'image/jpeg',
20
+ '.jpeg': 'image/jpeg',
21
+ '.gif': 'image/gif',
22
+ '.svg': 'image/svg+xml',
23
+ '.ico': 'image/x-icon',
24
+ '.txt': 'text/plain',
25
+ '.xml': 'application/xml'
26
+ };
27
+
28
+ /**
29
+ * Get the MIME type of a file
30
+ */
31
+ function getMimeType(filePath) {
32
+ const ext = extname(filePath).toLowerCase();
33
+ return MIME_TYPES[ext] || 'application/octet-stream';
34
+ }
35
+
36
+ /**
37
+ * 解析IPX参数
38
+ */
39
+ function parseIPXParams(paramString) {
40
+ const params = {};
41
+
42
+ if (!paramString || paramString === '_') {
43
+ return params;
44
+ }
45
+
46
+ // IPX参数格式: w_800&h_600&q_80&f_webp
47
+ const paramPairs = paramString.split('&');
48
+
49
+ for (const pair of paramPairs) {
50
+ if (pair.includes('_')) {
51
+ const [key, value] = pair.split('_', 2);
52
+
53
+ switch (key) {
54
+ case 'w':
55
+ params.width = parseInt(value);
56
+ break;
57
+ case 'h':
58
+ params.height = parseInt(value);
59
+ break;
60
+ case 's':
61
+ // 尺寸格式: s_800x600
62
+ if (value.includes('x')) {
63
+ const [w, h] = value.split('x');
64
+ params.width = parseInt(w);
65
+ params.height = parseInt(h);
66
+ }
67
+ break;
68
+ case 'q':
69
+ params.quality = parseInt(value);
70
+ break;
71
+ case 'f':
72
+ params.format = value;
73
+ break;
74
+ case 'fit':
75
+ params.fit = value;
76
+ break;
77
+ case 'b':
78
+ case 'bg':
79
+ params.background = value;
80
+ break;
81
+ case 'blur':
82
+ params.blur = parseInt(value);
83
+ break;
84
+ }
85
+ }
86
+ }
87
+
88
+ return params;
89
+ }
90
+
91
+ /**
92
+ * 处理IPX图片请求 - 简化版本,直接调用Nitro的IPX处理器
93
+ */
94
+ async function processIPXImage(ipxPath) {
95
+ try {
96
+ const app = await getNitroApp();
97
+ const fullPath = `/_ipx/${ipxPath}`;
98
+
99
+
100
+ // 检查本地文件是否存在,如果是本地文件且存在,直接处理
101
+ if (!ipxPath.includes('http')) {
102
+ const pathParts = ipxPath.split('/');
103
+ let filePath = '';
104
+ let params = {};
105
+
106
+ // 找到实际的文件路径(跳过参数)
107
+ for (let i = 0; i < pathParts.length; i++) {
108
+ const part = pathParts[i];
109
+ if (part.includes('.')) {
110
+ // 找到文件扩展名,这是文件路径的开始
111
+ filePath = pathParts.slice(i).join('/');
112
+ // 前面的部分是参数
113
+ if (i > 0) {
114
+ const paramString = pathParts.slice(0, i).join('&');
115
+ params = parseIPXParams(paramString);
116
+ }
117
+ break;
118
+ }
119
+ }
120
+
121
+ const localFilePath = resolve(ASSET_DIR, filePath);
122
+ // console.log(`Checking local file: ${localFilePath}`);
123
+ // console.log(`File exists: ${existsSync(localFilePath)}`);
124
+
125
+ // 如果是本地文件且存在,直接返回文件内容(暂时跳过IPX处理)
126
+ if (existsSync(localFilePath)) {
127
+ const fileContent = readFileSync(localFilePath);
128
+ const mimeType = getMimeType(localFilePath);
129
+
130
+ return {
131
+ statusCode: 200,
132
+ headers: {
133
+ 'Content-Type': mimeType,
134
+ 'Content-Length': fileContent.length.toString(),
135
+ 'Cache-Control': 'public, max-age=31536000',
136
+ 'from-server': 'true'
137
+ },
138
+ body: fileContent
139
+ };
140
+ }
141
+ }
142
+
143
+ const response = await app.localCall({
144
+ url: fullPath,
145
+ method: 'GET',
146
+ headers: {
147
+ 'accept': 'image/*'
148
+ },
149
+ body: ''
150
+ });
151
+
152
+ if (!response || response.status !== 200) {
153
+ console.log('IPX processing failed, status code:', response?.status);
154
+ return null;
155
+ }
156
+
157
+ // 处理响应体
158
+ let responseBody;
159
+
160
+ if (response.body) {
161
+ if (Buffer.isBuffer(response.body)) {
162
+ responseBody = response.body;
163
+ } else if (typeof response.body === 'string') {
164
+ // 对于图片数据,使用latin1编码保持二进制完整性
165
+ responseBody = Buffer.from(response.body, 'latin1');
166
+ } else if (response.body && typeof response.body.getReader === 'function') {
167
+ // 处理ReadableStream
168
+ const reader = response.body.getReader();
169
+ const chunks = [];
170
+
171
+ while (true) {
172
+ const { done, value } = await reader.read();
173
+ if (done) break;
174
+ chunks.push(Buffer.from(value));
175
+ }
176
+
177
+ responseBody = Buffer.concat(chunks);
178
+ } else if (response.body instanceof Uint8Array) {
179
+ responseBody = Buffer.from(response.body);
180
+ } else {
181
+ return null;
182
+ }
183
+ } else if (response._data) {
184
+ if (Buffer.isBuffer(response._data)) {
185
+ responseBody = response._data;
186
+ } else if (response._data instanceof Uint8Array) {
187
+ responseBody = Buffer.from(response._data);
188
+ } else if (typeof response._data === 'string') {
189
+ responseBody = Buffer.from(response._data, 'latin1');
190
+ } else {
191
+ console.log('Unknown _data format:', typeof response._data);
192
+ return null;
193
+ }
194
+ } else {
195
+ console.log('No image data found in IPX response');
196
+ return null;
197
+ }
198
+
199
+ if (!responseBody || responseBody.length === 0) {
200
+ console.log('Image data is empty after IPX processing');
201
+ return null;
202
+ }
203
+
204
+ // 获取内容类型
205
+ let contentType = 'image/jpeg';
206
+ if (response.headers) {
207
+ const headers = response.headers instanceof Headers ? response.headers : new Headers(response.headers);
208
+ contentType = headers.get('content-type') || contentType;
209
+ }
210
+
211
+ // 验证图片数据完整性
212
+ const isValidImage = responseBody.length > 0 && (
213
+ responseBody[0] === 0xFF || // JPEG
214
+ (responseBody[0] === 0x89 && responseBody[1] === 0x50) || // PNG
215
+ (responseBody[0] === 0x47 && responseBody[1] === 0x49) // GIF
216
+ );
217
+
218
+ // console.log(`IPX processing successful: size=${responseBody.length}bytes, type=${contentType}, valid image=${isValidImage}`);
219
+ // console.log(`Image header bytes: ${responseBody.slice(0, 10).toString('hex')}`);
220
+
221
+ return {
222
+ statusCode: 200,
223
+ headers: {
224
+ 'Content-Type': contentType,
225
+ 'Content-Length': responseBody.length.toString(),
226
+ 'Cache-Control': 'public, max-age=31536000', // 1年缓存
227
+ 'Accept-Ranges': 'bytes',
228
+ 'Access-Control-Allow-Origin': '*',
229
+ 'from-server': 'true'
230
+ },
231
+ body: responseBody
232
+ };
233
+
234
+ } catch (error) {
235
+ console.error('IPX processing error:', error);
236
+ console.error('Error stack:', error.stack);
237
+ return null;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Handle static file requests
243
+ */
244
+ async function handleStaticFile(url) {
245
+ try {
246
+ // Remove query parameters
247
+ let cleanUrl = url.split('?')[0];
248
+
249
+ // Handle IPX image processing paths from @nuxt/image
250
+ // 直接使用IPX处理图片,而不是重定向
251
+ if (cleanUrl.startsWith('/_ipx/')) {
252
+ const ipxPath = cleanUrl.slice(6); // Remove '/_ipx/'
253
+ return processIPXImage(ipxPath);
254
+ }
255
+
256
+ // Handle EdgeOne SSR functions IPX paths
257
+ // 直接使用IPX处理图片
258
+ if(cleanUrl.includes('-ssr-functions/_ipx/')) {
259
+ // 提取IPX路径部分
260
+ const ipxIndex = cleanUrl.indexOf('_ipx/');
261
+ if (ipxIndex !== -1) {
262
+ const ipxPath = cleanUrl.slice(ipxIndex + 5); // Remove '_ipx/'
263
+ return processIPXImage(ipxPath);
264
+ }
265
+ return null;
266
+ }
267
+
268
+ const possiblePaths = [];
269
+
270
+ // Direct file path
271
+ const directPath = resolve(ASSET_DIR, cleanUrl.startsWith('/') ? cleanUrl.slice(1) : cleanUrl);
272
+ possiblePaths.push(directPath);
273
+
274
+ // Try each possible path
275
+ for (const filePath of possiblePaths) {
276
+ // Security check: ensure file is within asset directory
277
+ if (!filePath.startsWith(ASSET_DIR)) {
278
+ continue;
279
+ }
280
+
281
+ if (existsSync(filePath) && statSync(filePath).isFile()) {
282
+ const content = readFileSync(filePath);
283
+ const mimeType = getMimeType(filePath);
284
+
285
+ return {
286
+ statusCode: 200,
287
+ headers: {
288
+ 'Content-Type': mimeType,
289
+ 'Content-Length': content.length.toString(),
290
+ 'Cache-Control': 'public, max-age=31536000' // 1 year cache
291
+ },
292
+ body: content
293
+ };
294
+ }
295
+ }
296
+ } catch (error) {
297
+ console.error('Static file error:', error);
298
+ }
299
+
300
+ return null;
301
+ }
302
+
303
+ /**
304
+ * Lazy load Nitro application
305
+ */
306
+ let nitroApp = null;
307
+ async function getNitroApp() {
308
+ if (!nitroApp) {
309
+ // Set environment variables to prevent automatic server startup
310
+ process.env.NITRO_PORT = '';
311
+ process.env.PORT = '';
312
+
313
+ // Set correct static assets path
314
+ process.env.NITRO_PUBLIC_DIR = ASSET_DIR;
315
+
316
+ const { j: useNitroApp } = await import('./chunks/nitro/nitro.mjs');
317
+ nitroApp = useNitroApp();
318
+
319
+ // 检查IPX配置
320
+ const runtimeConfig = nitroApp.hooks.callHook ? await nitroApp.hooks.callHook('render:route', { url: '/' }).catch(() => null) : null;
321
+ console.log('Nitro application initialized');
322
+ }
323
+ return nitroApp;
324
+ }
325
+
326
+ /**
327
+ * Handle HTTP response
328
+ */
329
+ function handleResponse(response, event) {
330
+ if (!response) {
331
+ return {
332
+ statusCode: 500,
333
+ headers: { 'Content-Type': 'text/plain' },
334
+ body: 'Internal Server Error'
335
+ };
336
+ }
337
+
338
+ const headers = {};
339
+
340
+ // Ensure response.headers is a Headers object
341
+ if (!(response.headers instanceof Headers)) {
342
+ response.headers = new Headers(response.headers || {});
343
+ }
344
+
345
+ // Correctly iterate over Headers object (using entries() method)
346
+ for (const [key, value] of response.headers.entries()) {
347
+ headers[key] = value;
348
+ }
349
+
350
+ // Check if Content-Type already exists (case-insensitive)
351
+ const hasContentType = response.headers.has('content-type');
352
+
353
+ // Only set default value if Content-Type is missing
354
+ if (!hasContentType) {
355
+ headers['Content-Type'] = 'text/html; charset=utf-8';
356
+ }
357
+
358
+ headers['from-server'] = 'true';
359
+
360
+ // Handle set-cookie header (special handling, as there may be multiple values)
361
+ if (response.headers.has('set-cookie')) {
362
+ const cookieArr = response.headers.getSetCookie();
363
+ headers['set-cookie'] = Array.isArray(cookieArr) ? cookieArr : [cookieArr];
364
+ }
365
+
366
+ return {
367
+ statusCode: response.status || response.statusCode || 200,
368
+ headers,
369
+ body: response.body || response._data || ''
370
+ };
371
+ }
372
+
373
+ /**
374
+ * EdgeOne function handler
375
+ */
376
+ export async function handler(event, context) {
377
+ try {
378
+ const url = event.path || '/';
379
+ const method = event.httpMethod || event.method || 'GET';
380
+ const headers = event.headers || {};
381
+ const body = event.body || '';
382
+
383
+ // First try to handle static assets
384
+ if (method === 'GET') {
385
+ const staticResponse = await handleStaticFile(url);
386
+ if (staticResponse) {
387
+ return staticResponse;
388
+ }
389
+ }
390
+
391
+ // Handle dynamic requests
392
+ const app = await getNitroApp();
393
+
394
+ try {
395
+ const response = await app.localCall({
396
+ url,
397
+ method,
398
+ headers,
399
+ body
400
+ });
401
+
402
+ return handleResponse(response, event);
403
+ } catch (nitroError) {
404
+ // Handle Nitro static file read errors (prerender files not found)
405
+ // Check error and its cause property (H3Error may wrap actual error in cause)
406
+ const actualError = nitroError?.cause || nitroError;
407
+ const errorPath = actualError?.path || nitroError?.path;
408
+ const errorCode = actualError?.code || nitroError?.code;
409
+
410
+ // If error is due to prerender static file not found, try dynamic rendering
411
+ if (errorCode === 'ENOENT' &&
412
+ errorPath &&
413
+ (errorPath.includes('/assets/') || errorPath.includes('assets/')) &&
414
+ (errorPath.includes('/index.html') || errorPath.includes('index.html'))) {
415
+ console.warn(`Prerender file not found: ${errorPath}, falling back to dynamic rendering for ${url}`);
416
+
417
+ // If static file handling has been tried but file not found, should perform dynamic rendering
418
+ // Nitro should be able to handle dynamic routes, but if it still tries to read static files,
419
+ // it may be due to configuration issues. We throw an error directly to let user know to build or check configuration
420
+ throw new Error(`Prerender route ${url} not found. Make sure to run build first or configure routeRules correctly. Original error: ${actualError?.message || nitroError?.message}`);
421
+ }
422
+
423
+ // Other errors are thrown directly
424
+ throw nitroError;
425
+ }
426
+ } catch (error) {
427
+ console.error('EdgeOne handler error:', error);
428
+ return {
429
+ statusCode: 500,
430
+ headers: { 'Content-Type': 'text/plain' },
431
+ body: `Internal Server Error: ${error.message}`
432
+ };
433
+ }
434
+ }
435
+
436
+ import('http').then(async (http) => {
437
+ const { createServer } = http;
438
+ // Dynamically import stream module to handle ReadableStream
439
+ await import('stream').then(({ Readable, pipeline }) => {
440
+ const server = createServer(async (req, res) => {
441
+ try {
442
+ const event = {
443
+ path: req.url,
444
+ httpMethod: req.method,
445
+ headers: req.headers,
446
+ body: ''
447
+ };
448
+
449
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
450
+ const chunks = [];
451
+ for await (const chunk of req) {
452
+ chunks.push(chunk);
453
+ }
454
+ event.body = Buffer.concat(chunks).toString();
455
+ }
456
+
457
+ const result = await handler(event, {});
458
+
459
+ res.statusCode = result.statusCode;
460
+ Object.entries(result.headers).forEach(([key, value]) => {
461
+ if(key === 'set-cookie') {
462
+ res.setHeader('set-cookie', Array.isArray(value) ? value[0].split(',') : value);
463
+ } else {
464
+ res.setHeader(key, value);
465
+ }
466
+ });
467
+
468
+ // Handle response body: support Buffer, string, and ReadableStream
469
+ if (Buffer.isBuffer(result.body)) {
470
+ res.end(result.body);
471
+ } else if (result.body && typeof result.body === 'object' && typeof result.body.getReader === 'function') {
472
+ // Detect ReadableStream (Web Streams API)
473
+ try {
474
+ const nodeStream = Readable.fromWeb(result.body);
475
+ nodeStream.pipe(res);
476
+ } catch (streamError) {
477
+ console.error('Stream conversion error:', streamError);
478
+ // If conversion fails, try to read the entire stream
479
+ const reader = result.body.getReader();
480
+ const chunks = [];
481
+ try {
482
+ while (true) {
483
+ const { done, value } = await reader.read();
484
+ if (done) break;
485
+ chunks.push(Buffer.from(value));
486
+ }
487
+ res.end(Buffer.concat(chunks));
488
+ } catch (readError) {
489
+ console.error('Stream read error:', readError);
490
+ res.end();
491
+ }
492
+ }
493
+ } else {
494
+ // Handle string or other types
495
+ res.end(result.body || '');
496
+ }
497
+ } catch (error) {
498
+ console.error('Local server error:', error);
499
+ res.statusCode = 500;
500
+ res.setHeader('Content-Type', 'text/plain');
501
+ res.end(`Server Error: ${error.message}`);
502
+ }
503
+ });
504
+
505
+ const port = process.env.DEV_PORT || 9000;
506
+ server.listen(port, () => {
507
+ console.log(`EdgeOne development server running at http://localhost:${port}`);
508
+ console.log(`Static assets served from: ${ASSET_DIR}`);
509
+ });
510
+ });
511
+ });