1d9598b35SJames Ide# Expo JavaScript Style Guide 2d9598b35SJames Ide 3275a6932SEvan Bacon- [Modern JavaScript](#modern-javascript) 4275a6932SEvan Bacon- [ESLint and Prettier](#eslint-and-prettier) 5275a6932SEvan Bacon - [Editor Integration](#editor-integration) 6275a6932SEvan Bacon- [Formatting](#formatting) 7275a6932SEvan Bacon - [Prettier](#prettier) 8275a6932SEvan Bacon - [Comments](#comments) 9275a6932SEvan Bacon - [Imports](#imports) 10275a6932SEvan Bacon- [Naming](#naming) 11275a6932SEvan Bacon - [Classes, functions, and variables](#classes--functions--and-variables) 12275a6932SEvan Bacon - [Async functions](#async-functions) 13275a6932SEvan Bacon - [Private variables](#private-variables) 14275a6932SEvan Bacon - [Boolean names](#boolean-names) 15d6435c85SJames Ide- [Declarations](#declarations) 16d6435c85SJames Ide - [let and const](#let-and-const) 17275a6932SEvan Bacon- [Examples](#examples) 18275a6932SEvan Bacon- [Babel](#babel) 19275a6932SEvan Bacon 20d9598b35SJames IdeThis guide explains style guidelines for writing JavaScript for Expo. It prioritizes readability for the team and also is meant to simplify small decisions when writing code. Most of this guide applies widely across the Expo repository but sometimes writing JavaScript differs between React, the web, and Node. 21d9598b35SJames Ide 22d9598b35SJames Ide# Modern JavaScript 23d9598b35SJames Ide 24d9598b35SJames IdeWe generally use modern JavaScript on Expo, which means stable versions of the ES20xx specification with a few extensions, like JSX. We stay near the leading edge but away from the bleeding edge. 25d9598b35SJames Ide 26d9598b35SJames Ide# ESLint and Prettier 27d9598b35SJames Ide 28d9598b35SJames IdeESLint reports errors and warnings for several style guidelines. Generally, the Expo ESLint configuration will report an error when it detects something that will prevent the code from working and a warning when it detects a style or formatting nit. The Expo configuration is written leniently and you should almost never have to use `/* eslint-disable */` comments. If you find yourself wanting to disable it, tell @ide so we can adjust the ESLint configuration to always be on. 29d9598b35SJames Ide 30d9598b35SJames IdeESLint also uses Prettier, a code formatter, to check code formatting and to reformat code automatically; with Expo’s configuration, running ESLint runs Prettier too. 31d9598b35SJames Ide 32d9598b35SJames IdeESLint has a `--fix` flag that tells it to fix errors and warnings when it can. Not all errors and warnings are automatically fixable but several are, including those reported by Prettier. 33d9598b35SJames Ide 34d9598b35SJames Ide## Editor Integration 35d9598b35SJames Ide 36d9598b35SJames IdeMany popular editors have ESLint plugins. Since the Expo ESLint configuration uses Prettier, if you configure your editor to use ESLint, it will use Prettier as well. These are some popular plugins: 37d9598b35SJames Ide 38d9598b35SJames Ide- **VS Code:** https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint 39d9598b35SJames Ide- **Atom and Nuclide:** https://atom.io/packages/linter-eslint 40d9598b35SJames Ide- **Sublime Text:** https://github.com/roadhump/SublimeLinter-eslint 41d9598b35SJames Ide- **Emacs:** Flycheck with javascript-eslint 42d9598b35SJames Ide - Configure it to use the nearest available copy of ESLint by searching up `node_modules`: https://github.com/codesuki/add-node-modules-path 43d9598b35SJames Ide- **Vim:** Syntastic: https://github.com/vim-syntastic/syntastic/blob/master/syntax_checkers/javascript/eslint.vim 44d9598b35SJames Ide - Configure it to use the nearest available copy of ESLint by searching up `node_modules` 45d9598b35SJames Ide 46d9598b35SJames Ide# Formatting 47d9598b35SJames Ide 48d9598b35SJames Ide## Prettier 49d9598b35SJames Ide 50d9598b35SJames IdeWe use Prettier with Expo-specific settings for most of our code formatting. These settings are in `.prettierrc` in the Expo repository. Most small decisions about how to format code disappear with Prettier so we think less about formatting when writing and reviewing code. 51d9598b35SJames Ide 52d9598b35SJames IdeSometimes Prettier makes code hard enough to read that we don’t want Prettier to format it. Add a `// prettier-ignore` comment above the expression whose formatting you want to preserve and let Prettier format the rest of the file. 53d9598b35SJames Ide 54d9598b35SJames Ide```js 55d9598b35SJames Ide// prettier-ignore 56d9598b35SJames Idelet matrix = [ 57d9598b35SJames Ide -c, 1, 1, 58d9598b35SJames Ide 1, -c, 1, 59d9598b35SJames Ide 1, 1, -c, 60d9598b35SJames Ide]; 61d9598b35SJames Ide``` 62d9598b35SJames Ide 63d9598b35SJames IdeIf you would like Prettier to ignore the entire file rather than only a portion of it, add the file path to the `.prettierignore` file in the Expo repository. 64d9598b35SJames Ide 65d9598b35SJames IdeSince Prettier formats entire files (except ignored lines), we need to keep our files “pretty” so that the next person who runs Prettier on a file reformats only the lines they’re changing in their commit. We’ll talk more about Prettier later in this document. 66d9598b35SJames Ide 67d9598b35SJames Ide## Comments 68d9598b35SJames Ide 69d9598b35SJames IdeUse `// line` comments in most places. Use `/** block */` comments above classes, methods, and other structures and use `/* inline block */` comments in the middle of lines: 70d9598b35SJames Ide 71d9598b35SJames Ide```js 72d9598b35SJames Ide// CORRECT 73d9598b35SJames Ide/** 74d9598b35SJames Ide * Gets the latest version of Android that's been released. This is a version 75d9598b35SJames Ide * string like 7.1 instead of the code name Nougat. 76d9598b35SJames Ide */ 77d9598b35SJames Idefunction getLatestAndroidVersion() { 78d9598b35SJames Ide // Keep this logic in sync with Google's versioning scheme 79275a6932SEvan Bacon return maxBy(getAndroidVersions(/* includePrereleases */ false), linearizeSemver); 80d9598b35SJames Ide} 81d9598b35SJames Ide``` 82d9598b35SJames Ide 83d9598b35SJames IdeRemove commented-out code before pushing it to GitHub. 84d9598b35SJames Ide 85d9598b35SJames Ide## Imports 86d9598b35SJames Ide 87d9598b35SJames Ide(Note: we don’t programmatically sort nor check the order of imports since there currently isn’t a linter plugin for these choices. This section is meant to be read as light guidance and not for code reviewers to spend much attention on.) 88d9598b35SJames Ide 89d9598b35SJames IdeGroup and sort `import` statements and `require()` calls in this order: 90d9598b35SJames Ide 91d9598b35SJames Ide1. `import` statements before `require()` calls 92d9598b35SJames Ide1. JavaScript hoists `import` statements; write the code to reflect that 93275a6932SEvan Bacon1. Unassigned imports (`import 'side-effect'`) before assigned imports (`import React from 'react'`) 94d9598b35SJames Ide1. Unassigned imports almost always have side effects, which we usually want to apply earlier in the program’s lifetime. 95275a6932SEvan Bacon1. External modules and Node.js built-in modules (`path`, `react`) before aliased internal modules (`www/module`) before relative modules (`../a/b`, `./c`) 96d9598b35SJames Ide 97d9598b35SJames Ide```js 98d9598b35SJames Ide// CORRECT 99d9598b35SJames Ideimport 'side-effect'; 100d9598b35SJames Ide 101d9598b35SJames Ideimport invariant from 'invariant'; 102d9598b35SJames Ideimport Expo, { Audio } from 'expo'; 103d9598b35SJames Ideimport path from 'path'; 104d9598b35SJames Ide 105d9598b35SJames Ideimport HomeScreen from '../screens/HomeScreen'; 106d9598b35SJames Ideimport Colors from '../style/Colors'; 107d9598b35SJames Ideimport calculateViewport from '../style/calculateViewport'; 108d9598b35SJames Ideimport LoginButton './LoginButton'; 109d9598b35SJames Ide 110d9598b35SJames Ideconst assert = require('assert'); 111d9598b35SJames Ide``` 112d9598b35SJames Ide 113d9598b35SJames IdeWithin each group, sort the statements by the names of the imported modules, not their assigned variables. Use ASCII order: uppercase before lowercase before scoped modules. 114d9598b35SJames Ide 115d9598b35SJames Ide```js 116d9598b35SJames Ide// CORRECT 117d9598b35SJames Ideimport Z from 'Z'; 118d9598b35SJames Ideimport b from 'x'; 119d9598b35SJames Ideimport a from 'y'; 120d9598b35SJames Ideimport p from '@scope/package'; 121d9598b35SJames Ide``` 122d9598b35SJames Ide 123d9598b35SJames IdeWrite default imports before namespace imports before named imports: 124d9598b35SJames Ide 125*2494ec4aSJakub```js 126d9598b35SJames Ide// CORRECT 127d9598b35SJames Ideimport a, * as b, { c } from 'module'; 128c93271faSJon Samp``` 129d9598b35SJames Ide 130d9598b35SJames Ide## React and JSX 131d9598b35SJames Ide 132d9598b35SJames IdeWhen writing React components, place your declarations and static methods near the top, followed by the constructor and lifecycle methods, followed by the render method and methods it calls, and other methods. 133d9598b35SJames Ide 134d9598b35SJames IdeUse Prettier to format JSX. 135d9598b35SJames Ide 136d9598b35SJames Ide```jsx 137d9598b35SJames Ide// CORRECT 138d9598b35SJames Idetype Props = { 139d9598b35SJames Ide title: string, 140d9598b35SJames Ide onPress?: event => void, 141d9598b35SJames Ide}; 142d9598b35SJames Ide 143d9598b35SJames Idetype State = { 144d9598b35SJames Ide isPressed: boolean, 145d9598b35SJames Ide}; 146d9598b35SJames Ide 147d9598b35SJames Ideclass Button extends React.Component { 148d9598b35SJames Ide props: Props; 149d9598b35SJames Ide state: State = { 150d9598b35SJames Ide isPressed: true, 151d9598b35SJames Ide }; 152d9598b35SJames Ide 153d9598b35SJames Ide constructor(props, context) { 154d9598b35SJames Ide super(props, context); 155d9598b35SJames Ide this.state = { 156d9598b35SJames Ide ...this.state, 157d9598b35SJames Ide bounce: new Animated.Value(1), 158d9598b35SJames Ide }; 159d9598b35SJames Ide } 160d9598b35SJames Ide 161d9598b35SJames Ide componentWillUnmount() { 162d9598b35SJames Ide if (this.state.animation) { 163d9598b35SJames Ide this.state.animation.stop(); 164d9598b35SJames Ide } 165d9598b35SJames Ide } 166d9598b35SJames Ide 167d9598b35SJames Ide render() { 168d9598b35SJames Ide return ( 169d9598b35SJames Ide <Animated.View 170d9598b35SJames Ide onPress={this._handlePress} 171d9598b35SJames Ide style={{ transform: [{ scale: this.state.bounce }] }}> 172d9598b35SJames Ide <Text> 173d9598b35SJames Ide {this.props.title} 174d9598b35SJames Ide </Text> 175d9598b35SJames Ide </Animated.View> 176d9598b35SJames Ide ); 177d9598b35SJames Ide } 178d9598b35SJames Ide 179d9598b35SJames Ide _handlePress = event => { 180d9598b35SJames Ide this._bounce(); 181d9598b35SJames Ide if (this.props.onPress) { 182d9598b35SJames Ide this.props.onPress(event); 183d9598b35SJames Ide } 184d9598b35SJames Ide }; 185d9598b35SJames Ide 186d9598b35SJames Ide _bounce() { 187d9598b35SJames Ide this.setState(state => { 188d9598b35SJames Ide state.bounce.setValue(0); 189d9598b35SJames Ide let animation = Animated.spring(state.bounce, { toValue: 1 }); 190d9598b35SJames Ide animation.start(({ finished }) => { 191d9598b35SJames Ide if (finished) { 192d9598b35SJames Ide this.setState(() => ({ animation: null })); 193d9598b35SJames Ide } 194d9598b35SJames Ide }); 195d9598b35SJames Ide return { animation }; 196d9598b35SJames Ide }); 197d9598b35SJames Ide } 198d9598b35SJames Ide} 199275a6932SEvan Bacon```` 200d9598b35SJames Ide 201d9598b35SJames Ide# Naming 202d9598b35SJames Ide 203d9598b35SJames IdePrioritize the reader when naming things. Choosing a greppable name tends to have a lot of benefits since it’s easier to find how the thing with the name is used, easier to rename and refactor, and is less context-sensitive. 204d9598b35SJames Ide 205d9598b35SJames Ide```js 206d9598b35SJames Ideclass TestPipeline { 207d9598b35SJames Ide // PREFERRED 208d9598b35SJames Ide runTests() { ... } 209d9598b35SJames Ide 210d9598b35SJames Ide // DISFAVORED 211d9598b35SJames Ide run() { ... } 212d9598b35SJames Ide} 213d9598b35SJames Ide 214d9598b35SJames Ide// "runTests" is a lot easier to grep for than "run". It also plainly communicates 215d9598b35SJames Ide// more about what it does without being too wordy. 216d9598b35SJames Ide``` 217d9598b35SJames Ide 218d9598b35SJames Ide## Classes, functions, and variables 219d9598b35SJames Ide 220d9598b35SJames IdeUse camel case for all names. Capitalize the names of classes and constructor functions. Start other names with lowercase. 221d9598b35SJames Ide 222d9598b35SJames Ide```js 223d9598b35SJames Ide// CORRECT 224d9598b35SJames Ideclass Aquarium { 225d9598b35SJames Ide filterWater() {...} 226d9598b35SJames Ide} 227d9598b35SJames Ide 228d9598b35SJames Idefunction Fish() {...} 229d9598b35SJames IdeObject.assign(Fish.prototype, ...); 230d9598b35SJames Ide 231d9598b35SJames Idefunction populateAquarium(aquarium, school) {...} 232d9598b35SJames Ide``` 233275a6932SEvan Bacon 234d9598b35SJames Ide```js 235d9598b35SJames Ide// INCORRECT 236d9598b35SJames Ideclass house { 237d9598b35SJames Ide CloseWindows() {...} 238d9598b35SJames Ide} 239d9598b35SJames Ide 240d9598b35SJames Idefunction EstimatePrice(house) {...} 241d9598b35SJames Ide``` 242d9598b35SJames Ide 243d9598b35SJames Ide## Async functions 244d9598b35SJames Ide 245d9598b35SJames IdeName async functions and other functions that return promises with “Async” at the end if they may complete asynchronously. This communicates that the function does work (often I/O) asynchronously and we need to await its result. 246d9598b35SJames Ide 247d9598b35SJames Ide```js 248d9598b35SJames Ide// CORRECT 249d9598b35SJames Ideasync function fetchAccountAsync(accountId: ID): Promise<Account> { ... } 250d9598b35SJames Ide``` 251d9598b35SJames Ide 252d9598b35SJames IdeIt doesn’t matter how the function creates a promise for its asynchronous work. If the function isn’t defined with the `async` keyword but still looks like an async function from its call site, use the same naming convention. 253d9598b35SJames Ide 254d9598b35SJames Ide```js 255d9598b35SJames Ide// CORRECT 256d9598b35SJames Idefunction readSettingsFileAsync(): Promise<string> { 257d9598b35SJames Ide return Promise((resolve, reject) => { 258d9598b35SJames Ide fs.readFile('settings.txt', 'utf8', ...); 259d9598b35SJames Ide }); 260d9598b35SJames Ide} 261d9598b35SJames Ide``` 262d9598b35SJames Ide 263d9598b35SJames IdeHowever, if a function does synchronous work but still returns a promise, it might make sense to omit the “Async” suffix. 264d9598b35SJames Ide 265d9598b35SJames Ide```jsx 266d9598b35SJames Ide// OK 267d9598b35SJames Idefunction multiplexPromises(promises: Promise<*>[]): Promise<Array<* | Error>> { 268d9598b35SJames Ide // Given an array of promises, returns a promise that resolves to an array of 269d9598b35SJames Ide // promise results or errors. Semantically, this function doesn't do asynchronous 270d9598b35SJames Ide // work itself and the reader sees it operates on promises that do the actual work. 271d9598b35SJames Ide} 272d9598b35SJames Ide``` 273d9598b35SJames Ide 274d9598b35SJames Ide## Private variables 275d9598b35SJames Ide 276d9598b35SJames IdeUse an underscore to prefix instance variables that are intended to be private. This strikes a nice balance between communicating that the variable stores private data while keeping it accessible in a simple way for debugging, tests, and (sparingly) patches. 277d9598b35SJames Ide 278d9598b35SJames Ide```js 279d9598b35SJames Ide// CORRECT 280d9598b35SJames Ideclass Counter { 281d9598b35SJames Ide _currentNumber = 0; 282d9598b35SJames Ide getNextNumber() { ... } 283d9598b35SJames Ide} 284d9598b35SJames Ide``` 285d9598b35SJames Ide 286d9598b35SJames IdeIf it helps, use this same convention on variables that are internal to a module to make it clearer to readers which variables are defined and used only within the current module. 287d9598b35SJames Ide 288d9598b35SJames Ide```js 289d9598b35SJames Ide// CORRECT 290d9598b35SJames Ideexport default function prettyPrintAll(values) { 291d9598b35SJames Ide for (let value of values) { 292d9598b35SJames Ide _prettyPrint(value); 293d9598b35SJames Ide } 294d9598b35SJames Ide} 295d9598b35SJames Ide 296d9598b35SJames Idefunction _prettyPrint(value) { ... } 297d9598b35SJames Ide``` 298d9598b35SJames Ide 299d9598b35SJames Ide## Boolean names 300d9598b35SJames Ide 301d9598b35SJames IdeIf it helps, consider naming Boolean variables with “is” or a similar verb at the beginning. Sometimes the names of Boolean variables can ambiguously describe an object (or program state) or reference an object, and using verbs like “is”, “was”, and “did” help communicate the variable’s purpose. 302d9598b35SJames Ide 303d9598b35SJames Ide```js 304d9598b35SJames Ide// AMBIGUOUS 305d9598b35SJames Ideconsole.log(history.deleted); 306d9598b35SJames Ide 307d9598b35SJames Ide// CLEAR 308d9598b35SJames Ideconsole.log(history.isDeleted); 309d9598b35SJames Ide``` 310d9598b35SJames Ide 311d6435c85SJames Ide# Declarations 312d6435c85SJames Ide 313d6435c85SJames Ide## `let` and `const` 314d6435c85SJames Ide 315d6435c85SJames IdeWrite `const` where possible and `let` when you need to reassign a variable. This is simple to explain to developers working on Expo and easy to enforce with a linter. We are optimizing for a holistic combination of code quality and the attention we spend writing and reviewing code. 316d6435c85SJames Ide 31766af3ddcSJames IdeWhen optimizing for code quality alone, it demonstrates clearer thinking to use `const` to communicate when a variable stores a constant, rather than when a variable just happens not to be reassigned at this point in the code's lifetime, and `let` otherwise. 318d6435c85SJames Ide 31966af3ddcSJames IdeHowever, when optimizing for writing and reviewing code, using `const` when possible is easy to enforce and auto-fix with a linter. This guidance is also very easy to explain to developers and streamlines code reviews since the author does not need to carefully choose between `let` and `const` depending on semantic correctness. 320d6435c85SJames Ide 32166af3ddcSJames IdeSo, overall, we're trading an acceptable amount of code quality in exchange for reducing our attention cost by writing `const` by default and `let` when needed. 322d6435c85SJames Ide 323d9598b35SJames Ide# Examples 324275a6932SEvan Bacon 325d9598b35SJames Ide```js 326d9598b35SJames Ideimport Expo from 'expo'; 327d9598b35SJames Ideimport PropTypes from 'prop-types'; 328d9598b35SJames Ideimport React from 'react'; 329d9598b35SJames Ideimport { StyleSheet, Text } from 'react-native'; 330d9598b35SJames Ide 331d9598b35SJames Ideimport Log from '../log/Log'; 332d9598b35SJames Ideimport Colors from '../style/Colors'; 333d9598b35SJames Ide 334d9598b35SJames Ideexport default class GreetingText extends React.PureComponent { 335d9598b35SJames Ide static propTypes = { 336d9598b35SJames Ide greeting: PropTypes.string.isRequired, 337d9598b35SJames Ide ...Text.propTypes, 338d9598b35SJames Ide }; 339d9598b35SJames Ide 340d9598b35SJames Ide componentDidUpdate() { 341d9598b35SJames Ide Log.info('The greeting was re-rendered'); 342d9598b35SJames Ide } 343d9598b35SJames Ide 344d9598b35SJames Ide render() { 345d9598b35SJames Ide let { greeting, style, ...props } = this.props; 346d9598b35SJames Ide return ( 347275a6932SEvan Bacon <Text {...props} onPress={this._handlePress} style={[styles.greeting, style]}> 348d9598b35SJames Ide {greeting} 349d9598b35SJames Ide </Text> 350d9598b35SJames Ide ); 351d9598b35SJames Ide } 352d9598b35SJames Ide 353275a6932SEvan Bacon _handlePress = event => { 354d9598b35SJames Ide alert('Congratulations!'); 355d9598b35SJames Ide }; 356d9598b35SJames Ide} 357d9598b35SJames Ide 358d9598b35SJames Ideconst styles = StyleSheet.create({ 359d9598b35SJames Ide greeting: { 360d9598b35SJames Ide color: Colors.energetic, 361d9598b35SJames Ide fontSize: 30, 362d9598b35SJames Ide }, 363d9598b35SJames Ide}); 364d9598b35SJames Ide``` 365d9598b35SJames Ide 366d9598b35SJames Ide# Babel 367d9598b35SJames Ide 368d9598b35SJames IdeWe use Babel to enable some of the newer JavaScript features that are sufficiently stable for us. This mostly includes transforms for features that are in a finalized version of the JavaScript standard. 369d9598b35SJames Ide 370d9598b35SJames IdeWe use `babel-eslint`, which allows ESLint to use the Babel parser. In practice, with newer syntax extensions, Babel produces AST nodes that ESLint can’t consume; stable linter compatibility is another feature we look for in Babel plugins. 371