1# Expo Module Scripts
2
3This package contains a collection of common scripts for all Expo modules and the Expo SDK package. This sets us up to have a consistent way of compiling JS, testing, linting, and other common tasks so that the Expo SDK is coherent and unified. Knowledge and experience from working on an Expo module in this repository will carry over to working on other modules. And ultimately, we want the development experience for Expo developers to be similar across modules. A structurally unified way of developing Expo modules helps us achieve these goals.
4
5**This is the package that installs Babel CLI, TypeScript, Jest, and other common development dependencies.** Update the dependencies in this package when changing them for the Expo repository.
6
7- [Getting Started](#getting-started)
8- [Setup](#setup)
9  - [�� Config Plugin](#-config-plugin)
10  - [�� Jest](#-jest)
11  - [�� LICENSE](#-license)
12  - [Side Effects](#side-effects)
13  - [Entry Point and Types](#entry-point-and-types)
14  - [�� npm Linking](#-npm-linking)
15- [⌘ Commands](#-commands)
16  - [configure](#configure)
17  - [typecheck](#typecheck)
18  - [build](#build)
19  - [test](#test)
20  - [lint](#lint)
21  - [clean](#clean)
22- [Lifecycle Commands](#lifecycle-commands)
23  - [prepare (npm lifecycle)](#prepare--npm-lifecycle-)
24  - [prepublishOnly (npm lifecycle)](#prepublishonly--npm-lifecycle-)
25- [Excluding Files from npm](#excluding-files-from-npm)
26- [Unified Dependencies](#unified-dependencies)
27
28## Getting Started
29
30```sh
31yarn add -D expo-module-scripts
32
33# or
34
35npm install --save-dev expo-module-scripts
36```
37
38## Setup
39
40Add the following scripts to your `package.json` and run `yarn`
41
42```json
43{
44  "scripts": {
45    "build": "expo-module build",
46    "clean": "expo-module clean",
47    "test": "expo-module test",
48    "prepare": "expo-module prepare",
49    "prepublishOnly": "expo-module prepublishOnly",
50    "expo-module": "expo-module"
51  }
52}
53```
54
55Running `yarn` will now run the `prepare` script, which generates any missing files:
56
57- [`.eslintrc.js`](./templates/.eslintrc.js) ([docs](https://eslint.org/docs/user-guide/configuring)) this extends [`eslint-config-universe`](https://github.com/expo/expo/tree/main/packages/eslint-config-universe).
58  - Optionally you can customize Prettier too: [.prettierrc guide](https://github.com/expo/expo/tree/main/packages/eslint-config-universe#customizing-prettier).
59- [`.npmignore`](./templates/.npmignore) ([docs](https://docs.npmjs.com/misc/developers)) currently only ignores the `babel.config.js` in your module. You might also want to also add tests and docs.
60  - Expo modules use `.npmignore` **instead of** the `files` field in the `package.json`.
61  - (Pro Tip) Test which files get packaged by running `npm pack`. If you see files that aren't crucial to running the module, you should add them to `.npmignore`.
62- [`README.md`](./templates/README.md) A default template for Unimodule installation.
63  - Project docs should try to have relevant emojis in headers because OSS is fun.
64  - Use [badges](https://github.com/expo/expo#-badges)
65  - Try and incorporate a table of contents (TOC).
66- [`tsconfig.json`](./templates/tsconfig.json) ([docs](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)) extends [`tsconfig.base.json`](./tsconfig.base.json) this is important for ensuring all Unimodules use the same version of TypeScript.
67
68Besides, running `yarn prepare` script will also synchronize optional files from `expo-module-scripts` when the file is present and contains the `@generated` pattern:
69
70- [`source-login-scripts.sh`](./templates/scripts/source-login-scripts.sh): An Xcode build phase script helper for Node.js binary resolution. For example, we need to source login shell configs for `nvm`.
71
72### �� Config Plugin
73
74To create a [config plugin](https://github.com/expo/expo-cli/blob/main/packages/config-plugins/README.md) that automatically configures your native code, you have two options:
75
761. Create a `plugin` folder and write your plugin in TypeScript (recommended).
772. Create an `app.plugin.js` file in the project root and write the plugin in pure Node.js-compliant JavaScript.
78
79Config plugins must be transpiled for compatibility with Node.js (LTS). The features supported in Node.js are slightly different from those in Expo or React Native modules, which support ES6 import/export keywords and JSX, for example. This means we'll need two different `tsconfig.json` files and two different `src` (and `build`) folders — one for the code that will execute in an Expo or React Native app and the other for the plugin that executes in Node.js.
80
81This can quickly become complex, so we've created a system for easily targeting the plugin folder.
82
83#### Plugin setup
84
85The following files are required for a TypeScript plugin:
86
87```
88╭── app.plugin.js ➡️ Entry file
89╰── plugin/ ➡️ All code related to the plugin
90    ├── __tests__/ ➡️ Optional: Folder for tests related to the plugin
91    ├── tsconfig.json ➡️ The TypeScript config for transpiling the plugin to JavaScript
92    ├── jest.config.js ➡️ Optional: The Jest preset
93    ╰── src/index.ts ➡️ The TypeScript entry point for your plugin
94```
95
96Create an `app.plugin.js` (the entry point for a config plugin):
97
98```js
99module.exports = require('./plugin/build');
100```
101
102Create a `plugin/tsconfig.json` file. Notice that this uses `tsconfig.plugin` as the base config:
103
104```json
105{
106  "extends": "expo-module-scripts/tsconfig.plugin",
107  "compilerOptions": {
108    "outDir": "build",
109    "rootDir": "src"
110  },
111  "include": ["./src"],
112  "exclude": ["**/__mocks__/*", "**/__tests__/*"]
113}
114```
115
116In your `plugin/src/index.ts` file, write your TypeScript config plugin:
117
118```ts
119import { ConfigPlugin } from '@expo/config-plugins';
120
121const withNewName: ConfigPlugin<{ name?: string }> = (config, { name = 'my-app' } = {}) => {
122  config.name = name;
123  return config;
124};
125
126export default withNewName;
127```
128
129> �� Tip: Using named functions makes debugging easier with `EXPO_DEBUG=true`
130
131Optionally, you can add `plugin/jest.config.js` to override the default project Jest preset.
132
133```ts
134module.exports = require('expo-module-scripts/jest-preset-plugin');
135```
136
137Use the following scripts to interact with the plugin:
138
139- `yarn build plugin`: Build the plugin.
140- `yarn clean plugin`: Delete the `plugin/build` folder.
141- `yarn lint plugin`: Lint the `plugin/src` folder.
142- `yarn test plugin`: Alias for `npx jest --rootDir ./plugin --config ./plugin/jest.config.js`, uses the project's Jest preset if `plugin/jest.config.js` doesn't exist.
143- `yarn prepare`: Prepare the plugin and module for publishing.
144
145### �� Jest
146
147The Jest preset extends [`jest-expo`](https://github.com/expo/expo/tree/main/packages/jest-expo) or [`jest-expo-enzyme`](https://github.com/expo/expo/tree/main/packages/jest-expo-enzyme) and adds proper TypeScript support and type declarations to the presets.
148
149**For unit testing API-based modules:**
150
151```json
152{
153  "jest": {
154    "preset": "expo-module-scripts"
155  }
156}
157```
158
159**For unit testing component-based modules:**
160
161```json
162{
163  "jest": {
164    "preset": "expo-module-scripts/enzyme"
165  }
166}
167```
168
169### �� LICENSE
170
171This makes it easier for other members of the community to work with your package. Expo usually has the **MIT** license.
172
173```json
174{
175  "license": "MIT"
176}
177```
178
179### Side Effects
180
181The [`@expo/webpack-config`](https://www.npmjs.com/package/@expo/webpack-config) is optimized for tree-shaking, you should always make sure to list whatever files in your module have side effects. In Expo modules we use the `.fx.*` extension on these files (this makes it easier to target them with `sideEffects`).
182
183[**Learn more about side effects**](https://webpack.js.org/guides/tree-shaking/)
184
185```json
186{
187  "sideEffects": false
188}
189```
190
191### Entry Point and Types
192
193We recommend you name the initial file after the module for easier searching. Be sure to define the `types` file as well.
194
195> �� Note that the `"typings"` field is synonymous with `"types"` field, Expo uses the TypeScript preferred `"types"` field.
196
197[**Learn more about "types" field**](https://webpack.js.org/guides/tree-shaking/)
198
199```json
200{
201  "main": "build/Camera.js",
202  "types": "build/Camera.d.ts"
203}
204```
205
206> �� You technically don't need to define the types file if it's named the same as the `main` file but Expo modules always define it (which is what TypeScript recommends).
207
208### �� npm Linking
209
210Make your package accessible to npm users by adding the following fields:
211
212Expo modules use the long form object when possible to better accommodate monorepos and hyperlinks:
213
214- [homepage docs](https://docs.npmjs.com/files/package.json#homepage)
215- [bugs docs](https://docs.npmjs.com/files/package.json#bugs)
216- [repository docs](https://docs.npmjs.com/files/package.json#repository)
217
218```json
219{
220  "homepage": "https://github.com/YOU/expo-YOUR_PACKAGE#readme",
221  "repository": {
222    "type": "git",
223    "url": "git+https://github.com/YOU/expo-YOUR_PACKAGE.git"
224  },
225  "bugs": {
226    "url": "https://github.com/YOU/expo-YOUR_PACKAGE/issues"
227  }
228}
229```
230
231## ⌘ Commands
232
233This package defines a program called `expo-module` that accepts a command (ex: `expo-module build`). This allows us to add more commands without changing the behavior of existing commands while not needing to define more programs. Typically, you'd invoke these commands from Yarn:
234
235```sh
236$ cd expo-example-module
237$ yarn expo-module test
238
239# For commonly run commands, add "expo-module test" as an npm script named "test"
240$ yarn test
241```
242
243For scripts that need to run as part of the npm lifecycle, you'd invoke the commands from npm scripts in package.json:
244
245```json
246{
247  "scripts": {
248    "prepare": "expo-module prepare",
249    "prepublishOnly": "expo-module prepublishOnly"
250  }
251}
252```
253
254These are the commands:
255
256### configure
257
258This generates common configuration files like `tsconfig.json` for the package. These auto-generated files are meant to be read-only and committed to Git.
259
260### typecheck
261
262This type checks the source TypeScript with `tsc`. This command is separate from `build` and does not emit compiled JS.
263
264### build
265
266This compiles the source JS or TypeScript to "compiled" JS that Expo can load. We use `tsc` instead of the Babel TypeScript plugin since `tsc` has complete support for the TypeScript language, while the Babel plugin has [some limitations](https://blogs.msdn.microsoft.com/typescript/2018/08/27/typescript-and-babel-7/). `tsc` also performs type checking in the same way that VS Code and other IDEs do.
267
268If we wished to switch to using just Babel with the TypeScript plugin, this package would let us change the implementation of the `build` command and apply it to all packages automatically.
269
270#### build plugin
271
272Running `build plugin` builds the plugin source code in `plugin/src`.
273
274### test
275
276We run tests using Jest with ts-jest, which runs TypeScript and Babel. This setup type checks test files and mimics the `build` command's approach of running `tsc` followed by Babel.
277
278If we were to use just Babel with the TypeScript plugin for the `build` command, Jest with `babel-jest` would be more closely aligned.
279
280### lint
281
282This runs ESLint over the source JS and TypeScript files.
283
284One of the rules enforced is restricting any imports from the `fbjs` library. As stated in that [library's readme](https://github.com/facebook/fbjs#purpose):
285
286> If you are consuming the code here and you are not also a Facebook project, be prepared for a bad time.
287
288Replacements for common `fbjs` uses-cases are listed below:
289
290- `invariant`- replace with [`invariant`](https://www.npmjs.com/package/invariant)
291- `ExecutionEnvironment`- replace with [`Platform` from `@unimodules/core`](https://github.com/expo/expo/blob/main/packages/%40unimodules/react-native-adapter/src/Platform.ts)
292
293#### lint plugin
294
295Running `lint plugin` will lints the plugin source code in `plugin/src`.
296
297### clean
298
299This deletes the build directory.
300
301#### clean plugin
302
303Running `clean plugin` will delete the `plugin/build` directory.
304
305## Lifecycle Commands
306
307These are commands to run as part of [the npm scripts lifecycle](https://docs.npmjs.com/misc/scripts).
308
309### prepare (npm lifecycle)
310
311Runs `clean` and `build`.
312
313### prepublishOnly (npm lifecycle)
314
315Runs `npm-proofread`, which ensures a [dist-tag](https://docs.npmjs.com/cli/dist-tag) is specified when publishing a prerelease version.
316
317## Excluding Files from npm
318
319By convention, `expo-module-scripts` uses `.npmignore` to exclude all top-level hidden directories (directories starting with `.`) from being published to npm. This behavior is useful for files that need to be in the Git repository but not in the npm package.
320
321## Unified Dependencies
322
323This package depends on common development dependencies like Babel and Jest. The commands for compiling and testing JS need these dependencies, and the most important benefit is that all Expo module packages use the same version of Babel, Jest, their various plugins, and other development dependencies. This does remove the flexibility to customize the dependency versions for each module. We intentionally make this tradeoff to prioritize Expo as a whole over individual modules.
324