Skip to content

Commit

Permalink
Expo Web Support in boilerplate apps when using —expo
Browse files Browse the repository at this point in the history
  • Loading branch information
markrickert committed May 21, 2021
1 parent f15299b commit 67ddcb2
Show file tree
Hide file tree
Showing 45 changed files with 292 additions and 122 deletions.
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]} />
}
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

0 comments on commit 67ddcb2

Please sign in to comment.