├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
20 | ))}
21 | >
22 | );
23 | }
24 | : () => null) as unknown as React.FC<{ entries?: string[] }>;
25 |
--------------------------------------------------------------------------------
/examples/hackernews/app/api.ts:
--------------------------------------------------------------------------------
1 | const story = (path: string) => `https://node-hnapi.herokuapp.com/${path}`;
2 | const user = (path: string) =>
3 | `https://hacker-news.firebaseio.com/v0/${path}.json`;
4 |
5 | export default async function fetchAPI(path: string): Promise {
6 | const url = path.startsWith("user") ? user(path) : story(path);
7 | const headers: Record = { "User-Agent": "chrome" };
8 |
9 | try {
10 | let response = await fetch(url, { headers });
11 | let text = await response.text();
12 | try {
13 | if (text === null) {
14 | throw { error: "Not found" };
15 | }
16 | return JSON.parse(text);
17 | } catch (e) {
18 | console.error(`Recevied from API: ${text}`);
19 | console.error(e);
20 | throw { error: e };
21 | }
22 | } catch (error) {
23 | throw { error };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/apple-music/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { LayoutConfig, LayoutProps } from "./layout.types";
2 | import "cal-sans";
3 | import "./layout.css";
4 |
5 | import { AppleMusicDemo } from "./apple-music-demo";
6 | import { Assets } from "fully-react/assets";
7 |
8 | export default async function Root({ children }: LayoutProps) {
9 | return (
10 |
11 |
12 | RSC Playground
13 |
14 |
15 |
16 |
17 |
18 |
19 | {children}
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/packages/fully-react/src/dev-server/server-components/utils.ts:
--------------------------------------------------------------------------------
1 | export function hasRscQuery(id: string) {
2 | const query = splitQuery(id)[1];
3 | return query.match(/(^|&)rsc($|&|=)/);
4 | }
5 |
6 | export function addRscQuery(id: string) {
7 | if (id.includes("?")) {
8 | return id + "&rsc";
9 | } else {
10 | return id + "?rsc";
11 | }
12 | }
13 |
14 | export function removeRscQuery(id: string) {
15 | const [base, query] = splitQuery(id);
16 | if (!query) return id;
17 |
18 | const newQuery = query
19 | .split("&")
20 | .filter((part) => !part.match(/rsc($|=)/))
21 | .join("&");
22 |
23 | if (!newQuery) return base;
24 |
25 | return base + "?" + newQuery;
26 | }
27 |
28 | export function splitQuery(id: string) {
29 | const index = id.indexOf("?");
30 | if (index === -1) return [id, ""];
31 | return [id.slice(0, index), id.slice(index + 1)];
32 | }
33 |
--------------------------------------------------------------------------------
/examples/hackernews-client/app/api.ts:
--------------------------------------------------------------------------------
1 | const story = (path: string) => `https://node-hnapi.herokuapp.com/${path}`;
2 | const user = (path: string) =>
3 | `https://hacker-news.firebaseio.com/v0/${path}.json`;
4 |
5 | export default async function fetchAPI(path: string): Promise {
6 | const url = path.startsWith("user") ? user(path) : story(path);
7 | console.log(`Fetching ${url}`);
8 | const headers: Record = { "User-Agent": "chrome" };
9 |
10 | try {
11 | let response = await fetch(url, { headers });
12 | let text = await response.text();
13 | try {
14 | if (text === null) {
15 | throw { error: "Not found" };
16 | }
17 | return JSON.parse(text);
18 | } catch (e) {
19 | console.error(`Recevied from API: ${text}`);
20 | console.error(e);
21 | throw { error: e };
22 | }
23 | } catch (error) {
24 | throw { error };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/helpers/create-fixture.ts:
--------------------------------------------------------------------------------
1 | import { test } from "@playwright/test";
2 | import type { Fixture, FixtureInit } from "./create-server.js";
3 | import { createServer } from "./create-server.js";
4 |
5 | export { js } from "./create-server.js";
6 | export function createFixture(t: typeof test, init: FixtureInit) {
7 | let fixture: Fixture;
8 | test.beforeAll(async () => {
9 | fixture = await createServer({
10 | files: {},
11 | });
12 |
13 | // appFixture = await fixture.createServer();
14 | });
15 |
16 | test.afterAll(async () => {
17 | // await appFixture.close();
18 | });
19 |
20 | let logs: string[] = [];
21 |
22 | test.beforeEach(({ page }) => {
23 | page.on("console", (msg) => {
24 | logs.push(msg.text());
25 | });
26 | });
27 |
28 | return new Proxy(
29 | {},
30 | {
31 | get(target, prop) {
32 | return fixture[prop];
33 | },
34 | },
35 | ) as Fixture;
36 | }
37 |
--------------------------------------------------------------------------------
/examples/apple-music/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Input.displayName = "Input"
23 |
24 | export { Input }
25 |
--------------------------------------------------------------------------------
/packages/fully-react/src/web/webpack.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | var moduleCache: Map;
3 | function __webpack_chunk_load__(chunk: string): Promise;
4 | function __webpack_require__(id: string): any;
5 |
6 | // type client
7 | var manifest: { root: string; client: { [key: string]: string } };
8 | }
9 |
10 | export function setupWebpackEnv(
11 | load: (chunk: string) => Promise = async (chunk) =>
12 | await import(/* @vite-ignore */ chunk),
13 | ) {
14 | const cache = new Map();
15 |
16 | (globalThis as any).__webpack_chunk_load__ = async (chunk: string) => {
17 | console.log("Loading chunk", chunk);
18 | cache.set(chunk, await load(chunk));
19 | };
20 |
21 | (globalThis as any).__webpack_require__ = (id: string) => {
22 | console.log("Requiring chunk", id);
23 | if (!cache.has(id)) throw new Error(`Module ${id} not found`);
24 | return cache.get(id);
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/packages/fully-react/src/cache/dataProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import * as React from "react";
3 | import {
4 | CacheContext,
5 | DataCache,
6 | RehydrationContextProvider,
7 | } from "./rehydration-context";
8 |
9 | export const DataCacheSingleton = Symbol.for("dataCacheSingleton");
10 | const SuspenseCacheSingleton = Symbol.for("ApolloSuspenseCacheSingleton");
11 |
12 | declare global {
13 | interface Window {
14 | [DataCacheSingleton]?: DataCache;
15 | // [SuspenseCacheSingleton]?: SuspenseCache;
16 | }
17 | }
18 |
19 | export function globalCache() {
20 | return window[DataCacheSingleton];
21 | }
22 | export const DataProvider = ({
23 | cache,
24 | children,
25 | }: React.PropsWithChildren<{
26 | cache: DataCache;
27 | }>) => {
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/examples/apple-music/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/examples/apple-music/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/packages/fully-react/src/shared/not-found.ts:
--------------------------------------------------------------------------------
1 | const NOT_FOUND_ERROR_CODE = "REACT_NOT_FOUND";
2 |
3 | type NotFoundError = Error & { digest: typeof NOT_FOUND_ERROR_CODE };
4 |
5 | /**
6 | * When used in a React server component, this will set the status code to 404.
7 | * When used in a custom app route it will just send a 404 status.
8 | */
9 | export function notFound(): never {
10 | // eslint-disable-next-line no-throw-literal
11 | const error = new Error(NOT_FOUND_ERROR_CODE);
12 | (error as NotFoundError).digest = NOT_FOUND_ERROR_CODE;
13 | throw error;
14 | }
15 |
16 | /**
17 | * Checks an error to determine if it's an error generated by the `notFound()`
18 | * helper.
19 | *
20 | * @param error the error that may reference a not found error
21 | * @returns true if the error is a not found error
22 | */
23 | export function isNotFoundError(error: any): error is NotFoundError {
24 | return error?.digest === NOT_FOUND_ERROR_CODE;
25 | }
26 |
--------------------------------------------------------------------------------
/examples/nested/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-server-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 | "postinstall": "prisma generate"
12 | },
13 | "dependencies": {
14 | "@prisma/client": "^4.12.0",
15 | "@vinxi/vite-plugin-inspect": "^0.6.27",
16 | "react": "0.0.0-experimental-efb381bbf-20230505",
17 | "react-dom": "0.0.0-experimental-efb381bbf-20230505",
18 | "react-server-dom-webpack": "0.0.0-experimental-efb381bbf-20230505"
19 | },
20 | "devDependencies": {
21 | "@types/react": "^18.0.33",
22 | "@types/react-dom": "^18.0.11",
23 | "fully-react": "^0.0.2",
24 | "prisma": "^4.12.0",
25 | "typescript": "5.1.0-beta",
26 | "vite": "^4.3"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/fully-react/src/client/dynamic/loadable.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, lazy } from "react";
2 | import { NoSSR } from "./dynamic-no-ssr";
3 |
4 | function Loadable(options: any) {
5 | const opts = Object.assign(
6 | {
7 | loader: null,
8 | loading: null,
9 | ssr: true,
10 | },
11 | options,
12 | );
13 |
14 | opts.lazy = lazy(opts.loader);
15 |
16 | function LoadableComponent(props: any) {
17 | const Loading = opts.loading;
18 | const fallbackElement = (
19 |
20 | );
21 |
22 | const Wrap = opts.ssr ? Fragment : NoSSR;
23 | const Lazy = opts.lazy;
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | LoadableComponent.displayName = "LoadableComponent";
35 |
36 | return LoadableComponent;
37 | }
38 |
39 | export default Loadable;
40 |
--------------------------------------------------------------------------------
/examples/hackernews/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-server-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 | "postinstall": "prisma generate"
12 | },
13 | "dependencies": {
14 | "@prisma/client": "^4.12.0",
15 | "@vinxi/vite-plugin-inspect": "^0.6.27",
16 | "react": "0.0.0-experimental-efb381bbf-20230505",
17 | "react-dom": "0.0.0-experimental-efb381bbf-20230505",
18 | "react-server-dom-webpack": "0.0.0-experimental-efb381bbf-20230505"
19 | },
20 | "devDependencies": {
21 | "@types/react": "^18.0.33",
22 | "@types/react-dom": "^18.0.11",
23 | "fully-react": "^0.0.2",
24 | "prisma": "^4.12.0",
25 | "typescript": "5.1.0-beta",
26 | "vite": "^4.3"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/apple-music/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/examples/apple-music/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/hooks/use-toast"
4 |
5 | import {
6 | Toast,
7 | ToastClose,
8 | ToastDescription,
9 | ToastProvider,
10 | ToastTitle,
11 | ToastViewport,
12 | } from "@/components/ui/toast"
13 |
14 | export function Toaster() {
15 | const { toasts } = useToast()
16 |
17 | return (
18 |
19 | {toasts.map(function ({ id, title, description, action, ...props }) {
20 | return (
21 |
22 |
23 | {title && {title}}
24 | {description && (
25 | {description}
26 | )}
27 |
28 | {action}
29 |
30 |
31 | )
32 | })}
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/packages/fully-react/src/app-router/fs.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from "react";
2 | import { createNestedPageRoutes } from "../fs-router/nested";
3 | import { createRouter as createPageRouter } from "./server/create-router";
4 | import routesManifest from "../conte";
5 | import viteDevServer from "../dev-server";
6 | import type { Context } from "../server/context";
7 |
8 | const isServer = typeof window === "undefined";
9 | const routes = createNestedPageRoutes(
10 | globalThis.context
11 | ? globalThis.context
12 | : ({
13 | manifests: {
14 | routesManifest,
15 | },
16 | lazyComponent(id: string) {
17 | const importPath = `/@fs${id}`;
18 | const importer = () =>
19 | isServer
20 | ? viteDevServer.ssrLoadModule(importPath)
21 | : import(/* @vite-ignore */ importPath);
22 | return lazy(importer);
23 | },
24 | } as unknown as Context),
25 | "root",
26 | undefined,
27 | import.meta.env.ROUTER_MODE,
28 | );
29 |
30 | export default createPageRouter(routes);
31 |
--------------------------------------------------------------------------------
/test/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import solid from "solid-start/vite";
2 | import { defineConfig } from "vite";
3 |
4 | export default defineConfig({
5 | test: {
6 | exclude: ["e2e", "node_modules"],
7 |
8 | // globals: true,
9 | environment: "jsdom",
10 | transformMode:
11 | process.env.TEST_ENV === "server"
12 | ? {
13 | ssr: [/.[tj]sx?$/]
14 | }
15 | : {
16 | web: [/.[tj]sx?$/]
17 | },
18 | // solid needs to be inline to work around
19 | // a resolution issue in vitest:
20 | deps: {
21 | inline: [/solid-js/]
22 | }
23 | // if you have few tests, try commenting one
24 | // or both out to improve performance:
25 | // threads: false,
26 | // isolate: false,
27 | },
28 | build: {
29 | target: "esnext",
30 | polyfillDynamicImport: false
31 | },
32 | resolve: {
33 | conditions: process.env.TEST_ENV === "server" ? [] : ["development", "browser"]
34 | },
35 | plugins: [solid()]
36 | });
37 |
--------------------------------------------------------------------------------
/packages/fully-react/src/server/htmlescape.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Properly escape JSON for usage as an object literal inside of a `