Skip to content

Commit 71c92d3

Browse files
authored
Add WebExample (software-mansion#3646)
## Description TL;DR This PR adds WebExample app which uses Expo for Web. There is a problem when WebExample components use a different version or even different instance of the same version of React than Reanimated does. It works fine when using 2.9.1 or even 3.0.0-rc.3 but fails when using `"react-native-reanimated": "link:../"`. One solution is to remove `"react": "17.0.2"` from `devDependencies` in `package.json` in the root directory. WebExample works fine, `npm ls react` shows only deduped 18.1.0 (which is correct), but running ESLint in root directory fails because it cannot find `react` module. Other solution would to remove `"react-native-reanimated": "link:../"` and try linking module manually but after multiple attempts with `babel.config.js` I still wasn't able to achieve it. For some reason, despite the fact that WebExample has a direct dependency on `"react": "18.0.0"`, it uses the instance from `node_modules` in the root directory. So as a workaround I've added a module resolver in `babel.config.js`. Fast reload also works. ## Changes - Added WebExample app ## Test code and steps to reproduce ```sh yarn cd WebExample yarn yarn web ``` ## Checklist - [ ] Included code example that can be used to test this change - [ ] Updated TS types - [ ] Added TS types tests - [ ] Added unit / integration tests - [ ] Updated documentation - [ ] Ensured that CI passes
1 parent 7602c4c commit 71c92d3

15 files changed

+10885
-1
lines changed

WebExample/.expo-shared/assets.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3+
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4+
}

WebExample/.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
node_modules/
2+
.expo/
3+
dist/
4+
npm-debug.*
5+
*.jks
6+
*.p8
7+
*.p12
8+
*.key
9+
*.mobileprovision
10+
*.orig.*
11+
web-build/
12+
13+
# macOS
14+
.DS_Store

WebExample/App.tsx

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import Animated, {
2+
useAnimatedStyle,
3+
useSharedValue,
4+
withSpring,
5+
} from 'react-native-reanimated';
6+
import {
7+
Gesture,
8+
GestureDetector,
9+
enableExperimentalWebImplementation,
10+
} from 'react-native-gesture-handler';
11+
import { StyleSheet, View } from 'react-native';
12+
13+
import { StatusBar } from 'expo-status-bar';
14+
15+
enableExperimentalWebImplementation(true);
16+
17+
export default function App() {
18+
const isPressed = useSharedValue(false);
19+
const offset = useSharedValue({ x: 0, y: 0 });
20+
21+
const animatedStyle = useAnimatedStyle(() => {
22+
return {
23+
transform: [
24+
{ translateX: offset.value.x },
25+
{ translateY: offset.value.y },
26+
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
27+
],
28+
backgroundColor: isPressed.value ? 'blue' : 'navy',
29+
cursor: isPressed.value ? 'grabbing' : 'grab',
30+
};
31+
});
32+
33+
const gesture = Gesture.Pan()
34+
.manualActivation(true)
35+
.onBegin(() => {
36+
'worklet';
37+
isPressed.value = true;
38+
})
39+
.onChange((e) => {
40+
'worklet';
41+
offset.value = {
42+
x: e.changeX + offset.value.x,
43+
y: e.changeY + offset.value.y,
44+
};
45+
})
46+
.onFinalize(() => {
47+
'worklet';
48+
isPressed.value = false;
49+
})
50+
.onTouchesMove((_, state) => {
51+
state.activate();
52+
});
53+
54+
return (
55+
<View style={styles.container}>
56+
<StatusBar style="auto" />
57+
<GestureDetector gesture={gesture}>
58+
<Animated.View style={[styles.ball, animatedStyle]} />
59+
</GestureDetector>
60+
</View>
61+
);
62+
}
63+
64+
const styles = StyleSheet.create({
65+
container: {
66+
flex: 1,
67+
backgroundColor: '#fff',
68+
alignItems: 'center',
69+
justifyContent: 'center',
70+
},
71+
ball: {
72+
width: 100,
73+
height: 100,
74+
borderRadius: 100,
75+
backgroundColor: 'blue',
76+
alignSelf: 'center',
77+
},
78+
});

WebExample/AppEntry.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Workaround from https://github.com/expo/expo/issues/18485
2+
3+
import 'expo/build/Expo.fx';
4+
5+
import App from './App';
6+
import { createRoot } from 'react-dom/client';
7+
import withExpoRoot from 'expo/build/launch/withExpoRoot';
8+
9+
const rootTag = createRoot(
10+
document.getElementById('root') ?? document.getElementById('main')
11+
);
12+
const RootComponent = withExpoRoot(App);
13+
rootTag.render(<RootComponent />);

WebExample/app.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"expo": {
3+
"name": "WebExample",
4+
"slug": "WebExample",
5+
"version": "1.0.0",
6+
"orientation": "portrait",
7+
"icon": "./assets/icon.png",
8+
"userInterfaceStyle": "light",
9+
"splash": {
10+
"image": "./assets/splash.png",
11+
"resizeMode": "contain",
12+
"backgroundColor": "#ffffff"
13+
},
14+
"updates": {
15+
"fallbackToCacheTimeout": 0
16+
},
17+
"assetBundlePatterns": [
18+
"**/*"
19+
],
20+
"ios": {
21+
"supportsTablet": true
22+
},
23+
"android": {
24+
"adaptiveIcon": {
25+
"foregroundImage": "./assets/adaptive-icon.png",
26+
"backgroundColor": "#FFFFFF"
27+
}
28+
},
29+
"web": {
30+
"favicon": "./assets/favicon.png"
31+
}
32+
}
33+
}

WebExample/assets/adaptive-icon.png

17.1 KB
Loading

WebExample/assets/favicon.png

14.8 KB
Loading

WebExample/assets/icon.png

21.9 KB
Loading

WebExample/assets/splash.png

46.2 KB
Loading

WebExample/babel.config.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = function (api) {
2+
api.cache(true);
3+
return {
4+
presets: ['babel-preset-expo'],
5+
plugins: [
6+
[
7+
'module-resolver',
8+
{
9+
extensions: ['.js', '.ts', '.tsx'],
10+
alias: {
11+
react: './node_modules/react',
12+
'react-native': './node_modules/react-native-web',
13+
'react-native-reanimated': '../src/index',
14+
},
15+
},
16+
],
17+
'@babel/plugin-proposal-export-namespace-from',
18+
'react-native-reanimated/plugin',
19+
],
20+
};
21+
};

WebExample/package.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "webexample",
3+
"version": "1.0.0",
4+
"main": "AppEntry.js",
5+
"scripts": {
6+
"start": "expo start",
7+
"android": "expo start --android",
8+
"ios": "expo start --ios",
9+
"web": "expo start --web"
10+
},
11+
"dependencies": {
12+
"@expo/webpack-config": "^0.17.2",
13+
"expo": "~46.0.13",
14+
"expo-status-bar": "~1.4.0",
15+
"react": "18.0.0",
16+
"react-dom": "18.0.0",
17+
"react-native": "0.69.6",
18+
"react-native-gesture-handler": "2.7.0",
19+
"react-native-reanimated": "link:../",
20+
"react-native-web": "~0.18.7"
21+
},
22+
"devDependencies": {
23+
"@babel/core": "^7.12.9",
24+
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
25+
"@babel/preset-env": "^7.19.4",
26+
"@types/react": "~18.0.14",
27+
"@types/react-native": "~0.69.1",
28+
"typescript": "~4.3.5"
29+
},
30+
"private": true
31+
}

WebExample/tsconfig.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "expo/tsconfig.base",
3+
"compilerOptions": {
4+
"strict": true
5+
}
6+
}

WebExample/webpack.config.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const createExpoWebpackConfigAsync = require('@expo/webpack-config');
2+
3+
module.exports = async function (env, argv) {
4+
const config = await createExpoWebpackConfigAsync(
5+
{
6+
...env,
7+
babel: {
8+
dangerouslyAddModulePathsToTranspile: ['react-native-reanimated'],
9+
},
10+
},
11+
argv
12+
);
13+
return config;
14+
};

0 commit comments

Comments
 (0)