UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

380 lines (357 loc) 17.1 kB
import { describe, expect, it } from "vitest"; import { transformClientComponents } from "./transformClientComponents.mjs"; describe("transformClientComponents", () => { async function transform(code) { const result = await transformClientComponents(code, "/test/file.tsx", { environmentName: "worker", }); return result?.code; } it("transforms arrow function component", async () => { expect((await transform(`"use client" export const Component = () => { return jsx('div', { children: 'Hello' }); } `)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Component = registerClientReference(SSRModule, "/test/file.tsx", "Component"); export { Component }; `); }); it("transforms async arrow function component", async () => { expect((await transform(`"use client" export const Component = async () => { return jsx('div', { children: 'Hello' }); } `)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Component = registerClientReference(SSRModule, "/test/file.tsx", "Component"); export { Component }; `); }); it("transforms function declaration component", async () => { expect((await transform(`"use client" export function Component() { return jsx('div', { children: 'Hello' }); }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Component = registerClientReference(SSRModule, "/test/file.tsx", "Component"); export { Component }; `); }); it("transforms default export arrow function component", async () => { expect((await transform(`"use client" export default () => { return jsx('div', { children: 'Hello' }); }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); export default registerClientReference(SSRModule, "/test/file.tsx", "default"); `); }); it("transforms default export function declaration component", async () => { expect((await transform(`"use client" export default function Component({ prop1, prop2 }) { return jsx('div', { children: 'Hello' }); }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); export default registerClientReference(SSRModule, "/test/file.tsx", "default"); `); }); it("transforms mixed export styles (inline, grouped, and default)", async () => { expect((await transform(`"use client" export const First = () => { return jsx('div', { children: 'First' }); } const Second = () => { return jsx('div', { children: 'Second' }); } function Third() { return jsx('div', { children: 'Third' }); } const Fourth = () => { return jsx('div', { children: 'Fourth' }); } export default function Main() { return jsx('div', { children: 'Main' }); } export { Second, Third } export { Fourth as AnotherName }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const First = registerClientReference(SSRModule, "/test/file.tsx", "First"); const Second = registerClientReference(SSRModule, "/test/file.tsx", "Second"); const Third = registerClientReference(SSRModule, "/test/file.tsx", "Third"); const Fourth_AnotherName = registerClientReference(SSRModule, "/test/file.tsx", "AnotherName"); export { First, Second, Third, Fourth_AnotherName as AnotherName }; export default registerClientReference(SSRModule, "/test/file.tsx", "default"); `); }); it("transforms function declaration that is exported default separately", async () => { expect((await transform(` "use client" function Component({ prop1, prop2 }) { return jsx('div', { children: 'Hello' }); } export default Component;`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); export default registerClientReference(SSRModule, "/test/file.tsx", "default"); `); }); it("Works in dev", async () => { expect((await transform(`"use client" import { jsxDEV } from "react/jsx-dev-runtime"; import { sendMessage } from "./functions"; import { useState } from "react"; import { consumeEventStream } from "rwsdk/client"; export function Chat() { const [message, setMessage] = useState(""); const [reply, setReply] = useState(""); const onClick = async () => { setReply(""); (await sendMessage(message)).pipeTo( consumeEventStream({ onChunk: (event) => { setReply( (prev) => prev + (event.data === "[DONE]" ? "" : JSON.parse(event.data).response) ); } }) ); }; return /* @__PURE__ */ jsxDEV("div", { children: [ /* @__PURE__ */ jsxDEV( "input", { type: "text", value: message, placeholder: "Type a message...", onChange: (e) => setMessage(e.target.value), style: { width: "80%", padding: "10px", marginRight: "8px", borderRadius: "4px", border: "1px solid #ccc" } }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 28, columnNumber: 7 }, this ), /* @__PURE__ */ jsxDEV( "button", { onClick, style: { padding: "10px 20px", borderRadius: "4px", border: "none", backgroundColor: "#007bff", color: "white", cursor: "pointer" }, children: "Send" }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 41, columnNumber: 7 }, this ), /* @__PURE__ */ jsxDEV("div", { children: reply }, void 0, false, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 54, columnNumber: 7 }, this) ] }, void 0, true, { fileName: "/Users/justin/rw/sdk/experiments/ai-stream/src/app/pages/Chat/Chat.tsx", lineNumber: 27, columnNumber: 5 }, this); } `)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Chat = registerClientReference(SSRModule, "/test/file.tsx", "Chat"); export { Chat }; `); }); it("Does not transform when 'use client' is not directive", async () => { expect(await transform(`const message = "use client";`)).toEqual(undefined); }); it("properly handles export alias", async () => { expect((await transform(`"use client" const MyComponent = () => { return jsx('div', { children: 'Hello' }); } export { MyComponent as CustomName }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const MyComponent_CustomName = registerClientReference(SSRModule, "/test/file.tsx", "CustomName"); export { MyComponent_CustomName as CustomName }; `); }); it("correctly processes multiple component exports", async () => { expect((await transform(`"use client" const First = () => jsx('div', { children: 'First' }); const Second = () => jsx('div', { children: 'Second' }); const Third = () => jsx('div', { children: 'Third' }); export { First, Second, Third }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const First = registerClientReference(SSRModule, "/test/file.tsx", "First"); const Second = registerClientReference(SSRModule, "/test/file.tsx", "Second"); const Third = registerClientReference(SSRModule, "/test/file.tsx", "Third"); export { First, Second, Third }; `); }); it("handles combination of JSX and non-JSX exports", async () => { expect((await transform(`"use client" const Component = () => jsx('div', {}); const data = { value: 42 }; const helper = () => console.log('helper'); export { Component, data, helper }`)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Component = registerClientReference(SSRModule, "/test/file.tsx", "Component"); const data = registerClientReference(SSRModule, "/test/file.tsx", "data"); const helper = registerClientReference(SSRModule, "/test/file.tsx", "helper"); export { Component, data, helper }; `); }); it("transforms multiple exports aliases for the same component", async () => { expect((await transform(`"use client" export const Slot = () => { return jsx('div', { children: 'Slot' }); } export { Slot, Slot as Root } `)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Slot = registerClientReference(SSRModule, "/test/file.tsx", "Slot"); const Slot_Root = registerClientReference(SSRModule, "/test/file.tsx", "Root"); export { Slot, Slot_Root as Root }; `); }); it("handles a large number of named exports from a single module", async () => { const code = `"use client"; import * as React from "react"; const SidebarContext = React.createContext(null); function SidebarProvider() { return jsx("div", {}); } function useSidebar() {} function Sidebar() { return jsx("div", {}); } function SidebarTrigger() { return jsx("div", {}); } function SidebarRail() { return jsx("div", {}); } function SidebarInset() { return jsx("div", {}); } function SidebarInput() { return jsx("div", {}); } function SidebarHeader() { return jsx("div", {}); } function SidebarFooter() { return jsx("div", {}); } function SidebarSeparator() { return jsx("div", {}); } function SidebarContent() { return jsx("div", {}); } function SidebarGroup() { return jsx("div", {}); } function SidebarGroupLabel() { return jsx("div", {}); } function SidebarGroupAction() { return jsx("div", {}); } function SidebarGroupContent() { return jsx("div", {}); } function SidebarMenu() { return jsx("div", {}); } function SidebarMenuItem() { return jsx("div", {}); } function SidebarMenuButton() { return jsx("div", {}); } function SidebarMenuAction() { return jsx("div", {}); } function SidebarMenuBadge() { return jsx("div", {}); } function SidebarMenuSkeleton() { return jsx("div", {}); } function SidebarMenuSub() { return jsx("div", {}); } function SidebarMenuSubItem() { return jsx("div", {}); } function SidebarMenuSubButton() { return jsx("div", {}); } export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, };`; expect((await transform(code)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/file.tsx"); const Sidebar = registerClientReference(SSRModule, "/test/file.tsx", "Sidebar"); const SidebarContent = registerClientReference(SSRModule, "/test/file.tsx", "SidebarContent"); const SidebarFooter = registerClientReference(SSRModule, "/test/file.tsx", "SidebarFooter"); const SidebarGroup = registerClientReference(SSRModule, "/test/file.tsx", "SidebarGroup"); const SidebarGroupAction = registerClientReference(SSRModule, "/test/file.tsx", "SidebarGroupAction"); const SidebarGroupContent = registerClientReference(SSRModule, "/test/file.tsx", "SidebarGroupContent"); const SidebarGroupLabel = registerClientReference(SSRModule, "/test/file.tsx", "SidebarGroupLabel"); const SidebarHeader = registerClientReference(SSRModule, "/test/file.tsx", "SidebarHeader"); const SidebarInput = registerClientReference(SSRModule, "/test/file.tsx", "SidebarInput"); const SidebarInset = registerClientReference(SSRModule, "/test/file.tsx", "SidebarInset"); const SidebarMenu = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenu"); const SidebarMenuAction = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuAction"); const SidebarMenuBadge = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuBadge"); const SidebarMenuButton = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuButton"); const SidebarMenuItem = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuItem"); const SidebarMenuSkeleton = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuSkeleton"); const SidebarMenuSub = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuSub"); const SidebarMenuSubButton = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuSubButton"); const SidebarMenuSubItem = registerClientReference(SSRModule, "/test/file.tsx", "SidebarMenuSubItem"); const SidebarProvider = registerClientReference(SSRModule, "/test/file.tsx", "SidebarProvider"); const SidebarRail = registerClientReference(SSRModule, "/test/file.tsx", "SidebarRail"); const SidebarSeparator = registerClientReference(SSRModule, "/test/file.tsx", "SidebarSeparator"); const SidebarTrigger = registerClientReference(SSRModule, "/test/file.tsx", "SidebarTrigger"); const useSidebar = registerClientReference(SSRModule, "/test/file.tsx", "useSidebar"); export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar }; `); }); }); describe("transformClientComponents logic branches (from transformClientComponents.mts)", () => { it("returns code as-is if file does not start with 'use client'", async () => { const code = "const foo = 1;"; const result = await transformClientComponents(code, "/test/file.tsx", { environmentName: "worker", }); expect(result).toBeUndefined(); }); it("removes directive but does not transform if not a virtual SSR file", async () => { const code = '"use client"\nexport const foo = 1;'; const result = await transformClientComponents(code, "/test/file.tsx", { environmentName: "ssr", }); expect(result?.code).toEqual("export const foo = 1;"); }); }); describe("transformClientComponents (dev server node_modules)", () => { async function transformDev(code, id) { process.env.VITE_IS_DEV_SERVER = "1"; const result = await transformClientComponents(code, id, { environmentName: "worker", }); delete process.env.VITE_IS_DEV_SERVER; return result?.code; } it("uses barrel file import for node_modules files in dev", async () => { const id = "/test/node_modules/my-lib/component.js"; const code = `"use client"; export const MyComponent = () => {};`; expect((await transformDev(code, id)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/node_modules/my-lib/component.js"); const MyComponent = registerClientReference(SSRModule, "/test/node_modules/my-lib/component.js", "MyComponent"); export { MyComponent }; `); }); it("uses virtual module import for app source files in dev", async () => { const id = "/test/app/component.tsx"; const code = `"use client"; export const MyComponent = () => {};`; expect((await transformDev(code, id)) ?? "").toEqual(`import { ssrLoadModule } from "rwsdk/__ssr_bridge"; import { registerClientReference } from "rwsdk/worker"; const SSRModule = await ssrLoadModule("/test/app/component.tsx"); const MyComponent = registerClientReference(SSRModule, "/test/app/component.tsx", "MyComponent"); export { MyComponent }; `); }); });