Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Commit

Permalink
feat(expo): Experimental Expo Support (#299 by @jamonholmgren and @le…
Browse files Browse the repository at this point in the history
  • Loading branch information
jamonholmgren authored Mar 4, 2020
1 parent 33a4ac9 commit 273cb17
Show file tree
Hide file tree
Showing 25 changed files with 547 additions and 150 deletions.
18 changes: 18 additions & 0 deletions boilerplate/.babelrc.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"presets": [
<% if (!props.useExpo) { %>"module:react-native-dotenv",<% } %>
"module:metro-react-native-babel-preset"
],
"env": {
"production": {}
},
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
["@babel/plugin-proposal-optional-catch-binding"]
]
}
4 changes: 4 additions & 0 deletions boilerplate/.gitignore.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,7 @@ buck-out/

# VS Code
.vscode

# Configurations
app/config/env.*.js
!env.js
35 changes: 14 additions & 21 deletions boilerplate/app/app.tsx.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

import "./i18n"
import React, { useState, useEffect } from "react"
import { AppRegistry, YellowBox } from "react-native"
import { YellowBox } from "react-native"
import { StatefulNavigator } from "./navigation"
import { RootStore, RootStoreProvider, setupRootStore } from "./models/root-store"
import { BackButtonHandler, exitRoutes } from "./navigation"
<% if (props.useExpo) { -%>
import { initFonts } from "./theme/fonts"
<% } -%>
import { contains } from "ramda"
import { enableScreens } from "react-native-screens"

Expand All @@ -23,8 +26,10 @@ enableScreens();
YellowBox.ignoreWarnings([
"componentWillMount is deprecated",
"componentWillReceiveProps is deprecated",
"Require cycle:",
])

<% if (!props.useExpo) { -%>
/**
* Storybook still wants to use ReactNative's AsyncStorage instead of the
* react-native-community package; this causes a YellowBox warning. This hack
Expand All @@ -37,6 +42,7 @@ Object.defineProperty(ReactNative, "AsyncStorage", {
return require("@react-native-community/async-storage").default
},
})
<% } -%>

/**
* Are we allowed to exit the app? This is called when the back button
Expand All @@ -49,10 +55,15 @@ const canExit = (routeName: string) => contains(routeName, exitRoutes)
/**
* This is the root component of our app.
*/
export const App: React.FunctionComponent<{}> = () => {
export default function App() {
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined) // prettier-ignore
useEffect(() => {
setupRootStore().then(setRootStore)
(async () => {
<% if (props.useExpo) { -%>
await initFonts()
<% } -%>
setupRootStore().then(setRootStore)
})()
}, [])

// Before we show the app, we have to wait for our state to be ready.
Expand All @@ -76,21 +87,3 @@ export const App: React.FunctionComponent<{}> = () => {
</RootStoreProvider>
)
}

/**
* This needs to match what's found in your app_delegate.m and MainActivity.java.
*/
const APP_NAME = "<%= props.name %>"

// Should we show storybook instead of our app?
//
// ⚠️ Leave this as `false` when checking into git.
const SHOW_STORYBOOK = false

let RootComponent = App
if (__DEV__ && SHOW_STORYBOOK) {
// Only include Storybook if we're in dev mode
const { StorybookUIRoot } = require("../storybook")
RootComponent = StorybookUIRoot
}
AppRegistry.registerComponent(APP_NAME, () => RootComponent)
3 changes: 3 additions & 0 deletions boilerplate/app/config/env.dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
API_URL: "https://example.com",
}
3 changes: 3 additions & 0 deletions boilerplate/app/config/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = __DEV__
? require("./env.dev")
: require("./env.prod")
3 changes: 3 additions & 0 deletions boilerplate/app/config/env.prod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
API_URL: "https://example.com",
}
13 changes: 0 additions & 13 deletions boilerplate/app/i18n/i18n.ts

This file was deleted.

28 changes: 28 additions & 0 deletions boilerplate/app/i18n/i18n.ts.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<% /*
For now, we separated these packages that works the same because:
1. "react-native-localize" doesn't work on Expo
2. "expo-localization" depends on "react-native-unimodules" and it requires
manual setup.
*/ -%>
<% if (props.useExpo) { -%>
import * as Localization from "expo-localization"
<% } else { -%>
import * as RNLocalize from "react-native-localize"
<% } -%>
import i18n from "i18n-js"

const en = require("./en")
const ja = require("./ja")

i18n.fallbacks = true
i18n.translations = { en, ja }

const fallback = { languageTag: "en", isRTL: false }

<% if (props.useExpo) { -%>
i18n.locale = Localization.locale || fallback
<% } else { -%>
const { languageTag } =
RNLocalize.findBestAvailableLanguage(Object.keys(i18n.translations)) || fallback
i18n.locale = languageTag
<% } -%>
9 changes: 3 additions & 6 deletions boilerplate/app/navigation/primary-navigator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import createNativeStackNavigator from "react-native-screens/createNativeStackNavigator"
import {
WelcomeScreen,
DemoScreen,
} from "../screens"
import { createStackNavigator } from "react-navigation-stack"
import { WelcomeScreen, DemoScreen } from "../screens"

export const PrimaryNavigator = createNativeStackNavigator(
export const PrimaryNavigator = createStackNavigator(
{
welcome: { screen: WelcomeScreen },
demo: { screen: DemoScreen },
Expand Down
2 changes: 2 additions & 0 deletions boilerplate/app/navigation/root-navigator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createStackNavigator } from "react-navigation-stack"
import { PrimaryNavigator } from "./primary-navigator"

// prettier-ignore
import {
} from "../screens" // eslint-disable-line @typescript-eslint/no-unused-vars

Expand Down
5 changes: 4 additions & 1 deletion boilerplate/app/services/api/api-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { API_URL } from "react-native-dotenv"
// Use this import if you want to use "env.js" file
// const { API_URL } = require("../../config/env")
// Or just specify it directly like this:
const API_URL = "http://example.com"

/**
* The options used to configure the API.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Tron from "reactotron-react-native"
<% if (props.useExpo) { -%>
import { AsyncStorage } from "react-native"
<% } else { -%>
import AsyncStorage from "@react-native-community/async-storage"
<% } -%>
import { RootStore } from "../../models/root-store/root-store"
import { onSnapshot } from "mobx-state-tree"
import { ReactotronConfig, DEFAULT_REACTOTRON_CONFIG } from "./reactotron-config"
Expand Down
8 changes: 8 additions & 0 deletions boilerplate/app/theme/fonts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Font from "expo-font"

export const initFonts = async () => {
await Font.loadAsync({
"Montserrat": require("./Montserrat-Regular.ttf"),
"Montserrat-Regular": require("./Montserrat-Regular.ttf"),
})
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
<% if (props.useExpo) { -%>
import { AsyncStorage } from "react-native"
<% } else { -%>
import AsyncStorage from "@react-native-community/async-storage"
<% } -%>

<% if (props.useExpo) { -%>
jest.mock("react-native", () => ({
AsyncStorage: {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
multiSet: jest.fn(),
multiRemove: jest.fn(),
clear: jest.fn(),
},
}))
<% } -%>

import { load, loadString, save, saveString, clear, remove } from "./storage"

// fixtures
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<% if (props.useExpo) { -%>
import { AsyncStorage } from "react-native"
<% } else { -%>
import AsyncStorage from "@react-native-community/async-storage"
<% } -%>

/**
* Loads a string from storage.
Expand Down
2 changes: 1 addition & 1 deletion boilerplate/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
presets: ["module:metro-react-native-babel-preset", "module:react-native-dotenv"],
presets: ["module:metro-react-native-babel-preset"],
env: {
production: {},
},
Expand Down
27 changes: 26 additions & 1 deletion boilerplate/index.js.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,30 @@
// side effect of breaking other tooling like mobile-center and react-native-rename.
//
// It's easier just to leave it here.
import App from "./app/app.tsx"
<% if (!props.useExpo) { -%>
import { AppRegistry } from "react-native"
import "./app/app.tsx"
/**
* This needs to match what's found in your app_delegate.m and MainActivity.java.
*/
const APP_NAME = "<%= props.name %>"
<% } -%>

// Should we show storybook instead of our app?
//
// ⚠️ Leave this as `false` when checking into git.
const SHOW_STORYBOOK = false

let RootComponent = App
if (__DEV__ && SHOW_STORYBOOK) {
// Only include Storybook if we're in dev mode
const { StorybookUIRoot } = require("./storybook")
RootComponent = StorybookUIRoot
}

<% if (props.useExpo) { -%>
export default RootComponent
<% } else { -%>
AppRegistry.registerComponent(APP_NAME, () => RootComponent)
<% } -%>
51 changes: 38 additions & 13 deletions boilerplate/package.json.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,35 @@
"postinstall": "solidarity",
"prepare": "npm-run-all patch hack:*",
"storybook": "start-storybook -p 9001 -c ./storybook",
<% if (props.useExpo) { -%>
"test": "jest",
<% } -%>
"adb": "adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081"
},
"dependencies": {
"@react-native-community/async-storage": "^1.5.1",
"@react-native-community/masked-view": "0.1.5",
"apisauce": "1.0.3",
"i18n-js": "^3.0.11",
"lodash.throttle": "4.1.1",
"mobx": "^4.13.0",
"mobx-react-lite": "^1.4.1",
"mobx-state-tree": "^3.14.1",
"ramda": "0.26.1",
"react-native-localize": "^1.0.0",
"react-native-safe-area-context": "0.6.2",
"react-native-safe-area-view": "1.0.0",
"react-native-gesture-handler": "<%= props.reactNativeGestureHandlerVersion %>",
"react-native-keychain": "3.1.3",
"react-native-splash-screen": "3.1.1",
"react-navigation": "4.0.10",
"react-navigation-stack": "2.0.9",
"reactotron-mst": "^3.1.1",
"reactotron-react-native": "^4.0.0-beta.1",
"validate.js": "0.13.1",
"react-native-screens": "^2.0.0-alpha.6"
<% if (!props.useExpo) { -%>
"@react-native-community/async-storage": "^1.5.1",
"@react-native-community/masked-view": "0.1.5",
"react-native-safe-area-context": "0.6.2",
"react-native-safe-area-view": "1.0.0",
"react-native-gesture-handler": "<%= props.reactNativeGestureHandlerVersion %>",
"react-native-localize": "^1.0.0",
"react-native-screens": "^2.0.0-alpha.17",
"react-native-keychain": "3.1.3",
"react-navigation": "4.0.10",
"react-navigation-stack": "2.0.9",
<% } -%>
"validate.js": "0.13.1"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.0.0",
Expand All @@ -74,19 +79,23 @@
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-react-native": "^3.6.0",
"eslint-plugin-standard": "^4.0.0",
<% if (props.useExpo) { -%>
"jest": "^24.9.0",
"jest-expo": "^36.0.1",
<% } -%>
"jetifier": "^1.6.1",
"npm-run-all": "4.1.5",
"patch-package": "6.1.2",
"postinstall-prepare": "1.0.1",
"prettier": "1.18.2",
"react-native-dotenv": "^0.2.0",
"react-devtools-core": "3.6.3",
"react-powerplug": "1.0.0",
"rimraf": "2.6.3",
"solidarity": "2.3.1",
"typescript": "3.5.3"
"typescript": "3.7.3"
},
"jest": {
<% if (!props.useExpo) { -%>
"preset": "react-native",
"setupFiles": [
"<rootDir>/node_modules/react-native/jest/setup.js",
Expand All @@ -99,6 +108,22 @@
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-native|react-navigation|@react-navigation|@storybook|@react-native-community)"
]
<% } else { -%>
"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)"
]
},
{
"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)"
]
}
]
<% } -%>
},
"prettier": {
"printWidth": 100,
Expand Down
Loading

0 comments on commit 273cb17

Please sign in to comment.