@opennextjs/cloudflare 0.4.0 → 0.4.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.
- package/dist/api/cloudflare-context.js +18 -0
- package/dist/cli/build/bundle-server.js +14 -3
- package/dist/cli/build/patches/ast/optional-deps.d.ts +2 -2
- package/dist/cli/build/patches/ast/optional-deps.js +2 -2
- 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/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 -1
- package/dist/cli/build/patches/to-investigate/index.js +0 -1
- package/package.json +1 -1
- 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
|
@@ -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
|
|
@@ -8,6 +8,8 @@ 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
10
|
import * as patches from "./patches/index.js";
|
|
11
|
+
import inlineRequirePagePlugin from "./patches/plugins/require-page.js";
|
|
12
|
+
import setWranglerExternal from "./patches/plugins/wrangler-external.js";
|
|
11
13
|
import { normalizePath, patchCodeWithValidations } from "./utils/index.js";
|
|
12
14
|
/** The dist directory of the Cloudflare adapter package */
|
|
13
15
|
const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
@@ -34,8 +36,18 @@ export async function bundleServer(buildOpts) {
|
|
|
34
36
|
format: "esm",
|
|
35
37
|
target: "esnext",
|
|
36
38
|
minify: false,
|
|
37
|
-
plugins: [
|
|
38
|
-
|
|
39
|
+
plugins: [
|
|
40
|
+
createFixRequiresESBuildPlugin(buildOpts),
|
|
41
|
+
inlineRequirePagePlugin(buildOpts),
|
|
42
|
+
setWranglerExternal(),
|
|
43
|
+
],
|
|
44
|
+
external: [
|
|
45
|
+
"./middleware/handler.mjs",
|
|
46
|
+
// Next optional dependencies.
|
|
47
|
+
"caniuse-lite",
|
|
48
|
+
"jimp",
|
|
49
|
+
"probe-image-size",
|
|
50
|
+
],
|
|
39
51
|
alias: {
|
|
40
52
|
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
|
|
41
53
|
// eval("require")("bufferutil");
|
|
@@ -121,7 +133,6 @@ async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
|
|
|
121
133
|
["require", patches.patchRequire],
|
|
122
134
|
["`buildId` function", (code) => patches.patchBuildId(code, buildOpts)],
|
|
123
135
|
["`loadManifest` function", (code) => patches.patchLoadManifest(code, buildOpts)],
|
|
124
|
-
["next's require", (code) => patches.inlineNextRequire(code, buildOpts)],
|
|
125
136
|
["`findDir` function", (code) => patches.patchFindDir(code, buildOpts)],
|
|
126
137
|
["`evalManifest` function", (code) => patches.inlineEvalManifest(code, buildOpts)],
|
|
127
138
|
["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
|
|
@@ -1,12 +1,12 @@
|
|
|
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 const optionalDepRule = "\nrule:\n pattern: $$$LHS = require($$$REQ)\n has:\n pattern: $MOD\n kind: string_fragment\n stopBy: end\n regex: ^caniuse-lite(/|$)\n not:\n inside:\n kind: try_statement\n stopBy: end\n\nfix: |-\n try {\n $$$LHS = require($$$REQ);\n } catch {\n throw new Error('The optional dependency \"$MOD\" is not installed');\n }\n";
|
|
9
|
+
export declare const optionalDepRule = "\nrule:\n pattern: $$$LHS = require($$$REQ)\n has:\n pattern: $MOD\n kind: string_fragment\n stopBy: end\n regex: ^(caniuse-lite|jimp|probe-image-size)(/|$)\n not:\n inside:\n kind: try_statement\n stopBy: end\n\nfix: |-\n try {\n $$$LHS = require($$$REQ);\n } catch {\n throw new Error('The optional dependency \"$MOD\" is not installed');\n }\n";
|
|
10
10
|
export declare function patchOptionalDependencies(root: SgNode): {
|
|
11
11
|
edits: import("@ast-grep/napi").Edit[];
|
|
12
12
|
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>>[];
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
*
|
|
@@ -13,7 +13,7 @@ rule:
|
|
|
13
13
|
pattern: $MOD
|
|
14
14
|
kind: string_fragment
|
|
15
15
|
stopBy: end
|
|
16
|
-
regex: ^caniuse-lite(/|$)
|
|
16
|
+
regex: ^(caniuse-lite|jimp|probe-image-size)(/|$)
|
|
17
17
|
not:
|
|
18
18
|
inside:
|
|
19
19
|
kind: try_statement
|
|
@@ -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,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,6 +1,5 @@
|
|
|
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";
|
|
@@ -1,6 +1,5 @@
|
|
|
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";
|
package/package.json
CHANGED
|
@@ -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
|
-
}
|