App

所属分类:自动编程
开发工具:JavaScript
文件大小:15643KB
下载次数:0
上传日期:2023-06-14 01:27:09
上 传 者sh-1993
说明:  欢迎来到New Expensify:以聊天为中心,对金融合作的完全重新想象。帮助我们建立...
(Welcome to New Expensify: a complete re-imagination of financial collaboration, centered around chat. Help us build the next generation of Expensify by sharing feedback and contributing to the code.)

文件列表:
.buckconfig (114, 2023-09-22)
.bundle (0, 2023-09-22)
.bundle\config (59, 2023-09-22)
.env.example (1011, 2023-09-22)
.env.production (330, 2023-09-22)
.env.staging (335, 2023-09-22)
.eslintignore (57, 2023-09-22)
.eslintrc.js (7456, 2023-09-22)
... ...

#### Table of Contents * [Local Development](https://github.com/Expensify/App/blob/master/#local-development) * [Running The Tests](https://github.com/Expensify/App/blob/master/#running-the-tests) * [Debugging](https://github.com/Expensify/App/blob/master/#debugging) * [App Structure and Conventions](https://github.com/Expensify/App/blob/master/#app-structure-and-conventions) * [Philosophy](https://github.com/Expensify/App/blob/master/#Philosophy) * [Internationalization](https://github.com/Expensify/App/blob/master/#Internationalization) * [Deploying](https://github.com/Expensify/App/blob/master/#deploying) #### Additional Reading * [API Details](https://github.com/Expensify/App/blob/master/contributingGuides/API.md) * [Offline First](https://github.com/Expensify/App/blob/master/contributingGuides/OFFLINE_UX.md) * [Contributing to Expensify](https://github.com/Expensify/App/blob/master/contributingGuides/CONTRIBUTING.md) * [Expensify Code of Conduct](https://github.com/Expensify/App/blob/master/CODE_OF_CONDUCT.md) * [Contributor License Agreement](https://github.com/Expensify/App/blob/master/contributingGuides/CLA.md) ---- # Local development These instructions should get you set up ready to work on New Expensify ## Getting Started 1. Install `nvm` then `node` & `npm`: `brew install nvm && nvm install` 2. Install `watchman`: `brew install watchman` 3. Install dependencies: `npm install` You can use any IDE or code editing tool for developing on any platform. Use your favorite! ## Recommended `node` setup In order to have more consistent builds, we use a strict `node` and `npm` version as defined in the `package.json` `engines` field and `.nvmrc` file. `npm install` will fail if you do not use the version defined, so it is recommended to install `node` via `nvm` for easy node version management. Automatic `node` version switching can be installed for [`zsh`](https://github.com/Expensify/App/blob/master/https://github.com/nvm-sh/nvm#zsh) or [`bash`](https://github.com/Expensify/App/blob/master/https://github.com/nvm-sh/nvm#bash) using `nvm`. ## Running the web app * To run the **development web app**: `npm run web` * Changes applied to Javascript will be applied automatically via WebPack as configured in `webpack.dev.js` ## Running the iOS app “± For an M1 Mac, read this [SO](https://github.com/Expensify/App/blob/master/https://stackoverflow.com/questions/***901180/how-to-run-cocoapods-on-apple-silicon-m1) for installing cocoapods. * Install project gems, including cocoapods, using bundler to ensure everyone uses the same versions. In the project root, run: `bundle install` * If you get the error `Could not find 'bundler'`, install the bundler gem first: `gem install bundler` and try again. * If you are using MacOS and get the error `Gem::FilePermissionError` when trying to install the bundler gem, you're likely using system Ruby, which requires administrator permission to modify. To get around this, install another version of Ruby with a version manager like [rbenv](https://github.com/Expensify/App/blob/master/https://github.com/rbenv/rbenv#installation). * Before installing iOS dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. * To install the iOS dependencies, run: `npm install && npm run pod-install` * If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://github.com/Expensify/App/blob/master/https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Simulator**: `npm run ios` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile ## Running the Android app ¤– * Before installing Android dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. If you already did this step for iOS, there is no need to repeat this step. * Go through the instructions on [this SO post](https://github.com/Expensify/App/blob/master/https://stackoverflow.com/c/expensify/questions/13283/13284#13284) to start running the app on android. * For more information, go through the official React-Native instructions on [this page](https://github.com/Expensify/App/blob/master/https://reactnative.dev/docs/environment-setup#development-os) for "React Native CLI Quickstart" > Mac OS > Android * If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://github.com/Expensify/App/blob/master/https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Emulator**: `npm run android` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile ## Running the MacOS desktop app – * To run the **Development app**, run: `npm run desktop`, this will start a new Electron process running on your MacOS desktop in the `dist/Mac` folder. ## Troubleshooting 1. If you are having issues with **_Getting Started_**, please reference [React Native's Documentation](https://github.com/Expensify/App/blob/master/https://reactnative.dev/docs/environment-setup) 2. If you are running into CORS errors like (in the browser dev console) ```sh Access to fetch at 'https://www.expensify.com/api?command=BeginSignIn' from origin 'http://localhost:8080' has been blocked by CORS policy ``` You probably have a misconfigured `.env` file - remove it (`rm .env`) and try again **Note:** Expensify engineers that will be testing with the API in your local dev environment please refer to [these additional instructions](https://github.com/Expensify/App/blob/master/https://stackoverflow.com/c/expensify/questions/7699/7700). ## Environment variables Creating an `.env` file is not necessary. We advise external contributors against it. It can lead to errors when variables referenced here get updated since your local `.env` file is ignored. - `NEW_EXPENSIFY_URL` - The root URL used for the website - `SECURE_EXPENSIFY_URL` - The URL used to hit the Expensify secure API - `EXPENSIFY_URL` - The URL used to hit the Expensify API - `EXPENSIFY_PARTNER_NAME` - Constant used for the app when authenticating. - `EXPENSIFY_PARTNER_PASSWORD` - Another constant used for the app when authenticating. (This is OK to be public) - `PUSHER_APP_KEY` - Key used to authenticate with Pusher.com - `SECURE_NGROK_URL` - Secure URL used for `ngrok` when testing - `NGROK_URL` - URL used for `ngrok` when testing - `USE_NGROK` - Flag to turn `ngrok` testing on or off - `USE_WDYR` - Flag to turn [`Why Did You Render`](https://github.com/Expensify/App/blob/master/https://github.com/welldone-software/why-did-you-render) testing on or off - `USE_WEB_PROXY`- Used in web/desktop development, it starts a server along the local development server to proxy requests to the backend. External contributors should set this to `true` otherwise they'll have CORS errors. If you don't want to start the proxy server set this explicitly to `false` - `CAPTURE_METRICS` (optional) - Set this to `true` to capture performance metrics and see them in Flipper see [PERFORMANCE.md](https://github.com/Expensify/App/blob/master/contributingGuides/PERFORMANCE.md#performance-metrics-opt-in-on-local-release-builds) for more information - `ONYX_METRICS` (optional) - Set this to `true` to capture even more performance metrics and see them in Flipper see [React-Native-Onyx#benchmarks](https://github.com/Expensify/App/blob/master/https://github.com/Expensify/react-native-onyx#benchmarks) for more information - `E2E_TESTING` (optional) - This needs to be set to `true` when running the e2e tests for performance regression testing. This happens usually automatically, read [this](https://github.com/Expensify/App/blob/master/tests/e2e/README.md) for more information ---- # Running the tests ## Unit tests Unit tests are valuable when you want to test one component. They should be short, fast, and ideally only test one thing. Often times in order to write a unit test, you may need to mock data, a component, or library. We use the library [Jest](https://github.com/Expensify/App/blob/master/https://jestjs.io/) to help run our Unit tests. * To run the **Jest unit tests**: `npm run test` ---- # Debugging ### iOS 1. If running on the iOS simulator pressing `D` will open the debugging menu. 2. This will allow you to attach a debugger in your IDE, React Developer Tools, or your browser. 3. For more information on how to attach a debugger, see [React Native Debugging Documentation](https://github.com/Expensify/App/blob/master/https://reactnative.dev/docs/debugging#chrome-developer-tools) Alternatively, you can also set up debugger using [Flipper](https://github.com/Expensify/App/blob/master/https://fbflipper.com/). After installation, press `D` and select "Open Debugger". This will open Flipper window. To view data stored by Onyx, go to Plugin Manager and install `async-storage` plugin. ## Android Our React Native Android app now uses the `Hermes` JS engine which requires your browser for remote debugging. These instructions are specific to Chrome since that's what the Hermes documentation provided. 1. Navigate to `chrome://inspect` 2. Use the `Configure...` button to add the Metro server address (typically `localhost:8081`, check your `Metro` output) 3. You should now see a "Hermes React Native" target with an "inspect" link which can be used to bring up a debugger. If you don't see the "inspect" link, make sure the Metro server is running 4. You can now use the Chrome debug tools. See [React Native Debugging Hermes](https://github.com/Expensify/App/blob/master/https://reactnative.dev/docs/hermes#debugging-hermes-using-google-chromes-devtools) ## Web To make it easier to test things in web, we expose the Onyx object to the window, so you can easily do `Onyx.set('bla', 1)`. --- # App Structure and Conventions ## Onyx This is a persistent storage solution wrapped in a Pub/Sub library. In general that means: - Onyx stores and retrieves data from persistent storage - Data is stored as key/value pairs, where the value can be anything from a single piece of data to a complex object - Collections of data are usually not stored as a single key (eg. an array with multiple objects), but as individual keys+ID (eg. `report_1234`, `report_4567`, etc.). Store collections as individual keys when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `OptionRow.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action. - Onyx allows other code to subscribe to changes in data, and then publishes change events whenever data is changed - Anything needing to read Onyx data needs to: 1. Know what key the data is stored in (for web, you can find this by looking in the JS console > Application > IndexedDB > OnyxDB > keyvaluepairs) 2. Subscribe to changes of the data for a particular key or set of keys. React components use `withOnyx()` and non-React libs use `Onyx.connect()` 3. Get initialized with the current value of that key from persistent storage (Onyx does this by calling `setState()` or triggering the `callback` with the values currently on disk as part of the connection process) - Subscribing to Onyx keys is done using a constant defined in `ONYXKEYS`. Each Onyx key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like `report_1234`, if code needs to know about all the reports (eg. display a list of them in the nav menu), then it would subscribe to the key `ONYXKEYS.COLLECTION.REPORT`. ## Actions Actions are responsible for managing what is on disk. This is usually: - Subscribing to Pusher events to receive data from the server that will get put immediately into Onyx - Making XHRs to request necessary data from the server and then immediately putting that data into Onyx - Handling any business logic with input coming from the UI layer ## The UI layer This layer is solely responsible for: - Reflecting exactly the data that is in persistent storage by using `withOnyx()` to bind to Onyx data. - Taking user input and passing it to an action As a convention, the UI layer should never interact with device storage directly or call `Onyx.set()` or `Onyx.merge()`. Use an action! For example, check out this action that is signing in the user [here](https://github.com/Expensify/App/blob/master/https://github.com/Expensify/App/blob/919c890cc391ad38b670ca1b266c114c8b3c3285/src/pages/signin/PasswordForm.js#L78-L78). ```js validateAndSubmitForm() { // validate... signIn(this.state.password, this.state.twoFactorAuthCode); } ``` That action will then call `Onyx.merge()` to [set default data and a loading state, then make an API request, and set the response with another `Onyx.merge()`](https://github.com/Expensify/App/blob/master/https://github.com/Expensify/App/blob/919c890cc391ad38b670ca1b266c114c8b3c3285/src/libs/actions/Session.js#L228-L247). ```js function signIn(password, twoFactorAuthCode) { Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: true}); Authentication.Authenticate({ ...defaultParams, password, twoFactorAuthCode, }) .then((response) => { Onyx.merge(ONYXKEYS.SESSION, {authToken: response.authToken}); }) .catch((error) => { Onyx.merge(ONYXKEYS.ACCOUNT, {error: error.message}); }) .finally(() => { Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false}); }); } ``` Keeping our `Onyx.merge()` out of the view layer and in actions helps organize things as all interactions with device storage and API handling happen in the same place. In addition, actions that are called from inside views should not ever use the `.then()` method to set loading/error states, navigate or do any additional data processing. All of this stuff should ideally go into `Onyx` and be fed back to the component via `withOnyx()`. Design your actions so they clearly describe what they will do and encapsulate all their logic in that action. ```javascript // Bad validateAndSubmitForm() { // validate... this.setState({isLoading: true}); signIn() .then((response) => { if (result.jsonCode === 200) { return; } this.setState({error: response.message}); }) .finally(() => { this.setState({isLoading: false}); }); } // Good validateAndSubmitForm() { // validate... signIn(); } ``` ## Directory structure Almost all the code is located in the `src` folder, inside it there's some organization, we chose to name directories that are created to house a collection of items in plural form and using camelCase (eg: pages, libs, etc), the main ones we have for now are: - components: React native components that are re-used in several places. - libs: Library classes/functions, these are not React native components (ie: they are not UI) - pages: These are components that define pages in the app. The component that defines the page itself should be named `Page` if there are components used only inside one page, they should live in its own directory named after the `` - styles: These files define styles used among components/pages - contributingGuides: This is just a set of markdown files providing guides and insights to aid developers in learning how to contribute to this repo **Note:** There is also a directory called `/docs`, which houses the Expensify Help site. It's a static site that's built with Jekyll and hosted on GitHub Pages. ## File naming/structure Files should be named after the component/function/constants they export, respecting the casing used for it. ie: - If you export a constant named `CONST`, its file/directory should be named the `CONST`. - If you export a component named `Text`, the file/directory should be named `Text`. - If you export a function named `guid`, the file/directory should be named `guid`. - For files that are utilities that export several functions/classes use the UpperCamelCase version ie: `DateUtils`. - [Higher-Order Components](https://github.com/Expensify/App/blob/master/https://reactjs.org/docs/higher-order-components.html) (HOCs) should be named in camelCase, like `withOnyx`. - All React components should be PascalCase (a.k.a. UpperCamelCase ). ## Platform-Specific File Extensions In most cases, the code written for this repo should be platform-independent. In such cases, each module should have a single file, `index.js`, which defines the module's exports. There are, however, some cases in which a feature is intrinsically tied to the underlying platform. In such cases, the following file extensions can be used to export platform-specific code from a module: - Mobile => `index.native.js` - iOS Native App/Android Native App => `index.ios.js`/`index.android.js` - Web => `index.website.js` - Desktop => `index.desktop.js` Note that `index.js` should be the default and only platform-specific implementations should be done in their respective files. i.e: If you have mobile-specific implementation in `index.native.js`, then the desktop/web implementation can be contained in a shared `index.js`. `index.ios.js` and `index.android.js` are used when the app is running natively on respective platforms. These files are not used when users access the app through mobile browsers, but `index.website.js` is used instead. `index.native.js` are for both iOS and Android native apps. `index.native.js` should not be included in the same module as `index.ios.js` or `index.android.js`. ## API building When adding new API commands (and preferably when starting using a new one that was not yet used in this codebase) always prefer to return the created/updated data in the command itself, instead of saving and reloading. ie: if we call `CreateTransaction`, we should prefer making `CreateTransaction` return the data it just created instead of calling `CreateTransaction` then `Get` rvl=transactionList ## Storage Eviction Different platforms come with varying storage capacities and Onyx has a way to gracefully fail when those storage limits are encountered. When Onyx fails to set or modify a key the following steps are taken: 1. Onyx looks at a list of recently accessed keys (access is defined as subscribed to or modified) and locates the key that was least recently accessed 2. It then deletes this key and retries the original operation By default, Onyx will not evict anything from storage and will presume all keys are "unsafe" to remove unless explicitly told otherwise. **To flag a key as safe for removal:** - Add the key to the `safeEvictionKeys` option in `Onyx.init(options)` - Implement `canEvict` in the Onyx config for each component subscribing to a key - The key will only be deleted when all subscribers return `true` for `canEvict` e.g. ```js Onyx.init({ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], }); ``` ```js export default withOnyx({ reportActions: { key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, canEvict: props => !props.isActiveReport, }, })(ReportActionsView); ``` ## Things to know or brush up on before jumping into the code 1. The major difference between React Native and React are the [components](https://github.com/Expensify/App/blob/master/https://reactnative.dev/docs/components-and-apis) that are used in the `render()` method. Everything else is exactly the same. Any React skills you have can be applied to React Native. 1. The application uses [`react-navigation`](https://github.com/Expensify/App/blob/master/https://reactnavigation.org/) for navigating between parts of the app. 1. [Higher Order Components](https://github.com/Expensify/App/blob/master/https://reactjs.org/docs/higher-order-components.html) are used to connect React components to persistent storage via [`react-native-onyx`](https://github.com/Expensify/App/blob/master/https://github.com/Expensify/react-native-onyx). ---- # Philosophy This application is built with the following principles. 1. **Data Flow** - Ideally, this is how data flows through the app: 1. Server pushes data to the disk of any client (Server -> Pusher event -> Action listening to pusher event -> Onyx). >**Note:** Currently the code only does this with report comments. Until we make more server changes, this step is actually done by the client requesting data from the server via XHR and then storing the response in Onyx. 2. Disk pushes data to the UI (Onyx -> withOnyx() -> React component). 3. UI pushes data to people's brains (React component -> device screen). 4. Brain pushes data into UI inputs (D ... ...

近期下载者

相关文件


收藏者