diff --git a/package.json b/package.json index 3b08b04c..b694a71d 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": "5.0.5", + "version": "5.0.6", "license": "MIT", "keywords": [ "react-router", diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index bc95dc60..57a865ab 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -251,8 +251,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( process.rdt_port = server.config.server.port ?? 5173 port = process.rdt_port }) - //@ts-ignore - vite 5/6 compat - const channel = server.hot.channels.find((channel) => channel.name === "ws") ?? server.environments?.client.hot + const channel = server.hot const editor = args?.editor ?? DEFAULT_EDITOR_CONFIG const openInEditor = async (path: string | undefined, lineNum: string | undefined) => { if (!path) { @@ -268,9 +267,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( } if (routine === "request-event") { unusedEvents.set(parsedData.id + parsedData.startTime, parsedData) - for (const client of server.hot.channels) { - client.send("request-event", JSON.stringify(parsedData)) - } + server.hot.send("request-event", JSON.stringify(parsedData)) return } @@ -291,9 +288,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( routeInfo.set(id, { loader: [], action: [data] }) } } - for (const client of server.hot.channels) { - client.send("route-info", JSON.stringify({ type, data })) - } + server.hot.send("route-info", JSON.stringify({ type, data })) }) ) diff --git a/src/vite/utils/data-functions-augment.test.ts b/src/vite/utils/data-functions-augment.test.ts index abfc258d..fa9ac43a 100644 --- a/src/vite/utils/data-functions-augment.test.ts +++ b/src/vite/utils/data-functions-augment.test.ts @@ -92,8 +92,30 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; - import { loader } from "./loader.js"; - export { loader as _loader }; + import { loader as _loader } from "./loader.js"; + export const loader = _withLoaderWrapper(_loader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the loader export when it's imported from another file and exported and used", () => { + const result = augmentDataFetchingFunctions( + ` + import { loader } from "./loader.js"; + const test = () => { + const data = loader(); + } + export { loader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderWrapper as _withLoaderWrapper } from "react-router-devtools/server"; + import { loader as _loader } from "./loader.js"; + const test = () => { + const data = _loader(); + }; export const loader = _withLoaderWrapper(_loader, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -217,8 +239,30 @@ describe("transform", () => { ) 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 "./client-loader.js"; + export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's re-exported from another file and used by other code", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientLoader } from "./client-loader.js"; + const test = () => { + const data = clientLoader(); + } + export { clientLoader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + import { clientLoader as _clientLoader } from "./client-loader.js"; + const test = () => { + const data = _clientLoader(); + }; export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -235,8 +279,30 @@ describe("transform", () => { ) 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 "./client-loader.js"; + 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 and used by other code", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientLoader } from "./client-loader.js"; + const test = () => { + const data = clientLoader(); + } + export { clientLoader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderWrapper as _withClientLoaderWrapper } from "react-router-devtools/client"; + import { clientLoader as _clientLoader } from "./client-loader.js"; + const test = () => { + const data = _clientLoader(); + }; export const clientLoader = _withClientLoaderWrapper(_clientLoader, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -373,8 +439,30 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; - import { action } from "./action.js"; - export { action as _action }; + import { action as _action } from "./action.js"; + export const action = _withActionWrapper(_action, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the action export when it's imported from another file and exported and used by other code", () => { + const result = augmentDataFetchingFunctions( + ` + import { action } from "./action.js"; + const test = () => { + const data = action(); + } + export { action }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionWrapper as _withActionWrapper } from "react-router-devtools/server"; + import { action as _action } from "./action.js"; + const test = () => { + const data = _action(); + }; export const action = _withActionWrapper(_action, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -478,8 +566,31 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; - import { clientAction } from "./client-action.js"; - export { clientAction as _clientAction }; + import { clientAction as _clientAction } from "./client-action.js"; + export const clientAction = _withClientActionWrapper(_clientAction, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the client action export when it's re-exported from another file and keep it working if used somewhere", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientAction } from "./client-action.js"; + + const test = () => { + const data = clientAction(); + } + export { clientAction }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { clientAction as _clientAction } from "./client-action.js"; + const test = () => { + const data = _clientAction(); + }; export const clientAction = _withClientActionWrapper(_clientAction, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -512,13 +623,77 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; - import { clientAction } from "./client-action.js"; - export { clientAction as _clientAction }; + import { clientAction as _clientAction } from "./client-action.js"; export const clientAction = _withClientActionWrapper(_clientAction, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) }) + it("should transform the client action export when it's imported from another file and exported and used by other code", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientAction } from "./client-action.js"; + const test = () => { + const data = clientAction(); + } + export { clientAction }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { clientAction as _clientAction } from "./client-action.js"; + const test = () => { + const data = _clientAction(); + }; + export const clientAction = _withClientActionWrapper(_clientAction, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the client action export when it's imported from another file and exported and keep the export around if more things are exported", () => { + const result = augmentDataFetchingFunctions( + ` + import { clientAction } from "./client-action.js"; + const test = () => { + const data = clientAction(); + } + export { clientAction, test }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { clientAction as _clientAction } from "./client-action.js"; + const test = () => { + const data = _clientAction(); + }; + export { test }; + export const clientAction = _withClientActionWrapper(_clientAction, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + it("should transform the client action export when it's imported from another file and exported and keep the export around if more things are exported", () => { + const result = augmentDataFetchingFunctions( + ` + import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; + import { clientAction as _clientAction } from "./client-action.js"; + export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; + import { clientAction as _clientAction } from "./client-action.js"; + export const clientAction = _withClientActionWrapper(_withClientActionContextWrapper(_clientAction, "test"), "test"); + `) + 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( ` diff --git a/src/vite/utils/data-functions-augment.ts b/src/vite/utils/data-functions-augment.ts index 86f5d5ed..40184e2e 100644 --- a/src/vite/utils/data-functions-augment.ts +++ b/src/vite/utils/data-functions-augment.ts @@ -10,6 +10,8 @@ const ALL_EXPORTS = [...SERVER_COMPONENT_EXPORTS, ...CLIENT_COMPONENT_EXPORTS] const transform = (ast: ParseResult, routeId: string) => { const serverHocs: Array<[string, Babel.Identifier]> = [] const clientHocs: Array<[string, Babel.Identifier]> = [] + const imports: Array<[string, Babel.Identifier]> = [] + function getServerHocId(path: NodePath, hocName: string) { const uid = path.scope.generateUidIdentifier(hocName) const hasHoc = serverHocs.find(([name]) => name === hocName) @@ -35,6 +37,26 @@ const transform = (ast: ParseResult, routeId: string) => { const importDeclarations: Babel.ImportDeclaration[] = [] trav(ast, { + ImportDeclaration(path) { + const specifiers = path.node.specifiers + for (const specifier of specifiers) { + if (!t.isImportSpecifier(specifier) || !t.isIdentifier(specifier.imported)) { + continue + } + const name = specifier.imported.name + if (!ALL_EXPORTS.includes(name)) { + continue + } + const isReimported = specifier.local.name !== name + const uniqueName = isReimported ? specifier.local : path.scope.generateUidIdentifier(name) + imports.push([name, uniqueName]) + specifier.local = uniqueName + // Replace the import specifier with a new one + if (!isReimported) { + path.scope.rename(name, uniqueName.name) + } + } + }, ExportDeclaration(path) { if (path.isExportNamedDeclaration()) { const decl = path.get("declaration") @@ -148,14 +170,13 @@ const transform = (ast: ParseResult, routeId: string) => { } } 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 - ) + const existingImport = imports.find(([existingName]) => existingName === name) + const uniqueName = existingImport?.[1].name ?? path.scope.generateUidIdentifier(name).name + + const remainingSpecifiers = path.node.specifiers.filter( + (exportSpecifier) => !(t.isIdentifier(exportSpecifier.exported) && exportSpecifier.exported.name === name) ) + path.replaceWith(t.exportNamedDeclaration(null, remainingSpecifiers, path.node.source)) // Insert the wrapped export after the modified export statement path.insertAfter( @@ -169,6 +190,10 @@ const transform = (ast: ParseResult, routeId: string) => { [] ) ) + const newRemainingSpecifiers = path.node.specifiers.length + if (newRemainingSpecifiers === 0) { + path.remove() + } }) } } diff --git a/src/vite/utils/inject-context.test.ts b/src/vite/utils/inject-context.test.ts index 3bb6fe13..c663ea3b 100644 --- a/src/vite/utils/inject-context.test.ts +++ b/src/vite/utils/inject-context.test.ts @@ -91,8 +91,54 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; - import { loader } from "./loader.js"; - export { loader as _loader }; + import { loader as _loader } from "./loader.js"; + 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 and used by other code", () => { + const result = injectContext( + ` + import { loader } from "./loader.js"; + const test = () => { + return loader(); + } + export { loader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + import { loader as _loader } from "./loader.js"; + const test = () => { + return _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 and used by other code and doesn't remove export if multiple exports", () => { + const result = injectContext( + ` + import { loader } from "./loader.js"; + const test = () => { + return loader(); + } + export { loader, test }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withLoaderContextWrapper as _withLoaderContextWrapper } from "react-router-devtools/context"; + import { loader as _loader } from "./loader.js"; + const test = () => { + return _loader(); + }; + export { test }; export const loader = _withLoaderContextWrapper(_loader, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -170,8 +216,7 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; - import { clientLoader } from "./client-loader.js"; - export { clientLoader as _clientLoader }; + import { clientLoader as _clientLoader } from "./client-loader.js"; export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -188,8 +233,54 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; - import { clientLoader } from "./client-loader.js"; - export { clientLoader as _clientLoader }; + import { clientLoader as _clientLoader } from "./client-loader.js"; + export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's imported from another file and exported and used by other code", () => { + const result = injectContext( + ` + import { clientLoader } from "./client-loader.js"; + const test = () => { + return clientLoader(); + } + export { clientLoader }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; + import { clientLoader as _clientLoader } from "./client-loader.js"; + const test = () => { + return _clientLoader(); + }; + export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the client loader export when it's imported from another file and exported and used by other code and doesn't remove multiple export", () => { + const result = injectContext( + ` + import { clientLoader } from "./client-loader.js"; + const test = () => { + return clientLoader(); + } + export { clientLoader, test }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientLoaderContextWrapper as _withClientLoaderContextWrapper } from "react-router-devtools/context"; + import { clientLoader as _clientLoader } from "./client-loader.js"; + const test = () => { + return _clientLoader(); + }; + export { test }; export const clientLoader = _withClientLoaderContextWrapper(_clientLoader, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -299,8 +390,54 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; - import { action } from "./action.js"; - export { action as _action }; + import { action as _action } from "./action.js"; + export const action = _withActionContextWrapper(_action, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the action export when it's imported from another file and exported and used by other code", () => { + const result = injectContext( + ` + import { action } from "./action.js"; + const test = () => { + return action(); + } + export { action }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; + import { action as _action } from "./action.js"; + const test = () => { + return _action(); + }; + export const action = _withActionContextWrapper(_action, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should wrap the action export when it's imported from another file and exported and used by other code and doesn't remove multiple export", () => { + const result = injectContext( + ` + import { action } from "./action.js"; + const test = () => { + return action(); + } + export { action, test }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withActionContextWrapper as _withActionContextWrapper } from "react-router-devtools/context"; + import { action as _action } from "./action.js"; + const test = () => { + return _action(); + }; + export { test }; export const action = _withActionContextWrapper(_action, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -378,8 +515,54 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; - import { clientAction } from "./client-action.js"; - export { clientAction as _clientAction }; + import { clientAction as _clientAction } from "./client-action.js"; + export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the client action export when it's re-exported from another file and used by other code", () => { + const result = injectContext( + ` + import { clientAction } from "./client-action.js"; + const test = () => { + return clientAction(); + } + export { clientAction }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; + import { clientAction as _clientAction } from "./client-action.js"; + const test = () => { + return _clientAction(); + }; + export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) + + it("should transform the client action export when it's re-exported from another file and used by other code and doesn't remove multiple export", () => { + const result = injectContext( + ` + import { clientAction } from "./client-action.js"; + const test = () => { + return clientAction(); + } + export { clientAction, test }; + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; + import { clientAction as _clientAction } from "./client-action.js"; + const test = () => { + return _clientAction(); + }; + export { test }; export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) @@ -396,12 +579,30 @@ describe("transform", () => { ) const expected = removeWhitespace(` import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; - import { clientAction } from "./client-action.js"; - export { clientAction as _clientAction }; + import { clientAction as _clientAction } from "./client-action.js"; export const clientAction = _withClientActionContextWrapper(_clientAction, "test"); `) expect(removeWhitespace(result.code)).toStrictEqual(expected) }) + it("should transform the client action export when it's imported from another file and exported and already transformed", () => { + const result = injectContext( + ` + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { clientAction as _clientAction } from "./client-action.js"; + + export const clientAction = _withClientActionWrapper(_clientAction, "test"); + `, + "test", + "/file/path" + ) + const expected = removeWhitespace(` + import { withClientActionContextWrapper as _withClientActionContextWrapper } from "react-router-devtools/context"; + import { withClientActionWrapper as _withClientActionWrapper } from "react-router-devtools/client"; + import { clientAction as _clientAction } from "./client-action.js"; + export const clientAction = _withClientActionContextWrapper(_withClientActionWrapper(_clientAction, "test"),"test"); + `) + expect(removeWhitespace(result.code)).toStrictEqual(expected) + }) it("should transform the clientAction export when it's re-exported from another file and remove empty export declaration", () => { const result = injectContext( diff --git a/src/vite/utils/inject-context.ts b/src/vite/utils/inject-context.ts index d3762e95..5079cfd0 100644 --- a/src/vite/utils/inject-context.ts +++ b/src/vite/utils/inject-context.ts @@ -9,6 +9,8 @@ const ALL_EXPORTS = [...SERVER_COMPONENT_EXPORTS, ...CLIENT_COMPONENT_EXPORTS] const transform = (ast: ParseResult, routeId: string) => { const hocs: Array<[string, Babel.Identifier]> = [] + const imports: Array<[string, Babel.Identifier]> = [] + function getHocId(path: NodePath, hocName: string) { const uid = path.scope.generateUidIdentifier(hocName) const hasHoc = hocs.find(([name]) => name === hocName) @@ -25,6 +27,26 @@ const transform = (ast: ParseResult, routeId: string) => { const transformations: Array<() => void> = [] const importDeclarations: Babel.ImportDeclaration[] = [] trav(ast, { + ImportDeclaration(path) { + const specifiers = path.node.specifiers + for (const specifier of specifiers) { + if (!t.isImportSpecifier(specifier) || !t.isIdentifier(specifier.imported)) { + continue + } + const name = specifier.imported.name + if (!ALL_EXPORTS.includes(name)) { + continue + } + const isReimported = specifier.local.name !== name + const uniqueName = isReimported ? specifier.local : path.scope.generateUidIdentifier(name) + imports.push([name, uniqueName]) + specifier.local = uniqueName + // Replace the import specifier with a new one + if (!isReimported) { + path.scope.rename(name, uniqueName.name) + } + } + }, ExportDeclaration(path) { if (path.isExportNamedDeclaration()) { const decl = path.get("declaration") @@ -45,7 +67,7 @@ const transform = (ast: ParseResult, routeId: string) => { return } - + // not this if (decl.isFunctionDeclaration()) { const { id } = decl.node if (!id) return @@ -81,21 +103,16 @@ const transform = (ast: ParseResult, routeId: string) => { if (path.node.source) { // Special condition: export { loader, action } from "./path" const source = path.node.source.value - + const unique = path.scope.generateUidIdentifier(name) importDeclarations.push( - t.importDeclaration( - [t.importSpecifier(t.identifier(`_${name}`), t.identifier(name))], - t.stringLiteral(source) - ) + t.importDeclaration([t.importSpecifier(unique, 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)]) - ), + t.variableDeclarator(t.identifier(name), t.callExpression(uid, [unique, t.stringLiteral(routeId)])), ]) ) ) @@ -131,15 +148,14 @@ const transform = (ast: ParseResult, routeId: string) => { init.replaceWith(t.callExpression(uid, [init.node, t.stringLiteral(routeId)])) } } else { + // not this 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 existingImport = imports.find(([existingName]) => existingName === name) + const uniqueName = existingImport?.[1].name ?? path.scope.generateUidIdentifier(name).name + const remainingSpecifiers = path.node.specifiers.filter( + (exportSpecifier) => !(t.isIdentifier(exportSpecifier.exported) && exportSpecifier.exported.name === name) ) + path.replaceWith(t.exportNamedDeclaration(null, remainingSpecifiers, path.node.source)) // Insert the wrapped export after the modified export statement path.insertAfter( @@ -153,6 +169,11 @@ const transform = (ast: ParseResult, routeId: string) => { [] ) ) + + const newRemainingSpecifiers = path.node.specifiers.length + if (newRemainingSpecifiers === 0) { + path.remove() + } }) } } diff --git a/test-apps/react-router-vite/app/actions/home.ts b/test-apps/react-router-vite/app/actions/home.ts new file mode 100644 index 00000000..557f8f5e --- /dev/null +++ b/test-apps/react-router-vite/app/actions/home.ts @@ -0,0 +1,5 @@ +export function loader() { + return { + message: "Hello, world!", + }; +} \ No newline at end of file diff --git a/test-apps/react-router-vite/app/routes/home.tsx b/test-apps/react-router-vite/app/routes/home.tsx new file mode 100644 index 00000000..a6eee7c8 --- /dev/null +++ b/test-apps/react-router-vite/app/routes/home.tsx @@ -0,0 +1,18 @@ + +import { useLoaderData } from "react-router"; +import { loader } from "../actions/home"; +export { loader }; + +export function meta( ) { + return [ + { title: "New React Router App" }, + { name: "description", content: "Welcome to React Router!" }, + ]; +} + + + +export default function Home() { + const { message } = useLoaderData(); + return
; +} \ No newline at end of file