From 799290cbef9e529480aa6f6e71027b59cd6d120f Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Wed, 15 Jan 2025 15:18:12 -0800 Subject: [PATCH] feat: add with-rsc demo (#539) --- with-rsc/.env | 1 + with-rsc/README.md | 14 ++++ with-rsc/app.json | 9 +++ with-rsc/app/_layout.tsx | 5 ++ with-rsc/app/index.tsx | 41 +++++++++++ with-rsc/functions/render-home.tsx | 35 +++++++++ with-rsc/package.json | 31 ++++++++ ...ebpack+19.0.0-rc-6230622a1a-20240610.patch | 73 +++++++++++++++++++ with-rsc/tsconfig.json | 4 + 9 files changed, 213 insertions(+) create mode 100644 with-rsc/.env create mode 100644 with-rsc/README.md create mode 100644 with-rsc/app.json create mode 100644 with-rsc/app/_layout.tsx create mode 100644 with-rsc/app/index.tsx create mode 100644 with-rsc/functions/render-home.tsx create mode 100644 with-rsc/package.json create mode 100644 with-rsc/patches/react-server-dom-webpack+19.0.0-rc-6230622a1a-20240610.patch create mode 100644 with-rsc/tsconfig.json diff --git a/with-rsc/.env b/with-rsc/.env new file mode 100644 index 00000000..2571e22d --- /dev/null +++ b/with-rsc/.env @@ -0,0 +1 @@ +MY_SECRET=123 \ No newline at end of file diff --git a/with-rsc/README.md b/with-rsc/README.md new file mode 100644 index 00000000..6b08ba9e --- /dev/null +++ b/with-rsc/README.md @@ -0,0 +1,14 @@ +# Expo Router React Server Components Example + +Use [`expo-router`](https://docs.expo.dev/router/introduction/) with experimental React Server Components to create a simple app. + +## 🚀 How to use + +```sh +npx create-expo-app -e with-rsc +``` + +## 📝 Notes + +- [Expo Router: Docs](https://docs.expo.dev/router/introduction/) +- [Expo Router: React Server Components](https://docs.expo.dev/guides/server-components/) diff --git a/with-rsc/app.json b/with-rsc/app.json new file mode 100644 index 00000000..ec4a8f12 --- /dev/null +++ b/with-rsc/app.json @@ -0,0 +1,9 @@ +{ + "expo": { + "scheme": "acme", + "plugins": ["expo-router"], + "experiments": { + "reactServerFunctions": true + } + } +} diff --git a/with-rsc/app/_layout.tsx b/with-rsc/app/_layout.tsx new file mode 100644 index 00000000..ca73deab --- /dev/null +++ b/with-rsc/app/_layout.tsx @@ -0,0 +1,5 @@ +import { Stack } from "expo-router"; + +export default function Layout() { + return ; +} diff --git a/with-rsc/app/index.tsx b/with-rsc/app/index.tsx new file mode 100644 index 00000000..9346f401 --- /dev/null +++ b/with-rsc/app/index.tsx @@ -0,0 +1,41 @@ +/// + +// This route is a client component. + +import { StyleSheet, Text, View } from "react-native"; +import { renderHomeAsync } from "../functions/render-home"; +import React from "react"; + +export default function Page() { + return ( + + + Loading...}> + {renderHomeAsync()} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: "center", + padding: 24, + }, + main: { + flex: 1, + justifyContent: "center", + maxWidth: 960, + marginHorizontal: "auto", + }, + title: { + fontSize: 64, + fontWeight: "bold", + }, + subtitle: { + fontSize: 36, + color: "#38434D", + }, +}); diff --git a/with-rsc/functions/render-home.tsx b/with-rsc/functions/render-home.tsx new file mode 100644 index 00000000..487f3246 --- /dev/null +++ b/with-rsc/functions/render-home.tsx @@ -0,0 +1,35 @@ +"use server"; + +// This file runs in the server and can return server components. + +// Optionally: You can import "server-only" to ensure this file is ne +import "server-only"; +import { Stack } from "expo-router"; + +import { Image, Text, View } from "react-native"; + +export async function renderHomeAsync() { + // Secrets can be used in the server. + console.log("Secret:", process.env.MY_SECRET); + + const res = await fetch("https://pokeapi.co/api/v2/pokemon/2"); + + const json = await res.json(); + + return ( + <> + + + {json.name} + + + {json.abilities.map((ability) => ( + - {ability.ability.name} + ))} + + + ); +} diff --git a/with-rsc/package.json b/with-rsc/package.json new file mode 100644 index 00000000..96406849 --- /dev/null +++ b/with-rsc/package.json @@ -0,0 +1,31 @@ +{ + "name": "with-router", + "version": "1.0.0", + "main": "expo-router/entry", + "scripts": { + "start": "expo start" + }, + "dependencies": { + + "@types/react": "~18.3.12", + "expo": "^52.0.16", + "expo-constants": "~17.0.2", + "expo-linking": "~7.0.2", + "expo-router": "~4.0.2", + "expo-splash-screen": "~0.29.7", + "expo-status-bar": "~2.0.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-native": "0.76.3", + "react-native-gesture-handler": "~2.20.2", + "react-native-safe-area-context": "4.12.0", + "react-native-screens": "~4.1.0", + "react-native-web": "~0.19.6", + "typescript": "^5.3.3", + "react-server-dom-webpack": "19.0.0-rc-6230622a1a-20240610" + }, + "devDependencies": { + "patch-package": "^8.0.0", + "@babel/core": "^7.20.0" + } +} diff --git a/with-rsc/patches/react-server-dom-webpack+19.0.0-rc-6230622a1a-20240610.patch b/with-rsc/patches/react-server-dom-webpack+19.0.0-rc-6230622a1a-20240610.patch new file mode 100644 index 00000000..6d5d8161 --- /dev/null +++ b/with-rsc/patches/react-server-dom-webpack+19.0.0-rc-6230622a1a-20240610.patch @@ -0,0 +1,73 @@ +diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +index c9bc9ea..cf4eebd 100644 +--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js ++++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +@@ -2113,7 +2113,7 @@ function createModelReject(chunk) { + function createServerReferenceProxy(response, metaData) { + var callServer = response._callServer; + +- var proxy = function () { ++ var proxy = async function () { + // $FlowFixMe[method-unbinding] + var args = Array.prototype.slice.call(arguments); + var p = metaData.bound; +@@ -2128,10 +2128,10 @@ function createServerReferenceProxy(response, metaData) { + } // Since this is a fake Promise whose .then doesn't chain, we have to wrap it. + // TODO: Remove the wrapper once that's fixed. + +- +- return Promise.resolve(p).then(function (bound) { +- return callServer(metaData.id, bound.concat(args)); +- }); ++ // HACK: This is required to make native server actions return a non-undefined value. ++ // Seems like a bug in the Hermes engine since the same babel transforms work in Chrome/web. ++ const _bound = await p; ++ return callServer(metaData.id, _bound.concat(args)); + }; + + registerServerReference(proxy, metaData); +diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js +index 6a7df37..9a816c3 100644 +--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js ++++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js +@@ -696,18 +696,30 @@ function createModelReject(chunk) { + }; + } + function createServerReferenceProxy(response, metaData) { +- function proxy() { ++ var callServer = response._callServer; ++ ++ var proxy = async function () { ++ // $FlowFixMe[method-unbinding] + var args = Array.prototype.slice.call(arguments), + p = metaData.bound; +- return p +- ? "fulfilled" === p.status +- ? callServer(metaData.id, p.value.concat(args)) +- : Promise.resolve(p).then(function (bound) { +- return callServer(metaData.id, bound.concat(args)); +- }) +- : callServer(metaData.id, args); +- } +- var callServer = response._callServer; ++ ++ if (!p) { ++ return callServer(metaData.id, args); ++ } ++ ++ if (p.status === "fulfilled") { ++ var bound = p.value; ++ return callServer(metaData.id, bound.concat(args)); ++ } ++ // Since this is a fake Promise whose .then doesn't chain, we have to wrap it. ++ // TODO: Remove the wrapper once that's fixed. ++ ++ // HACK: This is required to make native server actions return a non-undefined value. ++ // Seems like a bug in the Hermes engine since the same babel transforms work in Chrome/web. ++ const _bound = await p; ++ return callServer(metaData.id, _bound.concat(args)); ++ }; ++ + knownServerReferences.set(proxy, metaData); + return proxy; + } diff --git a/with-rsc/tsconfig.json b/with-rsc/tsconfig.json new file mode 100644 index 00000000..0e6371f6 --- /dev/null +++ b/with-rsc/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": {}, + "extends": "expo/tsconfig.base" +}