diff --git a/integration/error-boundary-test.ts b/integration/error-boundary-test.ts index a71a3a38b3..fbe134442b 100644 --- a/integration/error-boundary-test.ts +++ b/integration/error-boundary-test.ts @@ -655,256 +655,6 @@ test.describe("ErrorBoundary", () => { }); }); -test.describe("loaderData in ErrorBoundary", () => { - let fixture: Fixture; - let appFixture: AppFixture; - let consoleErrors: string[]; - let oldConsoleError: () => void; - - test.beforeAll(async () => { - fixture = await createFixture({ - files: { - "app/root.tsx": js` - import { Links, Meta, Outlet, Scripts } from "react-router"; - - export default function Root() { - return ( - -
- -{useLoaderData()}
-{useLoaderData()}
-- {useMatches().find(m => m.id === 'routes/parent').data} -
-{error.message}
- > - ); - } - `, - - "app/routes/parent.child-with-boundary.tsx": js` - import { Form, useLoaderData, useRouteError } from "react-router"; - - export function loader() { - return "CHILD"; - } - - export function action() { - throw new Error("Broken!"); - } - - export default function () { - return ( - <> -{useLoaderData()}
- - > - ) - } - - export function ErrorBoundary() { - let error = useRouteError(); - return ( - <> -{useLoaderData()}
-{error.message}
- > - ); - } - `, - - "app/routes/parent.child-without-boundary.tsx": js` - import { Form, useLoaderData } from "react-router"; - - export function loader() { - return "CHILD"; - } - - export function action() { - throw new Error("Broken!"); - } - - export default function () { - return ( - <> -{useLoaderData()}
- - > - ) - } - `, - }, - }); - - appFixture = await createAppFixture(fixture, ServerMode.Development); - }); - - test.afterAll(() => { - appFixture.close(); - }); - - test.beforeEach(({ page }) => { - oldConsoleError = console.error; - console.error = () => {}; - consoleErrors = []; - // Listen for all console events and handle errors - page.on("console", (msg) => { - if (msg.type() === "error") { - consoleErrors.push(msg.text()); - } - }); - }); - - test.afterEach(() => { - console.error = oldConsoleError; - }); - - test.describe("without JavaScript", () => { - test.use({ javaScriptEnabled: false }); - runBoundaryTests(); - }); - - test.describe("with JavaScript", () => { - test.use({ javaScriptEnabled: true }); - runBoundaryTests(); - }); - - function runBoundaryTests() { - test("Prevents useLoaderData in self ErrorBoundary", async ({ - page, - javaScriptEnabled, - }) => { - let app = new PlaywrightFixture(appFixture, page); - await app.goto("/parent/child-with-boundary"); - - expect(await app.getHtml("#parent-data")).toEqual( - 'PARENT
' - ); - expect(await app.getHtml("#child-data")).toEqual( - 'CHILD
' - ); - expect(consoleErrors).toEqual([]); - - await app.clickSubmitButton("/parent/child-with-boundary"); - await page.waitForSelector("#child-error"); - - expect(await app.getHtml("#child-error")).toEqual( - 'Broken!
' - ); - expect(await app.getHtml("#parent-data")).toEqual( - 'PARENT
' - ); - expect(await app.getHtml("#child-data")).toEqual( - '' - ); - - // Only look for this message. Chromium browsers will also log the - // network error but firefox does not - // "Failed to load resource: the server responded with a status of 500 (Internal Server Error)", - let msg = - "You cannot `useLoaderData` in an errorElement (routeId: routes/parent.child-with-boundary)"; - if (javaScriptEnabled) { - expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]); - } else { - // We don't get the useLoaderData message in the client when JS is disabled - expect(consoleErrors.filter((m) => m === msg)).toEqual([]); - } - }); - - test("Prevents useLoaderData in bubbled ErrorBoundary", async ({ - page, - javaScriptEnabled, - }) => { - let app = new PlaywrightFixture(appFixture, page); - await app.goto("/parent/child-without-boundary"); - - expect(await app.getHtml("#parent-data")).toEqual( - 'PARENT
' - ); - expect(await app.getHtml("#child-data")).toEqual( - 'CHILD
' - ); - expect(consoleErrors).toEqual([]); - - await app.clickSubmitButton("/parent/child-without-boundary"); - await page.waitForSelector("#parent-error"); - - expect(await app.getHtml("#parent-error")).toEqual( - 'Broken!
' - ); - if (javaScriptEnabled) { - // This data remains in single fetch with JS because we don't revalidate - // due to the 500 action response - expect(await app.getHtml("#parent-matches-data")).toEqual( - 'PARENT
' - ); - } else { - // But without JS document requests call all loaders up to the - // boundary route so parent's data clears out - expect(await app.getHtml("#parent-matches-data")).toEqual( - '' - ); - } - expect(await app.getHtml("#parent-data")).toEqual( - '' - ); - - // Only look for this message. Chromium browsers will also log the - // network error but firefox does not - // "Failed to load resource: the server responded with a status of 500 (Internal Server Error)", - let msg = - "You cannot `useLoaderData` in an errorElement (routeId: routes/parent)"; - if (javaScriptEnabled) { - expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]); - } else { - // We don't get the useLoaderData message in the client when JS is disabled - expect(consoleErrors.filter((m) => m === msg)).toEqual([]); - } - }); - } -}); - test.describe("Default ErrorBoundary", () => { let fixture: Fixture; let appFixture: AppFixture; diff --git a/packages/react-router-dev/vite/babel.ts b/packages/react-router-dev/vite/babel.ts index a075e02836..b4449c65b3 100644 --- a/packages/react-router-dev/vite/babel.ts +++ b/packages/react-router-dev/vite/babel.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ import type { NodePath } from "@babel/traverse"; -import type { types as BabelTypes } from "@babel/core"; -import { parse } from "@babel/parser"; +import type { types as Babel } from "@babel/core"; +import { parse, type ParseResult } from "@babel/parser"; import * as t from "@babel/types"; // These `require`s were needed to support building within vite-ecosystem-ci, @@ -12,4 +12,4 @@ const generate = require("@babel/generator") .default as typeof import("@babel/generator").default; export { traverse, generate, parse, t }; -export type { BabelTypes, NodePath }; +export type { Babel, NodePath, ParseResult }; diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index 27a6a0afe8..3b6f7a4211 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -29,6 +29,7 @@ import colors from "picocolors"; import { type RouteManifestEntry, type RouteManifest } from "../config/routes"; import type { Manifest as ReactRouterManifest } from "../manifest"; import invariant from "../invariant"; +import { generate, parse } from "./babel"; import type { NodeRequestHandler } from "./node-adapter"; import { fromNodeRequest, toNodeRequest } from "./node-adapter"; import { getStylesForUrl, isCssModulesFile } from "./styles"; @@ -44,6 +45,7 @@ import { resolveEntryFiles, resolvePublicPath, } from "./config"; +import { withProps } from "./with-props"; export async function resolveViteConfig({ configFile, @@ -1263,7 +1265,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => { { name: "react-router-route-entry", enforce: "pre", - async transform(code, id, options) { + async transform(_code, id, options) { if (!isRouteEntry(id)) return; let routeModuleId = id.replace(ROUTE_ENTRY_QUERY_STRING, ""); @@ -1450,7 +1452,10 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => { let [filepath] = id.split("?"); - return removeExports(code, SERVER_ONLY_ROUTE_EXPORTS, { + let ast = parse(code, { sourceType: "module" }); + removeExports(ast, SERVER_ONLY_ROUTE_EXPORTS); + withProps(ast); + return generate(ast, { sourceMaps: true, filename: id, sourceFileName: filepath, @@ -1586,7 +1591,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => { } } - server.ws.send({ + server.hot.send({ type: "custom", event: "react-router:hmr", data: hmrEventData, diff --git a/packages/react-router-dev/vite/remove-exports-test.ts b/packages/react-router-dev/vite/remove-exports-test.ts index e5be493d5f..bf83438e53 100644 --- a/packages/react-router-dev/vite/remove-exports-test.ts +++ b/packages/react-router-dev/vite/remove-exports-test.ts @@ -1,8 +1,15 @@ +import { generate, parse } from "./babel"; import { removeExports } from "./remove-exports"; -describe("removeExports", () => { +function transform(code: string, exportsToRemove: string[]) { + let ast = parse(code, { sourceType: "module" }); + removeExports(ast, exportsToRemove); + return generate(ast); +} + +describe("transform", () => { test("arrow function", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = () => {} export const serverExport_2 = () => {} @@ -20,7 +27,7 @@ describe("removeExports", () => { }); test("arrow function with dependencies", () => { - let result = removeExports( + let result = transform( ` import { serverLib } from 'server-lib' import { clientLib } from 'client-lib' @@ -52,7 +59,7 @@ describe("removeExports", () => { }); test("function statement", () => { - let result = removeExports( + let result = transform( ` export function serverExport_1(){} export function serverExport_2(){} @@ -70,7 +77,7 @@ describe("removeExports", () => { }); test("function statement with dependencies", () => { - let result = removeExports( + let result = transform( ` import { serverLib } from 'server-lib' import { clientLib } from 'client-lib' @@ -110,7 +117,7 @@ describe("removeExports", () => { }); test("object", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = {} export const serverExport_2 = {} @@ -128,7 +135,7 @@ describe("removeExports", () => { }); test("object with dependencies", () => { - let result = removeExports( + let result = transform( ` import { serverLib } from 'server-lib' import { clientLib } from 'client-lib' @@ -164,7 +171,7 @@ describe("removeExports", () => { }); test("class", () => { - let result = removeExports( + let result = transform( ` export class serverExport_1 {} export class serverExport_2 {} @@ -182,7 +189,7 @@ describe("removeExports", () => { }); test("class with dependencies", () => { - let result = removeExports( + let result = transform( ` import { serverLib } from 'server-lib' import { clientLib } from 'client-lib' @@ -226,7 +233,7 @@ describe("removeExports", () => { }); test("function call", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = globalFunction() export const serverExport_2 = globalFunction() @@ -244,7 +251,7 @@ describe("removeExports", () => { }); test("function call with dependencies", () => { - let result = removeExports( + let result = transform( ` import { serverLib } from 'server-lib' import { clientLib } from 'client-lib' @@ -276,7 +283,7 @@ describe("removeExports", () => { }); test("iife", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = (() => {})() export const serverExport_2 = (() => {})() @@ -294,7 +301,7 @@ describe("removeExports", () => { }); test("iife with dependencies", () => { - let result = removeExports( + let result = transform( ` import { serverLib } from 'server-lib' import { clientLib } from 'client-lib' @@ -326,7 +333,7 @@ describe("removeExports", () => { }); test("aggregated export", () => { - let result = removeExports( + let result = transform( ` const serverExport_1 = 123 const serverExport_2 = 123 @@ -352,7 +359,7 @@ describe("removeExports", () => { }); test("aggregated export multiple", () => { - let result = removeExports( + let result = transform( ` const serverExport_1 = 123 const serverExport_2 = 123 @@ -374,7 +381,7 @@ describe("removeExports", () => { }); test("aggregated export multiple mixed", () => { - let result = removeExports( + let result = transform( ` const removeMe_1 = 123 const removeMe_2 = 123 @@ -397,7 +404,7 @@ describe("removeExports", () => { }); test("re-export", () => { - let result = removeExports( + let result = transform( ` export { serverExport_1 } from './server/1' export { serverExport_2 } from './server/2' @@ -415,7 +422,7 @@ describe("removeExports", () => { }); test("re-export multiple", () => { - let result = removeExports( + let result = transform( ` export { serverExport_1, serverExport_2 } from './server' export { clientExport_1, clientExport_2 } from './client' @@ -429,7 +436,7 @@ describe("removeExports", () => { }); test("re-export multiple mixed", () => { - let result = removeExports( + let result = transform( ` export { removeMe_1, keepMe_1 } from './1' export { removeMe_2, keepMe_2 } from './2' @@ -444,7 +451,7 @@ describe("removeExports", () => { }); test("re-export manual", () => { - let result = removeExports( + let result = transform( ` import { serverExport_1 } from './server/1' import { serverExport_2 } from './server/2' @@ -469,7 +476,7 @@ describe("removeExports", () => { }); test("re-export manual multiple", () => { - let result = removeExports( + let result = transform( ` import { serverExport_1, serverExport_2 } from './server' import { clientExport_1, clientExport_2 } from './client' @@ -487,7 +494,7 @@ describe("removeExports", () => { }); test("re-export manual multiple mixed", () => { - let result = removeExports( + let result = transform( ` import { removeMe_1, keepMe_1 } from './1' import { removeMe_2, keepMe_2 } from './2' @@ -507,7 +514,7 @@ describe("removeExports", () => { }); test("number", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = 123 export const serverExport_2 = 123 @@ -525,7 +532,7 @@ describe("removeExports", () => { }); test("string", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = 'string' export const serverExport_2 = 'string' @@ -543,7 +550,7 @@ describe("removeExports", () => { }); test("string reference", () => { - let result = removeExports( + let result = transform( ` const SERVER_STRING = 'SERVER_STRING'; const CLIENT_STRING = 'CLIENT_STRING'; @@ -565,7 +572,7 @@ describe("removeExports", () => { }); test("null", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = null export const serverExport_2 = null @@ -583,7 +590,7 @@ describe("removeExports", () => { }); test("multiple variable declarators", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = null, serverExport_2 = null @@ -601,11 +608,11 @@ describe("removeExports", () => { }); test("multiple variable declarators mixed", () => { - let result = removeExports( + let result = transform( ` export const serverExport_1 = null, clientExport_1 = null - + export const clientExport_2 = null, serverExport_2 = null `, @@ -620,7 +627,7 @@ describe("removeExports", () => { test("array destructuring throws on removed export", () => { expect(() => - removeExports( + transform( ` export const [serverExport_1, serverExport_2] = [null, null] @@ -635,7 +642,7 @@ describe("removeExports", () => { test("array rest destructuring throws on removed export", () => { expect(() => - removeExports( + transform( ` export const [...serverExport_1] = [null, null] @@ -650,7 +657,7 @@ describe("removeExports", () => { test("nested array destructuring throws on removed export", () => { expect(() => - removeExports( + transform( ` export const [keepMe_1, [{ nested: [ { nested: [serverExport_2] } ] }] ] = nested; `, @@ -662,7 +669,7 @@ describe("removeExports", () => { }); test("array destructuring works when nothing is removed", () => { - let result = removeExports( + let result = transform( ` export const [clientExport_1, clientExport_2] = [null, null] `, @@ -676,7 +683,7 @@ describe("removeExports", () => { test("object destructuring throws on removed export", () => { expect(() => - removeExports( + transform( ` export const { serverExport_1, serverExport_2 } = {} @@ -691,7 +698,7 @@ describe("removeExports", () => { test("object rest destructuring throws on removed export", () => { expect(() => - removeExports( + transform( ` export const { ...serverExport_1 } = {} @@ -706,7 +713,7 @@ describe("removeExports", () => { test("nested object destructuring throws on removed export", () => { expect(() => - removeExports( + transform( ` export const [keepMe_1, [{ nested: [ { nested: { serverExport_2 } } ] }]] = nested; `, @@ -718,7 +725,7 @@ describe("removeExports", () => { }); test("object destructuring works when nothing is removed", () => { - let result = removeExports( + let result = transform( ` export const { clientExport_1, clientExport_2 } = {} `, @@ -734,7 +741,7 @@ describe("removeExports", () => { }); test("default export", () => { - let result = removeExports( + let result = transform( ` export const keepMe = null; @@ -749,7 +756,7 @@ describe("removeExports", () => { }); test("default export aggregated", () => { - let result = removeExports( + let result = transform( ` export const keepMe = null; @@ -764,7 +771,7 @@ describe("removeExports", () => { }); test("default export aggregated mixed", () => { - let result = removeExports( + let result = transform( ` const keepMe = null; @@ -782,7 +789,7 @@ describe("removeExports", () => { }); test("default re-export", () => { - let result = removeExports( + let result = transform( ` export const keepMe = null; @@ -795,7 +802,7 @@ describe("removeExports", () => { }); test("default re-export mixed", () => { - let result = removeExports( + let result = transform( ` export { default, keepMe } from "./module"; `, @@ -808,7 +815,7 @@ describe("removeExports", () => { }); test("nothing removed", () => { - let result = removeExports( + let result = transform( ` export const clientExport_1 = () => {} export const clientExport_2 = () => {} diff --git a/packages/react-router-dev/vite/remove-exports.ts b/packages/react-router-dev/vite/remove-exports.ts index 7859134d76..342fb1178c 100644 --- a/packages/react-router-dev/vite/remove-exports.ts +++ b/packages/react-router-dev/vite/remove-exports.ts @@ -1,22 +1,18 @@ -import type { GeneratorOptions } from "@babel/generator"; import { findReferencedIdentifiers, deadCodeElimination, } from "babel-dead-code-elimination"; -import type { BabelTypes, NodePath } from "./babel"; -import { parse, traverse, generate } from "./babel"; +import type { Babel, NodePath, ParseResult } from "./babel"; +import { traverse } from "./babel"; export const removeExports = ( - source: string, - exportsToRemove: string[], - generateOptions: GeneratorOptions = {} + ast: ParseResult{navigation.state}
-- Foo Data:{data === undefined ? "undefined" : JSON.stringify(data)} -
-Foo Error:{error.message}
- > - ); - } - - expect(getHtml(container)).toMatchInlineSnapshot(` - "- idle -
-{navigation.state}
-- Layout Data: - {data === undefined ? "undefined" : JSON.stringify(data)} -
-Layout Error:{error.message}
- > - ); - } - - expect(getHtml(container)).toMatchInlineSnapshot(` - "- idle -
-- Layout Data: - undefined -
-- Layout Error: - Kaboom! -
-