@edgeone/nuxt-pages 1.0.15 → 1.0.16

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.
@@ -7,8 +7,8 @@
7
7
  import {
8
8
  addNitroBuildOutputConfig,
9
9
  resetNitroConfig
10
- } from "../../esm-chunks/chunk-BZJ7SD7E.js";
11
- import "../../esm-chunks/chunk-ZG3P7BHA.js";
10
+ } from "../../esm-chunks/chunk-7X4RPD4I.js";
11
+ import "../../esm-chunks/chunk-MONI3XWQ.js";
12
12
  import "../../esm-chunks/chunk-V2LFVP3C.js";
13
13
  import "../../esm-chunks/chunk-6BT4RYQJ.js";
14
14
  export {
@@ -5,15 +5,13 @@
5
5
  })();
6
6
 
7
7
  import {
8
- clearStaleServerHandlers,
9
8
  createServerHandler,
10
9
  patchNitroHandler
11
- } from "../../esm-chunks/chunk-DOOA3WBA.js";
10
+ } from "../../esm-chunks/chunk-RNEZUAPL.js";
12
11
  import "../../esm-chunks/chunk-NJ4SUJNF.js";
13
12
  import "../../esm-chunks/chunk-V2LFVP3C.js";
14
13
  import "../../esm-chunks/chunk-6BT4RYQJ.js";
15
14
  export {
16
- clearStaleServerHandlers,
17
15
  createServerHandler,
18
16
  patchNitroHandler
19
17
  };
@@ -5,14 +5,12 @@
5
5
  })();
6
6
 
7
7
  import {
8
- EDGE_HANDLER_NAME,
9
8
  PluginContext,
10
9
  SERVER_HANDLER_NAME
11
- } from "../esm-chunks/chunk-ESOQYHZZ.js";
12
- import "../esm-chunks/chunk-ZG3P7BHA.js";
10
+ } from "../esm-chunks/chunk-BZQMVWYQ.js";
11
+ import "../esm-chunks/chunk-MONI3XWQ.js";
13
12
  import "../esm-chunks/chunk-6BT4RYQJ.js";
14
13
  export {
15
- EDGE_HANDLER_NAME,
16
14
  PluginContext,
17
15
  SERVER_HANDLER_NAME
18
16
  };
@@ -5,14 +5,12 @@
5
5
  })();
6
6
 
7
7
  import {
8
- convertNuxtRoutePattern,
9
8
  createNuxtApiRoutesMeta,
10
9
  createNuxtPagesRouteMeta
11
- } from "../esm-chunks/chunk-KYSZO4TW.js";
12
- import "../esm-chunks/chunk-ZG3P7BHA.js";
10
+ } from "../esm-chunks/chunk-J25U56II.js";
11
+ import "../esm-chunks/chunk-MONI3XWQ.js";
13
12
  import "../esm-chunks/chunk-6BT4RYQJ.js";
14
13
  export {
15
- convertNuxtRoutePattern,
16
14
  createNuxtApiRoutesMeta,
17
15
  createNuxtPagesRouteMeta
18
16
  };
@@ -136,7 +136,15 @@ async function getNitroApp() {
136
136
  // Set correct static assets path
137
137
  process.env.NITRO_PUBLIC_DIR = ASSET_DIR;
138
138
 
139
- const { {{USE_NITRO_APP_SYMBOL}}: useNitroApp } = await import('./chunks/nitro/nitro.mjs');
139
+ const nitroModule = await (async () => {
140
+ try {
141
+ return await import('./chunks/nitro/nitro.mjs')
142
+ } catch {
143
+ return await import('./chunks/_/nitro.mjs')
144
+ }
145
+ })()
146
+
147
+ const { {{USE_NITRO_APP_SYMBOL}}: useNitroApp } = nitroModule
140
148
  nitroApp = useNitroApp();
141
149
  }
142
150
  return nitroApp;
@@ -7,7 +7,7 @@
7
7
  import {
8
8
  addCodeToGenerateEdgeoneWithAST,
9
9
  resetNitroConfigWithAST
10
- } from "./chunk-ZG3P7BHA.js";
10
+ } from "./chunk-MONI3XWQ.js";
11
11
  import {
12
12
  trace,
13
13
  wrapTracer
@@ -9,21 +9,19 @@ import {
9
9
  getPrerenderRoutesWithAST,
10
10
  getRouteRulesWithAST,
11
11
  getRoutesArrayWithAST
12
- } from "./chunk-ZG3P7BHA.js";
12
+ } from "./chunk-MONI3XWQ.js";
13
13
 
14
14
  // src/build/plugin-context.ts
15
15
  import { existsSync, readFileSync } from "node:fs";
16
16
  import { readFile } from "node:fs/promises";
17
- import { execSync } from "node:child_process";
17
+ import { createRequire } from "node:module";
18
18
  import { join, relative, resolve } from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
20
  var MODULE_DIR = fileURLToPath(new URL(".", import.meta.url));
21
21
  var PLUGIN_DIR = join(MODULE_DIR, "../..");
22
22
  var DEFAULT_BUILD_DIR = ".nuxt";
23
23
  var DEFAULT_OUTPUT_DIR = ".edgeone";
24
- var NUXT_STATIC_DIR = "assets";
25
24
  var SERVER_HANDLER_NAME = "server-handler";
26
- var EDGE_HANDLER_NAME = "edgeone-edge-handler";
27
25
  var PluginContext = class {
28
26
  edgeoneConfig;
29
27
  pluginName;
@@ -39,10 +37,6 @@ var PluginContext = class {
39
37
  get relPublishDir() {
40
38
  return this.constants.PUBLISH_DIR ?? join(this.constants.PACKAGE_PATH || "", DEFAULT_OUTPUT_DIR);
41
39
  }
42
- /** Temporary directory for stashing the build output */
43
- get tempPublishDir() {
44
- return this.resolveFromPackagePath(".edgeone/server-handler");
45
- }
46
40
  /** Absolute path of the publish directory */
47
41
  get publishDir() {
48
42
  return resolve(this.relPublishDir);
@@ -62,12 +56,6 @@ var PluginContext = class {
62
56
  get lambdaWorkingDirectory() {
63
57
  return "";
64
58
  }
65
- /**
66
- * Retrieves the root of the `.output` directory
67
- */
68
- get outputRootDir() {
69
- return join(this.publishDir, "server-handler");
70
- }
71
59
  /**
72
60
  * The resolved relative nuxt build directory defaults to `.nuxt`,
73
61
  * but can be configured through the nuxt.config.js. For monorepos this will include the packagePath
@@ -77,45 +65,10 @@ var PluginContext = class {
77
65
  const dir = this.buildConfig?.buildDir ?? DEFAULT_BUILD_DIR;
78
66
  return relative(process.cwd(), resolve(this.relativeAppDir, dir));
79
67
  }
80
- /** Represents the parent directory of the .nuxt folder or custom buildDir */
81
- get distDirParent() {
82
- return join(this.buildDir, "..");
83
- }
84
- /** The `.nuxt` folder or what the custom build dir is set to */
85
- get nuxtBuildDir() {
86
- return relative(this.distDirParent, this.buildDir);
87
- }
88
68
  /** The directory where the Nuxt output is stored */
89
69
  get outputDir() {
90
70
  return DEFAULT_OUTPUT_DIR;
91
71
  }
92
- /** Retrieves the `.output/server/` directory monorepo aware */
93
- get serverDir() {
94
- return join(this.outputRootDir);
95
- }
96
- /** The directory where the Nuxt static files are stored */
97
- get nuxtStaticDir() {
98
- return join(this.outputDir, NUXT_STATIC_DIR);
99
- }
100
- /**
101
- * Absolute path of the directory that is published and deployed to the CDN
102
- * Will be swapped with the publish directory
103
- * `.edgeone/static`
104
- */
105
- get staticDir() {
106
- return this.resolveFromPackagePath(".edgeone/assets");
107
- }
108
- /**
109
- * Absolute path of the directory that will be deployed to the blob store
110
- * region aware: `.edgeone/deploy/v1/blobs/deploy`
111
- * default: `.edgeone/blobs/deploy`
112
- */
113
- get blobDir() {
114
- if (this.useRegionalBlobs) {
115
- return this.resolveFromPackagePath(".edgeone/deploy/v1/blobs/deploy");
116
- }
117
- return this.resolveFromPackagePath(".edgeone/blobs/deploy");
118
- }
119
72
  get buildVersion() {
120
73
  return this.constants.BUILD_VERSION || "v0.0.0";
121
74
  }
@@ -145,17 +98,6 @@ var PluginContext = class {
145
98
  get nuxtServerHandler() {
146
99
  return "./.edgeone/dist/run/handlers/server.js";
147
100
  }
148
- /**
149
- * Absolute path of the directory containing the files for deno edge functions
150
- * `.edgeone/edge-functions`
151
- */
152
- get edgeFunctionsDir() {
153
- return this.resolveFromPackagePath(".edgeone/edge-functions");
154
- }
155
- /** Absolute path of the edge handler */
156
- get edgeHandlerDir() {
157
- return join(this.edgeFunctionsDir, EDGE_HANDLER_NAME);
158
- }
159
101
  constructor(options) {
160
102
  options = {
161
103
  ...options,
@@ -196,46 +138,6 @@ var PluginContext = class {
196
138
  return { routes: [], prerendered: [] };
197
139
  }
198
140
  }
199
- /** Get the nuxt build manifest */
200
- async getBuildManifest() {
201
- const buildInfoPath = join(this.nuxtBuildDir, "build-info.json");
202
- const nitroConfigPath = join(this.outputDir, "nitro.json");
203
- try {
204
- let manifestData;
205
- if (existsSync(buildInfoPath)) {
206
- manifestData = JSON.parse(await readFile(buildInfoPath, "utf-8"));
207
- } else if (existsSync(nitroConfigPath)) {
208
- const nitroConfig = JSON.parse(await readFile(nitroConfigPath, "utf-8"));
209
- manifestData = {
210
- version: nitroConfig.version || "1.0.0",
211
- config: nitroConfig.config || {},
212
- buildDir: this.buildDir,
213
- outputDir: this.outputDir,
214
- routes: nitroConfig.routes || {},
215
- assets: nitroConfig.assets || {}
216
- };
217
- } else {
218
- manifestData = {
219
- version: "1.0.0",
220
- config: {},
221
- buildDir: DEFAULT_BUILD_DIR,
222
- outputDir: DEFAULT_OUTPUT_DIR,
223
- routes: {},
224
- assets: {}
225
- };
226
- }
227
- return manifestData;
228
- } catch (error) {
229
- return {
230
- version: "1.0.0",
231
- config: {},
232
- buildDir: DEFAULT_BUILD_DIR,
233
- outputDir: DEFAULT_OUTPUT_DIR,
234
- routes: {},
235
- assets: {}
236
- };
237
- }
238
- }
239
141
  /**
240
142
  * Uses various heuristics to try to find the .output dir.
241
143
  * Works by looking for nitro.json, so requires the site to have been built
@@ -257,20 +159,6 @@ var PluginContext = class {
257
159
  }
258
160
  return false;
259
161
  }
260
- /**
261
- * Get Nuxt nitro config from the build output
262
- */
263
- async getNitroConfig() {
264
- const nitroPath = join(this.publishDir, "nitro.json");
265
- try {
266
- if (!existsSync(nitroPath)) {
267
- return {};
268
- }
269
- return JSON.parse(await readFile(nitroPath, "utf-8"));
270
- } catch (error) {
271
- return {};
272
- }
273
- }
274
162
  // don't make private as it is handy inside testing to override the config
275
163
  _buildManifest = null;
276
164
  /** Get Build manifest from build output **/
@@ -443,32 +331,53 @@ var PluginContext = class {
443
331
  }
444
332
  #nuxtVersion = void 0;
445
333
  /**
446
- * Get Nuxt version that was used to build the site
334
+ * Get the exact Nuxt version installed in the project.
335
+ *
336
+ * Prefer resolving `nuxt/package.json` from the app root (works for monorepo and Yarn PnP),
337
+ * then fall back to node_modules.
447
338
  */
448
339
  get nuxtVersion() {
449
- if (this.#nuxtVersion === void 0) {
340
+ if (this.#nuxtVersion !== void 0) return this.#nuxtVersion;
341
+ const roots = [this.constants.PACKAGE_PATH || process.cwd()].filter(Boolean);
342
+ const readJsonVersion = (pkgJsonPath) => {
450
343
  try {
451
- const output = execSync("npx nuxt info", {
452
- encoding: "utf-8",
453
- cwd: process.cwd(),
454
- stdio: ["pipe", "pipe", "pipe"]
455
- });
456
- const versionMatch = output.match(/(?:nuxt|version)[:\s]+(\d+\.\d+\.\d+)/i);
457
- if (versionMatch && versionMatch[1]) {
458
- this.#nuxtVersion = versionMatch[1];
459
- } else {
460
- this.#nuxtVersion = null;
461
- }
344
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
345
+ return typeof pkg.version === "string" && pkg.version.trim() ? pkg.version.trim() : null;
462
346
  } catch {
463
- this.#nuxtVersion = null;
347
+ return null;
348
+ }
349
+ };
350
+ try {
351
+ const require2 = createRequire(import.meta.url);
352
+ const resolved = require2.resolve("nuxt/package.json", { paths: roots });
353
+ const v = readJsonVersion(resolved);
354
+ if (v) {
355
+ this.#nuxtVersion = v;
356
+ return this.#nuxtVersion;
357
+ }
358
+ } catch {
359
+ }
360
+ for (const root of roots) {
361
+ const candidate = resolve(root, "node_modules", "nuxt", "package.json");
362
+ if (existsSync(candidate)) {
363
+ const v = readJsonVersion(candidate);
364
+ if (v) {
365
+ this.#nuxtVersion = v;
366
+ return this.#nuxtVersion;
367
+ }
464
368
  }
465
369
  }
370
+ this.#nuxtVersion = null;
466
371
  return this.#nuxtVersion;
467
372
  }
468
373
  #nuxtModules = null;
469
374
  async nuxtModules() {
470
375
  if (!this.#nuxtModules) {
471
376
  const nuxtConfig = await this.getNuxtConfig();
377
+ if (!nuxtConfig) {
378
+ this.#nuxtModules = null;
379
+ return this.#nuxtModules;
380
+ }
472
381
  try {
473
382
  const modules = getModulesWithAST(nuxtConfig);
474
383
  this.#nuxtModules = modules;
@@ -526,6 +435,5 @@ var PluginContext = class {
526
435
 
527
436
  export {
528
437
  SERVER_HANDLER_NAME,
529
- EDGE_HANDLER_NAME,
530
438
  PluginContext
531
439
  };
@@ -6,7 +6,7 @@
6
6
 
7
7
  import {
8
8
  getHandlersArrayWithAST
9
- } from "./chunk-ZG3P7BHA.js";
9
+ } from "./chunk-MONI3XWQ.js";
10
10
 
11
11
  // src/build/routes.ts
12
12
  import * as fs from "fs";
@@ -126,7 +126,9 @@ var createNuxtApiRoutesMeta = async (ctx) => {
126
126
  }
127
127
  const existingContent = await fs.readFileSync(metaFilePath, "utf-8");
128
128
  const existingMetaData = JSON.parse(existingContent);
129
- const nitroPath = path.join(edgeOneDir, "server-handler", "chunks", "nitro", "nitro.mjs");
129
+ const defaultNitroPath = path.join(edgeOneDir, "server-handler", "chunks", "nitro", "nitro.mjs");
130
+ const fallbackNitroPath = path.join(edgeOneDir, "server-handler", "chunks", "_", "nitro.mjs");
131
+ const nitroPath = fs.existsSync(defaultNitroPath) ? defaultNitroPath : fallbackNitroPath;
130
132
  let apiRoutes = [];
131
133
  if (fs.existsSync(nitroPath)) {
132
134
  try {
@@ -206,7 +208,6 @@ var createNuxtApiRoutesMeta = async (ctx) => {
206
208
  };
207
209
 
208
210
  export {
209
- convertNuxtRoutePattern,
210
211
  createNuxtPagesRouteMeta,
211
212
  createNuxtApiRoutesMeta
212
213
  };
@@ -15092,25 +15092,6 @@ function getModulesWithAST(code) {
15092
15092
  return [];
15093
15093
  }
15094
15094
  }
15095
- function checkModules(modules) {
15096
- const excludeModules = ["@nuxt/image"];
15097
- const invalidModules = modules.filter((module) => excludeModules.includes(module));
15098
- if (invalidModules.length > 0) {
15099
- console.error("\x1B[31mThe following modules are not supported: \x1B[0m", invalidModules.join(", "));
15100
- console.log("Further optimizations and adaptations are under development...");
15101
- process.exit(1);
15102
- }
15103
- }
15104
- function checkNuxtVersion(version) {
15105
- const versionData = version.split(".");
15106
- if (versionData.length === 3 && Number(versionData[0]) < 3) {
15107
- console.error("Nuxt version must be greater than 3.16.0. Compatibility for lower versions is under development...");
15108
- process.exit(1);
15109
- }
15110
- }
15111
- var removeServerHandler = async (ctx) => {
15112
- await rm(ctx.serverHandlerDir, { recursive: true, force: true });
15113
- };
15114
15095
 
15115
15096
  export {
15116
15097
  useStaticBuild,
@@ -15121,8 +15102,5 @@ export {
15121
15102
  getHandlersArrayWithAST,
15122
15103
  addCodeToGenerateEdgeoneWithAST,
15123
15104
  resetNitroConfigWithAST,
15124
- getModulesWithAST,
15125
- checkModules,
15126
- checkNuxtVersion,
15127
- removeServerHandler
15105
+ getModulesWithAST
15128
15106
  };
@@ -8,21 +8,16 @@ import {
8
8
  require_out,
9
9
  verifyNuxtHandlerDirStructure
10
10
  } from "./chunk-NJ4SUJNF.js";
11
- import {
12
- trace,
13
- wrapTracer
14
- } from "./chunk-V2LFVP3C.js";
15
11
  import {
16
12
  __require,
17
13
  __toESM
18
14
  } from "./chunk-6BT4RYQJ.js";
19
15
 
20
16
  // src/build/functions/server.ts
21
- import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
17
+ var import_fast_glob = __toESM(require_out(), 1);
18
+ import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
22
19
  import { join, relative } from "node:path";
23
20
  import { join as posixJoin } from "node:path/posix";
24
- var import_fast_glob = __toESM(require_out(), 1);
25
- var tracer = wrapTracer(trace.getTracer("Nuxt runtime"));
26
21
  var copyHandlerDependencies = async (ctx) => {
27
22
  const promises = [];
28
23
  const { included_files: includedFiles = [] } = {};
@@ -98,9 +93,6 @@ var writeHandlerFile = async (ctx) => {
98
93
  const handler = await getHandlerFile(ctx);
99
94
  await writeFile(join(ctx.serverHandlerRootDir, `handler.js`), handler);
100
95
  };
101
- var clearStaleServerHandlers = async (ctx) => {
102
- await rm(ctx.serverFunctionsDir, { recursive: true, force: true });
103
- };
104
96
  var createServerHandler = async (ctx) => {
105
97
  await mkdir(join(ctx.serverHandlerRuntimeModulesDir), { recursive: true });
106
98
  await copyHandlerDependencies(ctx);
@@ -109,7 +101,9 @@ var createServerHandler = async (ctx) => {
109
101
  };
110
102
  async function patchNitroHandler(ctx) {
111
103
  const fs = __require("fs");
112
- const nitroMjsPath = ".edgeone/server-handler/chunks/nitro/nitro.mjs";
104
+ const defaultNitroMjsPath = ".edgeone/server-handler/chunks/nitro/nitro.mjs";
105
+ const fallbackNitroMjsPath = ".edgeone/server-handler/chunks/_/nitro.mjs";
106
+ const nitroMjsPath = fs.existsSync(defaultNitroMjsPath) ? defaultNitroMjsPath : fallbackNitroMjsPath;
113
107
  const handlerTmplPath = ".edgeone/server-handler/handler.js";
114
108
  const nitroCode = fs.readFileSync(nitroMjsPath, "utf-8");
115
109
  const match = nitroCode.match(/export\s*\{[^}]*\buseNitroApp\s+as\s+([A-Za-z_$][\w$]*)/) ?? nitroCode.match(/\buseNitroApp\s+as\s+([A-Za-z_$][\w$]*)\b/);
@@ -121,7 +115,6 @@ async function patchNitroHandler(ctx) {
121
115
  }
122
116
 
123
117
  export {
124
- clearStaleServerHandlers,
125
118
  createServerHandler,
126
119
  patchNitroHandler
127
120
  };
package/dist/index.js CHANGED
@@ -6,34 +6,31 @@
6
6
 
7
7
  import {
8
8
  PluginContext
9
- } from "./esm-chunks/chunk-ESOQYHZZ.js";
9
+ } from "./esm-chunks/chunk-BZQMVWYQ.js";
10
10
  import {
11
11
  createNuxtApiRoutesMeta,
12
12
  createNuxtPagesRouteMeta
13
- } from "./esm-chunks/chunk-KYSZO4TW.js";
13
+ } from "./esm-chunks/chunk-J25U56II.js";
14
14
  import {
15
15
  addNitroBuildOutputConfig,
16
16
  resetNitroConfig
17
- } from "./esm-chunks/chunk-BZJ7SD7E.js";
17
+ } from "./esm-chunks/chunk-7X4RPD4I.js";
18
18
  import {
19
19
  resetEdgeOneConfig,
20
20
  useStaticBuild
21
- } from "./esm-chunks/chunk-ZG3P7BHA.js";
21
+ } from "./esm-chunks/chunk-MONI3XWQ.js";
22
22
  import {
23
23
  createServerHandler,
24
24
  patchNitroHandler
25
- } from "./esm-chunks/chunk-DOOA3WBA.js";
25
+ } from "./esm-chunks/chunk-RNEZUAPL.js";
26
26
  import "./esm-chunks/chunk-NJ4SUJNF.js";
27
27
  import "./esm-chunks/chunk-V2LFVP3C.js";
28
28
  import "./esm-chunks/chunk-6BT4RYQJ.js";
29
29
 
30
30
  // src/index.ts
31
- import { rm, cp, mkdir } from "node:fs/promises";
31
+ import { rm } from "node:fs/promises";
32
32
  import { join } from "node:path";
33
33
  import { existsSync } from "node:fs";
34
- import { exec } from "child_process";
35
- import { promisify } from "util";
36
- var execAsync = promisify(exec);
37
34
  async function checkModules(ctx, targetModules) {
38
35
  const modules = await ctx.nuxtModules();
39
36
  if (!modules) return false;
package/dist/utils.js CHANGED
@@ -6,29 +6,23 @@
6
6
 
7
7
  import {
8
8
  addCodeToGenerateEdgeoneWithAST,
9
- checkModules,
10
- checkNuxtVersion,
11
9
  getHandlersArrayWithAST,
12
10
  getModulesWithAST,
13
11
  getPrerenderRoutesWithAST,
14
12
  getRouteRulesWithAST,
15
13
  getRoutesArrayWithAST,
16
- removeServerHandler,
17
14
  resetEdgeOneConfig,
18
15
  resetNitroConfigWithAST,
19
16
  useStaticBuild
20
- } from "./esm-chunks/chunk-ZG3P7BHA.js";
17
+ } from "./esm-chunks/chunk-MONI3XWQ.js";
21
18
  import "./esm-chunks/chunk-6BT4RYQJ.js";
22
19
  export {
23
20
  addCodeToGenerateEdgeoneWithAST,
24
- checkModules,
25
- checkNuxtVersion,
26
21
  getHandlersArrayWithAST,
27
22
  getModulesWithAST,
28
23
  getPrerenderRoutesWithAST,
29
24
  getRouteRulesWithAST,
30
25
  getRoutesArrayWithAST,
31
- removeServerHandler,
32
26
  resetEdgeOneConfig,
33
27
  resetNitroConfigWithAST,
34
28
  useStaticBuild
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgeone/nuxt-pages",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "main": "./dist/index.js",
5
5
  "scripts": {
6
6
  "test": "ts-node src/test.ts",
@@ -1,511 +0,0 @@
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
- });