@opennextjs/cloudflare 0.2.0 → 0.3.0
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/README.md +48 -38
- package/dist/api/{get-cloudflare-context.d.mts → get-cloudflare-context.d.ts} +4 -4
- package/dist/api/get-cloudflare-context.js +39 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/kvCache.d.ts +27 -0
- package/dist/api/kvCache.js +121 -0
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.js +48 -0
- package/dist/cli/build/bundle-server.d.ts +6 -0
- package/dist/cli/build/bundle-server.js +188 -0
- package/dist/cli/build/index.d.ts +9 -0
- package/dist/cli/build/index.js +123 -0
- package/dist/cli/build/open-next/compile-env-files.d.ts +5 -0
- package/dist/cli/build/open-next/compile-env-files.js +9 -0
- package/dist/cli/build/open-next/copyCacheAssets.d.ts +2 -0
- package/dist/cli/build/open-next/copyCacheAssets.js +10 -0
- package/dist/cli/build/open-next/createServerBundle.d.ts +2 -0
- package/dist/cli/build/open-next/createServerBundle.js +216 -0
- package/dist/cli/build/patches/index.d.ts +2 -0
- package/dist/cli/build/patches/index.js +2 -0
- package/dist/cli/build/patches/investigated/copy-package-cli-files.d.ts +6 -0
- package/dist/cli/build/patches/investigated/copy-package-cli-files.js +12 -0
- package/dist/cli/build/patches/investigated/index.d.ts +4 -0
- package/dist/cli/build/patches/investigated/index.js +4 -0
- package/dist/cli/build/patches/investigated/patch-cache.d.ts +13 -0
- package/dist/cli/build/patches/investigated/patch-cache.js +22 -0
- package/dist/cli/build/patches/investigated/patch-require.d.ts +7 -0
- package/dist/cli/build/patches/investigated/patch-require.js +9 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.d.ts +13 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.js +82 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.test.d.ts +1 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.test.js +20 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.d.ts +19 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.js +76 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.test.d.ts +1 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.test.js +23 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.d.ts +14 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.js +22 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.test.d.ts +1 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.test.js +15 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.d.ts +8 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.js +22 -0
- package/dist/cli/build/patches/to-investigate/index.d.ts +8 -0
- package/dist/cli/build/patches/to-investigate/index.js +8 -0
- package/dist/cli/build/patches/to-investigate/inline-eval-manifest.d.ts +9 -0
- package/dist/cli/build/patches/to-investigate/inline-eval-manifest.js +32 -0
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.d.ts +6 -0
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.js +13 -0
- package/dist/cli/build/patches/to-investigate/inline-next-require.d.ts +6 -0
- package/dist/cli/build/patches/to-investigate/inline-next-require.js +36 -0
- package/dist/cli/build/patches/to-investigate/patch-exception-bubbling.d.ts +7 -0
- package/dist/cli/build/patches/to-investigate/patch-exception-bubbling.js +9 -0
- package/dist/cli/build/patches/to-investigate/patch-find-dir.d.ts +8 -0
- package/dist/cli/build/patches/to-investigate/patch-find-dir.js +21 -0
- package/dist/cli/build/patches/to-investigate/patch-load-instrumentation-module.d.ts +14 -0
- package/dist/cli/build/patches/to-investigate/patch-load-instrumentation-module.js +34 -0
- package/dist/cli/build/patches/to-investigate/patch-read-file.d.ts +3 -0
- package/dist/cli/build/patches/to-investigate/patch-read-file.js +29 -0
- package/dist/cli/build/patches/to-investigate/wrangler-deps.d.ts +2 -0
- package/dist/cli/build/patches/to-investigate/wrangler-deps.js +54 -0
- package/dist/cli/build/utils/extract-project-env-vars.d.ts +18 -0
- package/dist/cli/build/utils/extract-project-env-vars.js +32 -0
- package/dist/cli/build/utils/extract-project-env-vars.spec.d.ts +1 -0
- package/dist/cli/build/utils/extract-project-env-vars.spec.js +57 -0
- package/dist/cli/build/utils/index.d.ts +3 -0
- package/dist/cli/build/utils/index.js +3 -0
- package/dist/cli/build/utils/normalize-path.d.ts +1 -0
- package/dist/cli/build/utils/normalize-path.js +4 -0
- package/dist/cli/build/utils/read-paths-recursively.d.ts +7 -0
- package/dist/cli/build/utils/read-paths-recursively.js +20 -0
- package/dist/cli/build/utils/ts-parse-file.d.ts +8 -0
- package/dist/cli/build/utils/ts-parse-file.js +12 -0
- package/dist/cli/config.d.ts +41 -0
- package/dist/cli/config.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/templates/shims/empty.d.ts +2 -0
- package/dist/cli/templates/shims/env.d.ts +1 -0
- package/dist/cli/templates/shims/env.js +1 -0
- package/dist/cli/templates/shims/node-fs.d.ts +17 -0
- package/dist/cli/templates/shims/node-fs.js +51 -0
- package/dist/cli/templates/shims/throw.d.ts +0 -0
- package/dist/cli/templates/shims/{throw.ts → throw.js} +1 -0
- package/dist/cli/templates/worker.d.ts +5 -0
- package/dist/cli/templates/worker.js +67 -0
- package/package.json +29 -12
- package/dist/api/chunk-VTBEIZPQ.mjs +0 -32
- package/dist/api/get-cloudflare-context.mjs +0 -6
- package/dist/api/index.d.mts +0 -1
- package/dist/api/index.mjs +0 -6
- package/dist/cli/constants/incremental-cache.ts +0 -8
- package/dist/cli/index.mjs +0 -7424
- package/dist/cli/templates/cache-handler/index.ts +0 -1
- package/dist/cli/templates/cache-handler/open-next-cache-handler.ts +0 -148
- package/dist/cli/templates/cache-handler/utils.ts +0 -41
- package/dist/cli/templates/shims/env.ts +0 -1
- package/dist/cli/templates/shims/node-fs.ts +0 -69
- package/dist/cli/templates/worker.ts +0 -156
- /package/dist/cli/templates/shims/{empty.ts → empty.js} +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { cpSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { buildNextjsApp, setStandaloneBuildMode } from "@opennextjs/aws/build/buildNextApp.js";
|
|
5
|
+
import { compileCache } from "@opennextjs/aws/build/compileCache.js";
|
|
6
|
+
import { compileOpenNextConfig } from "@opennextjs/aws/build/compileConfig.js";
|
|
7
|
+
import { createCacheAssets, createStaticAssets } from "@opennextjs/aws/build/createAssets.js";
|
|
8
|
+
import { createMiddleware } from "@opennextjs/aws/build/createMiddleware.js";
|
|
9
|
+
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
10
|
+
import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js";
|
|
11
|
+
import logger from "@opennextjs/aws/logger.js";
|
|
12
|
+
import { containsDotNextDir, getConfig } from "../config.js";
|
|
13
|
+
import { bundleServer } from "./bundle-server.js";
|
|
14
|
+
import { compileEnvFiles } from "./open-next/compile-env-files.js";
|
|
15
|
+
import { copyCacheAssets } from "./open-next/copyCacheAssets.js";
|
|
16
|
+
import { createServerBundle } from "./open-next/createServerBundle.js";
|
|
17
|
+
/**
|
|
18
|
+
* Builds the application in a format that can be passed to workerd
|
|
19
|
+
*
|
|
20
|
+
* It saves the output in a `.worker-next` directory
|
|
21
|
+
*
|
|
22
|
+
* @param projectOpts The options for the project
|
|
23
|
+
*/
|
|
24
|
+
export async function build(projectOpts) {
|
|
25
|
+
printHeader("Cloudflare build");
|
|
26
|
+
showWarningOnWindows();
|
|
27
|
+
const baseDir = projectOpts.sourceDir;
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const openNextDistDir = dirname(require.resolve("@opennextjs/aws/index.js"));
|
|
30
|
+
const { config, buildDir } = await compileOpenNextConfig(baseDir);
|
|
31
|
+
ensureCloudflareConfig(config);
|
|
32
|
+
// Initialize options
|
|
33
|
+
const options = buildHelper.normalizeOptions(config, openNextDistDir, buildDir);
|
|
34
|
+
logger.setLevel(options.debug ? "debug" : "info");
|
|
35
|
+
// Do not minify the code so that we can apply string replacement patch.
|
|
36
|
+
// Note that wrangler will still minify the bundle.
|
|
37
|
+
options.minify = false;
|
|
38
|
+
// Pre-build validation
|
|
39
|
+
buildHelper.checkRunningInsideNextjsApp(options);
|
|
40
|
+
logger.info(`App directory: ${options.appPath}`);
|
|
41
|
+
buildHelper.printNextjsVersion(options);
|
|
42
|
+
buildHelper.printOpenNextVersion(options);
|
|
43
|
+
if (projectOpts.skipNextBuild) {
|
|
44
|
+
logger.warn("Skipping Next.js build");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Build the next app
|
|
48
|
+
printHeader("Building Next.js app");
|
|
49
|
+
setStandaloneBuildMode(options);
|
|
50
|
+
buildNextjsApp(options);
|
|
51
|
+
}
|
|
52
|
+
if (!containsDotNextDir(projectOpts.sourceDir)) {
|
|
53
|
+
throw new Error(`.next folder not found in ${projectOpts.sourceDir}`);
|
|
54
|
+
}
|
|
55
|
+
// Generate deployable bundle
|
|
56
|
+
printHeader("Generating bundle");
|
|
57
|
+
buildHelper.initOutputDir(options);
|
|
58
|
+
// Compile cache.ts
|
|
59
|
+
compileCache(options);
|
|
60
|
+
// Compile .env files
|
|
61
|
+
compileEnvFiles(options);
|
|
62
|
+
// Compile middleware
|
|
63
|
+
await createMiddleware(options, { forceOnlyBuildOnce: true });
|
|
64
|
+
createStaticAssets(options);
|
|
65
|
+
if (config.dangerous?.disableIncrementalCache !== true) {
|
|
66
|
+
createCacheAssets(options);
|
|
67
|
+
copyCacheAssets(options);
|
|
68
|
+
}
|
|
69
|
+
await createServerBundle(options);
|
|
70
|
+
// TODO: drop this copy.
|
|
71
|
+
// Copy the .next directory to the output directory so it can be mutated.
|
|
72
|
+
cpSync(join(projectOpts.sourceDir, ".next"), join(projectOpts.outputDir, ".next"), { recursive: true });
|
|
73
|
+
const projConfig = getConfig(projectOpts);
|
|
74
|
+
// TODO: rely on options only.
|
|
75
|
+
await bundleServer(projConfig, options);
|
|
76
|
+
logger.info("OpenNext build complete.");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Ensures open next is configured for cloudflare.
|
|
80
|
+
*
|
|
81
|
+
* @param config OpenNext configuration.
|
|
82
|
+
*/
|
|
83
|
+
function ensureCloudflareConfig(config) {
|
|
84
|
+
const requirements = {
|
|
85
|
+
dftUseCloudflareWrapper: config.default?.override?.wrapper === "cloudflare-node",
|
|
86
|
+
dftUseEdgeConverter: config.default?.override?.converter === "edge",
|
|
87
|
+
dftMaybeUseCache: config.default?.override?.incrementalCache === "dummy" ||
|
|
88
|
+
typeof config.default?.override?.incrementalCache === "function",
|
|
89
|
+
dftUseDummyTagCacheAndQueue: config.default?.override?.tagCache === "dummy" && config.default?.override?.queue === "dummy",
|
|
90
|
+
disableCacheInterception: config.dangerous?.enableCacheInterception !== true,
|
|
91
|
+
mwIsMiddlewareExternal: config.middleware?.external == true,
|
|
92
|
+
mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge",
|
|
93
|
+
mwUseEdgeConverter: config.middleware?.override?.converter === "edge",
|
|
94
|
+
mwUseFetchProxy: config.middleware?.override?.proxyExternalRequest === "fetch",
|
|
95
|
+
};
|
|
96
|
+
if (Object.values(requirements).some((satisfied) => !satisfied)) {
|
|
97
|
+
throw new Error(`open-next.config.ts should contain:
|
|
98
|
+
{
|
|
99
|
+
default: {
|
|
100
|
+
override: {
|
|
101
|
+
wrapper: "cloudflare-node",
|
|
102
|
+
converter: "edge",
|
|
103
|
+
incrementalCache: "dummy" | function,
|
|
104
|
+
tagCache: "dummy",
|
|
105
|
+
queue: "dummy",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
middleware: {
|
|
110
|
+
external: true,
|
|
111
|
+
override: {
|
|
112
|
+
wrapper: "cloudflare-edge",
|
|
113
|
+
converter: "edge",
|
|
114
|
+
proxyExternalRequest: "fetch",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
"dangerous": {
|
|
119
|
+
"enableCacheInterception": false
|
|
120
|
+
}
|
|
121
|
+
}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { extractProjectEnvVars } from "../utils/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Compiles the values extracted from the project's env files to the output directory for use in the worker.
|
|
6
|
+
*/
|
|
7
|
+
export function compileEnvFiles(options) {
|
|
8
|
+
["production", "development", "test"].forEach((mode) => fs.appendFileSync(path.join(options.outputDir, `.env.mjs`), `export const ${mode} = ${JSON.stringify(extractProjectEnvVars(mode, options))};\n`));
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cpSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CACHE_ASSET_DIR } from "../../../api/kvCache.js";
|
|
4
|
+
export function copyCacheAssets(options) {
|
|
5
|
+
const { outputDir } = options;
|
|
6
|
+
const srcPath = join(outputDir, "cache");
|
|
7
|
+
const dstPath = join(outputDir, "assets", CACHE_ASSET_DIR);
|
|
8
|
+
mkdirSync(dstPath, { recursive: true });
|
|
9
|
+
cpSync(srcPath, dstPath, { recursive: true });
|
|
10
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Copy-Edit of @opennextjs/aws packages/open-next/src/build/createServerBundle.ts
|
|
2
|
+
// Adapted for cloudflare workers
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { bundleNextServer } from "@opennextjs/aws/build/bundleNextServer.js";
|
|
6
|
+
import { compileCache } from "@opennextjs/aws/build/compileCache.js";
|
|
7
|
+
import { copyTracedFiles } from "@opennextjs/aws/build/copyTracedFiles.js";
|
|
8
|
+
import { generateEdgeBundle } from "@opennextjs/aws/build/edge/createEdgeBundle.js";
|
|
9
|
+
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
10
|
+
import { installDependencies } from "@opennextjs/aws/build/installDeps.js";
|
|
11
|
+
import logger from "@opennextjs/aws/logger.js";
|
|
12
|
+
import { minifyAll } from "@opennextjs/aws/minimize-js.js";
|
|
13
|
+
import { openNextEdgePlugins } from "@opennextjs/aws/plugins/edge.js";
|
|
14
|
+
import { openNextReplacementPlugin } from "@opennextjs/aws/plugins/replacement.js";
|
|
15
|
+
import { openNextResolvePlugin } from "@opennextjs/aws/plugins/resolve.js";
|
|
16
|
+
export async function createServerBundle(options) {
|
|
17
|
+
const { config } = options;
|
|
18
|
+
const foundRoutes = new Set();
|
|
19
|
+
// Get all functions to build
|
|
20
|
+
const defaultFn = config.default;
|
|
21
|
+
const functions = Object.entries(config.functions ?? {});
|
|
22
|
+
// Recompile cache.ts as ESM if any function is using Deno runtime
|
|
23
|
+
if (defaultFn.runtime === "deno" || functions.some(([, fn]) => fn.runtime === "deno")) {
|
|
24
|
+
compileCache(options, "esm");
|
|
25
|
+
}
|
|
26
|
+
const promises = functions.map(async ([name, fnOptions]) => {
|
|
27
|
+
const routes = fnOptions.routes;
|
|
28
|
+
routes.forEach((route) => foundRoutes.add(route));
|
|
29
|
+
if (fnOptions.runtime === "edge") {
|
|
30
|
+
await generateEdgeBundle(name, options, fnOptions);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await generateBundle(name, options, fnOptions);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
//TODO: throw an error if not all edge runtime routes has been bundled in a separate function
|
|
37
|
+
// We build every other function than default before so we know which route there is left
|
|
38
|
+
await Promise.all(promises);
|
|
39
|
+
const remainingRoutes = new Set();
|
|
40
|
+
const { appBuildOutputPath, monorepoRoot } = options;
|
|
41
|
+
const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
|
|
42
|
+
// Find remaining routes
|
|
43
|
+
const serverPath = path.join(appBuildOutputPath, ".next", "standalone", packagePath, ".next", "server");
|
|
44
|
+
// Find app dir routes
|
|
45
|
+
if (fs.existsSync(path.join(serverPath, "app"))) {
|
|
46
|
+
const appPath = path.join(serverPath, "app");
|
|
47
|
+
buildHelper.traverseFiles(appPath, ({ relativePath }) => relativePath.endsWith("page.js") || relativePath.endsWith("route.js"), ({ relativePath }) => {
|
|
48
|
+
const route = `app/${relativePath.replace(/\.js$/, "")}`;
|
|
49
|
+
if (!foundRoutes.has(route)) {
|
|
50
|
+
remainingRoutes.add(route);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Find pages dir routes
|
|
55
|
+
if (fs.existsSync(path.join(serverPath, "pages"))) {
|
|
56
|
+
const pagePath = path.join(serverPath, "pages");
|
|
57
|
+
buildHelper.traverseFiles(pagePath, ({ relativePath }) => relativePath.endsWith(".js"), ({ relativePath }) => {
|
|
58
|
+
const route = `pages/${relativePath.replace(/\.js$/, "")}`;
|
|
59
|
+
if (!foundRoutes.has(route)) {
|
|
60
|
+
remainingRoutes.add(route);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Generate default function
|
|
65
|
+
await generateBundle("default", options, {
|
|
66
|
+
...defaultFn,
|
|
67
|
+
// @ts-expect-error - Those string are RouteTemplate
|
|
68
|
+
routes: Array.from(remainingRoutes),
|
|
69
|
+
patterns: ["*"],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function generateBundle(name, options, fnOptions) {
|
|
73
|
+
const { appPath, appBuildOutputPath, config, outputDir, monorepoRoot } = options;
|
|
74
|
+
logger.info(`Building server function: ${name}...`);
|
|
75
|
+
// Create output folder
|
|
76
|
+
const outputPath = path.join(outputDir, "server-functions", name);
|
|
77
|
+
// Resolve path to the Next.js app if inside the monorepo
|
|
78
|
+
// note: if user's app is inside a monorepo, standalone mode places
|
|
79
|
+
// `node_modules` inside `.next/standalone`, and others inside
|
|
80
|
+
// `.next/standalone/package/path` (ie. `.next`, `server.js`).
|
|
81
|
+
// We need to output the handler file inside the package path.
|
|
82
|
+
const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
|
|
83
|
+
fs.mkdirSync(path.join(outputPath, packagePath), { recursive: true });
|
|
84
|
+
const ext = fnOptions.runtime === "deno" ? "mjs" : "cjs";
|
|
85
|
+
fs.copyFileSync(path.join(options.buildDir, `cache.${ext}`), path.join(outputPath, packagePath, "cache.cjs"));
|
|
86
|
+
if (fnOptions.runtime === "deno") {
|
|
87
|
+
addDenoJson(outputPath, packagePath);
|
|
88
|
+
}
|
|
89
|
+
// Bundle next server if necessary
|
|
90
|
+
const isBundled = fnOptions.experimentalBundledNextServer ?? false;
|
|
91
|
+
if (isBundled) {
|
|
92
|
+
await bundleNextServer(path.join(outputPath, packagePath), appPath, {
|
|
93
|
+
minify: options.minify,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// Copy middleware
|
|
97
|
+
if (!config.middleware?.external) {
|
|
98
|
+
fs.copyFileSync(path.join(options.buildDir, "middleware.mjs"), path.join(outputPath, packagePath, "middleware.mjs"));
|
|
99
|
+
}
|
|
100
|
+
// Copy open-next.config.mjs
|
|
101
|
+
buildHelper.copyOpenNextConfig(options.buildDir, path.join(outputPath, packagePath), true);
|
|
102
|
+
// Copy env files
|
|
103
|
+
buildHelper.copyEnvFile(appBuildOutputPath, packagePath, outputPath);
|
|
104
|
+
// Copy all necessary traced files
|
|
105
|
+
await copyTracedFiles(appBuildOutputPath, packagePath, outputPath, fnOptions.routes ?? ["app/page.tsx"], isBundled);
|
|
106
|
+
// Build Lambda code
|
|
107
|
+
// note: bundle in OpenNext package b/c the adapter relies on the
|
|
108
|
+
// "serverless-http" package which is not a dependency in user's
|
|
109
|
+
// Next.js app.
|
|
110
|
+
const disableNextPrebundledReact = buildHelper.compareSemver(options.nextVersion, "13.5.1") >= 0 ||
|
|
111
|
+
buildHelper.compareSemver(options.nextVersion, "13.4.1") <= 0;
|
|
112
|
+
const overrides = fnOptions.override ?? {};
|
|
113
|
+
const isBefore13413 = buildHelper.compareSemver(options.nextVersion, "13.4.13") <= 0;
|
|
114
|
+
const isAfter141 = buildHelper.compareSemver(options.nextVersion, "14.0.4") >= 0;
|
|
115
|
+
const disableRouting = isBefore13413 || config.middleware?.external;
|
|
116
|
+
const plugins = [
|
|
117
|
+
openNextReplacementPlugin({
|
|
118
|
+
name: `requestHandlerOverride ${name}`,
|
|
119
|
+
target: /core(\/|\\)requestHandler\.js/g,
|
|
120
|
+
deletes: [
|
|
121
|
+
...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
|
|
122
|
+
...(disableRouting ? ["withRouting"] : []),
|
|
123
|
+
],
|
|
124
|
+
}),
|
|
125
|
+
openNextReplacementPlugin({
|
|
126
|
+
name: `utilOverride ${name}`,
|
|
127
|
+
target: /core(\/|\\)util\.js/g,
|
|
128
|
+
deletes: [
|
|
129
|
+
...(disableNextPrebundledReact ? ["requireHooks"] : []),
|
|
130
|
+
...(disableRouting ? ["trustHostHeader"] : []),
|
|
131
|
+
...(!isBefore13413 ? ["requestHandlerHost"] : []),
|
|
132
|
+
...(isAfter141 ? ["experimentalIncrementalCacheHandler"] : ["stableIncrementalCache"]),
|
|
133
|
+
],
|
|
134
|
+
}),
|
|
135
|
+
openNextResolvePlugin({
|
|
136
|
+
fnName: name,
|
|
137
|
+
overrides,
|
|
138
|
+
}),
|
|
139
|
+
openNextEdgePlugins({
|
|
140
|
+
nextDir: path.join(options.appBuildOutputPath, ".next"),
|
|
141
|
+
edgeFunctionHandlerPath: path.join(options.openNextDistDir, "core", "edgeFunctionHandler.js"),
|
|
142
|
+
isInCloudfare: true,
|
|
143
|
+
}),
|
|
144
|
+
];
|
|
145
|
+
const outfileExt = fnOptions.runtime === "deno" ? "ts" : "mjs";
|
|
146
|
+
await buildHelper.esbuildAsync({
|
|
147
|
+
entryPoints: [path.join(options.openNextDistDir, "adapters", "server-adapter.js")],
|
|
148
|
+
outfile: path.join(outputPath, packagePath, `index.${outfileExt}`),
|
|
149
|
+
banner: {
|
|
150
|
+
js: [
|
|
151
|
+
`globalThis.monorepoPackagePath = "${packagePath}";`,
|
|
152
|
+
name === "default" ? "" : `globalThis.fnName = "${name}";`,
|
|
153
|
+
].join(""),
|
|
154
|
+
},
|
|
155
|
+
plugins,
|
|
156
|
+
alias: {
|
|
157
|
+
...(isBundled
|
|
158
|
+
? {
|
|
159
|
+
"next/dist/server/next-server.js": "./next-server.runtime.prod.js",
|
|
160
|
+
}
|
|
161
|
+
: {}),
|
|
162
|
+
},
|
|
163
|
+
}, options);
|
|
164
|
+
const isMonorepo = monorepoRoot !== appPath;
|
|
165
|
+
if (isMonorepo) {
|
|
166
|
+
addMonorepoEntrypoint(outputPath, packagePath);
|
|
167
|
+
}
|
|
168
|
+
installDependencies(outputPath, fnOptions.install);
|
|
169
|
+
if (fnOptions.minify) {
|
|
170
|
+
await minifyServerBundle(outputPath);
|
|
171
|
+
}
|
|
172
|
+
const shouldGenerateDocker = shouldGenerateDockerfile(fnOptions);
|
|
173
|
+
if (shouldGenerateDocker) {
|
|
174
|
+
fs.writeFileSync(path.join(outputPath, "Dockerfile"), typeof shouldGenerateDocker === "string"
|
|
175
|
+
? shouldGenerateDocker
|
|
176
|
+
: `
|
|
177
|
+
FROM node:18-alpine
|
|
178
|
+
WORKDIR /app
|
|
179
|
+
COPY . /app
|
|
180
|
+
EXPOSE 3000
|
|
181
|
+
CMD ["node", "index.mjs"]
|
|
182
|
+
`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function shouldGenerateDockerfile(options) {
|
|
186
|
+
return options.override?.generateDockerfile ?? false;
|
|
187
|
+
}
|
|
188
|
+
// Add deno.json file to enable "bring your own node_modules" mode.
|
|
189
|
+
// TODO: this won't be necessary in Deno 2. See https://github.com/denoland/deno/issues/23151
|
|
190
|
+
function addDenoJson(outputPath, packagePath) {
|
|
191
|
+
const config = {
|
|
192
|
+
// Enable "bring your own node_modules" mode
|
|
193
|
+
// and allow `__proto__`
|
|
194
|
+
unstable: ["byonm", "fs", "unsafe-proto"],
|
|
195
|
+
};
|
|
196
|
+
fs.writeFileSync(path.join(outputPath, packagePath, "deno.json"), JSON.stringify(config, null, 2));
|
|
197
|
+
}
|
|
198
|
+
//TODO: check if this PR is still necessary https://github.com/opennextjs/opennextjs-aws/pull/341
|
|
199
|
+
function addMonorepoEntrypoint(outputPath, packagePath) {
|
|
200
|
+
// Note: in the monorepo case, the handler file is output to
|
|
201
|
+
// `.next/standalone/package/path/index.mjs`, but we want
|
|
202
|
+
// the Lambda function to be able to find the handler at
|
|
203
|
+
// the root of the bundle. We will create a dummy `index.mjs`
|
|
204
|
+
// that re-exports the real handler.
|
|
205
|
+
// TOOD: use helper
|
|
206
|
+
// Always use posix path for import path
|
|
207
|
+
const packagePosixPath = packagePath.split(path.sep).join(path.posix.sep);
|
|
208
|
+
fs.writeFileSync(path.join(outputPath, "index.mjs"), `export * from "./${packagePosixPath}/index.mjs";`);
|
|
209
|
+
}
|
|
210
|
+
async function minifyServerBundle(outputDir) {
|
|
211
|
+
logger.info("Minimizing server function...");
|
|
212
|
+
await minifyAll(outputDir, {
|
|
213
|
+
compress_json: true,
|
|
214
|
+
mangle: true,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
2
|
+
import { Config } from "../../../config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
|
|
5
|
+
*/
|
|
6
|
+
export declare function copyPackageCliFiles(packageDistDir: string, config: Config, openNextConfig: BuildOptions): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
|
|
5
|
+
*/
|
|
6
|
+
export function copyPackageCliFiles(packageDistDir, config, openNextConfig) {
|
|
7
|
+
console.log("# copyPackageTemplateFiles");
|
|
8
|
+
const sourceDir = path.join(packageDistDir, "cli");
|
|
9
|
+
const destinationDir = path.join(config.paths.internal.package, "cli");
|
|
10
|
+
fs.cpSync(sourceDir, destinationDir, { recursive: true });
|
|
11
|
+
fs.copyFileSync(path.join(packageDistDir, "cli", "templates", "worker.js"), path.join(openNextConfig.outputDir, "worker.js"));
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sets up the OpenNext cache handler in a Next.js build.
|
|
4
|
+
*
|
|
5
|
+
* The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
|
|
6
|
+
* Next.js would then do a dynamic require on a transformed version of the path to retrieve the
|
|
7
|
+
* cache handler and create a new instance of it.
|
|
8
|
+
*
|
|
9
|
+
* This is problematic in workerd due to the dynamic import of the file that is not known from
|
|
10
|
+
* build-time. Therefore, we have to manually override the default way that the cache handler is
|
|
11
|
+
* instantiated with a dynamic require that uses a string literal for the path.
|
|
12
|
+
*/
|
|
13
|
+
export declare function patchCache(code: string, openNextOptions: BuildOptions): Promise<string>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Sets up the OpenNext cache handler in a Next.js build.
|
|
4
|
+
*
|
|
5
|
+
* The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
|
|
6
|
+
* Next.js would then do a dynamic require on a transformed version of the path to retrieve the
|
|
7
|
+
* cache handler and create a new instance of it.
|
|
8
|
+
*
|
|
9
|
+
* This is problematic in workerd due to the dynamic import of the file that is not known from
|
|
10
|
+
* build-time. Therefore, we have to manually override the default way that the cache handler is
|
|
11
|
+
* instantiated with a dynamic require that uses a string literal for the path.
|
|
12
|
+
*/
|
|
13
|
+
export async function patchCache(code, openNextOptions) {
|
|
14
|
+
const { appBuildOutputPath, outputDir, monorepoRoot } = openNextOptions;
|
|
15
|
+
// TODO: switch to cache.mjs
|
|
16
|
+
const outputPath = path.join(outputDir, "server-functions", "default");
|
|
17
|
+
const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
|
|
18
|
+
const cacheFile = path.join(outputPath, packagePath, "cache.cjs");
|
|
19
|
+
return code.replace("const { cacheHandler } = this.nextConfig;", `const cacheHandler = null;
|
|
20
|
+
CacheHandler = require('${cacheFile}').default;
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESBuild does not support CJS format
|
|
3
|
+
* See https://github.com/evanw/esbuild/issues/1921 and linked issues
|
|
4
|
+
* Some of the solutions are based on `module.createRequire()` not implemented in workerd.
|
|
5
|
+
* James on Aug 29: `module.createRequire()` is planned.
|
|
6
|
+
*/
|
|
7
|
+
export declare function patchRequire(code: string): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESBuild does not support CJS format
|
|
3
|
+
* See https://github.com/evanw/esbuild/issues/1921 and linked issues
|
|
4
|
+
* Some of the solutions are based on `module.createRequire()` not implemented in workerd.
|
|
5
|
+
* James on Aug 29: `module.createRequire()` is planned.
|
|
6
|
+
*/
|
|
7
|
+
export function patchRequire(code) {
|
|
8
|
+
return code.replace(/__require\d?\(/g, "require(").replace(/__require\d?\./g, "require.");
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as ts from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Gets the names of the variables that in the unminified webpack runtime file are called `installedChunks` and `installChunk`.
|
|
4
|
+
*
|
|
5
|
+
* Variables example: https://github.com/webpack/webpack/blob/dae16ad11e/examples/module-worker/README.md?plain=1#L256-L282
|
|
6
|
+
*
|
|
7
|
+
* @param sourceFile the webpack runtime file parsed with ts-morph
|
|
8
|
+
* @returns an object containing the two variable names
|
|
9
|
+
*/
|
|
10
|
+
export declare function getChunkInstallationIdentifiers(sourceFile: ts.SourceFile): Promise<{
|
|
11
|
+
installedChunks: string;
|
|
12
|
+
installChunk: string;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as ts from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Gets the names of the variables that in the unminified webpack runtime file are called `installedChunks` and `installChunk`.
|
|
4
|
+
*
|
|
5
|
+
* Variables example: https://github.com/webpack/webpack/blob/dae16ad11e/examples/module-worker/README.md?plain=1#L256-L282
|
|
6
|
+
*
|
|
7
|
+
* @param sourceFile the webpack runtime file parsed with ts-morph
|
|
8
|
+
* @returns an object containing the two variable names
|
|
9
|
+
*/
|
|
10
|
+
export async function getChunkInstallationIdentifiers(sourceFile) {
|
|
11
|
+
const installChunkDeclaration = getInstallChunkDeclaration(sourceFile);
|
|
12
|
+
const installedChunksDeclaration = getInstalledChunksDeclaration(sourceFile, installChunkDeclaration);
|
|
13
|
+
return {
|
|
14
|
+
installChunk: installChunkDeclaration.getName(),
|
|
15
|
+
installedChunks: installedChunksDeclaration.getName(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Gets the declaration for what in the unminified webpack runtime file is called `installChunk`(which is a function that registers the various chunks.
|
|
20
|
+
*
|
|
21
|
+
* `installChunk` example: https://github.com/webpack/webpack/blob/dae16ad11e/examples/module-worker/README.md?plain=1#L263-L282
|
|
22
|
+
*
|
|
23
|
+
* @param sourceFile the webpack runtime file parsed with ts-morph
|
|
24
|
+
* @returns the `installChunk` declaration
|
|
25
|
+
*/
|
|
26
|
+
function getInstallChunkDeclaration(sourceFile) {
|
|
27
|
+
const installChunkDeclaration = sourceFile
|
|
28
|
+
.getDescendantsOfKind(ts.SyntaxKind.VariableDeclaration)
|
|
29
|
+
.find((declaration) => {
|
|
30
|
+
const arrowFunction = declaration.getInitializerIfKind(ts.SyntaxKind.ArrowFunction);
|
|
31
|
+
// we're looking for an arrow function
|
|
32
|
+
if (!arrowFunction)
|
|
33
|
+
return false;
|
|
34
|
+
const functionParameters = arrowFunction.getParameters();
|
|
35
|
+
// the arrow function we're looking for has a single parameter (the chunkId)
|
|
36
|
+
if (functionParameters.length !== 1)
|
|
37
|
+
return false;
|
|
38
|
+
const arrowFunctionBodyBlock = arrowFunction.getFirstChildByKind(ts.SyntaxKind.Block);
|
|
39
|
+
// the arrow function we're looking for has a block body
|
|
40
|
+
if (!arrowFunctionBodyBlock)
|
|
41
|
+
return false;
|
|
42
|
+
const statementKinds = arrowFunctionBodyBlock.getStatements().map((statement) => statement.getKind());
|
|
43
|
+
// the function we're looking for has 2 for loops (a standard one and a for-in one)
|
|
44
|
+
const forInStatements = statementKinds.filter((s) => s === ts.SyntaxKind.ForInStatement);
|
|
45
|
+
const forStatements = statementKinds.filter((s) => s === ts.SyntaxKind.ForStatement);
|
|
46
|
+
if (forInStatements.length !== 1 || forStatements.length !== 1)
|
|
47
|
+
return false;
|
|
48
|
+
// the function we're looking for accesses its parameter three times, and it
|
|
49
|
+
// accesses its `modules`, `ids` and `runtime` properties (in this order)
|
|
50
|
+
const parameterName = functionParameters[0].getText();
|
|
51
|
+
const functionParameterAccessedProperties = arrowFunctionBodyBlock
|
|
52
|
+
.getDescendantsOfKind(ts.SyntaxKind.PropertyAccessExpression)
|
|
53
|
+
.filter((propertyAccessExpression) => propertyAccessExpression.getExpression().getText() === parameterName)
|
|
54
|
+
.map((propertyAccessExpression) => propertyAccessExpression.getName());
|
|
55
|
+
if (functionParameterAccessedProperties.join(", ") !== "modules, ids, runtime")
|
|
56
|
+
return false;
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
if (!installChunkDeclaration) {
|
|
60
|
+
throw new Error("ERROR: unable to find the installChunk function declaration");
|
|
61
|
+
}
|
|
62
|
+
return installChunkDeclaration;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets the declaration for what in the unminified webpack runtime file is called `installedChunks` which is an object that holds the various registered chunks.
|
|
66
|
+
*
|
|
67
|
+
* `installedChunks` example: https://github.com/webpack/webpack/blob/dae16ad11e/examples/module-worker/README.md?plain=1#L256-L261
|
|
68
|
+
*
|
|
69
|
+
* @param sourceFile the webpack runtime file parsed with ts-morph
|
|
70
|
+
* @param installChunkDeclaration the declaration for the `installChunk` variable
|
|
71
|
+
* @returns the `installedChunks` declaration
|
|
72
|
+
*/
|
|
73
|
+
function getInstalledChunksDeclaration(sourceFile, installChunkDeclaration) {
|
|
74
|
+
const allVariableDeclarations = sourceFile.getDescendantsOfKind(ts.SyntaxKind.VariableDeclaration);
|
|
75
|
+
const installChunkDeclarationIdx = allVariableDeclarations.findIndex((declaration) => declaration === installChunkDeclaration);
|
|
76
|
+
// the installedChunks declaration comes right before the installChunk one
|
|
77
|
+
const installedChunksDeclaration = allVariableDeclarations[installChunkDeclarationIdx - 1];
|
|
78
|
+
if (!installedChunksDeclaration?.getInitializer()?.isKind(ts.SyntaxKind.ObjectLiteralExpression)) {
|
|
79
|
+
throw new Error("ERROR: unable to find the installedChunks declaration");
|
|
80
|
+
}
|
|
81
|
+
return installedChunksDeclaration;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { tsParseFile } from "../../../utils/index.js";
|
|
4
|
+
import { getChunkInstallationIdentifiers } from "./get-chunk-installation-identifiers.js";
|
|
5
|
+
describe("getChunkInstallationIdentifiers", () => {
|
|
6
|
+
test("gets chunk identifiers from unminified code", async () => {
|
|
7
|
+
const fileContent = await readFile(`${import.meta.dirname}/test-fixtures/unminified-webpacks-file.js`, "utf8");
|
|
8
|
+
const tsSourceFile = tsParseFile(fileContent);
|
|
9
|
+
const { installChunk, installedChunks } = await getChunkInstallationIdentifiers(tsSourceFile);
|
|
10
|
+
expect(installChunk).toEqual("installChunk");
|
|
11
|
+
expect(installedChunks).toEqual("installedChunks");
|
|
12
|
+
});
|
|
13
|
+
test("gets chunk identifiers from minified code", async () => {
|
|
14
|
+
const fileContent = await readFile(`${import.meta.dirname}/test-fixtures/minified-webpacks-file.js`, "utf8");
|
|
15
|
+
const tsSourceFile = tsParseFile(fileContent);
|
|
16
|
+
const { installChunk, installedChunks } = await getChunkInstallationIdentifiers(tsSourceFile);
|
|
17
|
+
expect(installChunk).toEqual("r");
|
|
18
|
+
expect(installedChunks).toEqual("e");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as ts from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Updates the function that in the unminified webpack runtime file appears as `__webpack_require__.f.require` which is a function that
|
|
4
|
+
* installs chunks by importing/requiring them at runtime.
|
|
5
|
+
*
|
|
6
|
+
* `__webpack_require__.f.require` example: https://github.com/webpack/webpack/blob/dae16ad11e/examples/module-worker/README.md?plain=1#L284-L304
|
|
7
|
+
*
|
|
8
|
+
* This function needs to be updated so that it requires chunks using the standard `require` function and not webpack's custom `require` logic
|
|
9
|
+
* which fails in the workerd runtime.
|
|
10
|
+
*
|
|
11
|
+
* @param sourceFile the webpack runtime file parsed with ts-morph (note: this gets side-effectfully updated)
|
|
12
|
+
* @param chunkInstallationIdentifiers the names of the `installedChunks` and `installChunk` variables
|
|
13
|
+
* @param chunks the identifiers of the chunks (found on the filesystem)
|
|
14
|
+
* @returns the content of the sourceFile but with the require function updated
|
|
15
|
+
*/
|
|
16
|
+
export declare function getFileContentWithUpdatedWebpackFRequireCode(sourceFile: ts.SourceFile, { installedChunks, installChunk }: {
|
|
17
|
+
installedChunks: string;
|
|
18
|
+
installChunk: string;
|
|
19
|
+
}, chunks: string[]): Promise<string>;
|