Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/e2e/app-router/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const nextConfig: NextConfig = {
transpilePackages: ["@example/shared"],
output: "standalone",
// outputFileTracingRoot: "../sst",
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
Expand Down
2 changes: 1 addition & 1 deletion examples/e2e/app-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"start": "next start --port 3001",
"lint": "next lint",
"clean": "rm -rf .turbo node_modules .next .open-next",
"build:worker-tofix": "pnpm opennextjs-cloudflare",
"build:worker": "pnpm opennextjs-cloudflare",
"dev:worker": "wrangler dev --port 8770 --inspector-port 9330",
"preview": "pnpm build:worker && pnpm dev:worker"
},
Expand Down
8 changes: 5 additions & 3 deletions packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,26 @@
"@types/mock-fs": "catalog:",
"@types/node": "catalog:",
"esbuild": "catalog:",
"eslint": "catalog:",
"eslint-plugin-import": "catalog:",
"eslint-plugin-simple-import-sort": "catalog:",
"eslint-plugin-unicorn": "catalog:",
"eslint": "catalog:",
"globals": "catalog:",
"mock-fs": "catalog:",
"next": "catalog:",
"rimraf": "catalog:",
"typescript-eslint": "catalog:",
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"vitest": "catalog:"
},
"dependencies": {
"@ast-grep/napi": "^0.33.1",
"@dotenvx/dotenvx": "catalog:",
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@704",
"enquirer": "^2.4.1",
"glob": "catalog:",
"ts-morph": "catalog:",
"enquirer": "^2.4.1"
"yaml": "^2.7.0"
},
"peerDependencies": {
"wrangler": "catalog:"
Expand Down
10 changes: 8 additions & 2 deletions packages/cloudflare/src/cli/build/bundle-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

import { Lang, parse } from "@ast-grep/napi";
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
import { build, Plugin } from "esbuild";

import { Config } from "../config.js";
import { patchOptionalDependencies } from "./patches/ast/optional-deps.js";
import * as patches from "./patches/index.js";
import { normalizePath, patchCodeWithValidations } from "./utils/index.js";

Expand Down Expand Up @@ -44,7 +46,7 @@ export async function bundleServer(config: Config, openNextOptions: BuildOptions
target: "esnext",
minify: false,
plugins: [createFixRequiresESBuildPlugin(config)],
external: ["./middleware/handler.mjs"],
external: ["./middleware/handler.mjs", "caniuse-lite"],
alias: {
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
// eval("require")("bufferutil");
Expand Down Expand Up @@ -176,7 +178,11 @@ async function updateWorkerBundledCode(
],
]);

await writeFile(workerOutputFile, patchedCode);
const bundle = parse(Lang.TypeScript, patchedCode).root();

const edits = patchOptionalDependencies(bundle);

await writeFile(workerOutputFile, bundle.commitEdits(edits));
}

function createFixRequiresESBuildPlugin(config: Config): Plugin {
Expand Down
41 changes: 41 additions & 0 deletions packages/cloudflare/src/cli/build/patches/ast/optional-deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { type SgNode } from "@ast-grep/napi";

import { applyRule } from "./util.js";

/**
* Handle optional dependencies.
*
* A top level `require(dep)` would throw when the dep is not installed.
*
* So we wrap any of
* - `t = require("dep")`
* - `t = require("dep/sub/path")`
* - `t = require("dep/sub/path/" + var)`
* - `e.exports = require("dep")`
*
* in a try/catch (only if not already).
*/
const rule = `
rule:
pattern: $$$LHS = require($$$REQ)
has:
pattern: $MOD
kind: string_fragment
stopBy: end
regex: ^caniuse-lite(/|$)
not:
inside:
kind: try_statement
stopBy: end

fix: |-
try {
$$$LHS = require($$$REQ);
} catch {
throw new Error('The optional dependency "$MOD" is not installed');
}
`;

export function patchOptionalDependencies(root: SgNode) {
return applyRule(rule, root);
}
48 changes: 48 additions & 0 deletions packages/cloudflare/src/cli/build/patches/ast/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { type Edit, type NapiConfig, type SgNode } from "@ast-grep/napi";
import yaml from "yaml";

/**
* Returns the `Edit`s for an ast-grep rule in yaml format
*
* The rule must have a `fix` to rewrite the matched node.
*
* Tip: use https://ast-grep.github.io/playground.html to create rules.
*
* @param yamlRule The rule in yaml format
* @param root The root node
* @param once only apply once
* @returns A list of edits.
*/
export function applyRule(yamlRule: string, root: SgNode, { once = false } = {}) {
const rule: NapiConfig & { fix?: string } = yaml.parse(yamlRule);
if (rule.transform) {
throw new Error("transform is not supported");
}
if (!rule.fix) {
throw new Error("no fix to apply");
}

const fix = rule.fix;

const matches = once ? [root.find(rule)].filter((m) => m !== null) : root.findAll(rule);

const edits: Edit[] = [];

matches.forEach((match) => {
edits.push(
match.replace(
// Replace known placeholders by their value
fix
.replace(/\$\$\$([A-Z0-9_]+)/g, (_m, name) =>
match
.getMultipleMatches(name)
.map((n) => n.text())
.join("")
)
.replace(/\$([A-Z0-9_]+)/g, (m, name) => match.getMatch(name)?.text() ?? m)
)
);
});

return edits;
}
Loading
Loading