From 1dab2d1497e86e877c6a1dfb2dd07676ad0b9920 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 21 Oct 2024 15:34:44 -0400 Subject: [PATCH 1/7] Remove installGlobals --- integration/helpers/create-fixture.ts | 3 - integration/helpers/vite.ts | 3 - integration/vite-basename-test.ts | 4 -- jest/jest.config.shared.js | 1 - .../react-router-architect/__tests__/setup.ts | 2 - .../react-router-express/__tests__/setup.ts | 2 - packages/react-router-node/__tests__/setup.ts | 3 - packages/react-router-node/globals.ts | 55 ------------------- packages/react-router-node/index.ts | 2 - packages/react-router-node/install.ts | 3 - packages/react-router-node/package.json | 4 -- packages/react-router-node/rollup.config.js | 4 +- packages/react-router-serve/cli.ts | 3 - packages/react-router/jest.config.js | 1 + playground/compiler-express/server.js | 3 - playground/compiler/vite.config.ts | 3 - 16 files changed, 2 insertions(+), 94 deletions(-) delete mode 100644 packages/react-router-architect/__tests__/setup.ts delete mode 100644 packages/react-router-express/__tests__/setup.ts delete mode 100644 packages/react-router-node/__tests__/setup.ts delete mode 100644 packages/react-router-node/globals.ts delete mode 100644 packages/react-router-node/install.ts diff --git a/integration/helpers/create-fixture.ts b/integration/helpers/create-fixture.ts index f8358c51f6..ee4d9fe648 100644 --- a/integration/helpers/create-fixture.ts +++ b/integration/helpers/create-fixture.ts @@ -15,7 +15,6 @@ import { UNSAFE_decodeViaTurboStream as decodeViaTurboStream, } from "react-router"; import { createRequestHandler as createExpressHandler } from "@react-router/express"; -import { installGlobals } from "@react-router/node"; import { viteConfig } from "./vite.js"; @@ -43,8 +42,6 @@ export function json(value: JsonObject) { } export async function createFixture(init: FixtureInit, mode?: ServerMode) { - installGlobals(); - let projectDir = await createFixtureProject(init, mode); let buildPath = url.pathToFileURL( path.join(projectDir, "build/server/index.js") diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index cb09f54f9a..3e33597f6b 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -69,11 +69,8 @@ export const EXPRESS_SERVER = (args: { }) => String.raw` import { createRequestHandler } from "@react-router/express"; - import { installGlobals } from "@react-router/node"; import express from "express"; - installGlobals(); - let viteDevServer = process.env.NODE_ENV === "production" ? undefined diff --git a/integration/vite-basename-test.ts b/integration/vite-basename-test.ts index 42f5c25e6c..f723b625b5 100644 --- a/integration/vite-basename-test.ts +++ b/integration/vite-basename-test.ts @@ -95,9 +95,7 @@ const customServerFile = ({ return js` import { createRequestHandler } from "@react-router/express"; - import { installGlobals } from "@react-router/node"; import express from "express"; - installGlobals(); const viteDevServer = process.env.NODE_ENV === "production" @@ -488,9 +486,7 @@ test.describe("Vite base / React Router basename / express build", async () => { // Slim server that only serves basename (route) requests from the React Router handler "server.mjs": String.raw` import { createRequestHandler } from "@react-router/express"; - import { installGlobals } from "@react-router/node"; import express from "express"; - installGlobals(); const app = express(); app.all( diff --git a/jest/jest.config.shared.js b/jest/jest.config.shared.js index 186df726b9..05322e3a9b 100644 --- a/jest/jest.config.shared.js +++ b/jest/jest.config.shared.js @@ -19,7 +19,6 @@ module.exports = { ), }, modulePathIgnorePatterns: ignorePatterns, - setupFiles: ["/__tests__/setup.ts"], testMatch: ["/**/*-test.[jt]s?(x)"], transform: { "\\.[jt]sx?$": require.resolve("./transform"), diff --git a/packages/react-router-architect/__tests__/setup.ts b/packages/react-router-architect/__tests__/setup.ts deleted file mode 100644 index 996e99893d..0000000000 --- a/packages/react-router-architect/__tests__/setup.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { installGlobals } from "@react-router/node"; -installGlobals(); diff --git a/packages/react-router-express/__tests__/setup.ts b/packages/react-router-express/__tests__/setup.ts deleted file mode 100644 index 996e99893d..0000000000 --- a/packages/react-router-express/__tests__/setup.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { installGlobals } from "@react-router/node"; -installGlobals(); diff --git a/packages/react-router-node/__tests__/setup.ts b/packages/react-router-node/__tests__/setup.ts deleted file mode 100644 index 53d59d28c0..0000000000 --- a/packages/react-router-node/__tests__/setup.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { installGlobals } from "../globals"; - -installGlobals(); diff --git a/packages/react-router-node/globals.ts b/packages/react-router-node/globals.ts deleted file mode 100644 index cbd188c312..0000000000 --- a/packages/react-router-node/globals.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - File as NodeFile, - fetch as nodeFetch, - FormData as NodeFormData, - Headers as NodeHeaders, - Request as NodeRequest, - Response as NodeResponse, -} from "undici"; -import { webcrypto as nodeWebCrypto } from "node:crypto"; - -declare global { - namespace NodeJS { - interface ProcessEnv { - NODE_ENV: "development" | "production" | "test"; - } - - interface Global { - File: typeof File; - - Headers: typeof Headers; - Request: typeof Request; - Response: typeof Response; - fetch: typeof fetch; - FormData: typeof FormData; - - ReadableStream: typeof ReadableStream; - WritableStream: typeof WritableStream; - - crypto: typeof nodeWebCrypto; - } - } - - interface RequestInit { - duplex?: "half"; - } -} - -export function installGlobals() { - global.File = NodeFile as unknown as typeof File; - // @ts-ignore - this shows as an error in VSCode but is not an error via TSC so we can't use `ts-expect-error` - global.Headers = NodeHeaders; - // @ts-expect-error - overriding globals - global.Request = NodeRequest; - // @ts-expect-error - overriding globals - global.Response = NodeResponse; - // @ts-expect-error - overriding globals - global.fetch = nodeFetch; - // @ts-expect-error - overriding globals - global.FormData = NodeFormData; - - if (!global.crypto) { - // @ts-expect-error - overriding globals - global.crypto = nodeWebCrypto; - } -} diff --git a/packages/react-router-node/index.ts b/packages/react-router-node/index.ts index 616231434b..7d7c108d44 100644 --- a/packages/react-router-node/index.ts +++ b/packages/react-router-node/index.ts @@ -1,5 +1,3 @@ -export { installGlobals } from "./globals"; - export { createFileSessionStorage } from "./sessions/fileStorage"; export { diff --git a/packages/react-router-node/install.ts b/packages/react-router-node/install.ts deleted file mode 100644 index e5bc566ae4..0000000000 --- a/packages/react-router-node/install.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { installGlobals } from "./globals"; - -installGlobals(); diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json index e4640d532a..b97dbc6fdf 100644 --- a/packages/react-router-node/package.json +++ b/packages/react-router-node/package.json @@ -18,10 +18,6 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, - "./install": { - "types": "./dist/install.d.ts", - "default": "./dist/install.js" - }, "./package.json": "./package.json" }, "sideEffects": [ diff --git a/packages/react-router-node/rollup.config.js b/packages/react-router-node/rollup.config.js index 49266fca02..b100ca6c4c 100644 --- a/packages/react-router-node/rollup.config.js +++ b/packages/react-router-node/rollup.config.js @@ -21,11 +21,9 @@ module.exports = function rollup() { "react-router-node" ); - const input = [`${SOURCE_DIR}/index.ts`, `${SOURCE_DIR}/install.ts`]; - return [ { - input, + input: `${SOURCE_DIR}/index.ts`, external: (id) => isBareModuleId(id), output: { banner: createBanner(name, version), diff --git a/packages/react-router-serve/cli.ts b/packages/react-router-serve/cli.ts index 8929f8624e..acd33b61b9 100644 --- a/packages/react-router-serve/cli.ts +++ b/packages/react-router-serve/cli.ts @@ -1,10 +1,8 @@ -import "@react-router/node/install"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import url from "node:url"; import type { ServerBuild } from "react-router"; -import { installGlobals } from "@react-router/node"; import { createRequestHandler } from "@react-router/express"; import compression from "compression"; import express from "express"; @@ -30,7 +28,6 @@ sourceMapSupport.install({ return null; }, }); -installGlobals(); run(); diff --git a/packages/react-router/jest.config.js b/packages/react-router/jest.config.js index 8408526b8d..dc45566ce5 100644 --- a/packages/react-router/jest.config.js +++ b/packages/react-router/jest.config.js @@ -1,6 +1,7 @@ /** @type {import('jest').Config} */ module.exports = { ...require("../../jest/jest.config.shared"), + setupFiles: ["/__tests__/setup.ts"], setupFilesAfterEnv: ["@testing-library/jest-dom"], testEnvironment: "jsdom", }; diff --git a/playground/compiler-express/server.js b/playground/compiler-express/server.js index 9d28534e9f..fa5048f32c 100644 --- a/playground/compiler-express/server.js +++ b/playground/compiler-express/server.js @@ -1,11 +1,8 @@ import { createRequestHandler } from "@react-router/express"; -import { installGlobals } from "@react-router/node"; import compression from "compression"; import express from "express"; import morgan from "morgan"; -installGlobals(); - const viteDevServer = process.env.NODE_ENV === "production" ? undefined diff --git a/playground/compiler/vite.config.ts b/playground/compiler/vite.config.ts index 6735e6449d..f910ad4c18 100644 --- a/playground/compiler/vite.config.ts +++ b/playground/compiler/vite.config.ts @@ -1,10 +1,7 @@ import { reactRouter } from "@react-router/dev/vite"; -import { installGlobals } from "@react-router/node"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; -installGlobals(); - export default defineConfig({ plugins: [reactRouter(), tsconfigPaths()], }); From a7dd7794487de06cd5d0f5c63bfb054820d83145 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 21 Oct 2024 15:36:15 -0400 Subject: [PATCH 2/7] Update minimum version to node 20 --- .github/workflows/integration-full.yml | 6 +++--- .github/workflows/integration-pr-ubuntu.yml | 2 +- .../integration-pr-windows-macos.yml | 6 +++--- .github/workflows/shared-integration.yml | 2 +- .github/workflows/test.yml | 2 +- .../helpers/node-template/package.json | 2 +- .../vite-cloudflare-template/package.json | 2 +- .../helpers/vite-template/package.json | 2 +- package.json | 2 +- packages/react-router-architect/package.json | 2 +- packages/react-router-cloudflare/package.json | 2 +- .../__tests__/fixtures/node/package.json | 2 +- packages/react-router-dev/cli/run.ts | 10 ++++++++-- packages/react-router-dev/package.json | 2 +- packages/react-router-dom/package.json | 2 +- packages/react-router-express/package.json | 2 +- packages/react-router-fs-routes/package.json | 2 +- packages/react-router-node/package.json | 2 +- .../package.json | 2 +- packages/react-router-serve/package.json | 2 +- .../__tests__/router/navigation-test.ts | 20 +++++++++---------- .../react-router/__tests__/router/ssr-test.ts | 6 ------ packages/react-router/package.json | 2 +- playground/compiler-express/package.json | 2 +- playground/compiler-spa/package.json | 2 +- playground/compiler/package.json | 2 +- rollup.utils.js | 2 +- 27 files changed, 46 insertions(+), 46 deletions(-) diff --git a/.github/workflows/integration-full.yml b/.github/workflows/integration-full.yml index bdebc1d319..3f79331816 100644 --- a/.github/workflows/integration-full.yml +++ b/.github/workflows/integration-full.yml @@ -34,7 +34,7 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "ubuntu-latest" - node_version: "[18, 20]" + node_version: "[20, 22]" browser: '["chromium", "firefox"]' integration-windows: @@ -43,7 +43,7 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "windows-latest" - node_version: "[18, 20]" + node_version: "[20, 22]" browser: '["msedge"]' integration-macos: @@ -52,5 +52,5 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "macos-latest" - node_version: "[18, 20]" + node_version: "[20, 22]" browser: '["webkit"]' diff --git a/.github/workflows/integration-pr-ubuntu.yml b/.github/workflows/integration-pr-ubuntu.yml index 6223906475..ac25713e08 100644 --- a/.github/workflows/integration-pr-ubuntu.yml +++ b/.github/workflows/integration-pr-ubuntu.yml @@ -31,5 +31,5 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "ubuntu-latest" - node_version: "[20]" + node_version: "[22]" browser: '["chromium"]' diff --git a/.github/workflows/integration-pr-windows-macos.yml b/.github/workflows/integration-pr-windows-macos.yml index c5e08598e6..780a81f289 100644 --- a/.github/workflows/integration-pr-windows-macos.yml +++ b/.github/workflows/integration-pr-windows-macos.yml @@ -21,7 +21,7 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "ubuntu-latest" - node_version: "[20]" + node_version: "[22]" browser: '["firefox"]' integration-msedge: @@ -30,7 +30,7 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "windows-latest" - node_version: "[20]" + node_version: "[22]" browser: '["msedge"]' integration-webkit: @@ -39,5 +39,5 @@ jobs: uses: ./.github/workflows/shared-integration.yml with: os: "macos-latest" - node_version: "[20]" + node_version: "[22]" browser: '["webkit"]' diff --git a/.github/workflows/shared-integration.yml b/.github/workflows/shared-integration.yml index 33536ca41f..cc2c3e0ccb 100644 --- a/.github/workflows/shared-integration.yml +++ b/.github/workflows/shared-integration.yml @@ -9,7 +9,7 @@ on: node_version: required: true # this is limited to string | boolean | number (https://github.community/t/can-action-inputs-be-arrays/16457) - # but we want to pass an array (node_version: "[18, 20]"), + # but we want to pass an array (node_version: "[20, 22]"), # so we'll need to manually stringify it for now type: string browser: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b85bab5c89..e19ec5116a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,8 +26,8 @@ jobs: fail-fast: false matrix: node: - - 18 - 20 + - 22 runs-on: ubuntu-latest diff --git a/integration/helpers/node-template/package.json b/integration/helpers/node-template/package.json index cfe718a8b2..e43fc371a6 100644 --- a/integration/helpers/node-template/package.json +++ b/integration/helpers/node-template/package.json @@ -34,6 +34,6 @@ "typescript": "^5.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/integration/helpers/vite-cloudflare-template/package.json b/integration/helpers/vite-cloudflare-template/package.json index cb05e97ba9..62caf49967 100644 --- a/integration/helpers/vite-cloudflare-template/package.json +++ b/integration/helpers/vite-cloudflare-template/package.json @@ -30,6 +30,6 @@ "wrangler": "^3.28.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/integration/helpers/vite-template/package.json b/integration/helpers/vite-template/package.json index 2555f0659a..286a7c9ca7 100644 --- a/integration/helpers/vite-template/package.json +++ b/integration/helpers/vite-template/package.json @@ -36,6 +36,6 @@ "vite-tsconfig-paths": "^4.2.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/package.json b/package.json index 73cf006e6f..7bbb32ed22 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "vite-tsconfig-paths": "^4.2.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "pnpm": { "patchedDependencies": { diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json index a9664a798f..0029467b24 100644 --- a/packages/react-router-architect/package.json +++ b/packages/react-router-architect/package.json @@ -48,7 +48,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-cloudflare/package.json b/packages/react-router-cloudflare/package.json index 472d627722..f261eb7812 100644 --- a/packages/react-router-cloudflare/package.json +++ b/packages/react-router-cloudflare/package.json @@ -40,7 +40,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-dev/__tests__/fixtures/node/package.json b/packages/react-router-dev/__tests__/fixtures/node/package.json index ffff19de35..922e211c64 100644 --- a/packages/react-router-dev/__tests__/fixtures/node/package.json +++ b/packages/react-router-dev/__tests__/fixtures/node/package.json @@ -24,6 +24,6 @@ "typescript": "^5.1.6" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/packages/react-router-dev/cli/run.ts b/packages/react-router-dev/cli/run.ts index 5751d49a68..518a92f1e0 100644 --- a/packages/react-router-dev/cli/run.ts +++ b/packages/react-router-dev/cli/run.ts @@ -81,9 +81,15 @@ ${colors.logoBlue("react-router")} export async function run(argv: string[] = process.argv.slice(2)) { // Check the node version let versions = process.versions; - if (versions && versions.node && semver.major(versions.node) < 18) { + let MINIMUM_NODE_VERSION = 20; + if ( + versions && + versions.node && + semver.major(versions.node) < MINIMUM_NODE_VERSION + ) { throw new Error( - `️🚨 Oops, Node v${versions.node} detected. react-router requires a Node version greater than 18.` + `️🚨 Oops, Node v${versions.node} detected. react-router requires ` + + `a Node version greater than ${MINIMUM_NODE_VERSION}.` ); } diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json index 7acf8e2124..c9abd90fd9 100644 --- a/packages/react-router-dev/package.json +++ b/packages/react-router-dev/package.json @@ -112,7 +112,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-dom/package.json b/packages/react-router-dom/package.json index dcfa6fcefd..66576cf452 100644 --- a/packages/react-router-dom/package.json +++ b/packages/react-router-dom/package.json @@ -47,6 +47,6 @@ "README.md" ], "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/packages/react-router-express/package.json b/packages/react-router-express/package.json index d714b261bb..127acb5c50 100644 --- a/packages/react-router-express/package.json +++ b/packages/react-router-express/package.json @@ -46,7 +46,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-fs-routes/package.json b/packages/react-router-fs-routes/package.json index 6f1792964c..1a8e28a7b2 100644 --- a/packages/react-router-fs-routes/package.json +++ b/packages/react-router-fs-routes/package.json @@ -40,7 +40,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json index b97dbc6fdf..a4ffb33922 100644 --- a/packages/react-router-node/package.json +++ b/packages/react-router-node/package.json @@ -48,7 +48,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-remix-config-routes-adapter/package.json b/packages/react-router-remix-config-routes-adapter/package.json index ec6a103d95..ed5795e7e3 100644 --- a/packages/react-router-remix-config-routes-adapter/package.json +++ b/packages/react-router-remix-config-routes-adapter/package.json @@ -37,7 +37,7 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router-serve/package.json b/packages/react-router-serve/package.json index f2198dbf7f..977299e83d 100644 --- a/packages/react-router-serve/package.json +++ b/packages/react-router-serve/package.json @@ -39,7 +39,7 @@ "@types/source-map-support": "^0.5.6" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "files": [ "dist/", diff --git a/packages/react-router/__tests__/router/navigation-test.ts b/packages/react-router/__tests__/router/navigation-test.ts index 9210490c91..1346b6d9e5 100644 --- a/packages/react-router/__tests__/router/navigation-test.ts +++ b/packages/react-router/__tests__/router/navigation-test.ts @@ -171,11 +171,11 @@ describe("navigations", () => { ); expect(t.router.state.loaderData).toEqual({}); - // Node 16/18 versus 20 output different errors here :/ - let expected = process.version.startsWith("v18") - ? "Unexpected token } in JSON at position 15" - : "Unexpected non-whitespace character after JSON at position 15"; - expect(t.router.state.errors?.foo).toEqual(new SyntaxError(expected)); + expect(t.router.state.errors?.foo).toEqual( + new SyntaxError( + "Unexpected non-whitespace character after JSON at position 15" + ) + ); }); it("bubbles errors when unwrapping Responses", async () => { @@ -207,11 +207,11 @@ describe("navigations", () => { ); expect(t.router.state.loaderData).toEqual({}); - // Node 16/18 versus 20 output different errors here :/ - let expected = process.version.startsWith("v18") - ? "Unexpected token } in JSON at position 15" - : "Unexpected non-whitespace character after JSON at position 15"; - expect(t.router.state.errors?.root).toEqual(new SyntaxError(expected)); + expect(t.router.state.errors?.root).toEqual( + new SyntaxError( + "Unexpected non-whitespace character after JSON at position 15" + ) + ); }); it("does not fetch unchanging layout data", async () => { diff --git a/packages/react-router/__tests__/router/ssr-test.ts b/packages/react-router/__tests__/router/ssr-test.ts index a38afa1f6e..4ef923a368 100644 --- a/packages/react-router/__tests__/router/ssr-test.ts +++ b/packages/react-router/__tests__/router/ssr-test.ts @@ -753,9 +753,6 @@ describe("ssr", () => { let e; try { let contextPromise = query(request); - // Note this works in Node 18+ - but it does not work if using the - // `abort-controller` polyfill which doesn't yet support a custom `reason` - // See: https://github.com/mysticatea/abort-controller/issues/33 controller.abort(new Error("Oh no!")); // This should resolve even though we never resolved the loader await contextPromise; @@ -2082,9 +2079,6 @@ describe("ssr", () => { let e; try { let statePromise = queryRoute(request, { routeId: "root" }); - // Note this works in Node 18+ - but it does not work if using the - // `abort-controller` polyfill which doesn't yet support a custom `reason` - // See: https://github.com/mysticatea/abort-controller/issues/33 controller.abort(new Error("Oh no!")); // This should resolve even though we never resolved the loader await statePromise; diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 215eb32c85..bc9f207be2 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -68,6 +68,6 @@ "README.md" ], "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/playground/compiler-express/package.json b/playground/compiler-express/package.json index 33e0243b91..3902c0aafc 100644 --- a/playground/compiler-express/package.json +++ b/playground/compiler-express/package.json @@ -34,6 +34,6 @@ "vite-tsconfig-paths": "^4.2.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/playground/compiler-spa/package.json b/playground/compiler-spa/package.json index 503fac9554..b42a057d06 100644 --- a/playground/compiler-spa/package.json +++ b/playground/compiler-spa/package.json @@ -25,6 +25,6 @@ "vite-tsconfig-paths": "^4.2.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/playground/compiler/package.json b/playground/compiler/package.json index 1c0efa9e8a..d6d8a3d145 100644 --- a/playground/compiler/package.json +++ b/playground/compiler/package.json @@ -27,6 +27,6 @@ "vite-tsconfig-paths": "^4.2.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/rollup.utils.js b/rollup.utils.js index e131cc72df..4de511b754 100644 --- a/rollup.utils.js +++ b/rollup.utils.js @@ -143,7 +143,7 @@ function isBareModuleId(id) { const remixBabelConfig = { presets: [ - ["@babel/preset-env", { targets: { node: "18" } }], + ["@babel/preset-env", { targets: { node: "20" } }], "@babel/preset-react", "@babel/preset-typescript", ], From ce82034b9145187bcdb755780fa03c3a486aeaab Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 21 Oct 2024 15:36:26 -0400 Subject: [PATCH 3/7] Update testing setup --- package.json | 1 - .../__tests__/router/router-memory-test.ts | 32 ------------------- packages/react-router/__tests__/setup.ts | 29 ++++------------- pnpm-lock.yaml | 17 ---------- 4 files changed, 6 insertions(+), 73 deletions(-) diff --git a/package.json b/package.json index 7bbb32ed22..e3fb9b90d6 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "@types/wait-on": "^5.3.2", "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", - "abort-controller": "^3.0.0", "babel-jest": "^29.7.0", "babel-plugin-dev-expression": "^0.2.3", "babel-plugin-transform-remove-console": "^6.9.4", diff --git a/packages/react-router/__tests__/router/router-memory-test.ts b/packages/react-router/__tests__/router/router-memory-test.ts index 49ae9617c7..5a06d3510f 100644 --- a/packages/react-router/__tests__/router/router-memory-test.ts +++ b/packages/react-router/__tests__/router/router-memory-test.ts @@ -211,36 +211,4 @@ describe("a memory router", () => { router.dispose(); }); - - it("throws on submitting FormData when it's not available", async () => { - if (global.FormData) { - // This is globally available in Node 18, this test is primarily for Node 16 - // eslint-disable-next-line jest/no-conditional-expect - expect(true).toBe(true); - return; - } - - let actionSpy = jest.fn(); - - let router = createRouter({ - routes: [ - { - path: "/", - action: actionSpy, - }, - ], - history: createMemoryHistory(), - }); - - await expect(() => - router.navigate("/", { - formMethod: "post", - body: { key: "value" }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"FormData is not available in this environment"` - ); - - router.dispose(); - }); }); diff --git a/packages/react-router/__tests__/setup.ts b/packages/react-router/__tests__/setup.ts index 357b5924bc..8c208adb43 100644 --- a/packages/react-router/__tests__/setup.ts +++ b/packages/react-router/__tests__/setup.ts @@ -1,15 +1,19 @@ // https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#configuring-your-testing-environment globalThis.IS_REACT_ACT_ENVIRONMENT = true; -if (!globalThis.fetch) { +if (!globalThis.TextEncoder || !globalThis.TextDecoder) { const { TextDecoder, TextEncoder } = require("node:util"); - globalThis.TextDecoder = TextDecoder; globalThis.TextEncoder = TextEncoder; + globalThis.TextDecoder = TextDecoder; +} +if (!globalThis.ReadableStream || !globalThis.WritableStream) { const { ReadableStream, WritableStream } = require("node:stream/web"); globalThis.ReadableStream = ReadableStream; globalThis.WritableStream = WritableStream; +} +if (!globalThis.fetch) { const { fetch, FormData, Request, Response, Headers } = require("undici"); globalThis.fetch = fetch; @@ -20,17 +24,6 @@ if (!globalThis.fetch) { globalThis.FormData = globalThis.FormData || FormData; } -if (!globalThis.AbortController) { - const { AbortController } = require("abort-controller"); - globalThis.AbortController = AbortController; -} - -if (!globalThis.TextEncoder || !globalThis.TextDecoder) { - const { TextDecoder, TextEncoder } = require("node:util"); - globalThis.TextEncoder = TextEncoder; - globalThis.TextDecoder = TextDecoder; -} - if (!globalThis.TextEncoderStream) { const { TextEncoderStream } = require("node:stream/web"); globalThis.TextEncoderStream = TextEncoderStream; @@ -40,13 +33,3 @@ if (!globalThis.TransformStream) { const { TransformStream } = require("node:stream/web"); globalThis.TransformStream = TransformStream; } - -if (!globalThis.File) { - const { File } = require("undici"); - globalThis.File = File; -} - -if (!globalThis.crypto) { - const { webcrypto } = require("node:crypto"); - globalThis.crypto = webcrypto; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 318d5e6b4c..574dc54fb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,9 +133,6 @@ importers: '@typescript-eslint/parser': specifier: ^7.5.0 version: 7.5.0(eslint@8.57.0)(typescript@5.4.5) - abort-controller: - specifier: ^3.0.0 - version: 3.0.0 babel-jest: specifier: ^29.7.0 version: 29.7.0(@babel/core@7.22.9) @@ -3108,10 +3105,6 @@ packages: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} deprecated: Use your platform's native atob() and btoa() methods instead - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -4255,10 +4248,6 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -10532,10 +10521,6 @@ snapshots: abab@2.0.6: {} - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -12083,8 +12068,6 @@ snapshots: '@types/node': 18.19.26 require-like: 0.1.2 - event-target-shim@5.0.1: {} - execa@5.1.1: dependencies: cross-spawn: 7.0.3 From cd21c5f205a52ec70d2e922d2185a9a2de042e25 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 21 Oct 2024 15:37:40 -0400 Subject: [PATCH 4/7] Add changeset --- .changeset/tidy-pens-help.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/tidy-pens-help.md diff --git a/.changeset/tidy-pens-help.md b/.changeset/tidy-pens-help.md new file mode 100644 index 0000000000..5db3b598d6 --- /dev/null +++ b/.changeset/tidy-pens-help.md @@ -0,0 +1,10 @@ +--- +"@react-router/express": major +"@react-router/node": major +"@react-router/dev": major +"react-router": major +--- + +Drop support for Node 18, update minimum Node vestion to 20 + +- Remove `installGlobals()` as this should no longer be necessary From 4e27dd71c68c0d86e3f8a64293bc463baa27936d Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 21 Oct 2024 15:58:15 -0400 Subject: [PATCH 5/7] Fix test on node 22 --- .../__tests__/router/navigation-test.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/react-router/__tests__/router/navigation-test.ts b/packages/react-router/__tests__/router/navigation-test.ts index 1346b6d9e5..356f6ec38d 100644 --- a/packages/react-router/__tests__/router/navigation-test.ts +++ b/packages/react-router/__tests__/router/navigation-test.ts @@ -171,10 +171,9 @@ describe("navigations", () => { ); expect(t.router.state.loaderData).toEqual({}); - expect(t.router.state.errors?.foo).toEqual( - new SyntaxError( - "Unexpected non-whitespace character after JSON at position 15" - ) + expect(t.router.state.errors?.foo).toBeInstanceOf(SyntaxError); + expect(t.router.state.errors?.foo.message).toContain( + "Unexpected non-whitespace character after JSON at position 15" ); }); @@ -207,10 +206,9 @@ describe("navigations", () => { ); expect(t.router.state.loaderData).toEqual({}); - expect(t.router.state.errors?.root).toEqual( - new SyntaxError( - "Unexpected non-whitespace character after JSON at position 15" - ) + expect(t.router.state.errors?.root).toBeInstanceOf(SyntaxError); + expect(t.router.state.errors?.root.message).toContain( + "Unexpected non-whitespace character after JSON at position 15" ); }); From 7e88a6879bd1289c7c6aa7f2f900bd7b13dfcdbc Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 22 Oct 2024 10:53:39 -0400 Subject: [PATCH 6/7] Add docs on polyfilling fetch --- docs/deploying/custom-node.md | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/deploying/custom-node.md b/docs/deploying/custom-node.md index d3021a698b..937da3914f 100644 --- a/docs/deploying/custom-node.md +++ b/docs/deploying/custom-node.md @@ -7,3 +7,41 @@ title: Custom Node.js This document is a work in progress. There's not much to see here (yet). + +## Polyfilling `fetch` + +React Router officially supports Active and Maintenance[^1] [Node LTS veleases][node-releases] at any given point in time. Dropping support for End of Life Node versions may be done in a React Router Minor release. + +[^1] Based on timing, React Router may drop support for a Node Maintenance LTS version shortly before it goes end-of-life if it better aligns with a React Router Major SemVer release. + +At the time React Router v7 was released, all versions had a usable `fetch` implementation so there is generally no need to polyfill any `fetch` APIs so long as you're on Node 22 or one of the later Node 20 releases. + +- Node 22 (Active LTS) has a stable [`fetch`][node-22-fetch] implementation +- Node 20 (Maintenance LTS) has an experimental (but suitable from our testing) [`fetch`][node-20-fetch] implementation + +If you do find that you need to polyfill anything, you can do so directly from the [undici] package which node uses internally. + +```ts +import { + fetch as nodeFetch, + File as NodeFile, + FormData as NodeFormData, + Headers as NodeHeaders, + Request as NodeRequest, + Response as NodeResponse, +} from "undici"; + +export function polyfillFetch() { + global.File = NodeFile; + global.Headers = NodeHeaders; + global.Request = NodeRequest; + global.Response = NodeResponse; + global.fetch = nodeFetch; + global.FormData = NodeFormData; +} +``` + +[node-releases]: https://nodejs.org/en/about/previous-releases +[node-20-fetch]: https://nodejs.org/docs/latest-v20.x/api/globals.html#fetch +[node-22-fetch]: https://nodejs.org/docs/latest-v22.x/api/globals.html#fetch +[undici]: https://github.com/nodejs/undici From f1fcf529ac1eb1928af503f3db2df25a7ed75f70 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 22 Oct 2024 11:23:45 -0400 Subject: [PATCH 7/7] Update docs/deploying/custom-node.md --- docs/deploying/custom-node.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/custom-node.md b/docs/deploying/custom-node.md index 937da3914f..af9ef966ed 100644 --- a/docs/deploying/custom-node.md +++ b/docs/deploying/custom-node.md @@ -12,7 +12,7 @@ title: Custom Node.js React Router officially supports Active and Maintenance[^1] [Node LTS veleases][node-releases] at any given point in time. Dropping support for End of Life Node versions may be done in a React Router Minor release. -[^1] Based on timing, React Router may drop support for a Node Maintenance LTS version shortly before it goes end-of-life if it better aligns with a React Router Major SemVer release. +[^1]: Based on timing, React Router may drop support for a Node Maintenance LTS version shortly before it goes end-of-life if it better aligns with a React Router Major SemVer release. At the time React Router v7 was released, all versions had a usable `fetch` implementation so there is generally no need to polyfill any `fetch` APIs so long as you're on Node 22 or one of the later Node 20 releases.