-
-
Notifications
You must be signed in to change notification settings - Fork 781
feat: add edgeone preset
#3884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add edgeone preset
#3884
Changes from all commits
53f6523
e81d848
5a70470
bd99387
197f4ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # EdgeOne | ||
|
|
||
| > Deploy Nitro apps to EdgeOne. | ||
|
|
||
| **Preset:** `edgeone-pages` | ||
|
|
||
| :read-more{to="https://pages.edgeone.ai/"} | ||
|
|
||
|
|
||
| ## Using the control panel | ||
|
|
||
| 1. In the [EdgeOne pages control panel](https://console.tencentcloud.com/edgeone/pages), click **Create project**. | ||
| 2. Choose **Import Git repository** as your deployment method. We support deployment on GitHub, GitLab, Gitee, and CNB. | ||
| 3. Choose the GitHub **repository** and **branch** containing your application code. | ||
| 4. Complete your project setup. | ||
| 5. Click the **Deploy** button. | ||
|
|
||
| ## Using the EdgeOne CLI | ||
|
|
||
| You can also install the Pages scaffolding tool. For detailed installation and usage, refer to [EdgeOne CLI](https://pages.edgeone.ai/document/edgeone-cli). | ||
|
|
||
| Once configured, use the edgeone pages deploy command to deploy the project. During deployment, the CLI will first automatically build the project, then upload and publish the build artifacts. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { defineNitroPreset } from "../_utils/preset.ts"; | ||
| import { writeEdgeOneRoutes } from "./utils.ts"; | ||
| import type { Nitro } from "nitro/types"; | ||
|
|
||
| const edgeone = defineNitroPreset( | ||
| { | ||
| entry: "./edgeone/runtime/edgeone", | ||
| extends: "node-server", | ||
| serveStatic: true, | ||
| output: { | ||
| dir: "{{ rootDir }}/.edgeone", | ||
| serverDir: "{{ output.dir }}/server-handler", | ||
| publicDir: "{{ output.dir }}/assets", | ||
| }, | ||
| rollupConfig: { | ||
| output: { | ||
| entryFileNames: "handler.js", | ||
| }, | ||
| }, | ||
| hooks: { | ||
| async compiled(nitro: Nitro) { | ||
| await writeEdgeOneRoutes(nitro); | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "edgeone-pages" as const, | ||
| } | ||
| ); | ||
|
|
||
| export default [edgeone] as const; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||
| import "#nitro/virtual/polyfills"; | ||||||||||||
| import { NodeRequest } from "srvx/node"; | ||||||||||||
| import { useNitroApp } from "nitro/app"; | ||||||||||||
| import type { IncomingMessage } from "node:http"; | ||||||||||||
|
|
||||||||||||
| const nitroApp = useNitroApp(); | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion | π Major Move Calling π Proposed fix-const nitroApp = useNitroApp();
-
interface EdgeOneRequest extends IncomingMessage {
url: string;
method: string;
headers: Record<string, string | string[] | undefined>;
}
// EdgeOne bootstrap expects: async (req, context) => Response
export default async function handle(req: EdgeOneRequest) {
+ const nitroApp = useNitroApp();
// Use srvx NodeRequest to convert Node.js request to Web Request
const request = new NodeRequest({ req });π§° Toolsπͺ Biome (2.1.2)[error] 6-6: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render. For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order. (lint/correctness/useHookAtTopLevel) π€ Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| interface EdgeOneRequest extends IncomingMessage { | ||||||||||||
| url: string; | ||||||||||||
| method: string; | ||||||||||||
| headers: Record<string, string | string[] | undefined>; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // EdgeOne bootstrap expects: async (req, context) => Response | ||||||||||||
| export default async function handle(req: EdgeOneRequest) { | ||||||||||||
|
Comment on lines
+14
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add the missing The comment indicates EdgeOne expects π Proposed fix-// EdgeOne bootstrap expects: async (req, context) => Response
-export default async function handle(req: EdgeOneRequest) {
+// EdgeOne bootstrap expects: async (req, context) => Response
+export default async function handle(req: EdgeOneRequest, context?: any) {
const nitroApp = useNitroApp();π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||
| // Use srvx NodeRequest to convert Node.js request to Web Request | ||||||||||||
| const request = new NodeRequest({ req }); | ||||||||||||
|
|
||||||||||||
| return nitroApp.fetch(request); | ||||||||||||
|
Comment on lines
17
to
19
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks a very strange signature mixing node
Is there an alternative that both req and res are the same format (either Node or Web, but web req is prefered)? |
||||||||||||
| } | ||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| import type { Nitro } from "nitro/types"; | ||
| import fsp from "node:fs/promises"; | ||
| import { relative, dirname, join } from "pathe"; | ||
| import consola from "consola"; | ||
| import { colors } from "consola/utils"; | ||
| interface FrameworkRoute { | ||
| path: string; | ||
| isStatic?: boolean; | ||
| isr?: number; | ||
| } | ||
|
|
||
| export async function writeEdgeOneRoutes(nitro: Nitro) { | ||
| // Ensure routes are synced | ||
| nitro.routing.sync(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is calling My small backend project based on Nitro οΌ* v3) can deploy to EdgeOne Pages properly without this.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. During our CD process, routing is allocated based on the request path. I call this to prevent route loss issues in more complex projects. |
||
| const meta = { | ||
| conf: { | ||
| ssr404: true, | ||
| }, | ||
| frameworkRoutes: [] as FrameworkRoute[], | ||
| }; | ||
|
|
||
| // 1. Get all API routes (server-side route handlers) | ||
| const apiRoutes = nitro.routing.routes.routes | ||
| .filter((route) => { | ||
| // Filter out middleware and wildcard routes (e.g., /**) | ||
| const handler = Array.isArray(route.data) ? route.data[0] : route.data; | ||
| return handler && !handler.middleware && route.route !== "/**"; | ||
| }) | ||
| .map((route) => ({ | ||
| path: route.route, | ||
| method: route.method || "*", | ||
| handler: Array.isArray(route.data) ? route.data[0] : route.data, | ||
| })); | ||
| for (const route of apiRoutes) { | ||
| meta.frameworkRoutes.push({ | ||
| path: route.path, | ||
| }); | ||
| } | ||
|
|
||
| // 2. Get all page routes (prerendered routes) | ||
| const pageRoutes = (nitro._prerenderedRoutes || []).map((route) => ({ | ||
| path: route.route, | ||
| fileName: route.fileName, | ||
| contentType: route.contentType, | ||
| })); | ||
|
|
||
| // 3. Get user-defined prerender routes | ||
| const userPrerenderRoutes = nitro.options.prerender?.routes || []; | ||
| // 4. Get all routes marked as prerender in route rules | ||
| const prerenderRouteRules = Object.entries(nitro.options.routeRules || {}) | ||
| .filter(([_, rules]) => rules.prerender) | ||
| .map(([path]) => path); | ||
|
|
||
| // 5. Get all routes with SWR/cache settings from route rules | ||
| // Note: `swr: true` shortcut is normalized to `cache: { swr: true }` after config resolution | ||
| const swrRouteRules = Object.entries(nitro.options.routeRules || {}) | ||
| .filter(([_, rules]) => { | ||
| // Check if cache.swr is enabled (normalized form) | ||
| if (rules.cache && typeof rules.cache === "object" && rules.cache.swr) { | ||
| return true; | ||
| } | ||
| return false; | ||
| }) | ||
| .map(([path, rules]) => ({ | ||
| path, | ||
| cache: rules.cache as { swr?: boolean; maxAge?: number }, | ||
| })); | ||
| for (const route of swrRouteRules) { | ||
| const maxAge = route.cache.maxAge; | ||
| for (const frameworkRoute of meta.frameworkRoutes) { | ||
| if (frameworkRoute.path === route.path) { | ||
| Reflect.set(frameworkRoute, "isStatic", false); | ||
| Reflect.set(frameworkRoute, "isr", maxAge); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+68
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SWR routes may not match if added after API routes. The SWR/ISR annotation loop (lines 68-76) iterates over Consider reordering: collect all routes first, then apply SWR annotations, or apply SWR settings when adding prerender routes. π€ Prompt for AI Agents |
||
|
|
||
| // Merge all prerender routes | ||
| const allPrerenderRoutes = [ | ||
| ...new Set([ | ||
| ...userPrerenderRoutes, | ||
| ...prerenderRouteRules, | ||
| ...pageRoutes.map((r) => r.path), | ||
| ]), | ||
| ]; | ||
| for (const route of allPrerenderRoutes) { | ||
| meta.frameworkRoutes.push({ | ||
| path: route, | ||
| isStatic: true, | ||
| }); | ||
| } | ||
|
|
||
| await writeFile( | ||
| join(nitro.options.output.dir, "meta.json"), | ||
| JSON.stringify(meta, null, 2) | ||
| ); | ||
| await writeFile( | ||
| join(nitro.options.output.serverDir, "meta.json"), | ||
| JSON.stringify(meta, null, 2) | ||
| ); | ||
|
|
||
| // Return all route information | ||
| return { | ||
| apiRoutes, | ||
| pageRoutes, | ||
|
Comment on lines
+104
to
+105
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please clarify, what is difference between handling api routes and pages routes in platform? (possibly linking relavant docs to code might be useful) Additionally, (also important) we need to register a fallback route that handles any uncaught route pattern (middleware for example can also catch routes) |
||
| userPrerenderRoutes, | ||
| prerenderRouteRules, | ||
| allPrerenderRoutes, | ||
| swrRouteRules, | ||
| }; | ||
| } | ||
|
|
||
| function prettyPath(p: string, highlight = true) { | ||
| p = relative(process.cwd(), p); | ||
| return highlight ? colors.cyan(p) : p; | ||
| } | ||
|
|
||
| async function writeFile(file: string, contents: Buffer | string, log = false) { | ||
| await fsp.mkdir(dirname(file), { recursive: true }); | ||
| await fsp.writeFile( | ||
| file, | ||
| contents, | ||
| typeof contents === "string" ? "utf8" : undefined | ||
| ); | ||
| if (log) { | ||
| consola.info("Generated", prettyPath(file)); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.