From 966103936377bd9e51496f077496166ddc12627c Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 29 Jun 2026 11:36:15 -0400 Subject: [PATCH 1/3] Filter removed change files from pr check workflow --- scripts/pr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pr.ts b/scripts/pr.ts index 7575e5465c..0da0c9dc53 100644 --- a/scripts/pr.ts +++ b/scripts/pr.ts @@ -147,7 +147,7 @@ async function changeFileCheck(ctx: CheckContext): Promise { let regex = /^packages\/[^/]+\/\.changes\/(major|minor|patch|unstable)\.[^/]+\.md$/; let summaries: ChangeFileSummary[] = files - .filter((f) => regex.test(f.filename)) + .filter((f) => f.status !== "removed" && regex.test(f.filename)) .map((f) => { let type = f.filename.match(regex)?.[1] ?? "unknown"; let firstLine = From 526f9c37e37d9f1037d04d01b3edb2e4e16e6ee8 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 29 Jun 2026 12:03:27 -0400 Subject: [PATCH 2/3] Fix Vite node conditions for non-Node runtimes (#15242) * Fix Vite node conditions for non-Node runtimes * Address Cloudflare fixture review * Remove helper --- integration/vite-plugin-cloudflare-test.ts | 47 +++++++++++++++++++ .../patch.vite-node-external-conditions.md | 3 ++ packages/react-router-dev/vite/plugin.ts | 26 ++++++++-- 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 packages/react-router-dev/.changes/patch.vite-node-external-conditions.md diff --git a/integration/vite-plugin-cloudflare-test.ts b/integration/vite-plugin-cloudflare-test.ts index e77eae582a..a8ceb5754a 100644 --- a/integration/vite-plugin-cloudflare-test.ts +++ b/integration/vite-plugin-cloudflare-test.ts @@ -93,6 +93,53 @@ test.describe("vite-plugin-cloudflare", () => { ); }); + test("does not force node export conditions", async ({ dev, page }) => { + const baseFiles = defineFiles(); + const files: Files = async (args) => ({ + ...(await baseFiles(args)), + "app/routes/conditional-export.tsx": tsx` + import { runtime } from "conditional-runtime"; + + export function loader() { + return { runtime }; + } + + export default function ConditionalExportsRoute({ + loaderData, + }: { + loaderData: { runtime: string }; + }) { + return
{loaderData.runtime}
; + } + `, + "node_modules/conditional-runtime/package.json": JSON.stringify({ + name: "conditional-runtime", + type: "module", + exports: { + ".": { + node: "./node.js", + default: "./worker.js", + }, + }, + }), + "node_modules/conditional-runtime/node.js": tsx` + import "node:http"; + export const runtime = "node"; + `, + "node_modules/conditional-runtime/worker.js": tsx` + export const runtime = "worker"; + `, + }); + const { port } = await dev(files, "vite-plugin-cloudflare-template"); + + await page.goto(`http://localhost:${port}/conditional-export`, { + waitUntil: "networkidle", + }); + + expect(page.errors).toEqual([]); + await expect(page.locator("[data-runtime]")).toHaveText("worker"); + }); + test.describe("without JavaScript", () => { test.use({ javaScriptEnabled: false }); diff --git a/packages/react-router-dev/.changes/patch.vite-node-external-conditions.md b/packages/react-router-dev/.changes/patch.vite-node-external-conditions.md new file mode 100644 index 0000000000..63b2e4c502 --- /dev/null +++ b/packages/react-router-dev/.changes/patch.vite-node-external-conditions.md @@ -0,0 +1,3 @@ +Only add the `"node"` Vite server condition for Framework mode apps that declare a Node server adapter dependency + +This prevents non-Node SSR runtimes from resolving Node-specific package exports by default. diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index a9853cd198..682dcc4420 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -36,6 +36,7 @@ import pick from "lodash/pick.js"; import jsesc from "jsesc"; import colors from "picocolors"; import kebabCase from "lodash/kebabCase.js"; +import { readPackageJSON, type PackageJson } from "pkg-types"; const nodeRequire = createRequire(import.meta.url); @@ -3500,6 +3501,9 @@ export async function getEnvironmentOptionsResolvers( viteCommand: Vite.ResolvedConfig["command"], ): Promise { let { serverBuildFile, serverModuleFormat } = ctx.reactRouterConfig; + let pkgJson: PackageJson = await readPackageJSON(ctx.rootDirectory).catch( + () => ({}), + ); let packageRoot = path.dirname( nodeRequire.resolve("@react-router/dev/package.json"), @@ -3564,10 +3568,24 @@ export async function getEnvironmentOptionsResolvers( // https://vite.dev/guide/migration.html#default-value-for-resolve-conditions let maybeDefaultServerConditions = vite.defaultServerConditions || []; - // There is no helpful export with the default external conditions (see - // https://github.com/vitejs/vite/pull/20279 for more details). So, for now, - // we are hardcording the default here. - let defaultExternalConditions = ["node"]; + // Vite added this in 7.1, so we need to be defensive since our minimum version is 7.0 + let defaultExternalConditions = vite.defaultExternalConditions ?? ["node"]; + + // If we couldn't find the package.json, we assume node for backwards compatibility + let isNode = + !pkgJson.dependencies || + pkgJson.dependencies["@react-router/node"] || + pkgJson.dependencies["@react-router/express"] || + pkgJson.dependencies["@react-router/serve"]; + + if (!isNode) { + maybeDefaultServerConditions = maybeDefaultServerConditions.filter( + (c) => c !== "node", + ); + defaultExternalConditions = defaultExternalConditions.filter( + (c) => c !== "node", + ); + } let baseConditions = [ ...maybeDevelopmentConditions, From eccfdd12d4367e0302f1c432881e3d908576425e Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 29 Jun 2026 12:18:50 -0400 Subject: [PATCH 3/3] Convert scripts to ndoe parseArgs --- scripts/changes/migrate-changesets.ts | 11 +++++++++-- scripts/changes/pr.ts | 12 ++++++++++-- scripts/docs.ts | 2 -- scripts/experimental.ts | 14 +++++++++++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/scripts/changes/migrate-changesets.ts b/scripts/changes/migrate-changesets.ts index d8a38a20e1..78f8c40faf 100644 --- a/scripts/changes/migrate-changesets.ts +++ b/scripts/changes/migrate-changesets.ts @@ -21,6 +21,7 @@ import * as cp from "node:child_process"; import * as fs from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; import { GITHUB_REPO_URL, packageNameToDirectoryName, @@ -31,8 +32,14 @@ const repoRoot = path.resolve(__dirname, "..", ".."); const changesetsDir = path.join(repoRoot, ".changeset"); const packagesDir = path.join(repoRoot, "packages"); -const args = process.argv.slice(2); -const dryRun = args.includes("--dry-run"); +const { values } = parseArgs({ + options: { + "dry-run": { + type: "boolean", + }, + }, +}); +const dryRun = values["dry-run"] === true; const validBumps = new Set(["major", "minor", "patch"]); diff --git a/scripts/changes/pr.ts b/scripts/changes/pr.ts index 21b6d73225..a7f86519ff 100644 --- a/scripts/changes/pr.ts +++ b/scripts/changes/pr.ts @@ -7,6 +7,8 @@ * Environment: * GITHUB_TOKEN - Required (unless --preview) */ +import { parseArgs } from "node:util"; + import * as semver from "semver"; import { readJson } from "../utils/fs.ts"; @@ -26,8 +28,14 @@ import { parseAllChangeFiles, } from "./changes.ts"; -let args = process.argv.slice(2); -let preview = args.includes("--preview"); +let { values } = parseArgs({ + options: { + preview: { + type: "boolean", + }, + }, +}); +let preview = values.preview === true; let baseBranch = logAndExec("git rev-parse --abbrev-ref HEAD", true).trim(); let releaseBranches = ["main", "hotfix", "v7"]; diff --git a/scripts/docs.ts b/scripts/docs.ts index 0f2776064b..f5db20ec5b 100644 --- a/scripts/docs.ts +++ b/scripts/docs.ts @@ -99,7 +99,6 @@ const isComponentApi = (c: SimplifiedComment) => // Read a filename from standard input using the node parseArgs utility const { values: args } = util.parseArgs({ - args: process.argv.slice(2), options: { path: { type: "string", @@ -122,7 +121,6 @@ const { values: args } = util.parseArgs({ short: "h", }, }, - allowPositionals: true, }); if (args.help) { diff --git a/scripts/experimental.ts b/scripts/experimental.ts index ddafdfc3cb..32bbbebac1 100644 --- a/scripts/experimental.ts +++ b/scripts/experimental.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; import { colorize, colors } from "./utils/color.ts"; import { logAndExec } from "./utils/process.ts"; @@ -11,9 +12,16 @@ const packageDirNames = fs .readdirSync("packages") .filter((name) => fs.statSync(`packages/${name}`).isDirectory()); -const command = process.argv[2]; -const args = process.argv.slice(3); -const dryRun = args.includes("--dry-run"); +const { values, positionals } = parseArgs({ + allowPositionals: true, + options: { + "dry-run": { + type: "boolean", + }, + }, +}); +const command = positionals[0]; +const dryRun = values["dry-run"] === true; if (command === "version") { await bumpVersion();