@opennextjs/cloudflare 0.4.0 → 0.4.2
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.
- package/dist/api/cloudflare-context.js +18 -0
- package/dist/cli/build/bundle-server.d.ts +4 -0
- package/dist/cli/build/bundle-server.js +35 -15
- package/dist/cli/build/patches/ast/optional-deps.d.ts +10 -3
- package/dist/cli/build/patches/ast/optional-deps.js +34 -21
- package/dist/cli/build/patches/ast/optional-deps.spec.js +60 -14
- package/dist/cli/build/patches/{investigated → ast}/patch-vercel-og-library.js +2 -2
- package/dist/cli/build/patches/investigated/index.d.ts +0 -1
- package/dist/cli/build/patches/investigated/index.js +0 -1
- package/dist/cli/build/patches/plugins/require-page.d.ts +6 -0
- package/dist/cli/build/patches/plugins/require-page.js +70 -0
- package/dist/cli/build/patches/plugins/require.d.ts +5 -0
- package/dist/cli/build/patches/plugins/require.js +44 -0
- package/dist/cli/build/patches/plugins/wrangler-external.d.ts +20 -0
- package/dist/cli/build/patches/plugins/wrangler-external.js +36 -0
- package/dist/cli/build/patches/to-investigate/index.d.ts +0 -2
- package/dist/cli/build/patches/to-investigate/index.js +0 -2
- package/package.json +4 -4
- package/dist/cli/build/patches/to-investigate/inline-next-require.d.ts +0 -6
- package/dist/cli/build/patches/to-investigate/inline-next-require.js +0 -40
- package/dist/cli/build/patches/to-investigate/wrangler-deps.d.ts +0 -2
- package/dist/cli/build/patches/to-investigate/wrangler-deps.js +0 -115
- /package/dist/cli/build/patches/{investigated → ast}/patch-vercel-og-library.d.ts +0 -0
- /package/dist/cli/build/patches/{investigated → ast}/patch-vercel-og-library.spec.d.ts +0 -0
- /package/dist/cli/build/patches/{investigated → ast}/patch-vercel-og-library.spec.js +0 -0
|
@@ -41,10 +41,28 @@ export function getCloudflareContext() {
|
|
|
41
41
|
* Note: this function should only be called inside the Next.js config file, and although async it doesn't need to be `await`ed
|
|
42
42
|
*/
|
|
43
43
|
export async function initOpenNextCloudflareForDev() {
|
|
44
|
+
const shouldInitializationRun = shouldContextInitializationRun();
|
|
45
|
+
if (!shouldInitializationRun)
|
|
46
|
+
return;
|
|
44
47
|
const context = await getCloudflareContextFromWrangler();
|
|
45
48
|
addCloudflareContextToNodejsGlobal(context);
|
|
46
49
|
await monkeyPatchVmModuleEdgeContext(context);
|
|
47
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Next dev server imports the config file twice (in two different processes, making it hard to track),
|
|
53
|
+
* this causes the initialization to run twice as well, to keep things clean, not allocate extra
|
|
54
|
+
* resources (i.e. instantiate two miniflare instances) and avoid extra potential logs, it would be best
|
|
55
|
+
* to run the initialization only once, this function is used to try to make it so that it does, it returns
|
|
56
|
+
* a flag which indicates if the initialization should run in the current process or not.
|
|
57
|
+
*
|
|
58
|
+
* @returns boolean indicating if the initialization should run
|
|
59
|
+
*/
|
|
60
|
+
function shouldContextInitializationRun() {
|
|
61
|
+
// via debugging we've seen that AsyncLocalStorage is only set in one of the
|
|
62
|
+
// two processes so we're using it as the differentiator between the two
|
|
63
|
+
const AsyncLocalStorage = globalThis["AsyncLocalStorage"];
|
|
64
|
+
return !!AsyncLocalStorage;
|
|
65
|
+
}
|
|
48
66
|
/**
|
|
49
67
|
* Adds the cloudflare context to the global scope in which the Next.js dev node.js process runs in, enabling
|
|
50
68
|
* future calls to `getCloudflareContext` to retrieve and return such context
|
|
@@ -3,6 +3,10 @@ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
|
3
3
|
* Bundle the Open Next server.
|
|
4
4
|
*/
|
|
5
5
|
export declare function bundleServer(buildOpts: BuildOptions): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* This function applies patches required for the code to run on workers.
|
|
8
|
+
*/
|
|
9
|
+
export declare function updateWorkerBundledCode(workerOutputFile: string, buildOpts: BuildOptions): Promise<void>;
|
|
6
10
|
/**
|
|
7
11
|
* Gets the path of the worker.js file generated by the build process
|
|
8
12
|
*
|
|
@@ -7,10 +7,27 @@ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
|
7
7
|
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
|
|
8
8
|
import { build } from "esbuild";
|
|
9
9
|
import { patchOptionalDependencies } from "./patches/ast/optional-deps.js";
|
|
10
|
+
import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
|
|
10
11
|
import * as patches from "./patches/index.js";
|
|
12
|
+
import fixRequire from "./patches/plugins/require.js";
|
|
13
|
+
import inlineRequirePagePlugin from "./patches/plugins/require-page.js";
|
|
14
|
+
import setWranglerExternal from "./patches/plugins/wrangler-external.js";
|
|
11
15
|
import { normalizePath, patchCodeWithValidations } from "./utils/index.js";
|
|
12
16
|
/** The dist directory of the Cloudflare adapter package */
|
|
13
17
|
const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
18
|
+
/**
|
|
19
|
+
* List of optional Next.js dependencies.
|
|
20
|
+
* They are not required for Next.js to run but only needed to enabled specific features.
|
|
21
|
+
* When one of those dependency is required, it should be installed by the application.
|
|
22
|
+
*/
|
|
23
|
+
const optionalDependencies = [
|
|
24
|
+
"caniuse-lite",
|
|
25
|
+
"critters",
|
|
26
|
+
"jimp",
|
|
27
|
+
"probe-image-size",
|
|
28
|
+
// `server.edge` is not available in react-dom@18
|
|
29
|
+
"react-dom/server.edge",
|
|
30
|
+
];
|
|
14
31
|
/**
|
|
15
32
|
* Bundle the Open Next server.
|
|
16
33
|
*/
|
|
@@ -20,22 +37,27 @@ export async function bundleServer(buildOpts) {
|
|
|
20
37
|
const serverFiles = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/required-server-files.json");
|
|
21
38
|
const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config;
|
|
22
39
|
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
|
|
23
|
-
patches.patchWranglerDeps(buildOpts);
|
|
24
40
|
await patches.updateWebpackChunksFile(buildOpts);
|
|
25
|
-
|
|
41
|
+
patchVercelOgLibrary(buildOpts);
|
|
26
42
|
const outputPath = path.join(outputDir, "server-functions", "default");
|
|
27
43
|
const packagePath = getPackagePath(buildOpts);
|
|
28
44
|
const openNextServer = path.join(outputPath, packagePath, `index.mjs`);
|
|
29
45
|
const openNextServerBundle = path.join(outputPath, packagePath, `handler.mjs`);
|
|
30
|
-
await build({
|
|
46
|
+
const result = await build({
|
|
31
47
|
entryPoints: [openNextServer],
|
|
32
48
|
bundle: true,
|
|
33
49
|
outfile: openNextServerBundle,
|
|
34
50
|
format: "esm",
|
|
35
51
|
target: "esnext",
|
|
36
52
|
minify: false,
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
metafile: true,
|
|
54
|
+
plugins: [
|
|
55
|
+
createFixRequiresESBuildPlugin(buildOpts),
|
|
56
|
+
inlineRequirePagePlugin(buildOpts),
|
|
57
|
+
setWranglerExternal(),
|
|
58
|
+
fixRequire(),
|
|
59
|
+
],
|
|
60
|
+
external: ["./middleware/handler.mjs", ...optionalDependencies],
|
|
39
61
|
alias: {
|
|
40
62
|
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
|
|
41
63
|
// eval("require")("bufferutil");
|
|
@@ -63,7 +85,6 @@ export async function bundleServer(buildOpts) {
|
|
|
63
85
|
"process.env.NODE_ENV": '"production"',
|
|
64
86
|
"process.env.NEXT_MINIMAL": "true",
|
|
65
87
|
},
|
|
66
|
-
// We need to set platform to node so that esbuild doesn't complain about the node imports
|
|
67
88
|
platform: "node",
|
|
68
89
|
banner: {
|
|
69
90
|
js: `
|
|
@@ -91,7 +112,7 @@ const CustomRequest = class extends globalThis.Request {
|
|
|
91
112
|
// https://github.com/cloudflare/workerd/issues/2746
|
|
92
113
|
// https://github.com/cloudflare/workerd/issues/3245
|
|
93
114
|
Object.defineProperty(init, "body", {
|
|
94
|
-
value: init.body instanceof __cf_stream.Readable ? ReadableStream.from(init.body) : init.body
|
|
115
|
+
value: init.body instanceof __cf_stream.Readable ? ReadableStream.from(init.body) : init.body
|
|
95
116
|
});
|
|
96
117
|
}
|
|
97
118
|
super(input, init);
|
|
@@ -105,6 +126,11 @@ globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
|
|
|
105
126
|
`,
|
|
106
127
|
},
|
|
107
128
|
});
|
|
129
|
+
if (result.errors.length > 0) {
|
|
130
|
+
result.errors.forEach((error) => console.error(error));
|
|
131
|
+
throw new Error(`There was a problem bundling the server.`);
|
|
132
|
+
}
|
|
133
|
+
fs.writeFileSync(openNextServerBundle + ".meta.json", JSON.stringify(result.metafile, null, 2));
|
|
108
134
|
await updateWorkerBundledCode(openNextServerBundle, buildOpts);
|
|
109
135
|
const isMonorepo = monorepoRoot !== appPath;
|
|
110
136
|
if (isMonorepo) {
|
|
@@ -115,13 +141,12 @@ globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
|
|
|
115
141
|
/**
|
|
116
142
|
* This function applies patches required for the code to run on workers.
|
|
117
143
|
*/
|
|
118
|
-
async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
|
|
144
|
+
export async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
|
|
119
145
|
const code = await readFile(workerOutputFile, "utf8");
|
|
120
146
|
const patchedCode = await patchCodeWithValidations(code, [
|
|
121
147
|
["require", patches.patchRequire],
|
|
122
148
|
["`buildId` function", (code) => patches.patchBuildId(code, buildOpts)],
|
|
123
149
|
["`loadManifest` function", (code) => patches.patchLoadManifest(code, buildOpts)],
|
|
124
|
-
["next's require", (code) => patches.inlineNextRequire(code, buildOpts)],
|
|
125
150
|
["`findDir` function", (code) => patches.patchFindDir(code, buildOpts)],
|
|
126
151
|
["`evalManifest` function", (code) => patches.inlineEvalManifest(code, buildOpts)],
|
|
127
152
|
["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
|
|
@@ -137,11 +162,6 @@ async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
|
|
|
137
162
|
// TODO: implement for cf (possibly in @opennextjs/aws)
|
|
138
163
|
.replace("patchAsyncStorage();", "//patchAsyncStorage();"),
|
|
139
164
|
],
|
|
140
|
-
[
|
|
141
|
-
'`eval("require")` calls',
|
|
142
|
-
(code) => code.replaceAll('eval("require")', "require"),
|
|
143
|
-
{ isOptional: true },
|
|
144
|
-
],
|
|
145
165
|
[
|
|
146
166
|
"`require.resolve` call",
|
|
147
167
|
// workers do not support dynamic require nor require.resolve
|
|
@@ -149,7 +169,7 @@ async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
|
|
|
149
169
|
],
|
|
150
170
|
]);
|
|
151
171
|
const bundle = parse(Lang.TypeScript, patchedCode).root();
|
|
152
|
-
const { edits } = patchOptionalDependencies(bundle);
|
|
172
|
+
const { edits } = patchOptionalDependencies(bundle, optionalDependencies);
|
|
153
173
|
await writeFile(workerOutputFile, bundle.commitEdits(edits));
|
|
154
174
|
}
|
|
155
175
|
function createFixRequiresESBuildPlugin(options) {
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { type SgNode } from "@ast-grep/napi";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Handles optional dependencies.
|
|
4
4
|
*
|
|
5
5
|
* A top level `require(optionalDep)` would throw when the dep is not installed.
|
|
6
6
|
*
|
|
7
7
|
* So we wrap `require(optionalDep)` in a try/catch (if not already present).
|
|
8
8
|
*/
|
|
9
|
-
export declare
|
|
10
|
-
|
|
9
|
+
export declare function buildOptionalDepRule(dependencies: string[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Wraps requires for passed dependencies in a `try ... catch`.
|
|
12
|
+
*
|
|
13
|
+
* @param root AST root node
|
|
14
|
+
* @param dependencies List of dependencies to wrap
|
|
15
|
+
* @returns matches and edits, see `applyRule`
|
|
16
|
+
*/
|
|
17
|
+
export declare function patchOptionalDependencies(root: SgNode, dependencies: string[]): {
|
|
11
18
|
edits: import("@ast-grep/napi").Edit[];
|
|
12
19
|
matches: SgNode<import("@ast-grep/napi/types/staticTypes.js").TypesMap, import("@ast-grep/napi/types/staticTypes.js").Kinds<import("@ast-grep/napi/types/staticTypes.js").TypesMap>>[];
|
|
13
20
|
};
|
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
import { applyRule } from "./util.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Handles optional dependencies.
|
|
4
4
|
*
|
|
5
5
|
* A top level `require(optionalDep)` would throw when the dep is not installed.
|
|
6
6
|
*
|
|
7
7
|
* So we wrap `require(optionalDep)` in a try/catch (if not already present).
|
|
8
8
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
kind:
|
|
9
|
+
export function buildOptionalDepRule(dependencies) {
|
|
10
|
+
// Build a regexp matching either
|
|
11
|
+
// - the full packages names, i.e. `package`
|
|
12
|
+
// - subpaths in the package, i.e. `package/...`
|
|
13
|
+
const regex = `^(${dependencies.join("|")})(/|$)`;
|
|
14
|
+
return `
|
|
15
|
+
rule:
|
|
16
|
+
pattern: $$$LHS = require($$$REQ)
|
|
17
|
+
has:
|
|
18
|
+
pattern: $MOD
|
|
19
|
+
kind: string_fragment
|
|
20
20
|
stopBy: end
|
|
21
|
+
regex: ${regex}
|
|
22
|
+
not:
|
|
23
|
+
inside:
|
|
24
|
+
kind: try_statement
|
|
25
|
+
stopBy: end
|
|
21
26
|
|
|
22
|
-
fix: |-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`;
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
fix: |-
|
|
28
|
+
try {
|
|
29
|
+
$$$LHS = require($$$REQ);
|
|
30
|
+
} catch {
|
|
31
|
+
throw new Error('The optional dependency "$MOD" is not installed');
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Wraps requires for passed dependencies in a `try ... catch`.
|
|
37
|
+
*
|
|
38
|
+
* @param root AST root node
|
|
39
|
+
* @param dependencies List of dependencies to wrap
|
|
40
|
+
* @returns matches and edits, see `applyRule`
|
|
41
|
+
*/
|
|
42
|
+
export function patchOptionalDependencies(root, dependencies) {
|
|
43
|
+
return applyRule(buildOptionalDepRule(dependencies), root);
|
|
31
44
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { buildOptionalDepRule } from "./optional-deps.js";
|
|
3
3
|
import { patchCode } from "./util.js";
|
|
4
4
|
describe("optional dependecy", () => {
|
|
5
5
|
it('should wrap a top-level require("caniuse-lite") in a try-catch', () => {
|
|
6
6
|
const code = `t = require("caniuse-lite");`;
|
|
7
|
-
expect(patchCode(code,
|
|
7
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
8
8
|
"try {
|
|
9
9
|
t = require("caniuse-lite");
|
|
10
10
|
} catch {
|
|
@@ -14,7 +14,7 @@ describe("optional dependecy", () => {
|
|
|
14
14
|
});
|
|
15
15
|
it('should wrap a top-level require("caniuse-lite/data") in a try-catch', () => {
|
|
16
16
|
const code = `t = require("caniuse-lite/data");`;
|
|
17
|
-
expect(patchCode(code,
|
|
17
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
18
18
|
"try {
|
|
19
19
|
t = require("caniuse-lite/data");
|
|
20
20
|
} catch {
|
|
@@ -24,7 +24,7 @@ describe("optional dependecy", () => {
|
|
|
24
24
|
});
|
|
25
25
|
it('should wrap e.exports = require("caniuse-lite") in a try-catch', () => {
|
|
26
26
|
const code = 'e.exports = require("caniuse-lite");';
|
|
27
|
-
expect(patchCode(code,
|
|
27
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
28
28
|
"try {
|
|
29
29
|
e.exports = require("caniuse-lite");
|
|
30
30
|
} catch {
|
|
@@ -34,7 +34,7 @@ describe("optional dependecy", () => {
|
|
|
34
34
|
});
|
|
35
35
|
it('should wrap module.exports = require("caniuse-lite") in a try-catch', () => {
|
|
36
36
|
const code = 'module.exports = require("caniuse-lite");';
|
|
37
|
-
expect(patchCode(code,
|
|
37
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
38
38
|
"try {
|
|
39
39
|
module.exports = require("caniuse-lite");
|
|
40
40
|
} catch {
|
|
@@ -44,7 +44,7 @@ describe("optional dependecy", () => {
|
|
|
44
44
|
});
|
|
45
45
|
it('should wrap exports.foo = require("caniuse-lite") in a try-catch', () => {
|
|
46
46
|
const code = 'exports.foo = require("caniuse-lite");';
|
|
47
|
-
expect(patchCode(code,
|
|
47
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
48
48
|
"try {
|
|
49
49
|
exports.foo = require("caniuse-lite");
|
|
50
50
|
} catch {
|
|
@@ -54,21 +54,21 @@ describe("optional dependecy", () => {
|
|
|
54
54
|
});
|
|
55
55
|
it('should not wrap require("lodash") in a try-catch', () => {
|
|
56
56
|
const code = 't = require("lodash");';
|
|
57
|
-
expect(patchCode(code,
|
|
57
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`"t = require("lodash");"`);
|
|
58
58
|
});
|
|
59
59
|
it('should not wrap require("other-module") if it does not match caniuse-lite regex', () => {
|
|
60
60
|
const code = 't = require("other-module");';
|
|
61
|
-
expect(patchCode(code,
|
|
61
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`"t = require("other-module");"`);
|
|
62
62
|
});
|
|
63
63
|
it("should not wrap a require() call already inside a try-catch", () => {
|
|
64
64
|
const code = `
|
|
65
65
|
try {
|
|
66
|
-
|
|
66
|
+
t = require("caniuse-lite");
|
|
67
67
|
} catch {}
|
|
68
68
|
`;
|
|
69
|
-
expect(patchCode(code,
|
|
69
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
70
70
|
"try {
|
|
71
|
-
|
|
71
|
+
t = require("caniuse-lite");
|
|
72
72
|
} catch {}
|
|
73
73
|
"
|
|
74
74
|
`);
|
|
@@ -76,14 +76,60 @@ try {
|
|
|
76
76
|
it("should handle require with subpath and not wrap if already in try-catch", () => {
|
|
77
77
|
const code = `
|
|
78
78
|
try {
|
|
79
|
-
|
|
79
|
+
t = require("caniuse-lite/path");
|
|
80
80
|
} catch {}
|
|
81
81
|
`;
|
|
82
|
-
expect(patchCode(code,
|
|
82
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
83
83
|
"try {
|
|
84
|
-
|
|
84
|
+
t = require("caniuse-lite/path");
|
|
85
85
|
} catch {}
|
|
86
86
|
"
|
|
87
87
|
`);
|
|
88
88
|
});
|
|
89
|
+
it("should handle multiple dependencies", () => {
|
|
90
|
+
const code = `
|
|
91
|
+
t1 = require("caniuse-lite");
|
|
92
|
+
t2 = require("caniuse-lite/path");
|
|
93
|
+
t3 = require("jimp");
|
|
94
|
+
t4 = require("jimp/path");
|
|
95
|
+
`;
|
|
96
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite", "jimp"]))).toMatchInlineSnapshot(`
|
|
97
|
+
"try {
|
|
98
|
+
t1 = require("caniuse-lite");
|
|
99
|
+
} catch {
|
|
100
|
+
throw new Error('The optional dependency "caniuse-lite" is not installed');
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
t2 = require("caniuse-lite/path");
|
|
104
|
+
} catch {
|
|
105
|
+
throw new Error('The optional dependency "caniuse-lite/path" is not installed');
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
t3 = require("jimp");
|
|
109
|
+
} catch {
|
|
110
|
+
throw new Error('The optional dependency "jimp" is not installed');
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
t4 = require("jimp/path");
|
|
114
|
+
} catch {
|
|
115
|
+
throw new Error('The optional dependency "jimp/path" is not installed');
|
|
116
|
+
};
|
|
117
|
+
"
|
|
118
|
+
`);
|
|
119
|
+
});
|
|
120
|
+
it("should not update partial matches", () => {
|
|
121
|
+
const code = `
|
|
122
|
+
t1 = require("before-caniuse-lite");
|
|
123
|
+
t2 = require("before-caniuse-lite/path");
|
|
124
|
+
t3 = require("caniuse-lite-after");
|
|
125
|
+
t4 = require("caniuse-lite-after/path");
|
|
126
|
+
`;
|
|
127
|
+
expect(patchCode(code, buildOptionalDepRule(["caniuse-lite"]))).toMatchInlineSnapshot(`
|
|
128
|
+
"t1 = require("before-caniuse-lite");
|
|
129
|
+
t2 = require("before-caniuse-lite/path");
|
|
130
|
+
t3 = require("caniuse-lite-after");
|
|
131
|
+
t4 = require("caniuse-lite-after/path");
|
|
132
|
+
"
|
|
133
|
+
`);
|
|
134
|
+
});
|
|
89
135
|
});
|
|
@@ -2,8 +2,8 @@ import { copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } fro
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
4
4
|
import { globSync } from "glob";
|
|
5
|
-
import { parseFile } from "
|
|
6
|
-
import { patchVercelOgFallbackFont, patchVercelOgImport } from "
|
|
5
|
+
import { parseFile } from "./util.js";
|
|
6
|
+
import { patchVercelOgFallbackFont, patchVercelOgImport } from "./vercel-og.js";
|
|
7
7
|
/**
|
|
8
8
|
* Patches the usage of @vercel/og to be compatible with Cloudflare Workers.
|
|
9
9
|
*
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
5
|
+
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
|
|
6
|
+
import { patchCode } from "../ast/util.js";
|
|
7
|
+
export default function inlineRequirePagePlugin(buildOpts) {
|
|
8
|
+
return {
|
|
9
|
+
name: "inline-require-page",
|
|
10
|
+
setup: async (build) => {
|
|
11
|
+
build.onLoad({
|
|
12
|
+
filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/require\.js$`, { escape: false }),
|
|
13
|
+
}, async ({ path }) => {
|
|
14
|
+
const jsCode = await readFile(path, "utf8");
|
|
15
|
+
if (/function requirePage\(/.test(jsCode)) {
|
|
16
|
+
return { contents: patchCode(jsCode, getRule(buildOpts)) };
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function getRule(buildOpts) {
|
|
23
|
+
const { outputDir } = buildOpts;
|
|
24
|
+
const serverDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server");
|
|
25
|
+
const pagesManifestFile = join(serverDir, "pages-manifest.json");
|
|
26
|
+
const appPathsManifestFile = join(serverDir, "app-paths-manifest.json");
|
|
27
|
+
const pagesManifests = existsSync(pagesManifestFile)
|
|
28
|
+
? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8")))
|
|
29
|
+
: [];
|
|
30
|
+
const appPathsManifests = existsSync(appPathsManifestFile)
|
|
31
|
+
? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8")))
|
|
32
|
+
: [];
|
|
33
|
+
const manifests = pagesManifests.concat(appPathsManifests);
|
|
34
|
+
const htmlFiles = manifests.filter((file) => file.endsWith(".html"));
|
|
35
|
+
const jsFiles = manifests.filter((file) => file.endsWith(".js"));
|
|
36
|
+
// Inline fs access and dynamic require that are not supported by workerd.
|
|
37
|
+
const fnBody = `
|
|
38
|
+
// html
|
|
39
|
+
${htmlFiles
|
|
40
|
+
.map((file) => `if (pagePath.endsWith("${file}")) {
|
|
41
|
+
return ${JSON.stringify(readFileSync(join(serverDir, file), "utf-8"))};
|
|
42
|
+
}`)
|
|
43
|
+
.join("\n")}
|
|
44
|
+
// js
|
|
45
|
+
process.env.__NEXT_PRIVATE_RUNTIME_TYPE = isAppPath ? 'app' : 'pages';
|
|
46
|
+
try {
|
|
47
|
+
${jsFiles
|
|
48
|
+
.map((file) => `if (pagePath.endsWith("${file}")) {
|
|
49
|
+
return require(${JSON.stringify(join(serverDir, file))});
|
|
50
|
+
}`)
|
|
51
|
+
.join("\n")}
|
|
52
|
+
} finally {
|
|
53
|
+
process.env.__NEXT_PRIVATE_RUNTIME_TYPE = '';
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
return {
|
|
57
|
+
rule: {
|
|
58
|
+
pattern: `
|
|
59
|
+
function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) {
|
|
60
|
+
const $_ = getPagePath($$$ARGS);
|
|
61
|
+
$$$_BODY
|
|
62
|
+
}`,
|
|
63
|
+
},
|
|
64
|
+
fix: `
|
|
65
|
+
function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) {
|
|
66
|
+
const pagePath = getPagePath($$$ARGS);
|
|
67
|
+
${fnBody}
|
|
68
|
+
}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
export default function fixRequire() {
|
|
3
|
+
return {
|
|
4
|
+
name: "fix-require",
|
|
5
|
+
setup: async (build) => {
|
|
6
|
+
build.onLoad({ filter: /.*/ }, async ({ path }) => {
|
|
7
|
+
let contents = await fs.readFile(path, "utf-8");
|
|
8
|
+
// `eval(...)` is not supported by workerd.
|
|
9
|
+
contents = contents.replaceAll(`eval("require")`, "require");
|
|
10
|
+
// `@opentelemetry` has a few issues.
|
|
11
|
+
//
|
|
12
|
+
// Next.js has the following code in `next/dist/server/lib/trace/tracer.js`:
|
|
13
|
+
//
|
|
14
|
+
// try {
|
|
15
|
+
// api = require('@opentelemetry/api');
|
|
16
|
+
// } catch (err) {
|
|
17
|
+
// api = require('next/dist/compiled/@opentelemetry/api');
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// The intent is to allow users to install their own version of `@opentelemetry/api`.
|
|
21
|
+
//
|
|
22
|
+
// The problem is that even when users do not explicitely install `@opentelemetry/api`,
|
|
23
|
+
// `require('@opentelemetry/api')` resolves to the package which is a dependency
|
|
24
|
+
// of Next.
|
|
25
|
+
//
|
|
26
|
+
// The second problem is that when Next traces files, it would not copy the `api/build/esm`
|
|
27
|
+
// folder (used by the `module` conditions in package.json) it would only copy `api/build/src`.
|
|
28
|
+
// This could be solved by updating the next config:
|
|
29
|
+
//
|
|
30
|
+
// const nextConfig: NextConfig = {
|
|
31
|
+
// // ...
|
|
32
|
+
// outputFileTracingIncludes: {
|
|
33
|
+
// "*": ["./node_modules/@opentelemetry/api/build/**/*"],
|
|
34
|
+
// },
|
|
35
|
+
// };
|
|
36
|
+
//
|
|
37
|
+
// We can consider doing that when we want to enable users to install their own version
|
|
38
|
+
// of `@opentelemetry/api`. For now we simply use the pre-compiled version.
|
|
39
|
+
contents = contents.replace(/require\(.@opentelemetry\/api.\)/g, `require("next/dist/compiled/@opentelemetry/api")`);
|
|
40
|
+
return { contents };
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESBuild plugin to mark files bundled by wrangler as external.
|
|
3
|
+
*
|
|
4
|
+
* `.wasm` and `.bin` will ultimately be bundled by wrangler.
|
|
5
|
+
* We should only mark them as external in the adapter.
|
|
6
|
+
*
|
|
7
|
+
* However simply marking them as external would copy the import path to the bundle,
|
|
8
|
+
* i.e. `import("./file.wasm?module")` and given than the bundle is generated in a
|
|
9
|
+
* different location than the input files, the relative path would not be valid.
|
|
10
|
+
*
|
|
11
|
+
* This ESBuild plugin convert relative paths to absolute paths so that they are
|
|
12
|
+
* still valid from inside the bundle.
|
|
13
|
+
*
|
|
14
|
+
* ref: https://developers.cloudflare.com/workers/wrangler/bundling/
|
|
15
|
+
*/
|
|
16
|
+
import type { PluginBuild } from "esbuild";
|
|
17
|
+
export default function setWranglerExternal(): {
|
|
18
|
+
name: string;
|
|
19
|
+
setup: (build: PluginBuild) => Promise<void>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESBuild plugin to mark files bundled by wrangler as external.
|
|
3
|
+
*
|
|
4
|
+
* `.wasm` and `.bin` will ultimately be bundled by wrangler.
|
|
5
|
+
* We should only mark them as external in the adapter.
|
|
6
|
+
*
|
|
7
|
+
* However simply marking them as external would copy the import path to the bundle,
|
|
8
|
+
* i.e. `import("./file.wasm?module")` and given than the bundle is generated in a
|
|
9
|
+
* different location than the input files, the relative path would not be valid.
|
|
10
|
+
*
|
|
11
|
+
* This ESBuild plugin convert relative paths to absolute paths so that they are
|
|
12
|
+
* still valid from inside the bundle.
|
|
13
|
+
*
|
|
14
|
+
* ref: https://developers.cloudflare.com/workers/wrangler/bundling/
|
|
15
|
+
*/
|
|
16
|
+
import { dirname, resolve } from "node:path";
|
|
17
|
+
export default function setWranglerExternal() {
|
|
18
|
+
return {
|
|
19
|
+
name: "wrangler-externals",
|
|
20
|
+
setup: async (build) => {
|
|
21
|
+
const namespace = "wrangler-externals-plugin";
|
|
22
|
+
build.onResolve({ filter: /(\.bin|\.wasm\?module)$/ }, ({ path, importer }) => {
|
|
23
|
+
return {
|
|
24
|
+
path: resolve(dirname(importer), path),
|
|
25
|
+
namespace,
|
|
26
|
+
external: true,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
build.onLoad({ filter: /.*/, namespace }, async ({ path }) => {
|
|
30
|
+
return {
|
|
31
|
+
contents: `export * from '${path}';`,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export * from "./inline-eval-manifest.js";
|
|
2
2
|
export * from "./inline-middleware-manifest-require.js";
|
|
3
|
-
export * from "./inline-next-require.js";
|
|
4
3
|
export * from "./patch-exception-bubbling.js";
|
|
5
4
|
export * from "./patch-find-dir.js";
|
|
6
5
|
export * from "./patch-load-instrumentation-module.js";
|
|
7
6
|
export * from "./patch-read-file.js";
|
|
8
|
-
export * from "./wrangler-deps.js";
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export * from "./inline-eval-manifest.js";
|
|
2
2
|
export * from "./inline-middleware-manifest-require.js";
|
|
3
|
-
export * from "./inline-next-require.js";
|
|
4
3
|
export * from "./patch-exception-bubbling.js";
|
|
5
4
|
export * from "./patch-find-dir.js";
|
|
6
5
|
export * from "./patch-load-instrumentation-module.js";
|
|
7
6
|
export * from "./patch-read-file.js";
|
|
8
|
-
export * from "./wrangler-deps.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennextjs/cloudflare",
|
|
3
3
|
"description": "Cloudflare builder for next apps",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opennextjs-cloudflare": "dist/cli/index.js"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@tsconfig/strictest": "^2.0.5",
|
|
48
48
|
"@types/mock-fs": "^4.13.4",
|
|
49
49
|
"@types/node": "^22.2.0",
|
|
50
|
-
"esbuild": "^0.
|
|
50
|
+
"esbuild": "^0.24.2",
|
|
51
51
|
"eslint": "^9.11.1",
|
|
52
52
|
"eslint-plugin-import": "^2.31.0",
|
|
53
53
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@ast-grep/napi": "^0.34.1",
|
|
65
65
|
"@dotenvx/dotenvx": "1.31.0",
|
|
66
|
-
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@
|
|
66
|
+
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@715",
|
|
67
67
|
"enquirer": "^2.4.1",
|
|
68
68
|
"glob": "^11.0.0",
|
|
69
69
|
"ts-morph": "^23.0.0",
|
|
70
70
|
"yaml": "^2.7.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"wrangler": "^3.
|
|
73
|
+
"wrangler": "^3.106.0"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"clean": "rimraf dist",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
2
|
-
/**
|
|
3
|
-
* The following avoid various Next.js specific files `require`d at runtime since we can just read
|
|
4
|
-
* and inline their content during build time
|
|
5
|
-
*/
|
|
6
|
-
export declare function inlineNextRequire(code: string, buildOpts: BuildOptions): string;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
4
|
-
/**
|
|
5
|
-
* The following avoid various Next.js specific files `require`d at runtime since we can just read
|
|
6
|
-
* and inline their content during build time
|
|
7
|
-
*/
|
|
8
|
-
// TODO(vicb): __NEXT_PRIVATE_RUNTIME_TYPE is not handled by this patch
|
|
9
|
-
export function inlineNextRequire(code, buildOpts) {
|
|
10
|
-
const { outputDir } = buildOpts;
|
|
11
|
-
const serverDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server");
|
|
12
|
-
const pagesManifestFile = join(serverDir, "pages-manifest.json");
|
|
13
|
-
const appPathsManifestFile = join(serverDir, "app-paths-manifest.json");
|
|
14
|
-
const pagesManifests = existsSync(pagesManifestFile)
|
|
15
|
-
? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8")))
|
|
16
|
-
: [];
|
|
17
|
-
const appPathsManifests = existsSync(appPathsManifestFile)
|
|
18
|
-
? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8")))
|
|
19
|
-
: [];
|
|
20
|
-
const manifests = pagesManifests.concat(appPathsManifests);
|
|
21
|
-
const htmlPages = manifests.filter((file) => file.endsWith(".html"));
|
|
22
|
-
const pageModules = manifests.filter((file) => file.endsWith(".js"));
|
|
23
|
-
return code.replace(/const pagePath = getPagePath\(.+?\);/, `$&
|
|
24
|
-
${htmlPages
|
|
25
|
-
.map((htmlPage) => `
|
|
26
|
-
if (pagePath.endsWith("${htmlPage}")) {
|
|
27
|
-
return ${JSON.stringify(readFileSync(join(serverDir, htmlPage), "utf-8"))};
|
|
28
|
-
}
|
|
29
|
-
`)
|
|
30
|
-
.join("\n")}
|
|
31
|
-
${pageModules
|
|
32
|
-
.map((module) => `
|
|
33
|
-
if (pagePath.endsWith("${module}")) {
|
|
34
|
-
return require(${JSON.stringify(join(serverDir, module))});
|
|
35
|
-
}
|
|
36
|
-
`)
|
|
37
|
-
.join("\n")}
|
|
38
|
-
throw new Error("Unknown pagePath: " + pagePath);
|
|
39
|
-
`);
|
|
40
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
4
|
-
import * as ts from "ts-morph";
|
|
5
|
-
import { tsParseFile } from "../../utils/index.js";
|
|
6
|
-
export function patchWranglerDeps(buildOpts) {
|
|
7
|
-
console.log("# patchWranglerDeps");
|
|
8
|
-
const { outputDir } = buildOpts;
|
|
9
|
-
const nextDistDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), "node_modules/next/dist");
|
|
10
|
-
const pagesRuntimeFile = join(nextDistDir, "compiled/next-server/pages.runtime.prod.js");
|
|
11
|
-
const patchedPagesRuntime = readFileSync(pagesRuntimeFile, "utf-8").replace(`e.exports=require("critters")`, `
|
|
12
|
-
try {
|
|
13
|
-
e.exports=require("critters");
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
console.error('critters is not installed');
|
|
17
|
-
}
|
|
18
|
-
`);
|
|
19
|
-
writeFileSync(pagesRuntimeFile, patchedPagesRuntime);
|
|
20
|
-
patchRequireReactDomServerEdge(nextDistDir);
|
|
21
|
-
// we shim @opentelemetry/api to the throwing shim so that it will throw right away, this is so that we throw inside the
|
|
22
|
-
// try block here: https://github.com/vercel/next.js/blob/9e8266a7/packages/next/src/server/lib/trace/tracer.ts#L27-L31
|
|
23
|
-
// causing the code to require the 'next/dist/compiled/@opentelemetry/api' module instead (which properly works)
|
|
24
|
-
const tracerFile = join(nextDistDir, "server/lib/trace/tracer.js");
|
|
25
|
-
const patchedTracer = readFileSync(tracerFile, "utf-8").replaceAll(/\w+\s*=\s*require\([^/]*opentelemetry.*\)/g, `throw new Error("@opentelemetry/api")`);
|
|
26
|
-
writeFileSync(tracerFile, patchedTracer);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* `react-dom` v>=19 has a `server.edge` export: https://github.com/facebook/react/blob/a160102f3/packages/react-dom/package.json#L79
|
|
30
|
-
* but version of `react-dom` <= 18 do not have this export but have a `server.browser` export instead: https://github.com/facebook/react/blob/8a015b68/packages/react-dom/package.json#L49
|
|
31
|
-
*
|
|
32
|
-
* Next.js also try-catches importing the `server.edge` export:
|
|
33
|
-
* https://github.com/vercel/next.js/blob/6784575/packages/next/src/server/ReactDOMServerPages.js
|
|
34
|
-
*
|
|
35
|
-
* The issue here is that in the `.next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js`
|
|
36
|
-
* file for whatever reason there is a non `try-catch`ed require for the `server.edge` export
|
|
37
|
-
*
|
|
38
|
-
* This functions fixes this issue by wrapping the require in a try-catch block in the same way Next.js does it
|
|
39
|
-
* (note: this will make the build succeed but doesn't guarantee that everything will necessarily work at runtime since
|
|
40
|
-
* it's not clear what code and how might be rely on this require call)
|
|
41
|
-
*
|
|
42
|
-
*/
|
|
43
|
-
function patchRequireReactDomServerEdge(nextDistDir) {
|
|
44
|
-
// Patch .next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js
|
|
45
|
-
const pagesRuntimeFile = join(nextDistDir, "compiled/next-server/pages.runtime.prod.js");
|
|
46
|
-
const code = readFileSync(pagesRuntimeFile, "utf-8");
|
|
47
|
-
const file = tsParseFile(code);
|
|
48
|
-
// we need to update this function: `e=>{"use strict";e.exports=require("react-dom/server.edge")}`
|
|
49
|
-
file.getDescendantsOfKind(ts.SyntaxKind.ArrowFunction).forEach((arrowFunction) => {
|
|
50
|
-
// the function has a single parameter
|
|
51
|
-
const p = arrowFunction.getParameters();
|
|
52
|
-
if (p.length !== 1) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const parameterName = p[0].getName();
|
|
56
|
-
const bodyChildren = arrowFunction.getBody().getChildren();
|
|
57
|
-
if (!(bodyChildren.length === 3 &&
|
|
58
|
-
bodyChildren[0].getFullText() === "{" &&
|
|
59
|
-
bodyChildren[2].getFullText() === "}")) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const bodyStatements = bodyChildren[1]?.getChildren();
|
|
63
|
-
// the function has only two statements: "use strict" and e.exports=require("react-dom/server.edge")
|
|
64
|
-
if (!(bodyStatements?.length === 2 &&
|
|
65
|
-
bodyStatements.every((statement) => statement.isKind(ts.SyntaxKind.ExpressionStatement)))) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const bodyExpressionStatements = bodyStatements;
|
|
69
|
-
const stringLiteralExpression = bodyExpressionStatements[0].getExpressionIfKind(ts.SyntaxKind.StringLiteral);
|
|
70
|
-
// the first statement needs to be "use strict"
|
|
71
|
-
if (stringLiteralExpression?.getText() !== '"use strict"') {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// the second statement (e.exports=require("react-dom/server.edge")) needs to be a binary expression
|
|
75
|
-
const binaryExpression = bodyExpressionStatements[1].getExpressionIfKind(ts.SyntaxKind.BinaryExpression);
|
|
76
|
-
if (!binaryExpression?.getOperatorToken().isKind(ts.SyntaxKind.EqualsToken)) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
// on the left we have `${parameterName}.exports`
|
|
80
|
-
const binaryLeft = binaryExpression.getLeft();
|
|
81
|
-
if (!binaryLeft.isKind(ts.SyntaxKind.PropertyAccessExpression) ||
|
|
82
|
-
binaryLeft.getExpressionIfKind(ts.SyntaxKind.Identifier)?.getText() !== parameterName ||
|
|
83
|
-
binaryLeft.getName() !== "exports") {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
// on the right we have `require("react-dom/server.edge")`
|
|
87
|
-
const binaryRight = binaryExpression.getRight();
|
|
88
|
-
if (!binaryRight.isKind(ts.SyntaxKind.CallExpression) ||
|
|
89
|
-
binaryRight.getExpressionIfKind(ts.SyntaxKind.Identifier)?.getText() !== "require") {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const requireArgs = binaryRight.getArguments();
|
|
93
|
-
if (requireArgs.length !== 1 || requireArgs[0].getText() !== '"react-dom/server.edge"') {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
arrowFunction.setBodyText(`
|
|
97
|
-
// OpenNext patch
|
|
98
|
-
let ReactDOMServer;
|
|
99
|
-
try {
|
|
100
|
-
ReactDOMServer = require('react-dom/server.edge');
|
|
101
|
-
} catch (error) {
|
|
102
|
-
if (
|
|
103
|
-
error.code !== 'MODULE_NOT_FOUND' &&
|
|
104
|
-
error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED'
|
|
105
|
-
) {
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
ReactDOMServer = require('react-dom/server.browser');
|
|
109
|
-
}
|
|
110
|
-
${parameterName}.exports = ReactDOMServer;
|
|
111
|
-
`);
|
|
112
|
-
});
|
|
113
|
-
const updatedCode = file.print();
|
|
114
|
-
writeFileSync(pagesRuntimeFile, updatedCode);
|
|
115
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|