├── packages ├── rsc-auth │ ├── readme.md │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── src │ │ ├── components.tsx │ │ └── index.ts │ ├── tsup.config.ts │ ├── LICENSE.react-router.md │ ├── package.json │ └── types.d.ts └── fully-react │ ├── src │ ├── app-router │ │ ├── context.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ ├── navigation-context.tsx │ │ │ └── paths.ts │ │ ├── server │ │ │ ├── index.ts │ │ │ ├── StatusCode.tsx │ │ │ └── AppRouter.tsx │ │ ├── types.ts │ │ ├── client │ │ │ ├── router │ │ │ │ ├── types │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── qss.ts │ │ │ │ │ ├── searchParams.ts │ │ │ │ │ └── routeMatch.ts │ │ │ │ ├── not-found-boundary.tsx │ │ │ │ └── redirect-boundary.tsx │ │ │ └── index.ts │ │ └── fs.ts │ ├── client │ │ ├── index.ts │ │ ├── form │ │ │ ├── index.ts │ │ │ ├── Form.client.tsx │ │ │ ├── Form.tsx │ │ │ └── use-submit.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ ├── use-router.ts │ │ │ ├── context.ts │ │ │ └── router-api.ts │ │ ├── hooks.tsx │ │ ├── dynamic │ │ │ ├── no-ssr-error.ts │ │ │ ├── dynamic-no-ssr.tsx │ │ │ └── loadable.tsx │ │ ├── refresh.ts │ │ ├── error-boundary.tsx │ │ ├── mutation.ts │ │ └── stream.tsx │ ├── root.tsx │ ├── web │ │ ├── router.ts │ │ ├── root.tsx │ │ ├── context.ts │ │ ├── client-router.tsx │ │ ├── webpack.ts │ │ └── server-component.tsx │ ├── winterkit │ │ ├── node.ts │ │ ├── entry │ │ │ ├── entry-vercel.ts │ │ │ └── entry-node.ts │ │ ├── adapter │ │ │ └── cloudflare-workers.ts │ │ └── expose-dev-server.ts │ ├── server │ │ ├── index.ts │ │ ├── dev │ │ │ ├── index.ts │ │ │ ├── react-refresh-script.tsx │ │ │ └── inline-styles.tsx │ │ ├── request.ts │ │ ├── action.tsx │ │ ├── async-context.ts │ │ ├── server-components.tsx │ │ ├── entry.tsx │ │ ├── htmlescape.ts │ │ ├── server-inserted-html.tsx │ │ ├── types.ts │ │ ├── context.ts │ │ ├── findAssetsInManifest.tsx │ │ └── webpack.ts │ ├── entry-server.ts │ ├── logger.ts │ ├── cache │ │ ├── index.ts │ │ ├── rehydrateSymbols.ts │ │ ├── dataProvider.tsx │ │ ├── lazyQueue.tsx │ │ ├── useTransportValue.ts │ │ └── dataTransport.ts │ ├── entry-client.tsx │ ├── dev-server │ │ ├── server-components │ │ │ ├── types.d.ts │ │ │ ├── index.test.ts │ │ │ └── utils.ts │ │ ├── index.ts │ │ └── vite-dev-server.ts │ ├── shared │ │ ├── navigation.ts │ │ ├── not-found.ts │ │ └── redirect.ts │ ├── component-server │ │ ├── stream.tsx │ │ └── render.tsx │ ├── server-root.tsx │ ├── client-root.tsx │ ├── measurer.ts │ ├── index.ts │ ├── fs-router │ │ └── types.ts │ └── build │ │ └── vercel │ │ ├── nft.ts │ │ └── redirects.ts │ ├── types │ └── internal.d.ts │ ├── .eslintrc.cjs │ ├── readme.md │ └── tsconfig.json ├── .codesandbox └── environment.json ├── .npmrc ├── test ├── unit │ └── unit-test.spec.tsx ├── helpers │ ├── index.ts │ ├── create-fixture.ts │ └── build-fixture.ts ├── template │ ├── README.md │ ├── public │ │ └── favicon.ico │ ├── vite.config.ts │ ├── tsconfig.json │ ├── app │ │ └── root.tsx │ └── package.json ├── rendering-test.ts ├── playwright.config.ts ├── vitest.config.ts └── package.json ├── examples ├── nested │ ├── README.md │ ├── .gitignore │ ├── public │ │ └── favicon.ico │ ├── app │ │ ├── [country] │ │ │ ├── [state] │ │ │ │ ├── page.tsx │ │ │ │ └── layout.tsx │ │ │ └── layout.tsx │ │ └── layout.tsx │ ├── vite.config.ts │ ├── tsconfig.json │ ├── components │ │ ├── toggle.tsx │ │ ├── comment.tsx │ │ └── story.tsx │ └── package.json ├── with-prisma-auth │ ├── .gitignore │ ├── README.md │ ├── prisma │ │ ├── dev.db │ │ └── schema.prisma │ ├── public │ │ └── favicon.ico │ ├── vite.config.ts │ ├── app │ │ ├── db.ts │ │ ├── routes │ │ │ └── api │ │ │ │ └── auth │ │ │ │ └── [...auth] │ │ │ │ └── route.ts │ │ ├── api.ts │ │ ├── Button.tsx │ │ ├── auth.ts │ │ └── root.tsx │ ├── tsconfig.json │ └── package.json ├── apple-music │ ├── prisma │ │ ├── dev.db │ │ └── schema.prisma │ ├── public │ │ └── favicon.ico │ ├── app │ │ ├── page.tsx │ │ ├── library │ │ │ └── [library] │ │ │ │ └── page.tsx │ │ ├── layout.css │ │ ├── playlist │ │ │ └── [playlist] │ │ │ │ └── page.tsx │ │ ├── api │ │ │ └── auth │ │ │ │ └── [...auth] │ │ │ │ └── route.ts │ │ ├── actions.ts │ │ ├── browse │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── home.tsx │ │ └── form.tsx │ ├── postcss.config.cjs │ ├── types │ │ └── nav.ts │ ├── lib │ │ ├── utils.ts │ │ ├── db.ts │ │ └── auth.ts │ ├── README.md │ ├── components │ │ ├── ui │ │ │ ├── aspect-ratio.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── label.tsx │ │ │ ├── input.tsx │ │ │ ├── textarea.tsx │ │ │ ├── separator.tsx │ │ │ ├── progress.tsx │ │ │ ├── toaster.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── popover.tsx │ │ │ ├── slider.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── switch.tsx │ │ │ ├── avatar.tsx │ │ │ ├── toggle.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── tabs.tsx │ │ │ ├── accordion.tsx │ │ │ └── button.tsx │ │ ├── layout.tsx │ │ ├── link.tsx │ │ ├── user-avatar.tsx │ │ ├── theme-toggle.tsx │ │ └── site-header.tsx │ ├── vite.config.ts │ ├── tsconfig.json │ ├── config │ │ └── site.ts │ ├── tailwind.config.cjs │ └── LICENSE.md ├── hackernews │ ├── README.md │ ├── .gitignore │ ├── public │ │ └── favicon.ico │ ├── vite.config.ts │ ├── app │ │ ├── types.ts │ │ ├── api.ts │ │ ├── users │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── stories │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── [...stories] │ │ │ └── page.tsx │ ├── tsconfig.json │ ├── components │ │ ├── toggle.tsx │ │ ├── comment.tsx │ │ └── story.tsx │ └── package.json ├── hackernews-client │ ├── README.md │ ├── .gitignore │ ├── public │ │ └── favicon.ico │ ├── app │ │ ├── button.tsx │ │ ├── types.ts │ │ ├── [...stories] │ │ │ └── hooks.tsx │ │ ├── api.ts │ │ ├── users │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── stories │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── QueryCache.tsx │ ├── vite.config.ts │ ├── tsconfig.json │ ├── components │ │ ├── toggle.tsx │ │ ├── comment.tsx │ │ └── story.tsx │ └── package.json └── server-counter-form │ ├── README.md │ ├── .gitignore │ ├── prisma │ ├── dev.db │ └── schema.prisma │ ├── public │ └── favicon.ico │ ├── vite.config.ts │ ├── app │ ├── page.tsx │ ├── db.ts │ ├── api.ts │ ├── counter.tsx │ ├── layout.tsx │ └── useAction.tsx │ ├── tsconfig.json │ └── package.json ├── pnpm-workspace.yaml ├── version ├── .prettierrc ├── LICENSE ├── package.json ├── .github └── workflows │ ├── cq.yml.disabled │ └── ci.yml.disabled └── patches ├── vite@4.3.0.patch └── react-server-dom-webpack@0.0.0-experimental-efb381bbf-20230505.patch /packages/rsc-auth/readme.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /packages/fully-react/src/app-router/context.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.codesandbox/environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodeVersion": "18" 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | side-effects-cache=false 3 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./stream"; 2 | -------------------------------------------------------------------------------- /packages/fully-react/src/root.tsx: -------------------------------------------------------------------------------- 1 | export { default } from "./client-root"; 2 | -------------------------------------------------------------------------------- /packages/fully-react/src/web/router.ts: -------------------------------------------------------------------------------- 1 | export * from "./client-router"; 2 | -------------------------------------------------------------------------------- /test/unit/unit-test.spec.tsx: -------------------------------------------------------------------------------- 1 | import { assert, expect, it, vi } from "vitest"; 2 | -------------------------------------------------------------------------------- /packages/fully-react/src/winterkit/node.ts: -------------------------------------------------------------------------------- 1 | export * from "@hattip/adapter-node"; 2 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/index.ts: -------------------------------------------------------------------------------- 1 | export type { Context as Env } from "./context"; 2 | -------------------------------------------------------------------------------- /packages/fully-react/types/internal.d.ts: -------------------------------------------------------------------------------- 1 | export { RouteManifest } from "../src/fs-router/types"; 2 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/form/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./use-submit"; 2 | export * from "./Form"; 3 | -------------------------------------------------------------------------------- /test/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create-fixture.js"; 2 | export * from "./playwright-fixture.js"; 3 | -------------------------------------------------------------------------------- /test/template/README.md: -------------------------------------------------------------------------------- 1 | # Vite RSC Counter Demo 2 | 3 | ## How to run 4 | 5 | ```bash 6 | pnpm dev 7 | ``` 8 | -------------------------------------------------------------------------------- /examples/nested/README.md: -------------------------------------------------------------------------------- 1 | # Vite RSC Hackernews Demo 2 | 3 | ## How to run 4 | 5 | ```bash 6 | pnpm dev 7 | ``` 8 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | -------------------------------------------------------------------------------- /examples/apple-music/prisma/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/apple-music/prisma/dev.db -------------------------------------------------------------------------------- /examples/hackernews/README.md: -------------------------------------------------------------------------------- 1 | # Vite RSC Hackernews Demo 2 | 3 | ## How to run 4 | 5 | ```bash 6 | pnpm dev 7 | ``` 8 | -------------------------------------------------------------------------------- /examples/nested/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | .vercel 5 | -------------------------------------------------------------------------------- /examples/nested/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/nested/public/favicon.ico -------------------------------------------------------------------------------- /test/template/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/test/template/public/favicon.ico -------------------------------------------------------------------------------- /examples/hackernews-client/README.md: -------------------------------------------------------------------------------- 1 | # Vite RSC Hackernews Demo 2 | 3 | ## How to run 4 | 5 | ```bash 6 | pnpm dev 7 | ``` 8 | -------------------------------------------------------------------------------- /examples/hackernews/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | .vercel 5 | -------------------------------------------------------------------------------- /examples/server-counter-form/README.md: -------------------------------------------------------------------------------- 1 | # Vite RSC Counter Demo 2 | 3 | ## How to run 4 | 5 | ```bash 6 | pnpm dev 7 | ``` 8 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/README.md: -------------------------------------------------------------------------------- 1 | # Vite RSC Counter Demo 2 | 3 | ## How to run 4 | 5 | ```bash 6 | pnpm dev 7 | ``` 8 | -------------------------------------------------------------------------------- /packages/fully-react/src/app-router/router/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./client-router"; 2 | export * from "./server-router"; 3 | -------------------------------------------------------------------------------- /packages/fully-react/src/entry-server.ts: -------------------------------------------------------------------------------- 1 | import { createHandler } from "./server/entry"; 2 | export default createHandler(); 3 | -------------------------------------------------------------------------------- /examples/apple-music/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/apple-music/public/favicon.ico -------------------------------------------------------------------------------- /examples/hackernews-client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | .vercel 5 | -------------------------------------------------------------------------------- /examples/hackernews/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/hackernews/public/favicon.ico -------------------------------------------------------------------------------- /examples/server-counter-form/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | .vercel 5 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/prisma/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/with-prisma-auth/prisma/dev.db -------------------------------------------------------------------------------- /packages/fully-react/src/app-router/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create-router"; 2 | export type { PageProps } from "../types"; 3 | -------------------------------------------------------------------------------- /examples/apple-music/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { PageConfig } from "./page.types"; 2 | 3 | export { ListenNow as default } from "./ListenNow"; 4 | -------------------------------------------------------------------------------- /examples/server-counter-form/prisma/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/server-counter-form/prisma/dev.db -------------------------------------------------------------------------------- /examples/with-prisma-auth/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/with-prisma-auth/public/favicon.ico -------------------------------------------------------------------------------- /examples/apple-music/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/hackernews-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/hackernews-client/public/favicon.ico -------------------------------------------------------------------------------- /packages/fully-react/src/client/router/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./router-api"; 2 | export * from "./context"; 3 | export * from "./use-router"; 4 | -------------------------------------------------------------------------------- /examples/server-counter-form/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nksaraf/fully-react/HEAD/examples/server-counter-form/public/favicon.ico -------------------------------------------------------------------------------- /packages/fully-react/src/logger.ts: -------------------------------------------------------------------------------- 1 | export function debug(...args: any[]) { 2 | // eslint-disable-next-line no-console 3 | // console.log(...args); 4 | } 5 | -------------------------------------------------------------------------------- /examples/apple-music/types/nav.ts: -------------------------------------------------------------------------------- 1 | export interface NavItem { 2 | title: string 3 | href?: string 4 | disabled?: boolean 5 | external?: boolean 6 | } 7 | -------------------------------------------------------------------------------- /packages/fully-react/src/cache/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./rehydration-context"; 2 | export * from "./useTransportValue"; 3 | export * from "./dataProvider"; 4 | -------------------------------------------------------------------------------- /packages/fully-react/src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import { AppRouter } from "./web/router"; 2 | import { mount } from "./web/entry"; 3 | 4 | mount(); 5 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/dev/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./find-styles"; 2 | export * from "./inline-styles"; 3 | export * from "./react-refresh-script"; 4 | -------------------------------------------------------------------------------- /packages/fully-react/src/dev-server/server-components/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "acorn-loose" { 2 | export function parse(source: string, options: any): any; 3 | } 4 | -------------------------------------------------------------------------------- /packages/fully-react/src/shared/navigation.ts: -------------------------------------------------------------------------------- 1 | export * from "./redirect"; 2 | export * from "./not-found"; 3 | // export * from "../server/server-inserted-html"; 4 | -------------------------------------------------------------------------------- /test/template/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "fully-react"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/fully-react/src/dev-server/server-components/index.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("TODO", () => { 4 | expect("TODO").toBe("TODO"); 5 | }); 6 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "fully-react"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }); 7 | -------------------------------------------------------------------------------- /examples/nested/app/[country]/[state]/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page({ params }) { 2 | console.log("/[country]/[state]/page.tsx", params); 3 | return
Hello world
; 4 | } 5 | -------------------------------------------------------------------------------- /examples/nested/app/[country]/[state]/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function Layout({ children, params }) { 2 | console.log("app/[country]/[state]/layout.tsx", params); 3 | return
{children}
; 4 | } 5 | -------------------------------------------------------------------------------- /packages/fully-react/src/winterkit/entry/entry-vercel.ts: -------------------------------------------------------------------------------- 1 | import handler from "virtual:entry-server"; 2 | import { createMiddleware } from "@hattip/adapter-node"; 3 | export default createMiddleware(handler); 4 | -------------------------------------------------------------------------------- /examples/apple-music/app/library/[library]/page.tsx: -------------------------------------------------------------------------------- 1 | import { PageProps } from "./[library].types"; 2 | 3 | export default function Playlist(props: PageProps) { 4 | return
{props.params.library}
; 5 | } 6 | -------------------------------------------------------------------------------- /examples/apple-music/app/layout.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --font-sans: 'Cal Sans', sans-serif; 7 | } 8 | 9 | html { 10 | font-size: 16px; 11 | } -------------------------------------------------------------------------------- /examples/apple-music/app/playlist/[playlist]/page.tsx: -------------------------------------------------------------------------------- 1 | import { PageProps } from "./[playlist].types"; 2 | 3 | export default function Playlist(props: PageProps) { 4 | return
{props.params.playlist}
; 5 | } 6 | -------------------------------------------------------------------------------- /examples/apple-music/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/router/use-router.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { routerContext } from "./context"; 3 | 4 | export function useRouter() { 5 | return useContext(routerContext); 6 | } 7 | -------------------------------------------------------------------------------- /packages/fully-react/src/web/root.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ServerComponent } from "./server-component"; 3 | 4 | export function Root() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /examples/apple-music/README.md: -------------------------------------------------------------------------------- 1 | # Vite Shadcn-ui RSC Example 2 | 3 | Go give love to [Shadcn-UI](https://ui.shadcn.com/). It's awesome and designed to be used with RSCs. 4 | 5 | ## How to run 6 | 7 | ```bash 8 | pnpm dev 9 | ``` -------------------------------------------------------------------------------- /examples/apple-music/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**" 3 | - "examples/**" 4 | - "test/**" 5 | - "apps/**" 6 | - "!**/.tmp/**" 7 | options: 8 | prefer-workspace-packages: true 9 | strict-peer-dependencies: false 10 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/router/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import { RouterAPI } from "./router-api"; 3 | 4 | export const routerContext = /*#__PURE__*/ createContext( 5 | null as any, 6 | ); 7 | -------------------------------------------------------------------------------- /examples/apple-music/lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | var db: PrismaClient; 5 | } 6 | 7 | globalThis.db = globalThis.db || new PrismaClient(); 8 | 9 | export default globalThis.db; 10 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/app/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | var db: PrismaClient; 5 | } 6 | 7 | globalThis.db = globalThis.db || new PrismaClient(); 8 | 9 | export default globalThis.db; 10 | -------------------------------------------------------------------------------- /packages/fully-react/src/app-router/server/StatusCode.tsx: -------------------------------------------------------------------------------- 1 | import { response } from "../../server/request"; 2 | 3 | export function StatusCode(props: { code: number }) { 4 | const res = response(); 5 | res.status = props.code; 6 | return null; 7 | } 8 | -------------------------------------------------------------------------------- /packages/fully-react/src/app-router/types.ts: -------------------------------------------------------------------------------- 1 | export type PageProps = { 2 | params: { [key: string]: string }; 3 | searchParams: { [key: string]: string }; 4 | children?: React.ReactNode; 5 | url: string; 6 | headers: { [key: string]: string }; 7 | }; 8 | -------------------------------------------------------------------------------- /examples/hackernews-client/app/button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | 4 | export function Counter() { 5 | const [count, setCount] = useState(0); 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | export function useRerender() { 4 | const [_, rerender] = useState(() => 0); 5 | return useCallback(() => { 6 | rerender((n) => n + 1); 7 | }, [rerender]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/fully-react/src/component-server/stream.tsx: -------------------------------------------------------------------------------- 1 | export { 2 | renderToReadableStream as renderToServerElementStream, 3 | renderToReadableStream as renderToResultStream, 4 | decodeReply as decodeServerFunctionArgs, 5 | decodeAction, 6 | } from "react-server-dom-webpack/server.edge"; 7 | -------------------------------------------------------------------------------- /packages/fully-react/src/winterkit/adapter/cloudflare-workers.ts: -------------------------------------------------------------------------------- 1 | export { default } from "@hattip/adapter-cloudflare-workers"; 2 | export { default as noStatic } from "@hattip/adapter-cloudflare-workers/no-static"; 3 | export type { CloudflareWorkersPlatformInfo } from "@hattip/adapter-cloudflare-workers"; 4 | -------------------------------------------------------------------------------- /examples/apple-music/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "fully-react"; 3 | import inspect from "@vinxi/vite-plugin-inspect"; 4 | 5 | export default defineConfig({ 6 | plugins: [inspect(), react()], 7 | ssr: { 8 | noExternal: ["@radix-ui/*"], 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/dynamic/no-ssr-error.ts: -------------------------------------------------------------------------------- 1 | // This has to be a shared module which is shared between client component error boundary and dynamic component 2 | 3 | export const NO_SSR_CODE = "NO_SSR"; 4 | 5 | export function isNoSSRError(error: any) { 6 | return error.digest === NO_SSR_CODE; 7 | } 8 | -------------------------------------------------------------------------------- /examples/hackernews/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "fully-react"; 3 | import inspect from "@vinxi/vite-plugin-inspect"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | inspect({ 8 | outDir: ".vite/inspect", 9 | }), 10 | react(), 11 | ], 12 | }); 13 | -------------------------------------------------------------------------------- /examples/nested/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "fully-react"; 3 | import inspect from "@vinxi/vite-plugin-inspect"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | inspect({ 8 | outDir: ".vite/inspect", 9 | }), 10 | react(), 11 | ], 12 | }); 13 | -------------------------------------------------------------------------------- /examples/server-counter-form/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from "vite"; 2 | 3 | import react from "fully-react"; 4 | 5 | process.env = { 6 | ...process.env, 7 | ...loadEnv("development", process.cwd()), 8 | }; 9 | 10 | export default defineConfig({ 11 | plugins: [react()], 12 | }); 13 | -------------------------------------------------------------------------------- /examples/apple-music/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import { SiteHeader } from "@/components/site-header" 2 | 3 | interface LayoutProps { 4 | children: React.ReactNode 5 | } 6 | 7 | export function Layout({ children }: LayoutProps) { 8 | return ( 9 | <> 10 | 11 |
{children}
12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /examples/server-counter-form/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { getCount, increment } from "./api"; 2 | import { Counter } from "./counter"; 3 | 4 | export default async function Root() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/refresh.ts: -------------------------------------------------------------------------------- 1 | import { createElementFromServer } from "./stream"; 2 | 3 | export const refresh = 4 | typeof window === "undefined" 5 | ? () => {} 6 | : function refresh() { 7 | const element = createElementFromServer(); 8 | globalThis.mutate(element); 9 | }; 10 | 11 | export default refresh; 12 | -------------------------------------------------------------------------------- /examples/nested/app/[country]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { A } from "fully-react/link"; 2 | 3 | export default function Layout({ children, params }) { 4 | console.log("app/[country]/layout.tsx", params); 5 | return ( 6 |
7 | {children} 8 | Florida 9 | Punjab 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/fully-react/src/server-root.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter } from "./app-router/server"; 2 | import { ServerContext } from "./server/ServerContext"; 3 | 4 | function createServerRouter(context: ServerContext) { 5 | const Router = createRouter(context.pageRoutes()); 6 | return Router; 7 | } 8 | 9 | export default createServerRouter(context); 10 | -------------------------------------------------------------------------------- /packages/rsc-auth/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | require("@cyco130/eslint-config/patch"); 2 | 3 | module.exports = { 4 | root: true, 5 | extends: ["@cyco130/eslint-config/node"], 6 | parserOptions: { tsconfigRootDir: __dirname }, 7 | settings: { 8 | "import/resolver": { 9 | typescript: { 10 | project: [__dirname + "/tsconfig.json"], 11 | }, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/fully-react/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | require("@cyco130/eslint-config/patch"); 2 | 3 | module.exports = { 4 | root: true, 5 | extends: ["@cyco130/eslint-config/node"], 6 | parserOptions: { tsconfigRootDir: __dirname }, 7 | settings: { 8 | "import/resolver": { 9 | typescript: { 10 | project: [__dirname + "/tsconfig.json"], 11 | }, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/rsc-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "types": ["react/experimental", "vite/client"], 9 | "skipLibCheck": true, 10 | "moduleResolution": "Node", 11 | "resolveJsonModule": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/hackernews-client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "fully-react"; 3 | import inspect from "@vinxi/vite-plugin-inspect"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | inspect({ 8 | outDir: ".vite/inspect", 9 | }), 10 | react({ 11 | router: { 12 | mode: "client", 13 | }, 14 | }), 15 | ], 16 | }); 17 | -------------------------------------------------------------------------------- /packages/fully-react/src/client-root.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { createRouter } from "./app-router/client/router/app-router-client"; 3 | import { ServerContext } from "./server/ServerContext"; 4 | 5 | function createClientRouter(context: ServerContext) { 6 | const Router = createRouter(context.pageRoutes()); 7 | return Router; 8 | } 9 | 10 | export default createClientRouter(context); 11 | -------------------------------------------------------------------------------- /examples/apple-music/app/api/auth/[...auth]/route.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from "@/lib/auth"; 2 | import { handleAuthRequest } from "rsc-auth"; 3 | 4 | export async function GET(request: Request) { 5 | return await handleAuthRequest(request, "/api/auth", authOptions); 6 | } 7 | 8 | export async function POST(request: Request) { 9 | return await handleAuthRequest(request, "/api/auth", authOptions); 10 | } 11 | -------------------------------------------------------------------------------- /examples/apple-music/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /examples/hackernews/app/types.ts: -------------------------------------------------------------------------------- 1 | export interface IComment { 2 | user: string; 3 | time_ago: string; 4 | content: string; 5 | comments: IComment[]; 6 | } 7 | 8 | export interface IStory { 9 | id: string; 10 | points: string; 11 | url: string; 12 | title: string; 13 | domain: string; 14 | type: string; 15 | time_ago: string; 16 | user: string; 17 | comments_count: number; 18 | comments: IComment[]; 19 | } 20 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/app/routes/api/auth/[...auth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handleAuthRequest } from "rsc-auth"; 2 | import { authOptions } from "~/auth"; 3 | 4 | export async function GET(request: Request) { 5 | return await handleAuthRequest(request, "/api/auth", authOptions); 6 | } 7 | 8 | export async function POST(request: Request) { 9 | return await handleAuthRequest(request, "/api/auth", authOptions); 10 | } 11 | -------------------------------------------------------------------------------- /examples/hackernews-client/app/types.ts: -------------------------------------------------------------------------------- 1 | export interface IComment { 2 | user: string; 3 | time_ago: string; 4 | content: string; 5 | comments: IComment[]; 6 | } 7 | 8 | export interface IStory { 9 | id: string; 10 | points: string; 11 | url: string; 12 | title: string; 13 | domain: string; 14 | type: string; 15 | time_ago: string; 16 | user: string; 17 | comments_count: number; 18 | comments: IComment[]; 19 | } 20 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/app/api.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { getSession } from "rsc-auth"; 4 | import { request } from "stream-react/request"; 5 | import { authOptions } from "./auth"; 6 | 7 | export async function sayHello() { 8 | let user = await getSession(request(), authOptions); 9 | if (!user) { 10 | throw new Error("You must be logged in to increment the counter."); 11 | } 12 | 13 | return "Hello"; 14 | } 15 | -------------------------------------------------------------------------------- /test/template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | "paths": { 14 | "~/*": ["app/*"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | "paths": { 14 | "~/*": ["app/*"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/server-counter-form/app/db.ts: -------------------------------------------------------------------------------- 1 | import { Connection, connect } from "@planetscale/database"; 2 | 3 | declare global { 4 | var db: Connection; 5 | } 6 | 7 | globalThis.db = 8 | globalThis.db || 9 | connect({ 10 | url: import.meta.env.VITE_DATABASE_URL, 11 | }); 12 | 13 | export default globalThis.db; 14 | 15 | export function execute(query: string) { 16 | return globalThis.db.execute(query) as unknown as Promise<{ rows: T[] }>; 17 | } 18 | -------------------------------------------------------------------------------- /packages/fully-react/src/app-router/client/router/types/index.ts: -------------------------------------------------------------------------------- 1 | export { default as invariant } from "tiny-invariant"; 2 | export { default as warning } from "tiny-warning"; 3 | export * from "./history"; 4 | export * from "./link"; 5 | export * from "./path"; 6 | export * from "./qss"; 7 | export * from "./route"; 8 | export * from "./routeInfo"; 9 | export * from "./routeMatch"; 10 | export * from "./router"; 11 | export * from "./searchParams"; 12 | export * from "./utils"; 13 | -------------------------------------------------------------------------------- /examples/apple-music/app/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import db from "@/lib/db"; 4 | 5 | export async function increment() { 6 | let count = await getCount(); 7 | await db.counter.update({ 8 | where: { id: 1 }, 9 | data: { value: count + 1 }, 10 | }); 11 | } 12 | 13 | export async function getCount() { 14 | return ( 15 | ( 16 | await db.counter.findFirst({ 17 | where: { id: 1 }, 18 | select: { value: true }, 19 | }) 20 | )?.value ?? 0 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/server-counter-form/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 7 | } 8 | 9 | datasource db { 10 | provider = "mysql" 11 | url = env("DATABASE_URL") 12 | } 13 | 14 | model Counter { 15 | id Int @id @default(autoincrement()) 16 | value Int @default(0) 17 | } 18 | -------------------------------------------------------------------------------- /examples/server-counter-form/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | "paths": { 14 | "~/*": ["app/*"] 15 | }, 16 | "rootDirs": [".", ".vite"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/request.ts: -------------------------------------------------------------------------------- 1 | export function request() { 2 | return globalThis.requestAsyncContext.getStore()!.request; 3 | } 4 | 5 | export function headers() { 6 | return globalThis.requestAsyncContext.getStore()!.request.headers; 7 | } 8 | 9 | export function response() { 10 | return globalThis.requestAsyncContext.getStore()!.internal.response; 11 | } 12 | 13 | export function measurer() { 14 | return globalThis.requestAsyncContext.getStore()!.internal.measurer; 15 | } 16 | -------------------------------------------------------------------------------- /packages/fully-react/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Router 4 | 5 | `router.push` 6 | `router.replace` 7 | `router.refresh` 8 | `router.forward` 9 | `router.back` 10 | 11 | ## Environments 12 | 13 | 14 | RouterContext 15 | - 16 | 17 | ### Vite dev server 18 | connect handler 19 | 20 | 21 | 22 | ### App server dev 23 | - Client Router mode: 24 | - 25 | 26 | ### App server prod 27 | 28 | 29 | ### React server dev 30 | 31 | 32 | ### React server prod 33 | 34 | ### Client dev 35 | 36 | ### Client prod -------------------------------------------------------------------------------- /packages/fully-react/src/client/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { useErrorBoundary } from "react-error-boundary"; 5 | 6 | export { ErrorBoundary } from "react-error-boundary"; 7 | 8 | export function ResetButton({ 9 | children, 10 | ...props 11 | }: React.ComponentProps<"button">) { 12 | const errorBoundary = useErrorBoundary(); 13 | return ( 14 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/form/Form.client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSubmitForm } from "../form"; 4 | import React from "react"; 5 | 6 | export function ClientForm({ ...props }: React.ComponentProps<"form">) { 7 | const submitForm = useSubmitForm(); 8 | return ( 9 |
{ 13 | e.preventDefault(); 14 | submitForm(new FormData(e.currentTarget)); 15 | }} 16 | > 17 | {props.children} 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/nested/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | 14 | "paths": { 15 | "~/*": ["app/*"] 16 | }, 17 | "rootDirs": [".", ".vite"] 18 | }, 19 | "exclude": ["node_modules", ".vite"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/hackernews/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | 14 | "paths": { 15 | "~/*": ["app/*"] 16 | }, 17 | "rootDirs": [".", ".vite"] 18 | }, 19 | "exclude": ["node_modules", ".vite"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/fully-react/src/dev-server/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteManifest } from "../server/context"; 2 | import type { ViteDevServer } from "vite"; 3 | 4 | const viteDevServer = ( 5 | process.env.NODE_ENV === "production" 6 | ? new Proxy({} as ViteDevServer, { 7 | get() { 8 | throw new Error("Vite dev server is not available in production"); 9 | }, 10 | }) 11 | : globalThis.__vite_dev_server__ 12 | ) as ViteDevServer & { rscWorker: any; routesManifest: RouteManifest }; 13 | 14 | export default viteDevServer; 15 | -------------------------------------------------------------------------------- /examples/hackernews-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | 14 | "paths": { 15 | "~/*": ["app/*"] 16 | }, 17 | "rootDirs": [".", ".vite"] 18 | }, 19 | "exclude": ["node_modules", ".vite"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/apple-music/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "Node", 10 | "types": ["vite/client", "react/experimental"], 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | "paths": { 14 | "~/*": ["app/*"], 15 | "@/*": ["./*"] 16 | }, 17 | "rootDirs": [".", ".vite"] 18 | }, 19 | "exclude": ["node_modules", ".vite"] 20 | } 21 | -------------------------------------------------------------------------------- /test/template/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { Assets } from "stream-react/assets"; 2 | import "./root.css"; 3 | 4 | export default function Root() { 5 | return ( 6 | 7 | 8 | RSC Playground 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Hello World
16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PACKAGES="vite-rsc fully-react @vite-rsc/*" 3 | 4 | WHITE='\033[1;37m' 5 | GREEN='\033[0;32m' 6 | BLUE='\033[1;34m' 7 | NC='\033[0m' 8 | 9 | echo -e ${WHITE}Updating package versions${NC} 10 | pnpm -r --filter=./packages/* exec -- bash -c "echo -e ${BLUE@Q}\$PNPM_PACKAGE_NAME${NC@Q}@${GREEN@Q}\`npm version --allow-same-version --no-git-tag-version $@\`${NC@Q}" 11 | echo -e ${WHITE}Updating dependency versions in examples${NC} 12 | pnpm -r --filter=./examples/* update --workspace --save-workspace-protocol=false $PACKAGES 13 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/dynamic/dynamic-no-ssr.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { NO_SSR_CODE as NO_SSR_CODE } from "./no-ssr-error"; 5 | 6 | export function suspense() { 7 | const error = new Error(NO_SSR_CODE); 8 | (error as any).digest = NO_SSR_CODE; 9 | throw error; 10 | } 11 | 12 | type Child = React.ReactElement; 13 | 14 | export function NoSSR({ children }: { children: Child }): Child { 15 | if (typeof window === "undefined") { 16 | suspense(); 17 | } 18 | 19 | return children; 20 | } 21 | -------------------------------------------------------------------------------- /examples/server-counter-form/app/api.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { execute } from "./db"; 4 | import { redirect } from "fully-react/navigation"; 5 | 6 | export async function increment() { 7 | await db.execute("UPDATE Counter SET value = value + 1"); 8 | redirect("/"); 9 | } 10 | 11 | export async function getCount() { 12 | try { 13 | let result = await execute<{ value: number }>( 14 | "SELECT value from Counter LIMIT 1", 15 | ); 16 | return result.rows[0].value ?? 0; 17 | } catch (e) { 18 | console.log(e); 19 | throw e; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/server-counter-form/app/counter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useAction } from "./useAction"; 3 | 4 | export function Counter({ 5 | count, 6 | increment, 7 | }: { 8 | count: number; 9 | increment: () => Promise; 10 | }) { 11 | const [optimisticCount, CounterForm] = useAction( 12 | increment, 13 | count, 14 | (prev) => prev + 1, 15 | ); 16 | return ( 17 | 18 |
{optimisticCount}
19 | 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /examples/nested/components/toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | 4 | export default function Toggle(props: { children: any }) { 5 | const [open, setOpen] = useState(true); 6 | 7 | return ( 8 | <> 9 | 14 |
    18 | {props.children} 19 |
20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/hackernews/components/toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | 4 | export default function Toggle(props: { children: any }) { 5 | const [open, setOpen] = useState(true); 6 | 7 | return ( 8 | <> 9 | 14 |
    18 | {props.children} 19 |
20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/server-counter-form/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./layout.css"; 2 | import { Assets } from "fully-react/assets"; 3 | import { LayoutProps } from "./layout.types"; 4 | 5 | export default function Root({ children }: LayoutProps) { 6 | return ( 7 | 8 | 9 | RSC Playground 10 | 11 | 12 | 13 | 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/action.tsx: -------------------------------------------------------------------------------- 1 | import type { Context } from "./context"; 2 | import { renderToResultStream } from "../component-server/stream"; 3 | 4 | export async function createActionResponse( 5 | action: any, 6 | args: any, 7 | renderOptions: Context, 8 | responseInit: ResponseInit = {}, 9 | ) { 10 | return new Response( 11 | renderToResultStream(await action(...args), renderOptions.clientModuleMap), 12 | { 13 | ...responseInit, 14 | headers: { 15 | "Content-Type": "application/json", 16 | ...(responseInit.headers ?? {}), 17 | }, 18 | }, 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/hackernews-client/components/toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | 4 | export default function Toggle(props: { children: any }) { 5 | const [open, setOpen] = useState(true); 6 | 7 | return ( 8 | <> 9 | 14 |
    18 | {props.children} 19 |
20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/rsc-auth/src/components.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { signIn, signOut } from "./client"; 5 | 6 | export function SignInButton({ 7 | children, 8 | ...props 9 | }: React.ComponentProps<"button">) { 10 | return ( 11 | 14 | ); 15 | } 16 | 17 | export function SignOutButton({ 18 | children, 19 | ...props 20 | }: React.ComponentProps<"button">) { 21 | return ( 22 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/async-context.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "node:async_hooks"; 2 | import { Measurer } from "../measurer"; 3 | 4 | interface RequestAsyncContext { 5 | internal: { 6 | response: ResponseInit; 7 | measurer?: Measurer; 8 | }; 9 | request: Request; 10 | } 11 | 12 | declare global { 13 | export var requestAsyncContext: AsyncLocalStorage; 14 | } 15 | 16 | export const requestAsyncContext = 17 | globalThis.requestAsyncContext ?? 18 | new AsyncLocalStorage(); 19 | 20 | globalThis.requestAsyncContext = requestAsyncContext; 21 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/server-components.tsx: -------------------------------------------------------------------------------- 1 | import type { Context } from "./context"; 2 | import { renderServerComponent } from "../component-server/render"; 3 | 4 | export async function createServerComponentResponse( 5 | component: string, 6 | props: any, 7 | env: Context, 8 | responseInit: ResponseInit = {}, 9 | ) { 10 | const serverElement = await renderServerComponent(component, props, env); 11 | 12 | return new Response(serverElement, { 13 | ...responseInit, 14 | headers: { 15 | "Content-Type": "text/x-component", 16 | ...(responseInit.headers ?? {}), 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/fully-react/src/web/context.ts: -------------------------------------------------------------------------------- 1 | import routeManifest from "react:route-manifest"; 2 | import { createNestedPageRoutes } from "../fs-router/nested"; 3 | import { lazy } from "react"; 4 | 5 | export class ClientContext { 6 | routeManifest = routeManifest; 7 | 8 | pageRoutes() { 9 | return createNestedPageRoutes( 10 | this, 11 | "root", 12 | undefined, 13 | import.meta.env.ROUTER_MODE, 14 | ); 15 | } 16 | 17 | lazyComponent(id: string) { 18 | const importPath = `/@fs${id}`; 19 | const importer = () => import(/* @vite-ignore */ importPath); 20 | return lazy(importer); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/rendering-test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "@playwright/test"; 2 | import { createFixture, js, selectHtml, prettyHtml } from "./helpers/index.js"; 3 | 4 | test.describe("rendering", () => { 5 | let fixture = createFixture(test, { 6 | files: {}, 7 | }); 8 | 9 | test("server renders matching routes", async () => { 10 | let res = await fixture.requestDocument("/"); 11 | expect(res.status).toBe(200); 12 | expect(res.headers.get("Content-Type")).toBe("text/html"); 13 | expect(selectHtml(await res.text(), "#root")).toBe( 14 | prettyHtml(`
Hello World
`), 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/entry.tsx: -------------------------------------------------------------------------------- 1 | import type { Context } from "./context"; 2 | import { createRequestRouter } from "./handler"; 3 | import { DevServerContext, ProdServerContext } from "./ServerContext"; 4 | 5 | const Context = import.meta.env.PROD ? ProdServerContext : DevServerContext; 6 | 7 | type Handler = ({ 8 | request, 9 | }: { 10 | request: Request; 11 | }) => Promise | Response; 12 | 13 | export function createHandler() { 14 | const context = new Context(); 15 | context.setupWebpackEnv(); 16 | globalThis.context = context; 17 | return createRequestRouter(context).buildHandler() as Handler; 18 | } 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "useTabs": true, 4 | "plugins": ["@trivago/prettier-plugin-sort-imports"], 5 | "importOrder": [ 6 | "^(fully-react/(.*)$)|^(fully-react$)", 7 | "", 8 | "^node:(.*)$", 9 | "^[./]" 10 | ], 11 | "importOrderSeparation": true, 12 | "importOrderSortSpecifiers": true, 13 | "overrides": [ 14 | { 15 | "files": "**/*.md", 16 | "options": { 17 | "tabWidth": 2, 18 | "useTabs": false 19 | } 20 | }, 21 | { 22 | "files": "**/package.json", 23 | "options": { 24 | "tabWidth": 2, 25 | "useTabs": false 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/app/Button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "stream-react/router"; 4 | import { useMutation } from "stream-react/mutation"; 5 | 6 | export default function Button({ onClick }: { onClick: () => Promise }) { 7 | const mutate = useMutation(); 8 | const router = useRouter(); 9 | return ( 10 |
11 | 18 | 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/fully-react/src/web/client-router.tsx: -------------------------------------------------------------------------------- 1 | import FSRouter from "../app-router/fs"; 2 | import { createRouter } from "../app-router/client/router/app-router-client"; 3 | import routeManifest from "react:route-manifest"; 4 | import { ClientContext } from "./context"; 5 | 6 | const context = new ClientContext(); 7 | const Router = createRouter(context.pageRoutes()); 8 | 9 | const url = new URL(window.location.href); 10 | export function AppRouter() { 11 | return ( 12 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/router/router-api.ts: -------------------------------------------------------------------------------- 1 | import type { History } from "history"; 2 | import type { Thenable, ReactElement, JSXElementConstructor } from "react"; 3 | 4 | export interface RouterAPI { 5 | push: (path: string, state?: any) => void; 6 | replace: (path: string, state?: any) => void; 7 | history: History; 8 | mutate: (fn: any) => void; 9 | refresh: () => void; 10 | preload: ( 11 | url: string, 12 | ) => 13 | | Thenable>> 14 | | Promise; 15 | // cache: Map>; 16 | url: string; 17 | enable(): void; 18 | disable(): void; 19 | } 20 | -------------------------------------------------------------------------------- /packages/fully-react/src/measurer.ts: -------------------------------------------------------------------------------- 1 | export class Measurer { 2 | #measures = new Set<{ 3 | name: string; 4 | duration: number; 5 | }>(); 6 | 7 | async time(name: string, fn: () => Promise): Promise { 8 | const start = Date.now(); 9 | try { 10 | return await fn(); 11 | } finally { 12 | const duration = Date.now() - start; 13 | this.#measures.add({ name, duration }); 14 | } 15 | } 16 | 17 | async toHeaders(headers = new Headers()) { 18 | for (const { name, duration } of this.#measures) { 19 | headers.append("Server-Timing", `${name};dur=${duration}`); 20 | } 21 | return headers; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/fully-react/src/cache/rehydrateSymbols.ts: -------------------------------------------------------------------------------- 1 | import { SuperJSONResult } from "superjson/dist/types"; 2 | import type { RehydrationCache, ResultsCache } from "../server/types"; 3 | import type { DataTransport } from "./dataTransport"; 4 | 5 | declare global { 6 | interface Window { 7 | [RehydrationCacheSymbol]?: RehydrationCache; 8 | [ResultCacheSymbol]?: ResultsCache; 9 | [SSRDataTransport]?: DataTransport; 10 | } 11 | } 12 | export const RehydrationCacheSymbol = Symbol.for("RehydrationCache"); 13 | export const ResultCacheSymbol = Symbol.for("ResultCache"); 14 | export const SSRDataTransport = Symbol.for("SSRDataTransport"); 15 | -------------------------------------------------------------------------------- /examples/apple-music/lib/auth.ts: -------------------------------------------------------------------------------- 1 | import db from "./db"; 2 | import { PrismaAdapter } from "@next-auth/prisma-adapter"; 3 | import Github, { GitHubProfile } from "@auth/core/providers/github"; 4 | import { AuthConfig } from "@auth/core"; 5 | import { Profile } from "@auth/core/types"; 6 | import { Provider } from "@auth/core/providers"; 7 | import { Adapter } from "@auth/core/adapters"; 8 | 9 | export let authOptions: AuthConfig = { 10 | adapter: PrismaAdapter(db) as Adapter, 11 | providers: [ 12 | Github({ 13 | clientId: process.env.GITHUB_CLIENT_ID!, 14 | clientSecret: process.env.GITHUB_CLIENT_SECRET!, 15 | }) as Provider, 16 | ], 17 | secret: "secret", 18 | }; 19 | -------------------------------------------------------------------------------- /examples/with-prisma-auth/app/auth.ts: -------------------------------------------------------------------------------- 1 | import db from "./db"; 2 | import { PrismaAdapter } from "@next-auth/prisma-adapter"; 3 | import Github, { GitHubProfile } from "@auth/core/providers/github"; 4 | import { AuthConfig } from "@auth/core"; 5 | import { Profile } from "@auth/core/types"; 6 | import { Provider } from "@auth/core/providers"; 7 | import { Adapter } from "@auth/core/adapters"; 8 | 9 | export let authOptions: AuthConfig = { 10 | adapter: PrismaAdapter(db) as Adapter, 11 | providers: [ 12 | Github({ 13 | clientId: process.env.GITHUB_CLIENT_ID!, 14 | clientSecret: process.env.GITHUB_CLIENT_SECRET!, 15 | }) as Provider, 16 | ], 17 | secret: "secret", 18 | }; 19 | -------------------------------------------------------------------------------- /examples/apple-music/app/browse/page.tsx: -------------------------------------------------------------------------------- 1 | import { getCount, increment } from "../actions"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Form } from "fully-react/form"; 5 | import FormDemo from "../form"; 6 | import { Link } from "@/components/link"; 7 | 8 | export default async function Browse() { 9 | return ( 10 |
11 |
12 |
{await getCount()}
13 | 14 |
15 | 16 | 17 | 18 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/fully-react/src/client/form/Form.tsx: -------------------------------------------------------------------------------- 1 | import { ClientForm } from "./Form.client"; 2 | import React from "react"; 3 | 4 | export function FormAction({ 5 | action, 6 | }: { 7 | action: ((formData: FormData) => void) & { $$id?: string }; 8 | }) { 9 | return ( 10 | 11 | ); 12 | } 13 | 14 | export function Form({ 15 | action, 16 | ...props 17 | }: Omit, "action"> & { 18 | action: (formData: FormData) => void; 19 | }) { 20 | return ( 21 | 22 | 23 | {props.children} 24 | 25 | ); 26 | } 27 | 28 | Form.Action = FormAction; 29 | -------------------------------------------------------------------------------- /examples/apple-music/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Label = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Label.displayName = LabelPrimitive.Root.displayName 22 | 23 | export { Label } 24 | -------------------------------------------------------------------------------- /packages/rsc-auth/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig([ 4 | // { 5 | // entry: ["./src"], 6 | // format: ["esm"], 7 | // external: [ 8 | // "virtual:vite-dev-server", 9 | // "react", 10 | // "react-server-dom-webpack", 11 | // "react-dom", 12 | // ], 13 | // dts: true, 14 | // }, 15 | { 16 | entry: ["./src"], 17 | format: ["esm"], 18 | esbuildOptions: (options) => { 19 | options.splitting = false; 20 | }, 21 | external: [ 22 | "~/root?rsc", 23 | "virtual:vite-dev-server", 24 | "react", 25 | "react-server-dom-webpack", 26 | "react-dom", 27 | ], 28 | platform: "node", 29 | target: "node14", 30 | dts: true, 31 | }, 32 | ]); 33 | -------------------------------------------------------------------------------- /packages/fully-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "ESNext", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "types": ["vite/client", "node", "react/experimental"], 11 | "lib": ["es2021", "dom", "DOM.Iterable", "ES2022"], 12 | "skipLibCheck": true, 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "jsx": "react-jsx", 16 | "declaration": true, 17 | "emitDeclarationOnly": true, 18 | "paths": { 19 | "types": ["./types/internal.d.ts"] 20 | } 21 | }, 22 | "include": ["src/**/*", "types/**/*"], 23 | "exclude": ["node_modules", "build", "dist"] 24 | } 25 | -------------------------------------------------------------------------------- /examples/hackernews-client/app/[...stories]/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { useTransportValue } from "fully-react/cache"; 2 | import { useContext } from "react"; 3 | import { 4 | QueryOptions, 5 | dehydrate, 6 | useQuery as useBaseQuery, 7 | useQueryClient, 8 | } from "@tanstack/react-query"; 9 | import { CacheContext } from "fully-react/cache"; 10 | 11 | export function useQuery(options: QueryOptions) { 12 | const dataCache = useContext(CacheContext); 13 | const queryClient = useQueryClient(); 14 | let query = useBaseQuery(options); 15 | 16 | if (query.status === "success" && typeof window == "undefined") { 17 | dataCache!.write(dehydrate(queryClient)); 18 | } 19 | 20 | useTransportValue({ 21 | status: query.status, 22 | }); 23 | return query; 24 | } 25 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/dev/react-refresh-script.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const ReactRefreshScript = import.meta.env.DEV 4 | ? function ReactRefreshScript() { 5 | if (!import.meta.env.DEV) { 6 | return null; 7 | } 8 | 9 | return ( 10 | 23 | ); 24 | } 25 | : () => null; 26 | -------------------------------------------------------------------------------- /examples/nested/components/comment.tsx: -------------------------------------------------------------------------------- 1 | import { A } from "fully-react/link"; 2 | import { IComment } from "~/types"; 3 | import Toggle from "./toggle"; 4 | 5 | function Comment(props: { comment: IComment }) { 6 | return ( 7 |
  • 8 |
    9 | {props.comment.user}{" "} 10 | {props.comment.time_ago} ago 11 |
    12 |
    16 | {props.comment.comments.length ? ( 17 | 18 | {props.comment.comments.map((comment) => ( 19 | 20 | ))} 21 | 22 | ) : null} 23 |
  • 24 | ); 25 | } 26 | 27 | export default Comment; 28 | -------------------------------------------------------------------------------- /test/playwright.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import type { PlaywrightTestConfig } from "@playwright/test"; 3 | import { devices } from "@playwright/test"; 4 | 5 | const config: PlaywrightTestConfig = { 6 | testDir: ".", 7 | testMatch: ["**/*-test.ts"], 8 | timeout: 300_000, 9 | expect: { 10 | timeout: 5_000, 11 | }, 12 | forbidOnly: !!process.env.CI, 13 | retries: process.env.CI ? 2 : 0, 14 | workers: process.env.CI ? 3 : undefined, 15 | reporter: process.env.CI 16 | ? "github" 17 | : [["html", { open: process.env.TEST_REPORT ? "always" : "none" }]], 18 | use: { actionTimeout: 0 }, 19 | projects: [ 20 | { 21 | name: "chromium", 22 | use: { 23 | ...devices["Desktop Chrome"], 24 | }, 25 | }, 26 | ], 27 | }; 28 | 29 | export default config; 30 | -------------------------------------------------------------------------------- /examples/hackernews/components/comment.tsx: -------------------------------------------------------------------------------- 1 | import { A } from "fully-react/link"; 2 | import { IComment } from "~/types"; 3 | import Toggle from "./toggle"; 4 | 5 | function Comment(props: { comment: IComment }) { 6 | return ( 7 |
  • 8 |
    9 | {props.comment.user}{" "} 10 | {props.comment.time_ago} ago 11 |
    12 |
    16 | {props.comment.comments.length ? ( 17 | 18 | {props.comment.comments.map((comment) => ( 19 | 20 | ))} 21 | 22 | ) : null} 23 |
  • 24 | ); 25 | } 26 | 27 | export default Comment; 28 | -------------------------------------------------------------------------------- /examples/hackernews-client/components/comment.tsx: -------------------------------------------------------------------------------- 1 | import { A } from "fully-react/link"; 2 | import { IComment } from "~/types"; 3 | import Toggle from "./toggle"; 4 | 5 | function Comment(props: { comment: IComment }) { 6 | return ( 7 |
  • 8 |
    9 | {props.comment.user}{" "} 10 | {props.comment.time_ago} ago 11 |
    12 |
    16 | {props.comment.comments.length ? ( 17 | 18 | {props.comment.comments.map((comment) => ( 19 | 20 | ))} 21 | 22 | ) : null} 23 |
  • 24 | ); 25 | } 26 | 27 | export default Comment; 28 | -------------------------------------------------------------------------------- /test/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-counter", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build:server": "vite build --ssr", 8 | "build:client": "vite build", 9 | "build": "npm run build:server && npm run build:client", 10 | "start": "node dist/server/index.js" 11 | }, 12 | "dependencies": { 13 | "react": "0.0.0-experimental-efb381bbf-20230505", 14 | "react-dom": "0.0.0-experimental-efb381bbf-20230505", 15 | "react-server-dom-webpack": "0.0.0-experimental-efb381bbf-20230505", 16 | "fully-react": "^0.0.2" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^18.0.33", 20 | "@types/react-dom": "^18.0.11", 21 | "typescript": "5.1.0-beta", 22 | "vite": "^4.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/apple-music/config/site.ts: -------------------------------------------------------------------------------- 1 | import { NavItem } from "@/types/nav"; 2 | 3 | interface SiteConfig { 4 | name: string; 5 | description: string; 6 | mainNav: NavItem[]; 7 | links: { 8 | twitter: string; 9 | github: string; 10 | docs: string; 11 | }; 12 | } 13 | 14 | export const siteConfig: SiteConfig = { 15 | name: "Vite React Server", 16 | description: 17 | "Beautifully designed components built with Radix UI and Tailwind CSS rendered with React Server Components and Vite.", 18 | mainNav: [ 19 | { 20 | title: "Home", 21 | href: "/", 22 | }, 23 | { 24 | title: "Apple Music", 25 | href: "/demo", 26 | }, 27 | ], 28 | links: { 29 | twitter: "https://twitter.com/shadcn", 30 | github: "https://github.com/shadcn/ui", 31 | docs: "https://ui.shadcn.com", 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/fully-react/src/server/dev/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { collectStyles } from "./find-styles"; 3 | 4 | export const InlineStyles: React.FC<{ entries?: string[] }> = (import.meta.env 5 | .DEV 6 | ? async function InlineStyles(props: { entries?: string[] }) { 7 | const { default: devServer } = await import("../../dev-server"); 8 | const styles = await collectStyles( 9 | devServer, 10 | props.entries ?? [import.meta.env.APP], 11 | ); 12 | return ( 13 | <> 14 | {Object.entries(styles ?? {}).map(([url, css]) => ( 15 |