Skip to content

Commit 578ad98

Browse files
blazejkustrascottmaswcandillon
authored
feat(🌎): React Native Web support (#273)
--------- Co-authored-by: Scott Ashton <[email protected]> Co-authored-by: William Candillon <[email protected]>
1 parent ab86696 commit 578ad98

26 files changed

+979
-221
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const path = require("path");
2+
const root = path.resolve(__dirname, "../..");
3+
const rnwPath = path.resolve(root, "node_modules/react-native-web");
4+
const assetRegistryPath = path.resolve(
5+
root,
6+
"node_modules/react-native-web/dist/modules/AssetRegistry/index",
7+
);
8+
9+
module.exports = function (metroConfig) {
10+
metroConfig.resolver.platforms = ["ios", "android", "web"];
11+
const origResolveRequest = metroConfig.resolver.resolveRequest;
12+
metroConfig.resolver.resolveRequest = (contextRaw, moduleName, platform) => {
13+
const context = {
14+
...contextRaw,
15+
preferNativePlatform: false,
16+
};
17+
18+
if (moduleName === "react-native") {
19+
return {
20+
filePath: path.resolve(rnwPath, "dist/index.js"),
21+
type: "sourceFile",
22+
};
23+
}
24+
25+
// Let default config handle other modules
26+
return origResolveRequest(context, moduleName, platform);
27+
};
28+
29+
metroConfig.transformer.assetRegistryPath = assetRegistryPath;
30+
31+
return metroConfig;
32+
};

β€Žapps/example/index.htmlβ€Ž

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7+
<link rel="icon" href="/src/assets/react.png" type="image/png">
8+
<script src="https://unpkg.com/canvaskit-wasm/bin/full/canvaskit.js"></script>
9+
10+
<title>Example</title>
11+
<!-- The `react-native-web` recommended style reset: https://necolas.github.io/react-native-web/docs/setup/#root-element -->
12+
<style id="react-native-web-reset">
13+
/* These styles make the body full-height */
14+
html,
15+
body {
16+
height: 100%;
17+
}
18+
/* These styles disable body scrolling if you are using <ScrollView> */
19+
body {
20+
overflow: hidden;
21+
}
22+
/* These styles make the root element full-height */
23+
#root {
24+
display: flex;
25+
height: 100%;
26+
flex: 1;
27+
}
28+
</style>
29+
</head>
30+
<body>
31+
<noscript>
32+
You need to enable JavaScript to run this app.
33+
</noscript>
34+
<div id="root"></div>
35+
<script src="index.bundle?platform=web&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.routerRoot=app&unstable_transformProfile=hermes-stable" defer></script>
36+
</body>
37+
</html>

β€Žapps/example/index.web.jsβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { AppRegistry } from "react-native";
2+
3+
import App from "./src/App";
4+
import { name as appName } from "./app.json";
5+
6+
AppRegistry.registerComponent(appName, () => App);
7+
8+
const rootTag = document.getElementById("root");
9+
if (process.env.NODE_ENV !== "production") {
10+
if (!rootTag) {
11+
throw new Error(
12+
'Required HTML element with id "root" was not found in the document HTML.',
13+
);
14+
}
15+
}
16+
17+
CanvasKitInit({
18+
locateFile: (file) => `https://unpkg.com/canvaskit-wasm/bin/full/${file}`,
19+
}).then((CanvasKit) => {
20+
window.CanvasKit = global.CanvasKit = CanvasKit;
21+
AppRegistry.runApplication(appName, { rootTag });
22+
});

β€Žapps/example/ios/Podfile.lockβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,7 +1865,7 @@ PODS:
18651865
- ReactCommon/turbomodule/core
18661866
- SocketRocket
18671867
- Yoga
1868-
- react-native-wgpu (0.3.2):
1868+
- react-native-wgpu (0.4.0):
18691869
- boost
18701870
- DoubleConversion
18711871
- fast_float
@@ -2903,7 +2903,7 @@ SPEC CHECKSUMS:
29032903
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
29042904
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
29052905
react-native-skia: 5bf2b2107cd7f2d806fd364f5e16b1c7554ed3cd
2906-
react-native-wgpu: 15ebc049194b0d06082ab00fb2e5d21ec871c2f4
2906+
react-native-wgpu: 5abf760f89d1517255e48de1b5208d2a307109a0
29072907
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
29082908
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
29092909
React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510

β€Žapps/example/metro.config.jsβ€Ž

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
22
const path = require('path');
3+
const getWebMetroConfig = require('./getWebMetroConfig');
34

45
const root = path.resolve(__dirname, '../..');
56
const threePackagePath = path.resolve(root, 'node_modules/three');
67

8+
const r3fPath = path.resolve(root, "node_modules/@react-three/fiber");
79
const defaultConfig = getDefaultConfig(__dirname);
810

911
const customConfig = {
@@ -32,6 +34,14 @@ const customConfig = {
3234
type: 'sourceFile',
3335
};
3436
}
37+
38+
if (moduleName === "@react-three/fiber") {
39+
//Just use the vanilla web build of react three fiber, not the stale "native" code path which has not been kept up to date.
40+
return {
41+
filePath: path.resolve(r3fPath, "dist/react-three-fiber.esm.js"),
42+
type: "sourceFile",
43+
};
44+
}
3545
// Let Metro handle other modules
3646
return context.resolveRequest(context, moduleName, platform);
3747
},
@@ -51,5 +61,4 @@ const customConfig = {
5161

5262
const metroConfig = mergeConfig(defaultConfig, customConfig);
5363

54-
55-
module.exports = metroConfig;
64+
module.exports = !!process.env.IS_WEB_BUILD ? getWebMetroConfig(metroConfig) : metroConfig;

β€Žapps/example/package.jsonβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"tsc": "tsc --noEmit",
88
"android": "react-native run-android",
99
"ios": "react-native run-ios",
10+
"web": "IS_WEB_BUILD=true react-native start",
1011
"start": "react-native start",
1112
"pod:install:ios": "pod install --project-directory=ios",
1213
"pod:install:macos": "pod install --project-directory=macos",
@@ -28,11 +29,13 @@
2829
"async-mutex": "^0.5.0",
2930
"fast-text-encoding": "^1.0.6",
3031
"react": "19.1.0",
32+
"react-dom": "19.1.0",
3133
"react-native": "0.81.4",
3234
"react-native-gesture-handler": "^2.28.0",
3335
"react-native-macos": "^0.79.0",
3436
"react-native-reanimated": "3.19.1",
3537
"react-native-safe-area-context": "^5.4.0",
38+
"react-native-web": "^0.21.2",
3639
"react-native-wgpu": "*",
3740
"teapot": "^1.0.0",
3841
"three": "0.172.0",
@@ -50,6 +53,7 @@
5053
"@rnx-kit/metro-config": "^2.0.0",
5154
"@types/node": "^20.14.7",
5255
"@types/react": "^18.2.6",
56+
"@types/react-dom": "^19.2.2",
5357
"@types/react-test-renderer": "^18.0.0",
5458
"@types/three": "0.172.0",
5559
"@webgpu/types": "0.1.65",

β€Žapps/example/src/App.tsxβ€Ž

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import "./resolveAssetSourcePolyfill";
2+
13
import { NavigationContainer } from "@react-navigation/native";
24
import { createStackNavigator } from "@react-navigation/stack";
35
import { GestureHandlerRootView } from "react-native-gesture-handler";
@@ -48,7 +50,10 @@ function App() {
4850
return (
4951
<GestureHandlerRootView style={{ flex: 1 }}>
5052
<NavigationContainer>
51-
<Stack.Navigator initialRouteName="Home">
53+
<Stack.Navigator
54+
initialRouteName="Home"
55+
screenOptions={{ cardStyle: { flex: 1 } }}
56+
>
5257
<Stack.Screen name="Home" component={Home} />
5358
<Stack.Screen name="HelloTriangle" component={HelloTriangle} />
5459
<Stack.Screen

β€Žapps/example/src/ComputeToys/ComputeToy.tsxβ€Ž

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useEffect, useRef, useState } from "react";
22
import type { CanvasRef } from "react-native-wgpu";
33
import { Canvas } from "react-native-wgpu";
4-
import { useWindowDimensions } from "react-native";
4+
import { Platform, useWindowDimensions } from "react-native";
55
import { useSharedValue } from "react-native-reanimated";
66
import { Gesture, GestureDetector } from "react-native-gesture-handler";
77

@@ -12,13 +12,24 @@ export interface ComputeToy {
1212
uniforms: Record<string, number>;
1313
}
1414

15+
// Use a CORS proxy for web to bypass CORS restrictions
16+
const getCorsProxyUrl = (url: string) => {
17+
return Platform.OS === "web"
18+
? `https://corsproxy.io/?${encodeURIComponent(url)}`
19+
: url;
20+
};
21+
1522
export const useComputeToy = (toyId: number) => {
1623
const [props, setProps] = useState<ComputeToy | null>(null);
1724

1825
useEffect(() => {
1926
(async () => {
20-
const shaderURL = `https://compute.toys/view/${toyId}/wgsl`;
21-
const uniformsURL = `https://compute.toys/view/${toyId}/json`;
27+
const shaderURL = getCorsProxyUrl(
28+
`https://compute.toys/view/${toyId}/wgsl`,
29+
);
30+
const uniformsURL = getCorsProxyUrl(
31+
`https://compute.toys/view/${toyId}/json`,
32+
);
2233

2334
// Execute both fetch requests in parallel
2435
const [shaderResponse, uniformsResponse] = await Promise.all([

β€Žapps/example/src/Home.tsxβ€Ž

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ export const examples = [
126126
];
127127

128128
const styles = StyleSheet.create({
129-
container: {},
129+
container: {
130+
flex: 1,
131+
},
130132
content: {
131-
paddingBottom: 32,
133+
marginBottom: 32,
132134
},
133135
thumbnail: {
134136
backgroundColor: "white",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const MNISTInference = () => {
2+
return null;
3+
};

0 commit comments

Comments
Β (0)