Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Expo Web out of the box! #1686

Merged
merged 1 commit into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Ignite apps include the following rock-solid technical decisions out of the box:
- apisauce (to talk to REST servers)
- Flipper-ready
- Reactotron-ready (and pre-integrated with MST)
- Supports Expo out of the box
- Supports Expo (and Expo web) out of the box
- And more!

## Quick Start
Expand Down
6 changes: 3 additions & 3 deletions boilerplate/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"slug": "HelloWorld",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"icon": "./assets/images/icon.png",
"splash": {
"image": "./assets/splash.png",
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
Expand All @@ -18,7 +18,7 @@
"supportsTablet": true
},
"web": {
"favicon": "./assets/favicon.png"
"favicon": "./assets/images/favicon.png"
}
}
}
2 changes: 1 addition & 1 deletion boilerplate/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
* This is the root component of our app.
*/
function App() {
const navigationRef = useRef<NavigationContainerRef>()
const navigationRef = useRef<NavigationContainerRef>(null)
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)

setRootNavigation(navigationRef)
Expand Down
31 changes: 31 additions & 0 deletions boilerplate/app/components/auto-image/auto-image.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-disable */
import * as React from "react"
import { storiesOf } from "@storybook/react-native"
import { StoryScreen, Story, UseCase } from "../../../storybook/views"
import { AutoImage } from "./auto-image"

declare let module

const bowser = require("../../screens/welcome/bowser.png")
const morty = { uri: "https://rickandmortyapi.com/api/character/avatar/2.jpeg" }

storiesOf("AutoImage", module)
.addDecorator((fn) => <StoryScreen>{fn()}</StoryScreen>)
.add("Style Presets", () => (
<Story>
<UseCase text="With require()">
<AutoImage source={bowser} />
<AutoImage source={bowser} style={{ width: 150 }} />
<AutoImage source={bowser} style={{ width: 150, height: 150 }} />
<AutoImage source={bowser} style={{ height: 150 }} />
<AutoImage source={bowser} style={{ height: 150, resizeMode: "contain" }} />
</UseCase>
<UseCase text="With URL">
<AutoImage source={morty} />
<AutoImage source={morty} style={{ width: 150 }} />
<AutoImage source={morty} style={{ width: 150, height: 150 }} />
<AutoImage source={morty} style={{ height: 150 }} />
<AutoImage source={morty} style={{ height: 150, resizeMode: "contain" }} />
</UseCase>
</Story>
))
33 changes: 33 additions & 0 deletions boilerplate/app/components/auto-image/auto-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useLayoutEffect, useState } from "react"
import {
Image as RNImage,
ImageProps as DefaultImageProps,
ImageURISource,
Platform,
} from "react-native"

type ImageProps = DefaultImageProps & {
source: ImageURISource
}

export function AutoImage(props: ImageProps) {
const [imageSize, setImageSize] = useState({ width: 0, height: 0 })

useLayoutEffect(() => {
if (props.source?.uri) {
RNImage.getSize(props.source.uri as any, (width, height) => {
setImageSize({ width, height })
})
} else if (Platform.OS === "web") {
// web requires a different method to get it's size
RNImage.getSize(props.source as any, (width, height) => {
setImageSize({ width, height })
})
} else {
const { width, height } = RNImage.resolveAssetSource(props.source)
setImageSize({ width, height })
}
}, [])

return <RNImage {...props} style={[imageSize, props.style]} />
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that React Native Web is considering a new API for the Image component, which may help us here. But in the meantime, this will do nicely.

necolas/react-native-web#1786

3 changes: 2 additions & 1 deletion boilerplate/app/components/icon/icon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from "react"
import { View, Image, ImageStyle } from "react-native"
import { View, ImageStyle } from "react-native"
import { AutoImage as Image } from "../auto-image/auto-image"
import { IconProps } from "./icon.props"
import { icons } from "./icons"

Expand Down
1 change: 1 addition & 0 deletions boilerplate/app/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./switch/switch"
export * from "./text/text"
export * from "./text-field/text-field"
export * from "./wallpaper/wallpaper"
export * from "./auto-image/auto-image"
2 changes: 1 addition & 1 deletion boilerplate/app/components/screen/screen.presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export type ScreenPresets = keyof typeof presets
*
* @param preset The preset to check
*/
export function isNonScrolling(preset: ScreenPresets) {
export function isNonScrolling(preset?: ScreenPresets) {
// any of these things will make you scroll
return !preset || !presets[preset] || preset === "fixed"
}
1 change: 1 addition & 0 deletions boilerplate/app/components/screen/screen.props.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react"
import { StyleProp, ViewStyle } from "react-native"
import { KeyboardOffsets, ScreenPresets } from "./screen.presets"

Expand Down
2 changes: 1 addition & 1 deletion boilerplate/app/components/wallpaper/wallpaper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { Image } from "react-native"
import { AutoImage as Image } from "../auto-image/auto-image"
import { presets } from "./wallpaper.presets"
import { WallpaperProps } from "./wallpaper.props"

Expand Down
4 changes: 2 additions & 2 deletions boilerplate/app/screens/demo/demo-list-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect } from "react"
import { Image, FlatList, TextStyle, View, ViewStyle, ImageStyle } from "react-native"
import { FlatList, TextStyle, View, ViewStyle, ImageStyle } from "react-native"
import { useNavigation } from "@react-navigation/native"
import { observer } from "mobx-react-lite"
import { Header, Screen, Text, Wallpaper } from "../../components"
import { Header, Screen, Text, Wallpaper, AutoImage as Image } from "../../components"
import { color, spacing } from "../../theme"
import { useStores } from "../../models"

Expand Down
14 changes: 12 additions & 2 deletions boilerplate/app/screens/demo/demo-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import React from "react"
import { Image, ImageStyle, Platform, TextStyle, View, ViewStyle } from "react-native"
import { ImageStyle, Platform, TextStyle, View, ViewStyle } from "react-native"
import { useNavigation } from "@react-navigation/native"
import { observer } from "mobx-react-lite"
import { BulletItem, Button, Header, Text, Screen, Wallpaper } from "../../components"
import {
BulletItem,
Button,
Header,
Text,
Screen,
Wallpaper,
AutoImage as Image,
} from "../../components"
import { color, spacing } from "../../theme"
import { Api } from "../../services/api"
import { save } from "../../utils/storage"
Expand Down Expand Up @@ -53,6 +61,8 @@ const TAGLINE: TextStyle = {
const IGNITE: ImageStyle = {
marginVertical: spacing[6],
alignSelf: "center",
width: 180,
height: 100,
}
const LOVE_WRAPPER: ViewStyle = {
flexDirection: "row",
Expand Down
8 changes: 5 additions & 3 deletions boilerplate/app/screens/welcome/welcome-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react"
import { View, Image, ViewStyle, TextStyle, ImageStyle, SafeAreaView } from "react-native"
import { View, ViewStyle, TextStyle, ImageStyle, SafeAreaView } from "react-native"
import { useNavigation } from "@react-navigation/native"
import { observer } from "mobx-react-lite"
import { Button, Header, Screen, Text, Wallpaper } from "../../components"
import { Button, Header, Screen, Text, Wallpaper, AutoImage as Image } from "../../components"
import { color, spacing, typography } from "../../theme"
const bowserLogo = require("./bowser.png")

Expand Down Expand Up @@ -50,6 +50,8 @@ const BOWSER: ImageStyle = {
alignSelf: "center",
marginVertical: spacing[5],
maxWidth: "100%",
width: 343,
height: 230,
}
const CONTENT: TextStyle = {
...TEXT,
Expand All @@ -69,7 +71,7 @@ const CONTINUE_TEXT: TextStyle = {
fontSize: 13,
letterSpacing: 2,
}
const FOOTER: ViewStyle = { backgroundColor: "#20162D", marginBottom: 64 }
const FOOTER: ViewStyle = { backgroundColor: "#20162D" }
const FOOTER_CONTENT: ViewStyle = {
paddingVertical: spacing[4],
paddingHorizontal: spacing[4],
Expand Down
15 changes: 9 additions & 6 deletions boilerplate/app/services/reactotron/reactotron.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Tron from "reactotron-react-native"
import { Tron } from "./tron"
import AsyncStorage from "@react-native-async-storage/async-storage"
import { RootStore } from "../../models/root-store/root-store"
import { onSnapshot } from "mobx-state-tree"
import { ReactotronConfig, DEFAULT_REACTOTRON_CONFIG } from "./reactotron-config"
import { mst } from "reactotron-mst"
import { clear } from "../../utils/storage"
import { RootNavigation } from "../../navigators"
import { Platform } from "react-native"

// Teach TypeScript about the bad things we want to do.
declare global {
Expand Down Expand Up @@ -118,12 +119,14 @@ export class Reactotron {
})

// hookup middleware
if (this.config.useAsyncStorage) {
Tron.setAsyncStorageHandler(AsyncStorage)
if (Platform.OS !== "web") {
if (this.config.useAsyncStorage) {
Tron.setAsyncStorageHandler(AsyncStorage)
}
Tron.useReactNative({
asyncStorage: this.config.useAsyncStorage ? undefined : false,
})
}
Tron.useReactNative({
asyncStorage: this.config.useAsyncStorage ? undefined : false,
})

// ignore some chatty `mobx-state-tree` actions
const RX = /postProcessSnapshot|@APPLY_SNAPSHOT/
Expand Down
2 changes: 2 additions & 0 deletions boilerplate/app/services/reactotron/tron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Reactotron from "reactotron-react-native"
export const Tron = Reactotron
2 changes: 2 additions & 0 deletions boilerplate/app/services/reactotron/tron.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Reactotron from "reactotron-react-js"
export const Tron = Reactotron
3 changes: 0 additions & 3 deletions boilerplate/app/utils/storage/async-storage.expo.ts

This file was deleted.

3 changes: 0 additions & 3 deletions boilerplate/app/utils/storage/async-storage.ts

This file was deleted.

15 changes: 1 addition & 14 deletions boilerplate/app/utils/storage/storage.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
import { AsyncStorage } from "./async-storage"

import AsyncStorage from "@react-native-async-storage/async-storage"
import { load, loadString, save, saveString, clear, remove } from "./storage"

// expo
jest.mock("react-native", () => ({
AsyncStorage: {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
multiSet: jest.fn(),
multiRemove: jest.fn(),
clear: jest.fn(),
},
}))

// fixtures
const VALUE_OBJECT = { x: 1 }
const VALUE_STRING = JSON.stringify(VALUE_OBJECT)
Expand Down
2 changes: 1 addition & 1 deletion boilerplate/app/utils/storage/storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AsyncStorage } from "./async-storage"
import AsyncStorage from "@react-native-async-storage/async-storage"

/**
* Loads a string from storage.
Expand Down
Binary file removed boilerplate/assets/icon.png
Binary file not shown.
Binary file added boilerplate/assets/images/adaptive-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added boilerplate/assets/images/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added boilerplate/assets/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added boilerplate/assets/images/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed boilerplate/assets/splash.png
Binary file not shown.
15 changes: 15 additions & 0 deletions boilerplate/babel.config.expo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
presets: ["babel-preset-expo"],
env: {
production: {},
},
plugins: [
[
"@babel/plugin-proposal-decorators",
{
legacy: true,
},
],
["@babel/plugin-proposal-optional-catch-binding"],
],
}
5 changes: 4 additions & 1 deletion boilerplate/bin/postInstall
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const os = require("os")
* have to remember these extra steps.
*/
;[
// Patch all the necessary modules.
{ command: "npx patch-package" },

// Make sure we're set up correctly
{ command: "solidarity" },

Expand All @@ -24,7 +27,7 @@ const os = require("os")
{ command: "pod install", cwd: "ios", onlyPlatforms: ["darwin"] },
]
.filter(({ onlyPlatforms }) => !onlyPlatforms || onlyPlatforms.includes(os.platform()))
.forEach(commandAndOptions => {
.forEach((commandAndOptions) => {
const { command, onlyPlatform: _, ...options } = commandAndOptions
try {
childProcess.execSync(command, {
Expand Down
6 changes: 6 additions & 0 deletions boilerplate/index.expo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This is the first file that ReactNative will run when it starts up.
import App from "./app/app.tsx"
import { registerRootComponent } from "expo"

registerRootComponent(App)
export default App
25 changes: 6 additions & 19 deletions boilerplate/package.expo.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,23 @@
"test:e2e": "./bin/downloadExpoApp.sh && detox test --configuration ios.sim.expo"
},
"dependencies": {
"@expo/webpack-config": "^0.12.71",
"expo": "40.0.1",
"expo-status-bar": "1.0.3",
"expo-status-bar": "~1.0.4",
"query-string": "7.0.0",
"react": "16.13.1",
"react-native-safe-area-context": "3.1.8",
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz"
},
"devDependencies": {
"@types/react": "16.9.35",
"@types/react-dom": "16.9.8",
"@types/react-native": "0.63.2",
"expo-detox-hook": "1.0.10",
"detox-expo-helpers": "0.6.0",
"jest-expo": "40.0.1"
"detox-expo-helpers": "0.6.0"
},
"jest": {
"projects": [
{
"preset": "jest-expo/ios",
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|native-base|@storybook)"
],
"testPathIgnorePatterns": ["/node_modules/", "/e2e"]
},
{
"preset": "jest-expo/android",
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|native-base|@storybook)"
],
"testPathIgnorePatterns": ["/node_modules/", "/e2e"]
}
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|native-base|@storybook)"
]
},
"detox": {
Expand Down
Loading