@edgeone/opennextjs-pages 0.1.5 → 0.1.6-beta.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.
@@ -2455,11 +2455,14 @@ async function compileTurbopack(inputPath, middlewareCode, options = {}) {
2455
2455
  }
2456
2456
  const externalChunks = [];
2457
2457
  const middlewareChunks = [];
2458
+ const libraryChunks = [];
2458
2459
  for (const chunkPath of chunkPaths) {
2459
2460
  if (chunkPath.includes("[externals]")) {
2460
2461
  externalChunks.push(chunkPath);
2461
2462
  } else if (chunkPath.includes("[root-of-the-server]")) {
2462
2463
  middlewareChunks.push(chunkPath);
2464
+ } else {
2465
+ libraryChunks.push(chunkPath);
2463
2466
  }
2464
2467
  }
2465
2468
  const chunksCode = [];
@@ -2474,6 +2477,17 @@ async function compileTurbopack(inputPath, middlewareCode, options = {}) {
2474
2477
  warnings.push(`External chunk not found: ${fullPath}`);
2475
2478
  }
2476
2479
  }
2480
+ for (const chunkPath of libraryChunks) {
2481
+ const relativePath = chunkPath.replace(/^server\//, "");
2482
+ const fullPath = join(middlewareDir, relativePath);
2483
+ if (existsSync(fullPath)) {
2484
+ let code = readFileSync(fullPath, "utf-8");
2485
+ code = transformForEdgeOneRuntime(code);
2486
+ chunksCode.push(code);
2487
+ } else {
2488
+ warnings.push(`Library chunk not found: ${fullPath}`);
2489
+ }
2490
+ }
2477
2491
  for (const chunkPath of middlewareChunks) {
2478
2492
  const relativePath = chunkPath.replace(/^server\//, "");
2479
2493
  const fullPath = join(middlewareDir, relativePath);
@@ -3525,28 +3539,61 @@ function matchesPath(pathname, matcher) {
3525
3539
  continue;
3526
3540
  }
3527
3541
  } else {
3528
- // \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362
3529
- let regexPattern = pattern;
3530
- regexPattern = regexPattern.split('/').join('\\\\/');
3531
-
3532
- const parts = regexPattern.split(':');
3533
- regexPattern = parts[0];
3534
- for (let i = 1; i < parts.length; i++) {
3535
- const part = parts[i];
3536
- let j = 0;
3537
- while (j < part.length && /[a-zA-Z0-9_]/.test(part[j])) {
3538
- j++;
3539
- }
3540
- if (j < part.length && part[j] === '*') {
3541
- regexPattern += '.*' + part.slice(j + 1);
3542
+ // \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362\u4E3A\u6B63\u5219\u8868\u8FBE\u5F0F
3543
+ // Next.js middleware matcher \u4F7F\u7528\u7C7B\u4F3C path-to-regexp \u7684\u8BED\u6CD5:
3544
+ // /dashboard/:path* -> \u5339\u914D /dashboard \u548C /dashboard/xxx\uFF08\u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
3545
+ // /api/:path -> \u5339\u914D /api/xxx\uFF08\u4E00\u4E2A\u8DEF\u5F84\u6BB5\uFF09
3546
+ // /api/:path+ -> \u5339\u914D /api/xxx/yyy\uFF08\u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
3547
+ let regexPattern = '';
3548
+ let ci = 0;
3549
+ const p = pattern;
3550
+ while (ci < p.length) {
3551
+ if (p[ci] === ':') {
3552
+ // \u89E3\u6790\u53C2\u6570\u540D
3553
+ ci++;
3554
+ while (ci < p.length && /[a-zA-Z0-9_]/.test(p[ci])) {
3555
+ ci++;
3556
+ }
3557
+ // \u68C0\u67E5\u4FEE\u9970\u7B26
3558
+ if (ci < p.length && p[ci] === '*') {
3559
+ // :param* -> \u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF0C\u524D\u9762\u7684 / \u4E5F\u53D8\u4E3A\u53EF\u9009
3560
+ if (regexPattern.endsWith('\\\\/')) {
3561
+ regexPattern = regexPattern.slice(0, -2);
3562
+ }
3563
+ regexPattern += '(?:\\\\/.*)?';
3564
+ ci++;
3565
+ } else if (ci < p.length && p[ci] === '+') {
3566
+ // :param+ -> \u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5
3567
+ regexPattern += '.+';
3568
+ ci++;
3569
+ } else {
3570
+ // :param -> \u5339\u914D\u4E00\u4E2A\u8DEF\u5F84\u6BB5
3571
+ regexPattern += '[^/]+';
3572
+ }
3573
+ } else if (p[ci] === '/') {
3574
+ regexPattern += '\\\\/';
3575
+ ci++;
3576
+ } else if (p[ci] === '*') {
3577
+ if (ci + 1 < p.length && p[ci + 1] === '*') {
3578
+ regexPattern += '.*';
3579
+ ci += 2;
3580
+ } else {
3581
+ regexPattern += '[^/]*';
3582
+ ci++;
3583
+ }
3542
3584
  } else {
3543
- regexPattern += '[^/]+' + part.slice(j);
3585
+ // \u8F6C\u4E49\u6B63\u5219\u7279\u6B8A\u5B57\u7B26
3586
+ const ch = p[ci];
3587
+ const specialChars = '.+?^$|(){}[]\\\\';
3588
+ if (specialChars.indexOf(ch) !== -1) {
3589
+ regexPattern += '\\\\' + ch;
3590
+ } else {
3591
+ regexPattern += ch;
3592
+ }
3593
+ ci++;
3544
3594
  }
3545
3595
  }
3546
3596
 
3547
- regexPattern = regexPattern.split('**').join('.*');
3548
- regexPattern = regexPattern.split('\\\\*').join('[^/]*');
3549
-
3550
3597
  regex = new RegExp('^' + regexPattern + '$');
3551
3598
  }
3552
3599
 
@@ -11,16 +11,20 @@ import { compile } from "./compiler.js";
11
11
  import { join } from "node:path";
12
12
  import { existsSync, writeFileSync, mkdirSync, readFileSync } from "node:fs";
13
13
  var compileMiddleware = async (ctx) => {
14
+ const nextDistDir = ctx.nextDistDir || ".next";
14
15
  const possiblePaths = [
15
- // 1. Next.js 15+: distDir/server/middleware.js (直接在 .next 目录下)
16
+ // 1. Next.js 15+: distDir/server/middleware.js (直接在 distDir 目录下)
16
17
  join(ctx.distDir, "server/middleware.js"),
17
- // 2. Next.js 15+: standaloneDir/.next/server/middleware.js
18
- join(ctx.standaloneDir, ".next/server/middleware.js"),
19
- // 3. 旧版: standaloneDir/.next/server/src/middleware.js
20
- join(ctx.standaloneDir, ".next/server/src/middleware.js"),
18
+ // 2. Next.js 15+: standaloneDir/{nextDistDir}/server/middleware.js
19
+ join(ctx.standaloneDir, nextDistDir, "server/middleware.js"),
20
+ // 3. 旧版: standaloneDir/{nextDistDir}/server/src/middleware.js
21
+ join(ctx.standaloneDir, nextDistDir, "server/src/middleware.js"),
21
22
  // 4. 旧版: distDir/server/src/middleware.js
22
23
  join(ctx.distDir, "server/src/middleware.js"),
23
- // 5. 相对于当前工作目录
24
+ // 5. 相对于当前工作目录(使用配置的 distDir)
25
+ join(process.cwd(), nextDistDir, "server/middleware.js"),
26
+ join(process.cwd(), nextDistDir, "server/src/middleware.js"),
27
+ // 6. 兼容默认 .next 目录(以防 nextDistDir 获取失败)
24
28
  join(process.cwd(), ".next/server/middleware.js"),
25
29
  join(process.cwd(), ".next/server/src/middleware.js")
26
30
  ];
@@ -41,7 +45,8 @@ var compileMiddleware = async (ctx) => {
41
45
  mkdirSync(outputDir, { recursive: true });
42
46
  const outputPath = join(outputDir, "compiled-middleware.js");
43
47
  writeFileSync(outputPath, result.code || "", "utf-8");
44
- const edgeFunctionPath = join(process.cwd(), ".edgeone/edge-functions/index.js");
48
+ const edgeoneDir = join(process.cwd(), ".edgeone");
49
+ const edgeFunctionPath = join(edgeoneDir, "edge-functions/index.js");
45
50
  if (existsSync(edgeFunctionPath)) {
46
51
  let edgeFunctionCode = readFileSync(edgeFunctionPath, "utf-8");
47
52
  if (edgeFunctionCode.includes(`'__MIDDLEWARE_BUNDLE_CODE__'`)) {
@@ -73,6 +73,22 @@ var responsePolyfill = `
73
73
  return originalRedirect.call(OriginalResponse, urlString, status);
74
74
  };
75
75
 
76
+ // Response.json \u9759\u6001\u65B9\u6CD5 polyfill
77
+ // \u67D0\u4E9B Edge \u8FD0\u884C\u65F6\u4E0D\u652F\u6301\u8FD9\u4E2A\u8F83\u65B0\u7684 Web API
78
+ const patchedJson = function(data, init) {
79
+ const body = JSON.stringify(data);
80
+ const responseInit = cleanResponseInit(init) || {};
81
+
82
+ // \u8BBE\u7F6E Content-Type header
83
+ const headers = new Headers(responseInit.headers || {});
84
+ if (!headers.has('Content-Type')) {
85
+ headers.set('Content-Type', 'application/json');
86
+ }
87
+ responseInit.headers = headers;
88
+
89
+ return new OriginalResponse(body, responseInit);
90
+ };
91
+
76
92
  // \u521B\u5EFA cookies \u5BF9\u8C61\u7684\u5DE5\u5382\u51FD\u6570
77
93
  function createResponseCookies(response) {
78
94
  const cookieStore = new Map();
@@ -137,6 +153,14 @@ var responsePolyfill = `
137
153
  if (prop === 'redirect') {
138
154
  return patchedRedirect;
139
155
  }
156
+ // \u62E6\u622A json \u9759\u6001\u65B9\u6CD5\uFF08polyfill for Edge runtime\uFF09
157
+ if (prop === 'json') {
158
+ // \u5982\u679C\u539F\u751F\u652F\u6301\u5C31\u7528\u539F\u751F\u7684\uFF0C\u5426\u5219\u7528 polyfill
159
+ if (typeof OriginalResponse.json === 'function') {
160
+ return OriginalResponse.json.bind(OriginalResponse);
161
+ }
162
+ return patchedJson;
163
+ }
140
164
  // \u5176\u4ED6\u9759\u6001\u65B9\u6CD5\u76F4\u63A5\u4ECE\u539F\u59CB Response \u83B7\u53D6
141
165
  return Reflect.get(target, prop, receiver);
142
166
  }
@@ -258,42 +258,61 @@ function matchesPath(pathname, matcher) {
258
258
  continue;
259
259
  }
260
260
  } else {
261
- // \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362
262
- // /dashboard/:path* -> /dashboard/.*
263
- // /api/:path -> /api/[^/]+
264
- // \u4F7F\u7528 split/join \u66FF\u4EE3\u6B63\u5219\uFF0C\u907F\u514D\u8F6C\u4E49\u95EE\u9898
265
- let regexPattern = pattern;
266
-
267
- // \u8F6C\u4E49\u659C\u6760: / -> /
268
- regexPattern = regexPattern.split('/').join('\\/');
269
-
270
- // :path* -> .*
271
- // :path -> [^/]+
272
- // \u5148\u5904\u7406 :xxx* \u518D\u5904\u7406 :xxx
273
- const parts = regexPattern.split(':');
274
- regexPattern = parts[0];
275
- for (let i = 1; i < parts.length; i++) {
276
- const part = parts[i];
277
- // \u627E\u5230\u53C2\u6570\u540D\u7ED3\u675F\u4F4D\u7F6E
278
- let j = 0;
279
- while (j < part.length && /[a-zA-Z0-9_]/.test(part[j])) {
280
- j++;
281
- }
282
- if (j < part.length && part[j] === '*') {
283
- // :path* -> .*
284
- regexPattern += '.*' + part.slice(j + 1);
261
+ // \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362\u4E3A\u6B63\u5219\u8868\u8FBE\u5F0F
262
+ // Next.js middleware matcher \u4F7F\u7528\u7C7B\u4F3C path-to-regexp \u7684\u8BED\u6CD5:
263
+ // /dashboard/:path* -> \u5339\u914D /dashboard \u548C /dashboard/xxx\uFF08\u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
264
+ // /api/:path -> \u5339\u914D /api/xxx\uFF08\u4E00\u4E2A\u8DEF\u5F84\u6BB5\uFF09
265
+ // /api/:path+ -> \u5339\u914D /api/xxx/yyy\uFF08\u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
266
+ let regexPattern = '';
267
+ let ci = 0;
268
+ const p = pattern;
269
+ while (ci < p.length) {
270
+ if (p[ci] === ':') {
271
+ // \u89E3\u6790\u53C2\u6570\u540D
272
+ ci++;
273
+ while (ci < p.length && /[a-zA-Z0-9_]/.test(p[ci])) {
274
+ ci++;
275
+ }
276
+ // \u68C0\u67E5\u4FEE\u9970\u7B26
277
+ if (ci < p.length && p[ci] === '*') {
278
+ // :param* -> \u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF0C\u524D\u9762\u7684 / \u4E5F\u53D8\u4E3A\u53EF\u9009
279
+ if (regexPattern.endsWith('\\/')) {
280
+ regexPattern = regexPattern.slice(0, -2);
281
+ }
282
+ regexPattern += '(?:\\/.*)?';
283
+ ci++;
284
+ } else if (ci < p.length && p[ci] === '+') {
285
+ // :param+ -> \u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5
286
+ regexPattern += '.+';
287
+ ci++;
288
+ } else {
289
+ // :param -> \u5339\u914D\u4E00\u4E2A\u8DEF\u5F84\u6BB5
290
+ regexPattern += '[^/]+';
291
+ }
292
+ } else if (p[ci] === '/') {
293
+ regexPattern += '\\/';
294
+ ci++;
295
+ } else if (p[ci] === '*') {
296
+ if (ci + 1 < p.length && p[ci + 1] === '*') {
297
+ regexPattern += '.*';
298
+ ci += 2;
299
+ } else {
300
+ regexPattern += '[^/]*';
301
+ ci++;
302
+ }
285
303
  } else {
286
- // :path -> [^/]+
287
- regexPattern += '[^/]+' + part.slice(j);
304
+ // \u8F6C\u4E49\u6B63\u5219\u7279\u6B8A\u5B57\u7B26
305
+ const ch = p[ci];
306
+ const specialChars = '.+?^$|(){}[]\\\\';
307
+ if (specialChars.indexOf(ch) !== -1) {
308
+ regexPattern += '\\' + ch;
309
+ } else {
310
+ regexPattern += ch;
311
+ }
312
+ ci++;
288
313
  }
289
314
  }
290
315
 
291
- // ** -> .*
292
- regexPattern = regexPattern.split('**').join('.*');
293
- // * -> [^/]* (\u4F46\u4E0D\u5F71\u54CD\u5DF2\u7ECF\u8F6C\u6362\u7684 .*)
294
- // \u7B80\u5355\u5904\u7406\uFF1A\u53EA\u66FF\u6362\u72EC\u7ACB\u7684 *
295
- regexPattern = regexPattern.split('\\*').join('[^/]*');
296
-
297
316
  regex = new RegExp('^' + regexPattern + '$');
298
317
  }
299
318
 
@@ -22,7 +22,7 @@ import { fileURLToPath } from "node:url";
22
22
  var MODULE_DIR = fileURLToPath(new URL(".", import.meta.url));
23
23
  var PLUGIN_DIR = join(MODULE_DIR, "../..");
24
24
  var DEFAULT_PUBLISH_DIR = ".next";
25
- var SERVER_HANDLER_NAME = "server-handler";
25
+ var SERVER_HANDLER_NAME = "cloud-functions/ssr-node";
26
26
  var EDGE_HANDLER_NAME = "edgeone-edge-handler";
27
27
  var PluginContext = class {
28
28
  edgeoneConfig;
@@ -40,7 +40,7 @@ var PluginContext = class {
40
40
  }
41
41
  /** Temporary directory for stashing the build output */
42
42
  get tempPublishDir() {
43
- return this.resolveFromPackagePath(".edgeone/.next");
43
+ return this.resolveFromPackagePath(join(".edgeone", this.nextDistDir));
44
44
  }
45
45
  /** Absolute path of the publish directory */
46
46
  get publishDir() {
@@ -152,13 +152,15 @@ var PluginContext = class {
152
152
  return join(this.edgeFunctionsDir, EDGE_HANDLER_NAME);
153
153
  }
154
154
  constructor(options) {
155
+ const detectedPublishDir = this.detectPublishDir(options?.constants?.PACKAGE_PATH);
155
156
  options = {
156
157
  ...options,
157
158
  functions: {
158
159
  "*": {}
159
160
  },
160
161
  constants: {
161
- PUBLISH_DIR: ".next"
162
+ ...options?.constants,
163
+ PUBLISH_DIR: detectedPublishDir
162
164
  // BUILD_VERSION: '32.1.4',
163
165
  }
164
166
  };
@@ -171,6 +173,32 @@ var PluginContext = class {
171
173
  this.serverlessWrapHandler = options.serverlessWrapHandler;
172
174
  this.getRuntimeShim = options.getRuntimeShim;
173
175
  }
176
+ /**
177
+ * 自动检测 Next.js 构建输出目录
178
+ * 搜索包含 BUILD_ID 文件的目录,支持自定义 distDir 配置
179
+ */
180
+ detectPublishDir(packagePath) {
181
+ const basePath = packagePath || process.cwd();
182
+ const possibleDirs = [
183
+ ".next",
184
+ // 默认
185
+ "build",
186
+ // 常见自定义
187
+ "dist",
188
+ // 常见自定义
189
+ "out",
190
+ // 常见自定义
191
+ ".build"
192
+ // 其他可能
193
+ ];
194
+ for (const dir of possibleDirs) {
195
+ const fullPath = resolve(basePath, dir);
196
+ if (existsSync(join(fullPath, "BUILD_ID"))) {
197
+ return dir;
198
+ }
199
+ }
200
+ return DEFAULT_PUBLISH_DIR;
201
+ }
174
202
  /** Resolves a path correctly with mono repository awareness for .edgeone directories mainly */
175
203
  resolveFromPackagePath(...args) {
176
204
  return resolve(this.constants.PACKAGE_PATH || "", ...args);
@@ -201,18 +229,21 @@ var PluginContext = class {
201
229
  /**
202
230
  * Uses various heuristics to try to find the .next dir.
203
231
  * Works by looking for BUILD_ID, so requires the site to have been built
232
+ * 支持自定义 distDir 配置(如 distDir: 'build')
204
233
  */
205
234
  findDotNext() {
206
- for (const dir of [
207
- // The publish directory
208
- this.publishDir,
209
- // In the root
210
- resolve(DEFAULT_PUBLISH_DIR),
211
- // The sibling of the publish directory
212
- resolve(this.publishDir, "..", DEFAULT_PUBLISH_DIR),
213
- // In the package dir
214
- resolve(this.constants.PACKAGE_PATH || "", DEFAULT_PUBLISH_DIR)
215
- ]) {
235
+ const possibleDirNames = [".next", "build", "dist", "out", ".build"];
236
+ const searchDirs = [
237
+ // The publish directory (已检测的目录)
238
+ this.publishDir
239
+ ];
240
+ for (const dirName of possibleDirNames) {
241
+ searchDirs.push(resolve(dirName));
242
+ searchDirs.push(resolve(this.publishDir, "..", dirName));
243
+ searchDirs.push(resolve(this.constants.PACKAGE_PATH || "", dirName));
244
+ }
245
+ const uniqueDirs = [...new Set(searchDirs)];
246
+ for (const dir of uniqueDirs) {
216
247
  if (existsSync(join(dir, "BUILD_ID"))) {
217
248
  return dir;
218
249
  }