$ npm install expo-module-scripts
This 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.
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.
yarn add -D expo-module-scripts
# or
npm install --save-dev expo-module-scripts
Add the following scripts to your package.json
and run yarn
{
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
}
}
Running yarn
will now run the prepare
script, which generates any missing files:
.eslintrc.js
(docs) this extends eslint-config-universe
.
.npmignore
(docs) currently only ignores the babel.config.js
in your module. You might also want to also add tests and docs.
.npmignore
instead of the files
field in the package.json
.npm pack
. If you see files that aren't crucial to running the module, you should add them to .npmignore
.README.md
A default template for Unimodule installation.
tsconfig.json
(docs) extends tsconfig.base.json
this is important for ensuring all Unimodules use the same version of TypeScript.Besides, running yarn prepare
script will also synchronize optional files from expo-module-scripts
when the file is present and contains the @generated
pattern:
with-node.sh
: An Xcode build phase script helper for Node.js binary resolution. It sources the project's .xcode.env and .xcode.env.local files, which may define an environment variable named NODE_BINARY
to specify the file path of the Node.js binary to run.To create a config plugin that automatically configures your native code, you have two options:
plugin
folder and write your plugin in TypeScript (recommended).app.plugin.js
file in the project root and write the plugin in pure Node.js-compliant JavaScript.Config 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.
This can quickly become complex, so we've created a system for easily targeting the plugin folder.
The following files are required for a TypeScript plugin:
╭── app.plugin.js ➡️ Entry file
╰── plugin/ ➡️ All code related to the plugin
├── __tests__/ ➡️ Optional: Folder for tests related to the plugin
├── tsconfig.json ➡️ The TypeScript config for transpiling the plugin to JavaScript
├── jest.config.js ➡️ Optional: The Jest preset
╰── src/index.ts ➡️ The TypeScript entry point for your plugin
Create an app.plugin.js
(the entry point for a config plugin):
module.exports = require('./plugin/build');
Create a plugin/tsconfig.json
file. Notice that this uses tsconfig.plugin
as the base config:
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}
In your plugin/src/index.ts
file, write your TypeScript config plugin:
import { ConfigPlugin } from '@expo/config-plugins';
const withNewName: ConfigPlugin<{ name?: string }> = (config, { name = 'my-app' } = {}) => {
config.name = name;
return config;
};
export default withNewName;
💡 Tip: Using named functions makes debugging easier with
EXPO_DEBUG=true
Optionally, you can add plugin/jest.config.js
to override the default project Jest preset.
module.exports = require('expo-module-scripts/jest-preset-plugin');
Use the following scripts to interact with the plugin:
yarn build plugin
: Build the plugin.yarn clean plugin
: Delete the plugin/build
folder.yarn lint plugin
: Lint the plugin/src
folder.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.yarn prepare
: Prepare the plugin and module for publishing.The Jest preset extends jest-expo
and adds proper TypeScript support and type declarations to the presets.
For unit testing API-based modules:
{
"jest": {
"preset": "expo-module-scripts"
}
}
For unit testing component-based modules use @testing-library/react and @testing-library/react-native.
This makes it easier for other members of the community to work with your package. Expo usually has the MIT license.
{
"license": "MIT"
}
The @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
).
{
"sideEffects": false
}
We recommend you name the initial file after the module for easier searching. Be sure to define the types
file as well.
💡 Note that the
"typings"
field is synonymous with"types"
field, Expo uses the TypeScript preferred"types"
field.
Learn more about "types" field
{
"main": "build/Camera.js",
"types": "build/Camera.d.ts"
}
💡 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).
Make your package accessible to npm users by adding the following fields:
Expo modules use the long form object when possible to better accommodate monorepos and hyperlinks:
{
"homepage": "https://github.com/YOU/expo-YOUR_PACKAGE#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/YOU/expo-YOUR_PACKAGE.git"
},
"bugs": {
"url": "https://github.com/YOU/expo-YOUR_PACKAGE/issues"
}
}
This 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:
$ cd expo-example-module
$ yarn expo-module test
# For commonly run commands, add "expo-module test" as an npm script named "test"
$ yarn test
For scripts that need to run as part of the npm lifecycle, you'd invoke the commands from npm scripts in package.json:
{
"scripts": {
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly"
}
}
These are the commands:
This generates common configuration files like tsconfig.json
for the package. These auto-generated files are meant to be read-only and committed to Git.
This type checks the source TypeScript with tsc
. This command is separate from build
and does not emit compiled JS.
This 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. tsc
also performs type checking in the same way that VS Code and other IDEs do.
If 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.
Running build plugin
builds the plugin source code in plugin/src
.
We 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.
If we were to use just Babel with the TypeScript plugin for the build
command, Jest with babel-jest
would be more closely aligned.
This runs ESLint over the source JS and TypeScript files.
One of the rules enforced is restricting any imports from the fbjs
library. As stated in that library's readme:
If you are consuming the code here and you are not also a Facebook project, be prepared for a bad time.
Replacements for common fbjs
uses-cases are listed below:
invariant
- replace with invariant
ExecutionEnvironment
- replace with Platform
from @unimodules/core
Running lint plugin
will lints the plugin source code in plugin/src
.
This deletes the build directory.
Running clean plugin
will delete the plugin/build
directory.
These are commands to run as part of the npm scripts lifecycle.
Runs clean
and build
.
Runs npm-proofread
, which ensures a dist-tag is specified when publishing a prerelease version.
By 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.
This 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.
© 2010 - cnpmjs.org x YWFE | Home | YWFE