Expo Starter: Initial Setup

Expo Starter: Initial Setup

Published August 23, 2021

Initialize Project

Create an Expo project using the blank TypeScript template.
% expo init -t expo-template-blank-typescript expo-starter && cd $_
Start Expo to make sure everything is working.
% yarn start

Setup Environment Configs

Store config in environment - The Twelve Factor App (Factor III)

Create Configurations

What people use for their environment names varies a little but Expo supports development and production so let's create those as well as a sample one (to show what variables are expected) and set the variable NAME in each.
// envs/.env.sample NAME=sample
// envs/.env.development NAME=development
// envs/.env.production NAME=production

Load Configurations

Now that we have created our different environment configurations, we can use them in our code by utilizing dotenv and babel-plugin-inline-dotenv.
% yarn add dotenv babel-plugin-inline-dotenv
// babel.config.js module.exports = function(api) { api.cache(true); return { presets: ['babel-preset-expo'], env: { development: { plugins: [["inline-dotenv", { path: './envs/.env.development' }]] }, production: { plugins: [["inline-dotenv", { path: './envs/.env.production' }]] } } }; };

Git Ignore Env Files

You should add your .env files to .gitignore so you don't commit any sensitive info (e.g. API keys, passwords, etc).
// .gitignore // envs envs/.env.development envs/.env.production


Okay, Let's make sure we are able to actually use the variable depending on the environment.
// App.tsx ... export default function App() { return ( <View style={styles.container}> <Text>process.env.NODE_ENV: {process.env.NODE_ENV}</Text> <Text>process.env.NAME: {process.env.NAME}</Text> <StatusBar style="auto" /> </View> ); } ...
notion image
notion image
Run development with expo start and production with expo start --no-dev.

Organize Project

This part is up to you but I prefer to move my code to src/ and organize my projects the following way,
.expo-shared node_modules locales src ├── api ├── assets ├── components ├── navigators ├── screens ├── services ├── styles ├── utilities ├── App.tsx .gitignore index.js <-- We are about to create this file app.json babel.config.js package.json tsconfig.json yarn.lock
As we add more functionality and libraries, this directory will change a bit but this serves as a good starting point.

Fix App Entry Point

Since we moved App.tsx from the root directory, which is where Expo expects our App to be, we have to create a new entry point file in the root and point to it.
// index.js import { registerRootComponent } from 'expo'; import App from './src/App'; export default registerRootComponent(App);
I'm pretty sure this file needs to be named index.js and be in the root of the directory. I had it different before and although it worked locally, my EAS builds failed.
// package.json { "main": "./index.js", ... }

Fix Asset Paths

Since we moved the assets, we have to also update those paths too.
// app.json { "expo": { ... "icon": "./src/assets/icon.png", "splash": { "image": "./src/assets/splash.png", ... }, ... "android": { "adaptiveIcon": { "foregroundImage": "./src/assets/adaptive-icon.png", ... } }, "web": { "favicon": "./src/assets/favicon.png" } } }


Start Expo (if it isn't already), and make sure it runs in the emulator and on the web.

Create Path Aliases

Path aliases (also called module resolution depending on the tool) are a nice way to import parts of your project without having to specify an exact path. This not only saves you typing but makes it easier to restructure later if need be. Since Expo utilizes different build tools, you have to declare these in multiple places.
import { SomeUtility } from '../../utilities/MyUtility'; // Changes to import { SomeUtility } from '@utilities/MyUtility';

Create Path Aliases for TypeScript

// tsconfig.json { "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, "baseUrl": "./", "paths": { "@/api/*": ["src/api/*"], "@/assets/*": ["src/assets/*"], "@/components/*": ["src/components/*"], "@/locales/*": ["locales/*"], "@/navigators/*": ["src/navigators/*"], "@/screens/*": ["src/screens/*"], "@/services/*": ["src/services/*"], "@/styles/*": ["src/styles/*"], "@/utilities/*": ["src/utiltiies/*"], } } }

Create Path Aliases for Expo Web

First we have to get access to the webpack configuration. Run expo customize:web and select to generate webpack.config.js.
// webpack.config.js const createExpoWebpackConfigAsync = require('@expo/webpack-config'); const path = require('path'); module.exports = async (env, argv) => { const config = await createExpoWebpackConfigAsync(env, argv); config.resolve.alias = { ...config.resolve.alias, '@/api': path.resolve(__dirname, 'src/api/'), '@/assets': path.resolve(__dirname, 'src/assets/'), '@/components': path.resolve(__dirname, 'src/components/'), '@/locales': path.resolve(__dirname, 'locales/'), '@/navigators': path.resolve(__dirname, 'src/navigators/'), '@/screens': path.resolve(__dirname, 'src/screens/'), '@/services': path.resolve(__dirname, 'src/services/'), '@/styles': path.resolve(__dirname, 'src/styles/'), '@/utilities': path.resolve(__dirname, 'src/utilities/'), }; return config; };

Fix Environment Variables

Generating this webpack config breaks environment variables for expo web for some reason. Luckily, to get them working again is pretty simple with dotenv-webpack.
% yarn add -D dotenv-webpack
// webpack.config.js ... const Dotenv = require('dotenv-webpack'); ... module.exports = async (env, argv) => { ... config.plugins = [ ...config.plugins, new Dotenv({ path: `./envs/.env.${process.env.NODE_ENV}`}) ] ... return config; };

Create Path Aliases for iOS/android

To handle the path aliases for iOS and Android, we will use babel-plugin-module-resolver.
% yarn add -D babel-plugin-module-resolver
// .babelrc.js { presets: [ ... ], plugins: [ [ 'module-resolver', { root: ['./'], alias: { '@/api': './src/api', '@/assets': './src/assets', '@/components': './src/components', '@/locales': './locales', '@/navigators': './src/navigators', '@/screens': './src/screens', '@/services': './src/services', '@/styles': './src/styles', '@/utilities': './src/utilities', }, } ] ], ... }

Create Path Aliases for Jest

We will be using Jest later so we might as well configure the path aliases for it as well.
// jest.config.js module.exports = { moduleNameMapper: { '^@/api/(.*)': '<rootDir>/src/api/$1', '^@/assets/(.*)': '<rootDir>/src/assets/$1', '^@/components/(.*)': '<rootDir>/src/components/$1', '^@/locales/(.*)': '<rootDir>/locales/$1', '^@/navigators/(.*)': '<rootDir>/src/navigators/$1', '^@/screens/(.*)': '<rootDir>/src/screens/$1', '^@/services/(.*)': '<rootDir>/src/services/$1', '^@/styles/(.*)': '<rootDir>/src/styles/$1', '^@/utilities/(.*)': '<rootDir>/src/utilities/$1', }, };


Okay, now that we have added the aliases to all the files we needed to, let's test it to make sure it is working on iOS, Android, and the web.
// src/utilities/test.ts export const SomeUtility = () => { return 'Success' };
// src/App.tsx ... import { SomeUtility } from '@utilities/test'; export default function App() { return ( <View style={styles.container}> <Text>Path Alias: {SomeUtility()}</Text> <StatusBar style="auto" /> </View>); } ...
Test your app with yarn start -c (-c clears the cache). You should see "Path Alias: Success" on all the platforms.
notion image
Click to rocket boost to the top of the page!