@edgeone/opennextjs-pages 0.0.1

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 (35) hide show
  1. package/dist/build/advanced-api-routes.js +147 -0
  2. package/dist/build/cache.js +36 -0
  3. package/dist/build/content/next-shims/telemetry-storage.cjs +55 -0
  4. package/dist/build/content/prerendered.js +292 -0
  5. package/dist/build/content/server.js +236 -0
  6. package/dist/build/content/static.js +119 -0
  7. package/dist/build/functions/server.js +133 -0
  8. package/dist/build/plugin-context.js +367 -0
  9. package/dist/build/routes.js +127 -0
  10. package/dist/build/templates/handler-monorepo.tmpl.js +210 -0
  11. package/dist/build/templates/handler.tmpl.js +206 -0
  12. package/dist/esm-chunks/chunk-5J3FID2N.js +5551 -0
  13. package/dist/esm-chunks/chunk-6BT4RYQJ.js +43 -0
  14. package/dist/esm-chunks/chunk-FKDTZJRV.js +832 -0
  15. package/dist/esm-chunks/chunk-TLQCAGE2.js +1921 -0
  16. package/dist/index.js +50 -0
  17. package/dist/run/config.js +37 -0
  18. package/dist/run/constants.js +19 -0
  19. package/dist/run/handlers/cache.cjs +369 -0
  20. package/dist/run/handlers/request-context.cjs +148 -0
  21. package/dist/run/handlers/server.js +3213 -0
  22. package/dist/run/handlers/tags-handler.cjs +92 -0
  23. package/dist/run/handlers/tracer.cjs +916 -0
  24. package/dist/run/handlers/use-cache-handler.js +1538 -0
  25. package/dist/run/handlers/wait-until.cjs +39 -0
  26. package/dist/run/headers.js +81 -0
  27. package/dist/run/next.cjs +100 -0
  28. package/dist/run/revalidate.js +34 -0
  29. package/dist/run/storage/regional-blob-store.cjs +64 -0
  30. package/dist/run/storage/request-scoped-in-memory-cache.cjs +1496 -0
  31. package/dist/run/storage/storage.cjs +37 -0
  32. package/dist/shared/blob-types.cjs +37 -0
  33. package/dist/shared/blobkey.js +25 -0
  34. package/dist/shared/cache-types.cjs +33 -0
  35. package/package.json +55 -0
@@ -0,0 +1,367 @@
1
+
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+ import {
8
+ require_semver
9
+ } from "../esm-chunks/chunk-TLQCAGE2.js";
10
+ import {
11
+ __toESM
12
+ } from "../esm-chunks/chunk-6BT4RYQJ.js";
13
+
14
+ // src/build/plugin-context.ts
15
+ var import_semver = __toESM(require_semver(), 1);
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { readFile } from "node:fs/promises";
18
+ import { createRequire } from "node:module";
19
+ import { join, relative, resolve } from "node:path";
20
+ import { join as posixJoin, relative as posixRelative } from "node:path/posix";
21
+ import { fileURLToPath } from "node:url";
22
+ var MODULE_DIR = fileURLToPath(new URL(".", import.meta.url));
23
+ var PLUGIN_DIR = join(MODULE_DIR, "../..");
24
+ var DEFAULT_PUBLISH_DIR = ".next";
25
+ var SERVER_HANDLER_NAME = "server-handler";
26
+ var EDGE_HANDLER_NAME = "edgeone-edge-handler";
27
+ var PluginContext = class {
28
+ edgeoneConfig;
29
+ pluginName;
30
+ pluginVersion;
31
+ utils;
32
+ constants;
33
+ packageJSON;
34
+ /** Absolute path of the next runtime plugin directory */
35
+ pluginDir = PLUGIN_DIR;
36
+ serverlessWrapHandler;
37
+ get relPublishDir() {
38
+ return this.constants.PUBLISH_DIR ?? join(this.constants.PACKAGE_PATH || "", DEFAULT_PUBLISH_DIR);
39
+ }
40
+ /** Temporary directory for stashing the build output */
41
+ get tempPublishDir() {
42
+ return this.resolveFromPackagePath(".edgeone/.next");
43
+ }
44
+ /** Absolute path of the publish directory */
45
+ get publishDir() {
46
+ return resolve(this.relPublishDir);
47
+ }
48
+ /**
49
+ * Relative package path in non monorepo setups this is an empty string
50
+ * This path is provided by Next.js RequiredServerFiles manifest
51
+ * @example ''
52
+ * @example 'apps/my-app'
53
+ */
54
+ get relativeAppDir() {
55
+ return this.requiredServerFiles.relativeAppDir ?? "";
56
+ }
57
+ /**
58
+ * The working directory inside the lambda that is used for monorepos to execute the serverless function
59
+ */
60
+ get lambdaWorkingDirectory() {
61
+ return join("./", this.distDirParent);
62
+ }
63
+ /**
64
+ * Retrieves the root of the `.next/standalone` directory
65
+ */
66
+ get standaloneRootDir() {
67
+ return join(this.publishDir, "standalone");
68
+ }
69
+ /**
70
+ * The resolved relative next dist directory defaults to `.next`,
71
+ * but can be configured through the next.config.js. For monorepos this will include the packagePath
72
+ * If we need just the plain dist dir use the `nextDistDir`
73
+ */
74
+ get distDir() {
75
+ const dir = this.buildConfig.distDir ?? DEFAULT_PUBLISH_DIR;
76
+ return relative(process.cwd(), resolve(this.relativeAppDir, dir));
77
+ }
78
+ /** Represents the parent directory of the .next folder or custom distDir */
79
+ get distDirParent() {
80
+ return join(this.distDir, "..");
81
+ }
82
+ /** The `.next` folder or what the custom dist dir is set to */
83
+ get nextDistDir() {
84
+ return relative(this.distDirParent, this.distDir);
85
+ }
86
+ /** Retrieves the `.next/standalone/` directory monorepo aware */
87
+ get standaloneDir() {
88
+ return join(this.standaloneRootDir, this.distDirParent);
89
+ }
90
+ /**
91
+ * Absolute path of the directory that is published and deployed to the CDN
92
+ * Will be swapped with the publish directory
93
+ * `.edgeone/static`
94
+ */
95
+ get staticDir() {
96
+ return this.resolveFromPackagePath(".edgeone/assets");
97
+ }
98
+ /**
99
+ * Absolute path of the directory that will be deployed to the blob store
100
+ * region aware: `.edgeone/deploy/v1/blobs/deploy`
101
+ * default: `.edgeone/blobs/deploy`
102
+ */
103
+ get blobDir() {
104
+ if (this.useRegionalBlobs) {
105
+ return this.resolveFromPackagePath(".edgeone/deploy/v1/blobs/deploy");
106
+ }
107
+ return this.resolveFromPackagePath(".edgeone/blobs/deploy");
108
+ }
109
+ get buildVersion() {
110
+ return this.constants.BUILD_VERSION || "v0.0.0";
111
+ }
112
+ get useRegionalBlobs() {
113
+ const REQUIRED_BUILD_VERSION = ">=29.41.5";
114
+ return (0, import_semver.satisfies)(this.buildVersion, REQUIRED_BUILD_VERSION, { includePrerelease: true });
115
+ }
116
+ /**
117
+ * Absolute path of the directory containing the files for the serverless lambda function
118
+ * `.edgeone/functions-internal`
119
+ */
120
+ get serverFunctionsDir() {
121
+ return this.resolveFromPackagePath(".edgeone");
122
+ }
123
+ /** Absolute path of the server handler */
124
+ get serverHandlerRootDir() {
125
+ return join(this.serverFunctionsDir, SERVER_HANDLER_NAME);
126
+ }
127
+ get serverHandlerDir() {
128
+ if (this.relativeAppDir.length === 0) {
129
+ return this.serverHandlerRootDir;
130
+ }
131
+ return join(this.serverHandlerRootDir, this.distDirParent);
132
+ }
133
+ get serverHandlerRuntimeModulesDir() {
134
+ return join(this.serverHandlerDir, ".edgeone");
135
+ }
136
+ get nextServerHandler() {
137
+ if (this.relativeAppDir.length !== 0) {
138
+ return join(this.lambdaWorkingDirectory, ".edgeone/dist/run/handlers/server.js");
139
+ }
140
+ return "./.edgeone/dist/run/handlers/server.js";
141
+ }
142
+ /**
143
+ * Absolute path of the directory containing the files for deno edge functions
144
+ * `.edgeone/edge-functions`
145
+ */
146
+ get edgeFunctionsDir() {
147
+ return this.resolveFromPackagePath(".edgeone/edge-functions");
148
+ }
149
+ /** Absolute path of the edge handler */
150
+ get edgeHandlerDir() {
151
+ return join(this.edgeFunctionsDir, EDGE_HANDLER_NAME);
152
+ }
153
+ constructor(options) {
154
+ options = {
155
+ ...options,
156
+ functions: {
157
+ "*": {}
158
+ },
159
+ constants: {
160
+ PUBLISH_DIR: ".next"
161
+ // BUILD_VERSION: '32.1.4',
162
+ }
163
+ };
164
+ this.constants = options.constants;
165
+ this.edgeoneConfig = options.edgeoneConfig;
166
+ this.packageJSON = JSON.parse(readFileSync(join(PLUGIN_DIR, "package.json"), "utf-8"));
167
+ this.pluginName = this.packageJSON.name;
168
+ this.pluginVersion = this.packageJSON.version;
169
+ this.utils = options.utils;
170
+ this.serverlessWrapHandler = options.serverlessWrapHandler;
171
+ }
172
+ /** Resolves a path correctly with mono repository awareness for .edgeone directories mainly */
173
+ resolveFromPackagePath(...args) {
174
+ return resolve(this.constants.PACKAGE_PATH || "", ...args);
175
+ }
176
+ /** Resolves a path correctly from site directory */
177
+ resolveFromSiteDir(...args) {
178
+ return resolve(this.requiredServerFiles.appDir, ...args);
179
+ }
180
+ /** Get the next prerender-manifest.json */
181
+ async getPrerenderManifest() {
182
+ return JSON.parse(await readFile(join(this.publishDir, "prerender-manifest.json"), "utf-8"));
183
+ }
184
+ async getPagesManifest() {
185
+ return JSON.parse(await readFile(join(this.publishDir, "server/pages-manifest.json"), "utf-8"));
186
+ }
187
+ // 获取 next api manifest
188
+ async getAppPathRoutesManifest() {
189
+ const manifestPath = join(this.publishDir, "app-path-routes-manifest.json");
190
+ try {
191
+ if (!existsSync(manifestPath)) {
192
+ return {};
193
+ }
194
+ return JSON.parse(await readFile(manifestPath, "utf-8"));
195
+ } catch (error) {
196
+ return {};
197
+ }
198
+ }
199
+ /**
200
+ * Uses various heuristics to try to find the .next dir.
201
+ * Works by looking for BUILD_ID, so requires the site to have been built
202
+ */
203
+ findDotNext() {
204
+ for (const dir of [
205
+ // The publish directory
206
+ this.publishDir,
207
+ // In the root
208
+ resolve(DEFAULT_PUBLISH_DIR),
209
+ // The sibling of the publish directory
210
+ resolve(this.publishDir, "..", DEFAULT_PUBLISH_DIR),
211
+ // In the package dir
212
+ resolve(this.constants.PACKAGE_PATH || "", DEFAULT_PUBLISH_DIR)
213
+ ]) {
214
+ if (existsSync(join(dir, "BUILD_ID"))) {
215
+ return dir;
216
+ }
217
+ }
218
+ return false;
219
+ }
220
+ /**
221
+ * Get Next.js middleware config from the build output
222
+ */
223
+ async getMiddlewareManifest() {
224
+ return JSON.parse(
225
+ await readFile(join(this.publishDir, "server/middleware-manifest.json"), "utf-8")
226
+ );
227
+ }
228
+ // don't make private as it is handy inside testing to override the config
229
+ _requiredServerFiles = null;
230
+ /** Get RequiredServerFiles manifest from build output **/
231
+ get requiredServerFiles() {
232
+ if (!this._requiredServerFiles) {
233
+ let requiredServerFilesJson = join(this.publishDir, "required-server-files.json");
234
+ if (!existsSync(requiredServerFilesJson)) {
235
+ const dotNext = this.findDotNext();
236
+ if (dotNext) {
237
+ requiredServerFilesJson = join(dotNext, "required-server-files.json");
238
+ }
239
+ }
240
+ this._requiredServerFiles = JSON.parse(
241
+ readFileSync(requiredServerFilesJson, "utf-8")
242
+ );
243
+ }
244
+ return this._requiredServerFiles;
245
+ }
246
+ #exportDetail = null;
247
+ /** Get metadata when output = export */
248
+ get exportDetail() {
249
+ if (this.buildConfig.output !== "export") {
250
+ return null;
251
+ }
252
+ if (!this.#exportDetail) {
253
+ const detailFile = join(
254
+ this.requiredServerFiles.appDir,
255
+ this.buildConfig.distDir,
256
+ "export-detail.json"
257
+ );
258
+ if (!existsSync(detailFile)) {
259
+ return null;
260
+ }
261
+ try {
262
+ this.#exportDetail = JSON.parse(readFileSync(detailFile, "utf-8"));
263
+ } catch {
264
+ }
265
+ }
266
+ return this.#exportDetail;
267
+ }
268
+ /** Get Next Config from build output **/
269
+ get buildConfig() {
270
+ return this.requiredServerFiles.config;
271
+ }
272
+ /**
273
+ * Get Next.js routes manifest from the build output
274
+ */
275
+ async getRoutesManifest() {
276
+ return JSON.parse(await readFile(join(this.publishDir, "routes-manifest.json"), "utf-8"));
277
+ }
278
+ /**
279
+ * Get Next.js images manifest from the build output
280
+ * This handles the image optimization routes for Next.js Image component
281
+ */
282
+ async getImagesManifest() {
283
+ const imagesManifestPath = join(this.publishDir, "images-manifest.json");
284
+ try {
285
+ if (!existsSync(imagesManifestPath)) {
286
+ return {};
287
+ }
288
+ return JSON.parse(await readFile(imagesManifestPath, "utf-8"));
289
+ } catch (error) {
290
+ return {};
291
+ }
292
+ }
293
+ #nextVersion = void 0;
294
+ /**
295
+ * Get Next.js version that was used to build the site
296
+ */
297
+ get nextVersion() {
298
+ if (this.#nextVersion === void 0) {
299
+ try {
300
+ const serverHandlerRequire = createRequire(posixJoin(this.standaloneRootDir, ":internal:"));
301
+ const { version } = serverHandlerRequire("next/package.json");
302
+ this.#nextVersion = version;
303
+ } catch {
304
+ this.#nextVersion = null;
305
+ }
306
+ }
307
+ return this.#nextVersion;
308
+ }
309
+ #fallbacks = null;
310
+ /**
311
+ * Get an array of localized fallback routes
312
+ *
313
+ * Example return value for non-i18n site: `['blog/[slug]']`
314
+ *
315
+ * Example return value for i18n site: `['en/blog/[slug]', 'fr/blog/[slug]']`
316
+ */
317
+ getFallbacks(prerenderManifest) {
318
+ if (!this.#fallbacks) {
319
+ const locales = this.buildConfig.i18n?.locales ?? [""];
320
+ this.#fallbacks = Object.entries(prerenderManifest.dynamicRoutes).reduce(
321
+ (fallbacks, [route, meta]) => {
322
+ if (typeof meta.fallback === "string") {
323
+ for (const locale of locales) {
324
+ const localizedRoute = posixJoin(locale, route.replace(/^\/+/g, ""));
325
+ fallbacks.push(localizedRoute);
326
+ }
327
+ }
328
+ return fallbacks;
329
+ },
330
+ []
331
+ );
332
+ }
333
+ return this.#fallbacks;
334
+ }
335
+ #fullyStaticHtmlPages = null;
336
+ /**
337
+ * Get an array of fully static pages router pages (no `getServerSideProps` or `getStaticProps`).
338
+ * Those are being served as-is without involving CacheHandler, so we need to keep track of them
339
+ * to make sure we apply permanent caching headers for responses that use them.
340
+ */
341
+ async getFullyStaticHtmlPages() {
342
+ if (!this.#fullyStaticHtmlPages) {
343
+ const pagesManifest = JSON.parse(
344
+ await readFile(join(this.publishDir, "server/pages-manifest.json"), "utf-8")
345
+ );
346
+ this.#fullyStaticHtmlPages = Object.values(pagesManifest).filter(
347
+ (filePath) => (
348
+ // Limit handling to pages router files (App Router pages should not be included in pages-manifest.json
349
+ // as they have their own app-paths-manifest.json)
350
+ filePath.startsWith("pages/") && // Fully static pages will have entries in the pages-manifest.json pointing to .html files.
351
+ // Pages with data fetching exports will point to .js files.
352
+ filePath.endsWith(".html")
353
+ )
354
+ ).map((filePath) => posixRelative("pages", filePath));
355
+ }
356
+ return this.#fullyStaticHtmlPages;
357
+ }
358
+ /** Fails a build with a message and an optional error */
359
+ failBuild(message, error) {
360
+ return console.error(message);
361
+ }
362
+ };
363
+ export {
364
+ EDGE_HANDLER_NAME,
365
+ PluginContext,
366
+ SERVER_HANDLER_NAME
367
+ };
@@ -0,0 +1,127 @@
1
+
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+ import "../esm-chunks/chunk-6BT4RYQJ.js";
8
+
9
+ // src/build/routes.ts
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ var convertNextRoutePattern = (path2) => {
13
+ if (!path2.includes("[")) {
14
+ return path2;
15
+ }
16
+ let convertedPath = path2;
17
+ const optionalCatchAllMatch = path2.match(/\[\[\.\.\.([^\]]+)\]\]/);
18
+ if (optionalCatchAllMatch) {
19
+ const paramName = optionalCatchAllMatch[1];
20
+ convertedPath = convertedPath.replace(/\[\[\.\.\.([^\]]+)\]\]/g, `:${paramName}*?`);
21
+ }
22
+ const catchAllMatch = path2.match(/\[\.\.\.([^\]]+)\]/);
23
+ if (catchAllMatch) {
24
+ const paramName = catchAllMatch[1];
25
+ convertedPath = convertedPath.replace(/\[\.\.\.([^\]]+)\]/g, `:${paramName}*`);
26
+ }
27
+ const dynamicMatch = path2.match(/\[([^\]]+)\]/);
28
+ if (dynamicMatch) {
29
+ const paramName = dynamicMatch[1];
30
+ convertedPath = convertedPath.replace(/\[([^\]]+)\]/g, `:${paramName}`);
31
+ }
32
+ return convertedPath;
33
+ };
34
+ var createRouteMeta = async (ctx) => {
35
+ const routeMap = {};
36
+ const manifest = await ctx.getPrerenderManifest();
37
+ if (manifest?.routes) {
38
+ for (const [route, routeInfo] of Object.entries(manifest.routes)) {
39
+ routeMap[route] = {
40
+ // 提取关键信息到routeMap
41
+ isStatic: routeInfo.initialRevalidateSeconds === false,
42
+ initialRevalidateSeconds: routeInfo.initialRevalidateSeconds || void 0,
43
+ srcRoute: routeInfo.srcRoute || void 0,
44
+ dataRoute: routeInfo.dataRoute || void 0
45
+ };
46
+ }
47
+ }
48
+ const pagesManifest = await ctx.getPagesManifest();
49
+ if (pagesManifest) {
50
+ for (const [route, filePath] of Object.entries(pagesManifest)) {
51
+ if (!routeMap[route]) {
52
+ routeMap[route] = {};
53
+ }
54
+ if (filePath.startsWith("pages") && filePath.endsWith(".html")) {
55
+ routeMap[route].isStatic = true;
56
+ }
57
+ }
58
+ }
59
+ const appPathRoutesManifest = await ctx.getAppPathRoutesManifest();
60
+ if (appPathRoutesManifest) {
61
+ for (const [route, actualRoute] of Object.entries(appPathRoutesManifest)) {
62
+ if (!routeMap[actualRoute]) {
63
+ routeMap[actualRoute] = {};
64
+ }
65
+ }
66
+ }
67
+ const routesManifest = await ctx.getRoutesManifest();
68
+ if (routesManifest) {
69
+ const dataRoutes = routesManifest.dataRoutes;
70
+ for (const { page, dataRouteRegex } of dataRoutes) {
71
+ routeMap[dataRouteRegex] = {
72
+ isStatic: routeMap[page]?.isStatic || false
73
+ };
74
+ }
75
+ }
76
+ const imagesManifest = await ctx.getImagesManifest();
77
+ if (imagesManifest) {
78
+ if (imagesManifest.images) {
79
+ const imageConfig = imagesManifest.images;
80
+ routeMap[imageConfig.path] = {};
81
+ }
82
+ }
83
+ const convertedRouteMap = {};
84
+ const pathsToDelete = [];
85
+ for (const [routePath, routeConfig] of Object.entries(routeMap)) {
86
+ const convertedPath = convertNextRoutePattern(routePath);
87
+ if (convertedPath !== routePath) {
88
+ pathsToDelete.push(routePath);
89
+ convertedRouteMap[convertedPath] = routeConfig;
90
+ }
91
+ }
92
+ for (const pathToDelete of pathsToDelete) {
93
+ delete routeMap[pathToDelete];
94
+ }
95
+ Object.assign(routeMap, convertedRouteMap);
96
+ const routesArray = Object.entries(routeMap).map(([path2, config]) => ({
97
+ path: path2,
98
+ ...config
99
+ }));
100
+ const edgeOneDir = path.join(process.cwd(), ".edgeone");
101
+ if (!fs.existsSync(edgeOneDir)) {
102
+ fs.mkdirSync(edgeOneDir, { recursive: true });
103
+ }
104
+ const metaFilePath = path.join(edgeOneDir, "meta.json");
105
+ let existingMetaData = {};
106
+ if (fs.existsSync(metaFilePath)) {
107
+ try {
108
+ const existingContent = fs.readFileSync(metaFilePath, "utf-8");
109
+ existingMetaData = JSON.parse(existingContent);
110
+ } catch (error) {
111
+ console.warn("Failed to parse existing meta.json:", error);
112
+ }
113
+ }
114
+ const mergedMetaData = {
115
+ ...existingMetaData,
116
+ nextRoutes: routesArray
117
+ };
118
+ fs.writeFileSync(
119
+ metaFilePath,
120
+ JSON.stringify(mergedMetaData, null, 2),
121
+ "utf-8"
122
+ );
123
+ };
124
+ export {
125
+ convertNextRoutePattern,
126
+ createRouteMeta
127
+ };
@@ -0,0 +1,210 @@
1
+ import { createServer } from 'http';
2
+ import {
3
+ createRequestContext,
4
+ runWithRequestContext,
5
+ } from './{{cwd}}/.edgeone/dist/run/handlers/request-context.cjs'
6
+ import { getTracer } from './{{cwd}}/.edgeone/dist/run/handlers/tracer.cjs'
7
+ import * as serverHandler from './{{cwd}}/.edgeone/dist/run/handlers/server.js'
8
+
9
+
10
+ process.chdir('./{{cwd}}')
11
+
12
+ // Set feature flag for regional blobs
13
+ process.env.USE_REGIONAL_BLOBS = '{{useRegionalBlobs}}'
14
+
15
+
16
+ async function handleResponse(res, response, passHeaders = {}) {
17
+ const startTime = Date.now();
18
+
19
+ if (!response) {
20
+ res.writeHead(404);
21
+ res.end(JSON.stringify({
22
+ error: "Not Found",
23
+ message: "The requested path does not exist"
24
+ }));
25
+ const endTime = Date.now();
26
+ console.log(`HandleResponse: 404 Not Found - ${endTime - startTime}ms`);
27
+ return;
28
+ }
29
+
30
+ try {
31
+ if (response instanceof Response) {
32
+ const headers = Object.fromEntries(response.headers);
33
+ Object.assign(headers, passHeaders);
34
+ if (response.headers.get('eop-client-geo')) {
35
+ // 删除 eop-client-geo 头部
36
+ response.headers.delete('eop-client-geo');
37
+ }
38
+
39
+ // 检查是否是流式响应
40
+ const isStream = response.body && (
41
+ response.headers.get('content-type')?.includes('text/event-stream') ||
42
+ response.headers.get('transfer-encoding')?.includes('chunked') ||
43
+ response.body instanceof ReadableStream ||
44
+ typeof response.body.pipe === 'function' ||
45
+ response.headers.get('x-content-type-stream') === 'true'
46
+ );
47
+
48
+ if (isStream) {
49
+ // 设置流式响应所需的头部
50
+ const streamHeaders = {
51
+ ...headers
52
+ };
53
+
54
+ if (response.headers.get('content-type')?.includes('text/event-stream')) {
55
+ streamHeaders['Content-Type'] = 'text/event-stream';
56
+ }
57
+
58
+ res.writeHead(response.status, streamHeaders);
59
+
60
+ if (typeof response.body.pipe === 'function') {
61
+ response.body.pipe(res);
62
+ } else {
63
+ const reader = response.body.getReader();
64
+ try {
65
+ while (true) {
66
+ const { done, value } = await reader.read();
67
+ if (done) break;
68
+
69
+ if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
70
+ res.write(value);
71
+ } else {
72
+ const chunk = new TextDecoder().decode(value);
73
+ res.write(chunk);
74
+ }
75
+ }
76
+ } finally {
77
+ reader.releaseLock();
78
+ res.end();
79
+ }
80
+ }
81
+ } else {
82
+ // 普通响应
83
+ res.writeHead(response.status, headers);
84
+ const body = await response.text();
85
+ res.end(body);
86
+ }
87
+ } else {
88
+ // 非 Response 对象,直接返回 JSON
89
+ res.writeHead(200, {
90
+ 'Content-Type': 'application/json'
91
+ });
92
+ res.end(JSON.stringify(response));
93
+ }
94
+ } catch (error) {
95
+ console.error('HandleResponse error', error);
96
+ // 错误处理
97
+ res.writeHead(500);
98
+ res.end(JSON.stringify({
99
+ error: "Internal Server Error",
100
+ message: error.message
101
+ }));
102
+ } finally {
103
+ const endTime = Date.now();
104
+ console.log(`HandleResponse: ${response?.status || 'unknown'} - ${endTime - startTime}ms`);
105
+ }
106
+ }
107
+
108
+
109
+ let cachedHandler
110
+ export default async function eoHandler (req, context) {
111
+ const requestContext = createRequestContext(req, context)
112
+ const tracer = getTracer()
113
+
114
+ const handlerResponse = await runWithRequestContext(requestContext, () => {
115
+ return tracer.withActiveSpan('Next.js Server Handler', async (span) => {
116
+ if (!cachedHandler) {
117
+ // const { default: handler } = await import('{{nextServerHandler}}')
118
+ cachedHandler = serverHandler.default
119
+ }
120
+ const response = await cachedHandler(req, context, span, requestContext)
121
+ return response
122
+ })
123
+ })
124
+
125
+ return handlerResponse
126
+ }
127
+
128
+
129
+ export const config = {
130
+ path: '/*',
131
+ preferStatic: true,
132
+ };
133
+
134
+ const port = 9000;
135
+
136
+ // 实时流转换函数
137
+ function createReadableStreamFromRequest(req) {
138
+ return new ReadableStream({
139
+ start(controller) {
140
+ req.on('data', chunk => {
141
+ // 将Buffer转换为Uint8Array
142
+ const uint8Array = new Uint8Array(chunk);
143
+ controller.enqueue(uint8Array);
144
+ });
145
+
146
+ req.on('end', () => {
147
+ controller.close();
148
+ });
149
+
150
+ req.on('error', error => {
151
+ controller.error(error);
152
+ });
153
+ },
154
+
155
+ cancel() {
156
+ // 清理资源
157
+ req.destroy();
158
+ }
159
+ });
160
+ }
161
+
162
+ const server = createServer(async (req, res) => {
163
+ try {
164
+ const requestStartTime = Date.now();
165
+
166
+ // 构造 handler 需要的 req 对象(可根据需要扩展)
167
+ const handlerReq = {
168
+ ...req,
169
+ headers: {
170
+ ...req.headers,
171
+ 'accept-encoding': 'identity',
172
+ },
173
+ method: req.method,
174
+ url: req.url,
175
+ };
176
+
177
+ // 读取 body(如果有)
178
+ // let body = [];
179
+ // req.on('data', chunk => body.push(chunk));
180
+ // await new Promise(resolve => req.on('end', resolve));
181
+ // if (body.length > 0) {
182
+ // handlerReq.body = Buffer.concat(body);
183
+ // }
184
+
185
+ handlerReq.body = createReadableStreamFromRequest(req);
186
+
187
+ const response = await eoHandler(handlerReq, {});
188
+
189
+ response.headers.set('functions-request-id', req.headers['x-scf-request-id'] || '');
190
+
191
+ const requestEndTime = Date.now();
192
+ const url = new URL(req.url, `http://${req.headers.host}`);
193
+ console.log(`Request path: ${url.pathname}`);
194
+ console.log(`Request processing time: ${requestEndTime - requestStartTime}ms`);
195
+ await handleResponse(res, response, {
196
+ 'functions-request-id': req.headers['x-scf-request-id'] || ''
197
+ });
198
+ return;
199
+ } catch (error) {
200
+ console.error('SSR Error:', error);
201
+ res.statusCode = 500;
202
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
203
+ res.end('<html><body><h1>Error</h1><p>'+error.message+'</p></body></html>');
204
+ }
205
+ });
206
+
207
+ server.listen(port, () => {
208
+ console.log('Server is running on http://localhost:9000');
209
+ });
210
+