diff --git a/package.json b/package.json index c5e7c54c..c5354848 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-router-devtools", "description": "Devtools for React Router - debug, trace, find hydration errors, catch bugs and inspect server/client data with react-router-devtools", "author": "Alem Tuzlak", - "version": "1.1.7", + "version": "1.1.8", "license": "MIT", "keywords": [ "react-router", diff --git a/src/vite/utils/data-functions-augment.test.ts b/src/vite/utils/data-functions-augment.test.ts index d2c3b679..8efef82c 100644 --- a/src/vite/utils/data-functions-augment.test.ts +++ b/src/vite/utils/data-functions-augment.test.ts @@ -3,383 +3,587 @@ import { augmentDataFetchingFunctions } from "./data-functions-augment" const removeWhitespace = (str: string) => str.replace(/\s/g, "") describe("transform", () => { - it("should transform the loader export when it's a function", () => { - const result = augmentDataFetchingFunctions( - ` + describe("loader transformations", () => { + it("should transform the loader export when it's a function", () => { + const result = augmentDataFetchingFunctions( + ` export function loader() {} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; export const loader = _withLoaderWrapper(function loader() {}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's a const variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the loader export when it's a const variable", () => { + const result = augmentDataFetchingFunctions( + ` export const loader = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; export const loader = _withLoaderWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's a let variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the loader export when it's a let variable", () => { + const result = augmentDataFetchingFunctions( + ` export let loader = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; export let loader = _withLoaderWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's a var variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the loader export when it's a var variable", () => { + const result = augmentDataFetchingFunctions( + ` export var loader = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; export var loader = _withLoaderWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's re-exported from another file", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the loader export when it's re-exported from another file", () => { + const result = augmentDataFetchingFunctions( + ` export { loader } from "./loader.js"; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; - export { loader as _loader } from "./loader.js"; + import { loader as _loader } from "./loader.js"; export const loader = _withLoaderWrapper(_loader, "test"); + export {} from "./loader.js"; `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the loader export when it's imported from another file and exported", () => { - const result = augmentDataFetchingFunctions( - ` + it("should wrap the loader export when it's imported from another file and exported", () => { + const result = augmentDataFetchingFunctions( + ` import { loader } from "./loader.js"; export { loader }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; import { loader } from "./loader.js"; export { loader as _loader }; export const loader = _withLoaderWrapper(_loader, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the client loader export when it's a function", () => { - const result = augmentDataFetchingFunctions( - ` - export function clientLoader() {} - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; - export const clientLoader = _withClientLoaderWrapper(function clientLoader() {}, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + it("should wrap the loader export when it's exported via export { loader } and declared within the file", () => { + const result = augmentDataFetchingFunctions( + ` + + function loader() { + return { name: 'React Router' }; + } - it("should wrap the client loader export when it's a const variable", () => { - const result = augmentDataFetchingFunctions( - ` - export const clientLoader = async ({ request }) => { return {};} - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; - export const clientLoader = _withClientLoaderWrapper(async ({ request }) => { return {};}, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) - it("should wrap the client loader export when it's a let variable", () => { - const result = augmentDataFetchingFunctions( - ` - export let clientLoader = async ({ request }) => { return {};} + export { loader }; + `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; - export let clientLoader = _withClientLoaderWrapper(async ({ request }) => { return {};}, "test"); + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; + const loader = _withLoaderWrapper(function loader() { + return { name: 'React Router' }; + }, "test"); + export { loader }; + `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + /* it("should work with export { loader } from 'x' pattern", () => { + const result = augmentDataFetchingFunctions( + ` - it("should wrap the client loader export when it's a var variable", () => { - const result = augmentDataFetchingFunctions( - ` - export var clientLoader = async ({ request }) => { return {};} + export { loader } from "./$slug"; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; - export var clientLoader = _withClientLoaderWrapper(async ({ request }) => { return {};}, "test"); + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; + import { loader as _loader } from "./$slug"; + const loader = _withLoaderWrapper(_loader, "test"); + export { loader }; + `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) */ }) - it("should wrap the client loader export when it's re-exported from another file", () => { - const result = augmentDataFetchingFunctions( - ` - import { clientLoader } from "./client-loader.js"; - export { clientLoader }; + describe("clientLoader transformations", () => { + it("should wrap the client loader export when it's a function", () => { + const result = augmentDataFetchingFunctions( + ` + export function clientLoader() {} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + export const clientLoader = _withClientLoaderWrapper(function clientLoader() {}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's a const variable", () => { + const result = augmentDataFetchingFunctions( + ` + export const clientLoader = async ({ request }) => { return {};} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + export const clientLoader = _withClientLoaderWrapper(async ({ request }) => { return {};}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's a let variable", () => { + const result = augmentDataFetchingFunctions( + ` + export let clientLoader = async ({ request }) => { return {};} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + export let clientLoader = _withClientLoaderWrapper(async ({ request }) => { return {};}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's a var variable", () => { + const result = augmentDataFetchingFunctions( + ` + export var clientLoader = async ({ request }) => { return {};} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + export var clientLoader = _withClientLoaderWrapper(async ({ request }) => { return {};}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's re-exported from another file", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientLoader } from "./client-loader.js"; + export { clientLoader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + import { clientLoader } from "./client-loader.js"; + export { clientLoader as _clientLoader }; + export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's imported from another file and exported", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientLoader } from "./client-loader.js"; + export { clientLoader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + import { clientLoader } from "./client-loader.js"; + export { clientLoader as _clientLoader }; + export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the client action export when it's re-exported from another file", () => { + const result = augmentDataFetchingFunctions( + ` + export { clientLoader } from "./clientLoader.js"; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; - import { clientLoader } from "./client-loader.js"; - export { clientLoader as _clientLoader }; + import { clientLoader as _clientLoader } from "./clientLoader.js"; export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); + export {} from "./clientLoader.js"; `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the clientLoader export when it's exported via export { clientLoader } and declared within the file", () => { + const result = augmentDataFetchingFunctions( + ` + + function clientLoader() { + return { name: 'React Router' }; + } + - it("should wrap the client loader export when it's imported from another file and exported", () => { - const result = augmentDataFetchingFunctions( - ` - import { clientLoader } from "./client-loader.js"; export { clientLoader }; + `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; - import { clientLoader } from "./client-loader.js"; - export { clientLoader as _clientLoader }; - export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); + const clientLoader = _withClientLoaderWrapper(function clientLoader() { + return { name: 'React Router' }; + }, "test"); + export { clientLoader }; + `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) }) - it("should transform the action export when it's a function", () => { - const result = augmentDataFetchingFunctions( - ` + describe("action transformations", () => { + it("should transform the action export when it's a function", () => { + const result = augmentDataFetchingFunctions( + ` export function action() {} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; export const action = _withActionWrapper(function action() {}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a const variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the action export when it's a const variable", () => { + const result = augmentDataFetchingFunctions( + ` export const action = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; export const action = _withActionWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a let variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the action export when it's a let variable", () => { + const result = augmentDataFetchingFunctions( + ` export let action = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; export let action = _withActionWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a var variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the action export when it's a var variable", () => { + const result = augmentDataFetchingFunctions( + ` export var action = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; export var action = _withActionWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's re-exported from another file", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the action export when it's re-exported from another file", () => { + const result = augmentDataFetchingFunctions( + ` export { action } from "./action.js"; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; - export { action as _action } from "./action.js"; + import { action as _action } from "./action.js"; export const action = _withActionWrapper(_action, "test"); + export {} from "./action.js"; `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the action export when it's imported from another file and exported", () => { - const result = augmentDataFetchingFunctions( - ` + it("should wrap the action export when it's imported from another file and exported", () => { + const result = augmentDataFetchingFunctions( + ` import { action } from "./action.js"; export { action }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; import { action } from "./action.js"; export { action as _action }; export const action = _withActionWrapper(_action, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the action export when it's exported via export { action } and declared within the file", () => { + const result = augmentDataFetchingFunctions( + ` + + function action() { + return { name: 'React Router' }; + } + - it("should transform the client action export when it's a function", () => { - const result = augmentDataFetchingFunctions( - ` + export { action }; + + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; + const action = _withActionWrapper(function action() { + return { name: 'React Router' }; + }, "test"); + export { action }; + + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + }) + describe("client action transformations", () => { + it("should transform the client action export when it's a function", () => { + const result = augmentDataFetchingFunctions( + ` export function clientAction() {} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; export const clientAction = _withClientActionWrapper(function clientAction() {}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's a const variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the client action export when it's a const variable", () => { + const result = augmentDataFetchingFunctions( + ` export const clientAction = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; export const clientAction = _withClientActionWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's a let variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the client action export when it's a let variable", () => { + const result = augmentDataFetchingFunctions( + ` export let clientAction = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; export let clientAction = _withClientActionWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's a var variable", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the client action export when it's a var variable", () => { + const result = augmentDataFetchingFunctions( + ` export var clientAction = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; export var clientAction = _withClientActionWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's re-exported from another file", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the client action export when it's re-exported from another file", () => { + const result = augmentDataFetchingFunctions( + ` import { clientAction } from "./client-action.js"; export { clientAction }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; import { clientAction } from "./client-action.js"; export { clientAction as _clientAction }; export const clientAction = _withClientActionWrapper(_clientAction, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's imported from another file and exported", () => { - const result = augmentDataFetchingFunctions( - ` + it("should transform the client action export when it's re-exported from another file", () => { + const result = augmentDataFetchingFunctions( + ` + export { clientAction } from "./clientAction.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { clientAction as _clientAction } from "./clientAction.js"; + export const clientAction = _withClientActionWrapper(_clientAction, "test"); + export {} from "./clientAction.js"; + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the client action export when it's imported from another file and exported", () => { + const result = augmentDataFetchingFunctions( + ` import { clientAction } from "./client-action.js"; export { clientAction }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; import { clientAction } from "./client-action.js"; export { clientAction as _clientAction }; export const clientAction = _withClientActionWrapper(_clientAction, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the clientAction export when it's exported via export { clientAction } and declared within the file", () => { + const result = augmentDataFetchingFunctions( + ` + + function clientAction() { + return { name: 'React Router' }; + } + + + export { clientAction }; + + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + const clientAction = _withClientActionWrapper(function clientAction() { + return { name: 'React Router' }; + }, "test"); + export { clientAction }; + + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) }) }) + +it("should transform the re-exports when it's re-exported from another file with multiple re-exports", () => { + const result = augmentDataFetchingFunctions( + ` + export { action, loader, default } from "./action.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionWrapper as _withActionWrapper, withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; + import { action as _action } from "./action.js"; + import { loader as _loader } from "./action.js"; + export const action = _withActionWrapper(_action, "test"); + export const loader = _withLoaderWrapper(_loader, "test"); + export { default } from "./action.js"; +`) + expect(removeWhitespace(result.code)).toStrictEqual(expected) +}) + +it("should transform the re-exports when it's re-exported from another file with multiple re-exports", () => { + const result = augmentDataFetchingFunctions( + ` + export { action, loader, default, blah } from "./action.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionWrapper as _withActionWrapper, withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; + import { action as _action } from "./action.js"; + import { loader as _loader } from "./action.js"; + export const action = _withActionWrapper(_action, "test"); + export const loader = _withLoaderWrapper(_loader, "test"); + export { default, blah } from "./action.js"; +`) + expect(removeWhitespace(result.code)).toStrictEqual(expected) +}) diff --git a/src/vite/utils/data-functions-augment.ts b/src/vite/utils/data-functions-augment.ts index 8a43620b..5f2ffac8 100644 --- a/src/vite/utils/data-functions-augment.ts +++ b/src/vite/utils/data-functions-augment.ts @@ -32,6 +32,8 @@ const transform = (ast: ParseResult, routeId: string) => { return str.charAt(0).toUpperCase() + str.slice(1) } const transformations: Array<() => void> = [] + + const importDeclarations: Babel.ImportDeclaration[] = [] trav(ast, { ExportDeclaration(path) { if (path.isExportNamedDeclaration()) { @@ -86,38 +88,93 @@ const transform = (ast: ParseResult, routeId: string) => { if (!ALL_EXPORTS.includes(name)) { return } + const uid = CLIENT_COMPONENT_EXPORTS.includes(name) ? getClientHocId(path, `with${uppercaseFirstLetter(name)}Wrapper`) : getServerHocId(path, `with${uppercaseFirstLetter(name)}Wrapper`) - transformations.push(() => { - const uniqueName = path.scope.generateUidIdentifier(name).name - path.replaceWith( - t.exportNamedDeclaration( - null, - [t.exportSpecifier(t.identifier(name), t.identifier(uniqueName))], - path.node.source + const binding = path.scope.getBinding(name) + + if (path.node.source) { + // Special condition: export { loader, action } from "./path" + const source = path.node.source.value + + importDeclarations.push( + t.importDeclaration( + [t.importSpecifier(t.identifier(`_${name}`), t.identifier(name))], + t.stringLiteral(source) ) ) + transformations.push(() => { + path.insertBefore( + t.exportNamedDeclaration( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(name), + t.callExpression(uid, [t.identifier(`_${name}`), t.stringLiteral(routeId)]) + ), + ]) + ) + ) + }) - // Insert the wrapped export after the modified export statement - path.insertAfter( - t.exportNamedDeclaration( - t.variableDeclaration("const", [ - t.variableDeclarator( - t.identifier(name), - t.callExpression(uid, [t.identifier(uniqueName), t.stringLiteral(routeId)]) - ), - ]), - [] + // Remove the specifier from the exports and add a manual export + transformations.push(() => { + const remainingSpecifiers = path.node.specifiers.filter( + (exportSpecifier) => !(t.isIdentifier(exportSpecifier.exported) && exportSpecifier.exported.name === name) ) + + path.replaceWith(t.exportNamedDeclaration(null, remainingSpecifiers, path.node.source)) + }) + } else if (binding?.path.isFunctionDeclaration()) { + // Replace the function declaration with a wrapped version + binding.path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(name), + t.callExpression(uid, [toFunctionExpression(binding.path.node), t.stringLiteral(routeId)]) + ), + ]) ) - }) + } else if (binding?.path.isVariableDeclarator()) { + // Wrap the variable declarator's initializer + const init = binding.path.get("init") + if (init.node) { + init.replaceWith(t.callExpression(uid, [init.node, t.stringLiteral(routeId)])) + } + } else { + transformations.push(() => { + const uniqueName = path.scope.generateUidIdentifier(name).name + path.replaceWith( + t.exportNamedDeclaration( + null, + [t.exportSpecifier(t.identifier(name), t.identifier(uniqueName))], + path.node.source + ) + ) + + // Insert the wrapped export after the modified export statement + path.insertAfter( + t.exportNamedDeclaration( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(name), + t.callExpression(uid, [t.identifier(uniqueName), t.stringLiteral(routeId)]) + ), + ]), + [] + ) + ) + }) + } } }, }) for (const transformation of transformations) { transformation() } + if (importDeclarations.length > 0) { + ast.program.body.unshift(...importDeclarations) + } if (serverHocs.length > 0) { ast.program.body.unshift( t.importDeclaration( diff --git a/src/vite/utils/inject-context.test.ts b/src/vite/utils/inject-context.test.ts index 5499a79d..32764cdb 100644 --- a/src/vite/utils/inject-context.test.ts +++ b/src/vite/utils/inject-context.test.ts @@ -3,383 +3,462 @@ import { injectContext } from "./inject-context" const removeWhitespace = (str: string) => str.replace(/\s/g, "") describe("transform", () => { - it("should transform the loader export when it's a function", () => { - const result = injectContext( - ` - export function loader() {} - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - export const loader = _withLoaderContextWrapper(function loader() {}, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + describe("loader transforms", () => { + it("should transform the loader export when it's a function", () => { + const result = injectContext( + ` + export function loader() {} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + export const loader = _withLoaderContextWrapper(function loader() {}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's a const variable", () => { - const result = injectContext( - ` - export const loader = async ({ request }) => { return {};} - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - export const loader = _withLoaderContextWrapper(async ({ request }) => { return {};}, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + it("should transform the loader export when it's a const variable", () => { + const result = injectContext( + ` + export const loader = async ({ request }) => { return {};} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + export const loader = _withLoaderContextWrapper(async ({ request }) => { return {};}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's a let variable", () => { - const result = injectContext( - ` - export let loader = async ({ request }) => { return {};} - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - export let loader = _withLoaderContextWrapper(async ({ request }) => { return {};}, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + it("should transform the loader export when it's a let variable", () => { + const result = injectContext( + ` + export let loader = async ({ request }) => { return {};} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + export let loader = _withLoaderContextWrapper(async ({ request }) => { return {};}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's a var variable", () => { - const result = injectContext( - ` - export var loader = async ({ request }) => { return {};} - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - export var loader = _withLoaderContextWrapper(async ({ request }) => { return {};}, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + it("should transform the loader export when it's a var variable", () => { + const result = injectContext( + ` + export var loader = async ({ request }) => { return {};} + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + export var loader = _withLoaderContextWrapper(async ({ request }) => { return {};}, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the loader export when it's re-exported from another file", () => { - const result = injectContext( - ` - export { loader } from "./loader.js"; - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - export { loader as _loader } from "./loader.js"; - export const loader = _withLoaderContextWrapper(_loader, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + it("should transform the loader export when it's re-exported from another file", () => { + const result = injectContext( + ` + export { loader } from "./loader.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + import { loader as _loader } from "./loader.js"; + export const loader = _withLoaderContextWrapper(_loader, "test"); + export {} from "./loader.js"; + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the loader export when it's imported from another file and exported", () => { - const result = injectContext( - ` - import { loader } from "./loader.js"; - export { loader }; - `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` - import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - import { loader } from "./loader.js"; - export { loader as _loader }; - export const loader = _withLoaderContextWrapper(_loader, "test"); - `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) + it("should wrap the loader export when it's imported from another file and exported", () => { + const result = injectContext( + ` + import { loader } from "./loader.js"; + export { loader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + import { loader } from "./loader.js"; + export { loader as _loader }; + export const loader = _withLoaderContextWrapper(_loader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) }) - - it("should wrap the client loader export when it's a function", () => { - const result = injectContext( - ` + describe("client loader transforms", () => { + it("should wrap the client loader export when it's a function", () => { + const result = injectContext( + ` export function clientLoader() {} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; export const clientLoader = _withClientLoaderContextWrapper(function clientLoader() {}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the client loader export when it's a const variable", () => { - const result = injectContext( - ` + it("should wrap the client loader export when it's a const variable", () => { + const result = injectContext( + ` export const clientLoader = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; export const clientLoader = _withClientLoaderContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the client loader export when it's a let variable", () => { - const result = injectContext( - ` + it("should wrap the client loader export when it's a let variable", () => { + const result = injectContext( + ` export let clientLoader = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; export let clientLoader = _withClientLoaderContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the client loader export when it's a var variable", () => { - const result = injectContext( - ` + it("should wrap the client loader export when it's a var variable", () => { + const result = injectContext( + ` export var clientLoader = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; export var clientLoader = _withClientLoaderContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the client loader export when it's re-exported from another file", () => { - const result = injectContext( - ` + it("should wrap the client loader export when it's re-exported from another file", () => { + const result = injectContext( + ` import { clientLoader } from "./client-loader.js"; export { clientLoader }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; import { clientLoader } from "./client-loader.js"; export { clientLoader as _clientLoader }; export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the client loader export when it's imported from another file and exported", () => { - const result = injectContext( - ` + it("should wrap the client loader export when it's imported from another file and exported", () => { + const result = injectContext( + ` import { clientLoader } from "./client-loader.js"; export { clientLoader }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; import { clientLoader } from "./client-loader.js"; export { clientLoader as _clientLoader }; export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a function", () => { - const result = injectContext( - ` + it("should transform the client loader export when it's re-exported from another file", () => { + const result = injectContext( + ` + export { clientLoader } from "./clientLoader.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; + import { clientLoader as _clientLoader } from "./clientLoader.js"; + export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); + export {} from "./clientLoader.js"; + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + }) + describe("action transforms", () => { + it("should transform the action export when it's a function", () => { + const result = injectContext( + ` export function action() {} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; export const action = _withActionContextWrapper(function action() {}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a const variable", () => { - const result = injectContext( - ` + it("should transform the action export when it's a const variable", () => { + const result = injectContext( + ` export const action = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; export const action = _withActionContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a let variable", () => { - const result = injectContext( - ` + it("should transform the action export when it's a let variable", () => { + const result = injectContext( + ` export let action = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; export let action = _withActionContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's a var variable", () => { - const result = injectContext( - ` + it("should transform the action export when it's a var variable", () => { + const result = injectContext( + ` export var action = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; export var action = _withActionContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the action export when it's re-exported from another file", () => { - const result = injectContext( - ` + it("should transform the action export when it's re-exported from another file", () => { + const result = injectContext( + ` export { action } from "./action.js"; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; - export { action as _action } from "./action.js"; + import { action as _action } from "./action.js"; export const action = _withActionContextWrapper(_action, "test"); + export {} from "./action.js"; `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should wrap the action export when it's imported from another file and exported", () => { - const result = injectContext( - ` + it("should wrap the action export when it's imported from another file and exported", () => { + const result = injectContext( + ` import { action } from "./action.js"; export { action }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; import { action } from "./action.js"; export { action as _action }; export const action = _withActionContextWrapper(_action, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) }) - - it("should transform the client action export when it's a function", () => { - const result = injectContext( - ` + describe("client action transforms", () => { + it("should transform the client action export when it's a function", () => { + const result = injectContext( + ` export function clientAction() {} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; export const clientAction = _withClientActionContextWrapper(function clientAction() {}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's a const variable", () => { - const result = injectContext( - ` + it("should transform the client action export when it's a const variable", () => { + const result = injectContext( + ` export const clientAction = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; export const clientAction = _withClientActionContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's a let variable", () => { - const result = injectContext( - ` + it("should transform the client action export when it's a let variable", () => { + const result = injectContext( + ` export let clientAction = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; export let clientAction = _withClientActionContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's a var variable", () => { - const result = injectContext( - ` + it("should transform the client action export when it's a var variable", () => { + const result = injectContext( + ` export var clientAction = async ({ request }) => { return {};} `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; export var clientAction = _withClientActionContextWrapper(async ({ request }) => { return {};}, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's re-exported from another file", () => { - const result = injectContext( - ` + it("should transform the client action export when it's re-exported from another file", () => { + const result = injectContext( + ` import { clientAction } from "./client-action.js"; export { clientAction }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; import { clientAction } from "./client-action.js"; export { clientAction as _clientAction }; export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) - }) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) - it("should transform the client action export when it's imported from another file and exported", () => { - const result = injectContext( - ` + it("should transform the client action export when it's imported from another file and exported", () => { + const result = injectContext( + ` import { clientAction } from "./client-action.js"; export { clientAction }; `, - "test", - "/file/path" - ) - const expected = removeWhitespace(` + "test", + "/file/path" + ) + const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; import { clientAction } from "./client-action.js"; export { clientAction as _clientAction }; export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); `) - expect(removeWhitespace(result.code)).toStrictEqual(expected) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the clientAction export when it's re-exported from another file", () => { + const result = injectContext( + ` + export { clientAction } from "./clientAction.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; + import { clientAction as _clientAction } from "./clientAction.js"; + export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); + export {} from "./clientAction.js"; + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) }) }) + +it("should transform the re-exports when it's re-exported from another file with multiple re-exports", () => { + const result = injectContext( + ` + export { action, loader, default } from "./action.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionContextWrapper as _withActionContextWrapper, withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + import { action as _action } from "./action.js"; + import { loader as _loader } from "./action.js"; + export const action = _withActionContextWrapper(_action, "test"); + export const loader = _withLoaderContextWrapper(_loader, "test"); + export { default } from "./action.js"; +`) + expect(removeWhitespace(result.code)).toStrictEqual(expected) +}) + +it("should transform the re-exports when it's re-exported from another file with multiple re-exports", () => { + const result = injectContext( + ` + export { action, loader, default, blah } from "./action.js"; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionContextWrapper as _withActionContextWrapper, withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + import { action as _action } from "./action.js"; + import { loader as _loader } from "./action.js"; + export const action = _withActionContextWrapper(_action, "test"); + export const loader = _withLoaderContextWrapper(_loader, "test"); + export { default, blah } from "./action.js"; +`) + expect(removeWhitespace(result.code)).toStrictEqual(expected) +}) diff --git a/src/vite/utils/inject-context.ts b/src/vite/utils/inject-context.ts index 2cff4ec2..7a8a2c43 100644 --- a/src/vite/utils/inject-context.ts +++ b/src/vite/utils/inject-context.ts @@ -23,6 +23,7 @@ const transform = (ast: ParseResult, routeId: string) => { return str.charAt(0).toUpperCase() + str.slice(1) } const transformations: Array<() => void> = [] + const importDeclarations: Babel.ImportDeclaration[] = [] trav(ast, { ExportDeclaration(path) { if (path.isExportNamedDeclaration()) { @@ -73,36 +74,91 @@ const transform = (ast: ParseResult, routeId: string) => { if (!ALL_EXPORTS.includes(name)) { return } + const uid = getHocId(path, `with${uppercaseFirstLetter(name)}ContextWrapper`) - transformations.push(() => { - const uniqueName = path.scope.generateUidIdentifier(name).name - path.replaceWith( - t.exportNamedDeclaration( - null, - [t.exportSpecifier(t.identifier(name), t.identifier(uniqueName))], - path.node.source + const binding = path.scope.getBinding(name) + + if (path.node.source) { + // Special condition: export { loader, action } from "./path" + const source = path.node.source.value + + importDeclarations.push( + t.importDeclaration( + [t.importSpecifier(t.identifier(`_${name}`), t.identifier(name))], + t.stringLiteral(source) ) ) + transformations.push(() => { + path.insertBefore( + t.exportNamedDeclaration( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(name), + t.callExpression(uid, [t.identifier(`_${name}`), t.stringLiteral(routeId)]) + ), + ]) + ) + ) + }) - // Insert the wrapped export after the modified export statement - path.insertAfter( - t.exportNamedDeclaration( - t.variableDeclaration("const", [ - t.variableDeclarator( - t.identifier(name), - t.callExpression(uid, [t.identifier(uniqueName), t.stringLiteral(routeId)]) - ), - ]), - [] + // Remove the specifier from the exports and add a manual export + transformations.push(() => { + const remainingSpecifiers = path.node.specifiers.filter( + (exportSpecifier) => !(t.isIdentifier(exportSpecifier.exported) && exportSpecifier.exported.name === name) ) + + path.replaceWith(t.exportNamedDeclaration(null, remainingSpecifiers, path.node.source)) + }) + } else if (binding?.path.isFunctionDeclaration()) { + // Replace the function declaration with a wrapped version + binding.path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(name), + t.callExpression(uid, [toFunctionExpression(binding.path.node), t.stringLiteral(routeId)]) + ), + ]) ) - }) + } else if (binding?.path.isVariableDeclarator()) { + // Wrap the variable declarator's initializer + const init = binding.path.get("init") + if (init.node) { + init.replaceWith(t.callExpression(uid, [init.node, t.stringLiteral(routeId)])) + } + } else { + transformations.push(() => { + const uniqueName = path.scope.generateUidIdentifier(name).name + path.replaceWith( + t.exportNamedDeclaration( + null, + [t.exportSpecifier(t.identifier(name), t.identifier(uniqueName))], + path.node.source + ) + ) + + // Insert the wrapped export after the modified export statement + path.insertAfter( + t.exportNamedDeclaration( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(name), + t.callExpression(uid, [t.identifier(uniqueName), t.stringLiteral(routeId)]) + ), + ]), + [] + ) + ) + }) + } } }, }) for (const transformation of transformations) { transformation() } + if (importDeclarations.length > 0) { + ast.program.body.unshift(...importDeclarations) + } if (hocs.length > 0) { ast.program.body.unshift( t.importDeclaration( diff --git a/test-apps/react-router-vite/app/routes/exports.tsx b/test-apps/react-router-vite/app/routes/exports.tsx new file mode 100644 index 00000000..c12558cd --- /dev/null +++ b/test-apps/react-router-vite/app/routes/exports.tsx @@ -0,0 +1 @@ +export { loader, default }from "./_index"; diff --git a/test-apps/react-router-vite/app/routes/unexported.tsx b/test-apps/react-router-vite/app/routes/unexported.tsx new file mode 100644 index 00000000..cad5878d --- /dev/null +++ b/test-apps/react-router-vite/app/routes/unexported.tsx @@ -0,0 +1,20 @@ + +function loader() { + return { name: 'React Router' }; +} + +function Home({ loaderData }: Route.ComponentProps) { + return ( +
+

Hello, {loaderData.name}

+ + React Router Docs + +
+ ); +} + +export { loader, Home as default };