├── .circleci └── config.yml ├── .github └── ISSUE_TEMPLATE │ ├── ask_a_question.md │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ └── index-test.js.snap └── index-test.js ├── index.js ├── package-lock.json ├── package.json └── samples ├── react-native-app ├── .buckconfig ├── .gitattributes ├── .gitignore ├── App.js ├── Container.js ├── README.md ├── __tests__ │ └── App.js ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── build_defs.bzl │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── reactnativeapp │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── drawable │ │ │ ├── splashscreen.xml │ │ │ └── splashscreen_image.png │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios │ ├── Podfile │ ├── Podfile.lock │ ├── reactnativeapp.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── reactnativeapp.xcscheme │ ├── reactnativeapp.xcworkspace │ │ └── contents.xcworkspacedata │ └── reactnativeapp │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── SplashScreen.imageset │ │ │ ├── Contents.json │ │ │ └── splashscreen.png │ │ ├── Info.plist │ │ ├── SplashScreen.storyboard │ │ ├── Supporting │ │ └── Expo.plist │ │ └── main.m ├── metro.config.js ├── package-lock.json └── package.json ├── single-page-app ├── .gitignore ├── README.md ├── config │ ├── env.js │ ├── getHttpsConfig.js │ ├── jest │ │ ├── babelTransform.js │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── modules.js │ ├── paths.js │ ├── webpack.config.js │ ├── webpack │ │ └── persistentCache │ │ │ └── createEnvironmentHash.js │ └── webpackDevServer.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── scripts │ ├── build.js │ ├── start.js │ └── test.js └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── Image.js │ ├── Redirect.js │ ├── Text.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ └── setupTests.js └── styled-components ├── .babelrc ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── components │ ├── App.css │ └── App.js ├── index.html └── index.js └── webpack.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | executors: 3 | browser-executor: 4 | docker: 5 | - image: cimg/node:16.20-browsers 6 | working_directory: ~/repo 7 | jobs: 8 | build-babel-plugin: 9 | executor: browser-executor 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | keys: 14 | - v1-dependencies-{{ checksum "package.json" }} 15 | - run: 16 | name: Install 17 | command: npm ci 18 | - save_cache: 19 | paths: 20 | - node_modules 21 | key: v1-dependencies-{{ checksum "package.json" }} 22 | - run: 23 | name: Audit 24 | command: npm audit --audit-level=low --omit=dev 25 | - run: 26 | name: Test 27 | command: npm run test 28 | - persist_to_workspace: 29 | root: . 30 | paths: [.] 31 | deploy-babel-plugin: 32 | executor: browser-executor 33 | steps: 34 | - attach_workspace: 35 | at: ~/repo 36 | - run: 37 | name: Authenticate with registry 38 | command: echo "//registry.npmjs.org/:_authToken=$npm_TOKEN" > .npmrc 39 | - run: 40 | name: Publish package 41 | command: npm publish --access public 42 | workflows: 43 | version: 2 44 | daily-run-workflow: 45 | when: 46 | and: 47 | - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] 48 | - equal: [ "daily build", << pipeline.schedule.name >> ] 49 | jobs: 50 | - build-babel-plugin 51 | test-deploy: 52 | when: 53 | not: 54 | equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] 55 | jobs: 56 | - build-babel-plugin: 57 | filters: 58 | tags: 59 | only: /.*/ 60 | - hold: 61 | type: approval 62 | requires: 63 | - build-babel-plugin 64 | filters: 65 | tags: 66 | only: /^v.*/ 67 | branches: 68 | ignore: /.*/ 69 | - deploy-babel-plugin: 70 | requires: 71 | - hold 72 | filters: 73 | tags: 74 | only: /^v.*/ 75 | branches: 76 | ignore: /.*/ 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask_a_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Ask a question for FullStory or the community 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Your Questions** 11 | A clear and concise description of your question and what you are trying to achieve. 12 | 13 | **Context** 14 | A broader context in which you’re asking the question. Please provide any context that might be helpful for the recipient to know. This may include your use case or a description of an unexpected behavior. 15 | 16 | **Attempts** 17 | Any attempts you've made to try to answer the question. 18 | 19 | **Additional comments** 20 | Add any other comments about the issue. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. Please include any code snippets or a link to your project repository that demonstrates the issue. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Actual behavior** 20 | A clear and concise description of what actually happened. 21 | 22 | **Node env (please complete the following information):** 23 | - fullstory-babel-plugin-annotate-react [Package version](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tags) [e.g. `v.2.3.0`] 24 | - Dev OS: [e.g. MacOS] 25 | - Node version (`node -v`) [e.g. `v18.4.0`] 26 | - NPM version (`npm -v`) [e.g. `8.12.1`] 27 | 28 | **Additional comments** 29 | Add any other comments about the issue. 30 | 31 | **Logs** 32 | If applicable, add any relevant errors or logs to help explain your problem. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to an issue? Please describe.** 11 | A clear and concise description of what the issue is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | samples 2 | __tests__ 3 | .circleci 4 | .gitignore 5 | node_modules 6 | 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.3.2 4 | - Added react-native-navigation and expo-router to incompatible node modules list. 5 | 6 | ## 2.3.1 7 | - Update some dependencies 8 | 9 | ## 2.3.0 10 | 11 | - Add [setFSTagName](./README.md#setfstagname-setting) configuration for React Native. 12 | 13 | ## 2.2.2 14 | 15 | - Fixed annotating short syntax Fragments when enabled. 16 | 17 | ## 2.2.1 18 | 19 | - Added a blocklist for known-incompatible node modules. 20 | 21 | ## 2.2.0 22 | 23 | - Added the ability to skip annotating certain elements. See the README for usage. 24 | 25 | ## 2.1.2 26 | 27 | - Version bump to trigger a green build 28 | 29 | ## 2.1.1 30 | 31 | - Fixed a bug where component file names were wrong on Windows builds 32 | 33 | ## 2.1.0 34 | 35 | - Annotating React.Fragments as a configurable option 36 | 37 | ## 2.0.1 38 | 39 | - Readme update 40 | 41 | ## 2.0.0 42 | 43 | - React.Fragments are no longer annotated 44 | - Updated to new dependency versions 45 | 46 | ## 1.0.2 47 | 48 | - Added a homepage link to package.json 49 | 50 | ## 1.0.1 51 | 52 | - Added an npm ignore list so that samples and tests aren't installed by npm. 53 | - Tweaked the explanation of `data-element` in the main README. 54 | - Stopped Jest from runing sample tests when testing the plugin. 55 | 56 | ## 1.0.0 57 | 58 | Initial Release 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to fullstory-babel-plugin-annotate-react 2 | 3 | Thanks for taking the time to contribute! 4 | 5 | Here are some important resources to consider: 6 | 7 | * [README](./README.md) 8 | * [FullStory's Annotate React plugin for Web & Native](https://help.fullstory.com/hc/articles/360049493054-FullStory-s-Annotate-React-plugin-for-Web-Native) 9 | * [Getting Started with FullStory React Native Capture](https://help.fullstory.com/hc/articles/360052419133) 10 | * [FullStory Help Center](https://help.fullstory.com/) 11 | * [FullStory Community](https://community.fullstory.com/) 12 | * [FullStory Developer Guide](https://developer.fullstory.com/) 13 | 14 | All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help. 15 | 16 | ## Table of Contents 17 | 18 | - [I Have a Question](#i-have-a-question) 19 | - [I Want To Contribute](#i-want-to-contribute) 20 | - [Making Code Changes](#making-code-changes) 21 | - [Reporting Bugs](#reporting-bugs) 22 | - [Suggesting Enhancements](#suggesting-enhancements) 23 | - [Pull Request Process](#pull-request-process) 24 | 25 | ## I Have a Question 26 | 27 | > Search for similar questions in our [Community Portal](https://community.fullstory.com/) and browse through our [Help Center](https://help.fullstory.com/) before asking a question. 28 | 29 | If you then still feel the need to ask a question and need clarification, we recommend: 30 | 31 | - Creating a new [GitHub issue](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/issues/new?template=ask_a_question.md), filling in the templated description. 32 | - Alternatively, you can submit your questions to our [Community](https://community.fullstory.com/). 33 | - Please provide as much context as you can with your question. 34 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant to your issue. 35 | 36 | ## I Want To Contribute 37 | 38 | ### Making Code Changes 39 | 40 | #### Preparing your environment 41 | 42 | Before getting started, it is important to configure your development environment and all dependencies correctly. 43 | 1. The `fullstory-babel-plugin-annotate-react` uses node 12. 44 | 1. Install [node 12 from the official website](https://nodejs.org/en/blog/release/v12.13.0) or use the [node version manager (nvm)](https://github.com/nvm-sh/nvm) to manage different versions of node on your machine. 45 | 2. Clone the repository using HTTPS, SSH, or the Github CLI. 46 | 1. **HTTPS**: `git clone https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react.git` 47 | 2. **SSH**: `git clone git@github.com:fullstorydev/fullstory-babel-plugin-annotate-react.git` 48 | 3. **GitHub CLI**: `gh repo clone fullstorydev/fullstory-babel-plugin-annotate-react` 49 | 3. Install dependencies from the root directory of the project using `npm i` 50 | 51 | #### Testing your changes 52 | 53 | We use Jest as our test runner to verify changes and behaviors in our package. We use both unit tests and snapshot tests to prevent regressions in our code base. In order to run the jest tests you must have prepared your [local environment](#preparing-your-environment). 54 | 55 | You can run the tests from the project root directory using `npm run test`. 56 | 57 | #### Sample Projects 58 | 59 | Use one of the available [sample apps](/samples/) if you would like to see your changes reflected in a live application. Run `npm i` in the sample application folder before serving the application. For more information on available commands for a sample application, read the scripts section of the corresponding application's `package.json` file. 60 | 61 | 1. [React Native App](/samples/react-native-app/package.json) 62 | 2. [Single Page App](/samples/single-page-app/package.json) 63 | 3. [Styled Components](/samples/styled-components/package.json) 64 | 65 | ### Reporting Bugs 66 | 67 | #### Before Submitting a Bug Report 68 | 69 | Please complete the following steps in advance to help us fix any potential bugs as fast as possible. 70 | 71 | - Make sure that you are using the latest version. 72 | - Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions. (If you are looking for support, you might want to check [this section](#i-have-a-question)). 73 | - Check if there is already an [open issue](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/issues?q=is%3Aopen+is%3Aissue+label%3Abug) for the problem you're encountering. 74 | - Also make sure to search the internet (including Stack Overflow) to see if other users have discussed the issue. 75 | - Collect information about the bug and create a minimal reproducible example. 76 | - Include sample source code or other data files and document the expected behavior. 77 | - The inputs and the outputs used to test your sample code. 78 | - Include a stack trace or traceback. 79 | - Describe the OS, Platform and Version (Windows, Linux, macOS, Android, iOS, x86, ARM). 80 | - Describe the current version of the `fullstory-babel-plugin-annotate-react` package that you are using. 81 | - Can you reliably reproduce the issue? And can you also reproduce it with older versions? 82 | 83 | #### How Do I Submit a Good Bug Report? 84 | 85 | > You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead follow the instructions outlined by our [Security Incident & Coordinated Vulnerability Disclosure](https://help.fullstory.com/hc/en-us/articles/360020624254-Security-Overview#h_01G9QN7Y3GYW36M01HG1RRTFXE) process or email [psirt@fullstory.com](mailto:psirt@fullstory.com). 86 | 87 | If you run into an issue with the project: 88 | - Create a new [GitHub issue](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/issues/new?&template=bug_report.md) with our bug report template. 89 | - Alternatively you can submit your request through the [FullStory Help Center](https://help.fullstory.com/hc/en-us/requests/new) with details about the error you're running into. 90 | - Provide the information you collected in the previous section. 91 | - For faster remediation you should isolate the problem and create a reduced test case or provide a minimal reproducible example. 92 | - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps. 93 | - If the team is able to reproduce the issue, it will be handled accordingly by our support team. We will do our best to maintain an open line of communication as we work on your request. 94 | 95 | 96 | ### Suggesting Enhancements 97 | 98 | This section guides you through submitting an enhancement suggestion such as a completely new feature or minor improvements to existing functionality. Following these guidelines will help maintainers to understand your suggestion. 99 | 100 | - Make sure that you are using the latest version. 101 | - Find out if the functionality is already covered. 102 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to convince the maintainers of the merits of this feature. We want features that will be useful to the majority of users. 103 | 104 | #### Creating a Feature Request 105 | 106 | Enhancements are suggested by creating a new [GitHub issue](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/issues/new?&template=feature_request.md) with our feature request template. 107 | 108 | - Use a descriptive title for the issue to identify the suggestion. 109 | - Provide a step-by-step description of the suggested enhancement, include screenshots and animated GIFs if necessary. 110 | - Describe the current behavior and explain which behavior you expected to see instead. 111 | - Describe alternatives to your solution and why the alternatives do not work for you. 112 | - Explain why this enhancement would be useful. 113 | 114 | ## Pull Request Process 115 | 116 | All pull requests should include a clear list of your changes (read more about [pull requests](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests)). Any pull request that makes functional changes to the library should also include unit tests. We can always use more test coverage. 117 | 118 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 119 | 120 | ``` 121 | $ git commit -m "A brief summary of the commit 122 | > 123 | > A paragraph describing what changed and its impact." 124 | ``` 125 | 126 | 1. Update the [README.md](./README.md) with details of changes to the interface, this includes new environment 127 | variables, exposed ports, useful file locations and container parameters. 128 | 2. Write a short description with your changes in the [CHANGELOG.md](./CHANGELOG.md). 129 | - The versioning scheme we use is [SemVer](http://semver.org/). 130 | 3. A project maintainer will merge your code if all tests pass and you've received an approval on the pull request of at least one project maintainer. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Engineering at FullStory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Babel Plugin: Annotate React 2 | [![CircleCI](https://circleci.com/gh/fullstorydev/fullstory-babel-plugin-annotate-react.svg?style=svg)](https://circleci.com/gh/fullstorydev/fullstory-babel-plugin-annotate-react) 3 | 4 | This is a Babel plugin that annotates React components with stable attributes that can be used to search and select using [FullStory](https://www.fullstory.com/). This is most useful when using a React system that generates dynamic names for Components or rearranges elements. 5 | 6 | # Getting started 7 | 8 | ## with npm 9 | 10 | ``` 11 | npm i @fullstory/babel-plugin-annotate-react --save 12 | ``` 13 | 14 | ## with yarn 15 | 16 | ``` 17 | yarn add @fullstory/babel-plugin-annotate-react 18 | ``` 19 | 20 | ## Babel configuration 21 | 22 | Add the `@fullstory/babel-plugin-annotate-react` plugin to your babel plugin configuration (e.g., **babel.config.js**). 23 | 24 | ``` 25 | module.exports = { 26 | /* ... */ 27 | plugins: [ 28 | /* ... */ 29 | "@fullstory/babel-plugin-annotate-react" 30 | ] 31 | }; 32 | ``` 33 | 34 | # Description 35 | 36 | For React on the web, the attributes `data-component`, `data-element`, and `data-source-file` are added to each element. For React Native, the attributes added are `dataComponent`, `dataElement`, and `dataSourceFile`. 37 | 38 | > Note that for both web and native, these attributes will appear as ***kebab-case*** in the Fullstory application due to backend processing. End users of Fullstory should create search filters (i.e. segments, metrics, etc) using ***kebab-case***. 39 | 40 | The component attribute names the `React.Component` and the element attribute names the original native elements like `View` or `Image` or an emitter of DOM elements like `Fragment`. 41 | 42 | Example input: 43 | 44 | class HelloComponent extends Component { 45 | render() { 46 | return
47 |

Hello world

48 |
; 49 | } 50 | } 51 | 52 | Example JS output: 53 | 54 | class HelloComponent extends Component { 55 | render() { 56 | return React.createElement("div", { 57 | "data-component": "HelloComponent", 58 | "data-file-source": "hello-component.js" 59 | }, React.createElement("h1", { 60 | null 61 | }, "Hello world")); 62 | } 63 | } 64 | 65 | Final render: 66 | 67 |
68 |

Hello world

69 |
70 | 71 | # Configuration options 72 | 73 | ## React Native 74 | 75 | To activate React Native support you must pass in a `native` plugin option like so: 76 | 77 | plugins: [ 78 | ["@fullstory/babel-plugin-annotate-react", { native: true }] 79 | ] 80 | 81 | See [Getting Started with FullStory React Native Capture](https://help.fullstory.com/hc/en-us/articles/360052419133-Getting-Started-with-FullStory-React-Native-Capture) for more info. 82 | 83 | ### `setFSTagName` setting 84 | 85 | When using this library with [FullStory for Mobile Apps](https://www.fullstory.com/platform/mobile-apps/), we recommend setting `setFSTagName: true` to generate better privacy selectors. This setting will automatically set [`fsTagName`](https://developer.fullstory.com/mobile/react-native/auto-capture/set-tag-name/) with the value of `dataElement` or `dataComponent`, which will truncate the privacy selector and avoid duplicate naming. 86 | 87 | Example: 88 | * Before `RCTSafeAreaView[data-source-file="App.tsx"][data-element="SafeAreaView"][data-component="App"]` 89 | * After `App[data-source-file="App.tsx"]` 90 | 91 | ``` 92 | plugins: [ 93 | '@fullstory/react-native', 94 | ["@fullstory/annotate-react", { 95 | native: true, 96 | setFSTagName: true, 97 | }] 98 | ] 99 | ``` 100 | 101 | ⚠️ Important: Existing FullStory privacy selectors and defined elements may need to be updated if the app was previously published without `setFSTagName: true`. 102 | 103 | 104 | 105 | ## Fragments 106 | 107 | By default, the plugin does not annotate `React.Fragment`s because they may or may not contain a child that ends up being an HTML element. 108 | 109 | An example with no child element: 110 | 111 | const componentName = () => ( 112 | Hello, there. 113 | ); 114 | 115 | An example with child elements: 116 | 117 | const componentName = () => ( 118 | 119 | Some text 120 |

Hello, there.

/* This one could be annotated */ 121 | Click me 122 |
123 | ); 124 | 125 | 126 | If you would like the plugin to attempt to annotate the first HTML element created by a Fragment (if it exists) then set the `annotate-fragments` flag: 127 | 128 | plugins: [ 129 | ["@fullstory/babel-plugin-annotate-react", { "annotate-fragments": true }] 130 | ] 131 | 132 | ## Ignoring Components 133 | 134 | If you would like the plugin to skip the annotation for certain components, use the `ignoreComponents` option: 135 | 136 | ```javascript 137 | plugins: [ 138 | [ 139 | "@fullstory/annotate-react", 140 | { 141 | ignoreComponents:[ 142 | // each item must be a string array containing three items: file name, component name, element name 143 | // corresponding to the values for data-source-file, data-component, data-element 144 | // use wild card (*) to match anything 145 | ["myBoxComponent.jsx","MyBox","Box"], 146 | ["App.jsx", "*", "ThemeProvider"], // use wild-card to match anything 147 | ["App.jsx", "App", "*"], 148 | ] 149 | } 150 | ], 151 | ] 152 | ``` 153 | 154 | ## Sample Apps 155 | 156 | We have a few samples to demonstrate this plugin: 157 | 158 | - [Single Page App](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tree/master/samples/single-page-app/) 159 | - [styled-components](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tree/master/samples/styled-components/) 160 | - [React native](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tree/master/samples/react-native-app/) 161 | 162 | Much of the logic for adding the attributes originated in the [transform-react-qa-classes](https://github.com/davesnx/babel-plugin-transform-react-qa-classes/) plugin. 163 | 164 | ## Getting Help 165 | 166 | Please refer to our [Knowledge Base article](https://help.fullstory.com/hc/en-us/articles/360049493054-FullStory-s-Annotate-React-plugin-for-Web-Native) or contact mobile-support@fullstory.com for additional help. 167 | 168 | ### React Native 169 | 170 | Please see our [Getting Started with FullStory React Native Capture](https://help.fullstory.com/hc/en-us/articles/360052419133) guide or email mobile-support@fullstory.com for additional help. 171 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/index-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`arrow snapshot matches 1`] = ` 4 | "import React, { Component } from 'react'; 5 | 6 | const componentName = () => { 7 | return /*#__PURE__*/React.createElement(\\"div\\", { 8 | \\"data-component\\": \\"componentName\\" 9 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 10 | }; 11 | 12 | export default componentName;" 13 | `; 14 | 15 | exports[`arrow-anonymous-fragment snapshot matches 1`] = ` 16 | "import React, { Component, Fragment } from 'react'; 17 | 18 | const componentName = () => { 19 | return (() => /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")))(); 20 | }; 21 | 22 | export default componentName;" 23 | `; 24 | 25 | exports[`arrow-anonymous-react-fragment snapshot matches 1`] = ` 26 | "import React, { Component } from 'react'; 27 | 28 | const componentName = () => { 29 | return (() => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")))(); 30 | }; 31 | 32 | export default componentName;" 33 | `; 34 | 35 | exports[`arrow-anonymous-shorthand-fragment snapshot matches 1`] = ` 36 | "import React, { Component } from 'react'; 37 | 38 | const componentName = () => { 39 | return (() => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")))(); 40 | }; 41 | 42 | export default componentName;" 43 | `; 44 | 45 | exports[`arrow-fragment snapshot matches 1`] = ` 46 | "import React, { Component, Fragment } from 'react'; 47 | 48 | const componentName = () => { 49 | return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 50 | }; 51 | 52 | export default componentName;" 53 | `; 54 | 55 | exports[`arrow-noreturn snapshot matches 1`] = ` 56 | "import React, { Component } from 'react'; 57 | 58 | const componentName = () => /*#__PURE__*/React.createElement(\\"div\\", { 59 | \\"data-component\\": \\"componentName\\" 60 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 61 | 62 | export default componentName;" 63 | `; 64 | 65 | exports[`arrow-noreturn-annotate-fragment snapshot matches 1`] = ` 66 | "import React, { Component, Fragment } from 'react'; 67 | 68 | const componentName = () => /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 69 | \\"data-component\\": \\"componentName\\", 70 | \\"data-source-file\\": \\"filename-test.js\\" 71 | }, \\"Hello world\\")); 72 | 73 | export default componentName;" 74 | `; 75 | 76 | exports[`arrow-noreturn-annotate-fragment-no-whitespace snapshot matches 1`] = ` 77 | "import React, { Component, Fragment } from 'react'; 78 | 79 | const componentName = () => /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 80 | \\"data-component\\": \\"componentName\\", 81 | \\"data-source-file\\": \\"filename-test.js\\" 82 | }, \\"Hello world\\"), /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hola Sol\\")); 83 | 84 | export default componentName;" 85 | `; 86 | 87 | exports[`arrow-noreturn-annotate-fragment-once snapshot matches 1`] = ` 88 | "import React, { Component, Fragment } from 'react'; 89 | 90 | const componentName = () => /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 91 | \\"data-component\\": \\"componentName\\", 92 | \\"data-source-file\\": \\"filename-test.js\\" 93 | }, \\"Hello world\\"), /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hola Sol\\")); 94 | 95 | export default componentName;" 96 | `; 97 | 98 | exports[`arrow-noreturn-annotate-react-fragment snapshot matches 1`] = ` 99 | "import React, { Component } from 'react'; 100 | 101 | const componentName = () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 102 | \\"data-component\\": \\"componentName\\", 103 | \\"data-source-file\\": \\"filename-test.js\\" 104 | }, \\"Hello world\\")); 105 | 106 | export default componentName;" 107 | `; 108 | 109 | exports[`arrow-noreturn-annotate-shorthand-fragment snapshot matches 1`] = ` 110 | "import React, { Component } from 'react'; 111 | 112 | const componentName = () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 113 | \\"data-component\\": \\"componentName\\", 114 | \\"data-source-file\\": \\"filename-test.js\\" 115 | }, \\"Hello world\\")); 116 | 117 | export default componentName;" 118 | `; 119 | 120 | exports[`arrow-noreturn-annotate-trivial-fragment snapshot matches 1`] = ` 121 | "import React, { Component, Fragment } from 'react'; 122 | 123 | const componentName = () => /*#__PURE__*/React.createElement(Fragment, null, \\"Hello world\\"); 124 | 125 | export default componentName;" 126 | `; 127 | 128 | exports[`arrow-noreturn-fragment snapshot matches 1`] = ` 129 | "import React, { Component, Fragment } from 'react'; 130 | 131 | const componentName = () => /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 132 | 133 | export default componentName;" 134 | `; 135 | 136 | exports[`arrow-noreturn-react-fragment snapshot matches 1`] = ` 137 | "import React, { Component } from 'react'; 138 | 139 | const componentName = () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 140 | 141 | export default componentName;" 142 | `; 143 | 144 | exports[`arrow-noreturn-shorthand-fragment snapshot matches 1`] = ` 145 | "import React, { Component } from 'react'; 146 | 147 | const componentName = () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 148 | 149 | export default componentName;" 150 | `; 151 | 152 | exports[`arrow-react-fragment snapshot matches 1`] = ` 153 | "import React, { Component } from 'react'; 154 | 155 | const componentName = () => { 156 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 157 | }; 158 | 159 | export default componentName;" 160 | `; 161 | 162 | exports[`arrow-shorthand-fragment snapshot matches 1`] = ` 163 | "import React from 'react'; 164 | 165 | const componentName = () => { 166 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 167 | }; 168 | 169 | export default componentName;" 170 | `; 171 | 172 | exports[`component snapshot matches 1`] = ` 173 | "import React, { Component } from 'react'; 174 | 175 | class componentName extends Component { 176 | render() { 177 | return /*#__PURE__*/React.createElement(\\"div\\", { 178 | \\"data-component\\": \\"componentName\\" 179 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 180 | } 181 | 182 | } 183 | 184 | export default componentName;" 185 | `; 186 | 187 | exports[`component-annotate-fragment snapshot matches 1`] = ` 188 | "import React, { Component } from 'react'; 189 | 190 | class componentName extends Component { 191 | render() { 192 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"A\\"); 193 | } 194 | 195 | } 196 | 197 | export default componentName;" 198 | `; 199 | 200 | exports[`component-annotate-react-fragment snapshot matches 1`] = ` 201 | "import React, { Component } from 'react'; 202 | 203 | class componentName extends Component { 204 | render() { 205 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 206 | \\"data-component\\": \\"componentName\\", 207 | \\"data-source-file\\": \\"filename-test.js\\" 208 | }, \\"Hello world\\")); 209 | } 210 | 211 | } 212 | 213 | export default componentName;" 214 | `; 215 | 216 | exports[`component-annotate-shorthand-fragment snapshot matches 1`] = ` 217 | "import React, { Component } from 'react'; 218 | 219 | class componentName extends Component { 220 | render() { 221 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", { 222 | \\"data-component\\": \\"componentName\\", 223 | \\"data-source-file\\": \\"filename-test.js\\" 224 | }, \\"Hello world\\")); 225 | } 226 | 227 | } 228 | 229 | export default componentName;" 230 | `; 231 | 232 | exports[`component-fragment snapshot matches 1`] = ` 233 | "import React, { Component, Fragment } from 'react'; 234 | 235 | class componentName extends Component { 236 | render() { 237 | return /*#__PURE__*/React.createElement(Fragment, null, \\"A\\"); 238 | } 239 | 240 | } 241 | 242 | export default componentName;" 243 | `; 244 | 245 | exports[`component-fragment-native snapshot matches 1`] = ` 246 | "import React, { Component, Fragment } from 'react'; 247 | 248 | class componentName extends Component { 249 | render() { 250 | return /*#__PURE__*/React.createElement(Fragment, null, \\"A\\"); 251 | } 252 | 253 | } 254 | 255 | export default componentName;" 256 | `; 257 | 258 | exports[`component-react-fragment snapshot matches 1`] = ` 259 | "import React, { Component } from 'react'; 260 | 261 | class componentName extends Component { 262 | render() { 263 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"A\\"); 264 | } 265 | 266 | } 267 | 268 | export default componentName;" 269 | `; 270 | 271 | exports[`component-shorthand-fragment snapshot matches 1`] = ` 272 | "import React, { Component } from 'react'; 273 | 274 | class componentName extends Component { 275 | render() { 276 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"A\\"); 277 | } 278 | 279 | } 280 | 281 | export default componentName;" 282 | `; 283 | 284 | exports[`nonJSX snapshot matches 1`] = ` 285 | "import React, { Component } from 'react'; 286 | 287 | class TestClass extends Component { 288 | test() { 289 | return true; 290 | } 291 | 292 | } 293 | 294 | export default TestClass;" 295 | `; 296 | 297 | exports[`option-attribute snapshot matches 1`] = ` 298 | "import React, { Component } from 'react'; 299 | 300 | const componentName = () => { 301 | return /*#__PURE__*/React.createElement(\\"div\\", { 302 | \\"data-component\\": \\"componentName\\" 303 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 304 | }; 305 | 306 | export default componentName;" 307 | `; 308 | 309 | exports[`option-format snapshot matches 1`] = ` 310 | "import React, { Component } from 'react'; 311 | 312 | const componentName = () => { 313 | return /*#__PURE__*/React.createElement(\\"div\\", { 314 | \\"data-component\\": \\"componentName\\" 315 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 316 | }; 317 | 318 | export default componentName;" 319 | `; 320 | 321 | exports[`pure snapshot matches 1`] = ` 322 | "import React from 'react'; 323 | 324 | class PureComponentName extends React.PureComponent { 325 | render() { 326 | return /*#__PURE__*/React.createElement(\\"div\\", { 327 | \\"data-component\\": \\"PureComponentName\\" 328 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 329 | } 330 | 331 | } 332 | 333 | export default PureComponentName;" 334 | `; 335 | 336 | exports[`pure-native snapshot matches 1`] = ` 337 | "import React from 'react'; 338 | 339 | class PureComponentName extends React.PureComponent { 340 | render() { 341 | return /*#__PURE__*/React.createElement(\\"div\\", { 342 | dataComponent: \\"PureComponentName\\", 343 | dataSourceFile: \\"filename-test.js\\" 344 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 345 | } 346 | 347 | } 348 | 349 | export default PureComponentName;" 350 | `; 351 | 352 | exports[`pureComponent-fragment snapshot matches 1`] = ` 353 | "import React, { Fragment } from 'react'; 354 | 355 | class PureComponentName extends React.PureComponent { 356 | render() { 357 | return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 358 | } 359 | 360 | } 361 | 362 | export default PureComponentName;" 363 | `; 364 | 365 | exports[`pureComponent-react-fragment snapshot matches 1`] = ` 366 | "import React from 'react'; 367 | 368 | class PureComponentName extends React.PureComponent { 369 | render() { 370 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 371 | } 372 | 373 | } 374 | 375 | export default PureComponentName;" 376 | `; 377 | 378 | exports[`pureComponent-shorthand-fragment snapshot matches 1`] = ` 379 | "import React from 'react'; 380 | 381 | class PureComponentName extends React.PureComponent { 382 | render() { 383 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"Hello world\\")); 384 | } 385 | 386 | } 387 | 388 | export default PureComponentName;" 389 | `; 390 | 391 | exports[`rawfunction snapshot matches 1`] = ` 392 | "import React, { Component } from 'react'; 393 | 394 | function SubComponent() { 395 | return /*#__PURE__*/React.createElement(\\"div\\", { 396 | \\"data-component\\": \\"SubComponent\\" 397 | }, \\"Sub\\"); 398 | } 399 | 400 | const componentName = () => { 401 | return /*#__PURE__*/React.createElement(\\"div\\", { 402 | \\"data-component\\": \\"componentName\\" 403 | }, /*#__PURE__*/React.createElement(SubComponent, { 404 | \\"data-element\\": \\"SubComponent\\" 405 | })); 406 | }; 407 | 408 | export default componentName;" 409 | `; 410 | 411 | exports[`rawfunction-annotate-fragment snapshot matches 1`] = ` 412 | "import React, { Component, Fragment } from 'react'; 413 | 414 | function SubComponent() { 415 | return /*#__PURE__*/React.createElement(Fragment, null, \\"Sub\\"); 416 | } 417 | 418 | const componentName = () => { 419 | return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(SubComponent, { 420 | \\"data-element\\": \\"SubComponent\\", 421 | \\"data-component\\": \\"componentName\\" 422 | })); 423 | }; 424 | 425 | export default componentName;" 426 | `; 427 | 428 | exports[`rawfunction-annotate-react-fragment snapshot matches 1`] = ` 429 | "import React, { Component } from 'react'; 430 | 431 | function SubComponent() { 432 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Sub\\"); 433 | } 434 | 435 | const componentName = () => { 436 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SubComponent, { 437 | \\"data-element\\": \\"SubComponent\\", 438 | \\"data-component\\": \\"componentName\\" 439 | })); 440 | }; 441 | 442 | export default componentName;" 443 | `; 444 | 445 | exports[`rawfunction-annotate-shorthand-fragment snapshot matches 1`] = ` 446 | "import React, { Component } from 'react'; 447 | 448 | function SubComponent() { 449 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Sub\\"); 450 | } 451 | 452 | const componentName = () => { 453 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SubComponent, { 454 | \\"data-element\\": \\"SubComponent\\", 455 | \\"data-component\\": \\"componentName\\" 456 | })); 457 | }; 458 | 459 | export default componentName;" 460 | `; 461 | 462 | exports[`rawfunction-fragment snapshot matches 1`] = ` 463 | "import React, { Component, Fragment } from 'react'; 464 | 465 | function SubComponent() { 466 | return /*#__PURE__*/React.createElement(Fragment, null, \\"Sub\\"); 467 | } 468 | 469 | const componentName = () => { 470 | return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(SubComponent, { 471 | \\"data-element\\": \\"SubComponent\\" 472 | })); 473 | }; 474 | 475 | export default componentName;" 476 | `; 477 | 478 | exports[`rawfunction-react-fragment snapshot matches 1`] = ` 479 | "import React, { Component } from 'react'; 480 | 481 | function SubComponent() { 482 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Sub\\"); 483 | } 484 | 485 | const componentName = () => { 486 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SubComponent, { 487 | \\"data-element\\": \\"SubComponent\\" 488 | })); 489 | }; 490 | 491 | export default componentName;" 492 | `; 493 | 494 | exports[`rawfunction-shorthand-fragment snapshot matches 1`] = ` 495 | "import React, { Component } from 'react'; 496 | 497 | function SubComponent() { 498 | return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Sub\\"); 499 | } 500 | 501 | const componentName = () => { 502 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SubComponent, { 503 | \\"data-element\\": \\"SubComponent\\" 504 | })); 505 | }; 506 | 507 | export default componentName;" 508 | `; 509 | 510 | exports[`tags snapshot matches 1`] = ` 511 | "import React, { Component } from 'react'; 512 | import { StyleSheet, Text, TextInput, View, Image, UIManager } from 'react-native'; 513 | UIManager.getViewManagerConfig('RCTView').NativeProps.fsClass = \\"String\\"; 514 | 515 | class Bananas extends Component { 516 | render() { 517 | let pic = { 518 | uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg' 519 | }; 520 | return /*#__PURE__*/React.createElement(Image, { 521 | source: pic, 522 | style: { 523 | width: 193, 524 | height: 110, 525 | marginTop: 10 526 | }, 527 | fsClass: \\"test-class\\", 528 | dataElement: \\"Image\\", 529 | dataComponent: \\"Bananas\\", 530 | dataSourceFile: \\"filename-test.js\\" 531 | }); 532 | } 533 | 534 | } 535 | 536 | class PizzaTranslator extends Component { 537 | constructor(props) { 538 | super(props); 539 | this.state = { 540 | text: '' 541 | }; 542 | } 543 | 544 | render() { 545 | return /*#__PURE__*/React.createElement(View, { 546 | style: { 547 | padding: 10 548 | }, 549 | dataElement: \\"View\\", 550 | dataComponent: \\"PizzaTranslator\\", 551 | dataSourceFile: \\"filename-test.js\\" 552 | }, /*#__PURE__*/React.createElement(TextInput, { 553 | style: { 554 | backgroundColor: '#000', 555 | color: '#eee', 556 | padding: 8 557 | }, 558 | placeholder: \\"Type here to translate!\\" // not supported on iOS 559 | , 560 | onChangeText: text => this.setState({ 561 | text 562 | }), 563 | value: this.state.text, 564 | dataElement: \\"TextInput\\", 565 | dataSourceFile: \\"filename-test.js\\" 566 | }), /*#__PURE__*/React.createElement(Text, { 567 | style: { 568 | padding: 10, 569 | fontSize: 42 570 | }, 571 | dataElement: \\"Text\\", 572 | dataSourceFile: \\"filename-test.js\\" 573 | }, this.state.text.split(' ').map(word => word && '🍕').join(' '))); 574 | } 575 | 576 | } 577 | 578 | export default function App() { 579 | return /*#__PURE__*/React.createElement(View, { 580 | style: styles.container, 581 | dataElement: \\"View\\", 582 | dataComponent: \\"App\\", 583 | dataSourceFile: \\"filename-test.js\\" 584 | }, /*#__PURE__*/React.createElement(Text, { 585 | style: { 586 | color: '#eee' 587 | }, 588 | dataElement: \\"Text\\", 589 | dataSourceFile: \\"filename-test.js\\" 590 | }, \\"FullStory ReactNative testing app\\"), /*#__PURE__*/React.createElement(Bananas, { 591 | dataElement: \\"Bananas\\", 592 | dataSourceFile: \\"filename-test.js\\" 593 | }), /*#__PURE__*/React.createElement(PizzaTranslator, { 594 | dataElement: \\"PizzaTranslator\\", 595 | dataSourceFile: \\"filename-test.js\\" 596 | })); 597 | } 598 | const styles = StyleSheet.create({ 599 | container: { 600 | flex: 1, 601 | justifyContent: 'center', 602 | alignItems: 'stretch', 603 | backgroundColor: '#222', 604 | alignItems: 'center', 605 | justifyContent: 'center' 606 | } 607 | });" 608 | `; 609 | 610 | exports[`unknown-element snapshot matches 1`] = ` 611 | "import React, { Component } from 'react'; 612 | 613 | class componentName extends Component { 614 | render() { 615 | return /*#__PURE__*/React.createElement(\\"bogus\\", { 616 | \\"data-element\\": \\"bogus\\", 617 | \\"data-component\\": \\"componentName\\", 618 | \\"data-source-file\\": \\"filename-test.js\\" 619 | }, /*#__PURE__*/React.createElement(\\"h1\\", null, \\"A\\")); 620 | } 621 | 622 | } 623 | 624 | export default componentName;" 625 | `; 626 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const webComponentName = 'data-component'; 2 | const webElementName = 'data-element'; 3 | const webSourceFileName = 'data-source-file'; 4 | 5 | const nativeComponentName = 'dataComponent'; 6 | const nativeElementName = 'dataElement'; 7 | const nativeSourceFileName = 'dataSourceFile'; 8 | const fsTagName = 'fsTagName' 9 | 10 | const annotateFragmentsOptionName = 'annotate-fragments'; 11 | const ignoreComponentsOptionName = 'ignoreComponents'; 12 | 13 | const knownIncompatiblePlugins = [ 14 | // This module might be causing an issue preventing clicks. For safety, we won't run on this module. 15 | 'react-native-testfairy', 16 | // This module checks for unexpected property keys and throws an exception. 17 | '@react-navigation', 18 | // Annotations on this module may cause unexpected UI behavior. VAL-9242 19 | 'react-native-navigation', 20 | // This module is incompatible with our ref rewrites on iOS. 21 | 'expo-router', 22 | // The victory* modules use `dataComponent` and we get a collision. 23 | 'victory', 24 | 'victory-area', 25 | 'victory-axis', 26 | 'victory-bar', 27 | 'victory-box-plot', 28 | 'victory-brush-container', 29 | 'victory-brush-line', 30 | 'victory-candlestick', 31 | 'victory-canvas', 32 | 'victory-chart', 33 | 'victory-core', 34 | 'victory-create-container', 35 | 'victory-cursor-container', 36 | 'victory-errorbar', 37 | 'victory-group', 38 | 'victory-histogram', 39 | 'victory-legend', 40 | 'victory-line', 41 | 'victory-native', 42 | 'victory-pie', 43 | 'victory-polar-axis', 44 | 'victory-scatter', 45 | 'victory-selection-container', 46 | 'victory-shared-events', 47 | 'victory-stack', 48 | 'victory-tooltip', 49 | 'victory-vendor', 50 | 'victory-voronoi', 51 | 'victory-voronoi-container', 52 | 'victory-zoom-container', 53 | ]; 54 | 55 | module.exports = function ({ types: t }) { 56 | return { 57 | pre() { 58 | this.ignoreComponentsFromOption = this.opts[ignoreComponentsOptionName] || []; 59 | if (this.opts.setFSTagName && !this.opts.native) { 60 | throw new Error('`setFSTagName: true` is invalid unless `native: true` is also set in the configuration for @fullstory/babel-plugin-annotate-react') 61 | } 62 | }, 63 | visitor: { 64 | FunctionDeclaration(path, state) { 65 | if (!path.node.id || !path.node.id.name) return 66 | if (isKnownIncompatiblePluginFromState(state)) return 67 | functionBodyPushAttributes( 68 | state.opts[annotateFragmentsOptionName] === true, 69 | t, 70 | path, 71 | path.node.id.name, 72 | sourceFileNameFromState(state), 73 | attributeNamesFromState(state), 74 | this.ignoreComponentsFromOption, 75 | ) 76 | }, 77 | ArrowFunctionExpression(path, state) { 78 | if (!path.parent.id || !path.parent.id.name) return 79 | if (isKnownIncompatiblePluginFromState(state)) return 80 | functionBodyPushAttributes( 81 | state.opts[annotateFragmentsOptionName] === true, 82 | t, 83 | path, 84 | path.parent.id.name, 85 | sourceFileNameFromState(state), 86 | attributeNamesFromState(state), 87 | this.ignoreComponentsFromOption, 88 | ) 89 | }, 90 | ClassDeclaration(path, state) { 91 | const name = path.get('id') 92 | const properties = path.get('body').get('body') 93 | const render = properties.find(prop => { 94 | return ( 95 | prop.isClassMethod() && 96 | prop.get('key').isIdentifier({ name: 'render' }) 97 | ) 98 | }) 99 | 100 | if (!render || !render.traverse) return 101 | if (isKnownIncompatiblePluginFromState(state)) return 102 | 103 | const ignoreComponentsFromOption = this.ignoreComponentsFromOption; 104 | 105 | render.traverse({ 106 | ReturnStatement(returnStatement) { 107 | const arg = returnStatement.get('argument') 108 | 109 | if (!arg.isJSXElement() && !arg.isJSXFragment()) return 110 | processJSX( 111 | state.opts[annotateFragmentsOptionName] === true, 112 | t, 113 | arg, 114 | name.node && name.node.name, 115 | sourceFileNameFromState(state), 116 | attributeNamesFromState(state), 117 | ignoreComponentsFromOption 118 | ) 119 | } 120 | }) 121 | }, 122 | } 123 | } 124 | } 125 | 126 | function fullSourceFileNameFromState(state) { 127 | const name = state.file.opts.parserOpts.sourceFileName 128 | if (typeof name !== 'string') { 129 | return undefined 130 | } 131 | return name 132 | } 133 | 134 | function sourceFileNameFromState(state) { 135 | const name = fullSourceFileNameFromState(state) 136 | if (name === undefined) { 137 | return undefined 138 | } 139 | 140 | if (name.indexOf('/') !== -1) { 141 | return name.split('/').pop() 142 | } else if (name.indexOf('\\') !== -1) { 143 | return name.split('\\').pop() 144 | } else { 145 | return name 146 | } 147 | } 148 | 149 | function isKnownIncompatiblePluginFromState(state) { 150 | const fullSourceFileName = fullSourceFileNameFromState(state) 151 | if (fullSourceFileName == undefined) { 152 | return false 153 | } 154 | 155 | for (let i = 0; i < knownIncompatiblePlugins.length; i += 1) { 156 | let pluginName = knownIncompatiblePlugins[i]; 157 | if (fullSourceFileName.includes("/node_modules/" + pluginName + "/") || 158 | fullSourceFileName.includes("\\node_modules\\" + pluginName + "\\")) { 159 | return true 160 | } 161 | } 162 | 163 | return false 164 | } 165 | 166 | function attributeNamesFromState(state) { 167 | if (state.opts.native) { 168 | if (state.opts.setFSTagName) { 169 | return [fsTagName, fsTagName, nativeSourceFileName]; 170 | } else { 171 | return [nativeComponentName, nativeElementName, nativeSourceFileName]; 172 | } 173 | } 174 | return [webComponentName, webElementName, webSourceFileName] 175 | } 176 | 177 | function isReactFragment(openingElement) { 178 | if (openingElement.isJSXFragment()) { 179 | return true 180 | } 181 | 182 | if ( 183 | !openingElement.node || 184 | !openingElement.node.name 185 | ) return 186 | 187 | if (openingElement.node.name.name === 'Fragment' || 188 | openingElement.node.name.name === 'React.Fragment' 189 | ) return true; 190 | 191 | if ( 192 | !openingElement.node.name.type || 193 | !openingElement.node.name.object || 194 | !openingElement.node.name.property 195 | ) return 196 | 197 | return ( 198 | openingElement.node.name.type === 'JSXMemberExpression' && 199 | openingElement.node.name.object.name === 'React' && 200 | openingElement.node.name.property.name === 'Fragment' 201 | ) 202 | } 203 | 204 | function applyAttributes(t, openingElement, componentName, sourceFileName, attributeNames, ignoreComponentsFromOption) { 205 | const [componentAttributeName, elementAttributeName, sourceFileAttributeName] = attributeNames; 206 | if (!openingElement 207 | || isReactFragment(openingElement) 208 | || !openingElement.node 209 | || !openingElement.node.name 210 | ) { 211 | return 212 | } 213 | if (!openingElement.node.attributes) openingElement.node.attributes = {} 214 | 215 | const elementName = openingElement.node.name.name || 'unknown' 216 | 217 | const ignoredComponentFromOptions = ignoreComponentsFromOption && !!ignoreComponentsFromOption.find(component => 218 | matchesIgnoreRule(component[0], sourceFileName) && 219 | matchesIgnoreRule(component[1], componentName) && 220 | matchesIgnoreRule(component[2], elementName) 221 | ) 222 | 223 | let ignoredElement = false 224 | // Add a stable attribute for the element name but only for non-DOM names 225 | if ( 226 | !ignoredComponentFromOptions && 227 | !hasNodeNamed(openingElement, componentAttributeName) && 228 | // if componentAttributeName and elementAttributeName are set to the same thing (fsTagName), then only set the element attribute when we don't have a component attribute 229 | ((componentAttributeName !== elementAttributeName) || !componentName) 230 | ) { 231 | if (defaultIgnoredElements.includes(elementName)) { 232 | ignoredElement = true 233 | } else { 234 | openingElement.node.attributes.push( 235 | t.jSXAttribute( 236 | t.jSXIdentifier(elementAttributeName), 237 | t.stringLiteral(elementName) 238 | ) 239 | ) 240 | } 241 | } 242 | 243 | // Add a stable attribute for the component name (absent for non-root elements) 244 | if ( 245 | componentName 246 | && !ignoredComponentFromOptions 247 | && !hasNodeNamed(openingElement, componentAttributeName)) { 248 | openingElement.node.attributes.push( 249 | t.jSXAttribute( 250 | t.jSXIdentifier(componentAttributeName), 251 | t.stringLiteral(componentName) 252 | ) 253 | ) 254 | } 255 | 256 | // Add a stable attribute for the source file name (absent for non-root elements) 257 | if ( 258 | sourceFileName 259 | && !ignoredComponentFromOptions 260 | && (componentName || ignoredElement === false) 261 | && !hasNodeNamed(openingElement, sourceFileAttributeName) 262 | ) { 263 | openingElement.node.attributes.push( 264 | t.jSXAttribute( 265 | t.jSXIdentifier(sourceFileAttributeName), 266 | t.stringLiteral(sourceFileName) 267 | ) 268 | ) 269 | } 270 | } 271 | 272 | function processJSX(annotateFragments, t, jsxNode, componentName, sourceFileName, attributeNames, ignoreComponentsFromOption) { 273 | if (!jsxNode) { 274 | return 275 | } 276 | 277 | // only a JSXElement contains openingElement 278 | const openingElement = jsxNode.get('openingElement') 279 | 280 | applyAttributes(t, openingElement, componentName, sourceFileName, attributeNames, ignoreComponentsFromOption) 281 | 282 | const children = jsxNode.get('children') 283 | if (children && children.length) { 284 | let shouldSetComponentName = annotateFragments 285 | for (let i = 0; i < children.length; i += 1) { 286 | const child = children[i] 287 | // Children don't receive the data-component attribute so we pass null for componentName unless it's the first child of a Fragment with a node and `annotateFragments` is true 288 | if (shouldSetComponentName && child.get('openingElement') && child.get('openingElement').node) { 289 | shouldSetComponentName = false 290 | processJSX(annotateFragments, t, child, componentName, sourceFileName, attributeNames, ignoreComponentsFromOption) 291 | } else { 292 | processJSX(annotateFragments, t, child, null, sourceFileName, attributeNames, ignoreComponentsFromOption) 293 | } 294 | } 295 | } 296 | } 297 | 298 | function functionBodyPushAttributes(annotateFragments, t, path, componentName, sourceFileName, attributeNames, ignoreComponentsFromOption) { 299 | let jsxNode = null 300 | const functionBody = path.get('body').get('body') 301 | if (functionBody.parent && 302 | (functionBody.parent.type === 'JSXElement' || functionBody.parent.type === 'JSXFragment') 303 | ) { 304 | const maybeJsxNode = functionBody.find(c => { 305 | return (c.type === 'JSXElement' || c.type === 'JSXFragment') 306 | }) 307 | if (!maybeJsxNode) return 308 | jsxNode = maybeJsxNode 309 | } else { 310 | const returnStatement = functionBody.find(c => { 311 | return c.type === 'ReturnStatement' 312 | }) 313 | if (!returnStatement) { 314 | return 315 | } 316 | const arg = returnStatement.get('argument') 317 | if (!arg) { 318 | return 319 | } 320 | if (!arg.isJSXFragment() && !arg.isJSXElement()) { 321 | return 322 | } 323 | jsxNode = arg 324 | } 325 | if (!jsxNode) return 326 | processJSX(annotateFragments, t, jsxNode, componentName, sourceFileName, attributeNames, ignoreComponentsFromOption) 327 | } 328 | 329 | function matchesIgnoreRule(rule, name) { 330 | return rule === '*' || rule === name; 331 | } 332 | 333 | function hasNodeNamed(openingElement, name) { 334 | return openingElement.node.attributes.find(node => { 335 | if (!node.name) return 336 | return node.name.name === name 337 | }) 338 | } 339 | 340 | // We don't write data-element attributes for these names 341 | const defaultIgnoredElements = [ 342 | 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 343 | 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 344 | 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 345 | 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 346 | 'em', 'embed', 347 | 'fieldset', 'figure', 'footer', 'form', 348 | 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 349 | 'i', 'iframe', 'img', 'input', 'ins', 350 | 'kbd', 'keygen', 351 | 'label', 'legend', 'li', 'link', 352 | 'main', 'map', 'mark', 'menu', 'menuitem', 'meter', 353 | 'nav', 'noscript', 354 | 'object', 'ol', 'optgroup', 'option', 'output', 355 | 'p', 'param', 'pre', 'progress', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 356 | 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 357 | 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 358 | 'u', 'ul', 359 | 'var', 'video', 360 | 'wbr' 361 | ] 362 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fullstory/babel-plugin-annotate-react", 3 | "version": "2.3.2", 4 | "description": "A Babel plugin that annotates React components, making them easier to target with FullStory search", 5 | "main": "index.js", 6 | "homepage": "https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/fullstory-babel-plugin-annotate-react.git" 10 | }, 11 | "scripts": { 12 | "test": "jest" 13 | }, 14 | "author": "FullStory", 15 | "license": "MIT", 16 | "jest": { 17 | "roots": [ 18 | "/__tests__/" 19 | ] 20 | }, 21 | "devDependencies": { 22 | "@babel/preset-react": "^7.9.4", 23 | "babel-core": "^7.0.0-0", 24 | "jest": "^28.1.3" 25 | }, 26 | "overrides": { 27 | "semver": "^7.5.3" 28 | }, 29 | "engines": { 30 | "npm": ">=8.19.4", 31 | "node": ">=16.20.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/react-native-app/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /samples/react-native-app/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /samples/react-native-app/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifacts 56 | *.jsbundle 57 | 58 | # CocoaPods 59 | /ios/Pods/ 60 | 61 | # Expo 62 | .expo/* 63 | web-build/ 64 | -------------------------------------------------------------------------------- /samples/react-native-app/App.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Platform, StyleSheet, Text, View } from 'react-native'; 3 | import Container from './Container'; 4 | 5 | const instructions = Platform.select({ 6 | ios: `Press Cmd+R to reload,\nCmd+D or shake for dev menu`, 7 | android: `Double tap R on your keyboard to reload,\nShake or press menu button for dev menu`, 8 | }); 9 | 10 | export default function App() { 11 | return ( 12 | 13 | 14 | Welcome to React Native! 15 | 16 | To get started, edit App.js 17 | {instructions} 18 | 19 | ); 20 | } 21 | 22 | const styles = StyleSheet.create({ 23 | container: { 24 | flex: 1, 25 | justifyContent: 'center', 26 | alignItems: 'center', 27 | backgroundColor: '#F5FCFF', 28 | }, 29 | welcome: { 30 | fontSize: 20, 31 | textAlign: 'center', 32 | margin: 10, 33 | }, 34 | instructions: { 35 | textAlign: 'center', 36 | color: '#333333', 37 | marginBottom: 5, 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /samples/react-native-app/Container.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function Container({children}) { 4 | return ( 5 |
{children}
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /samples/react-native-app/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React Native App](https://github.com/expo/create-react-native-app). 2 | 3 | ## Installation 4 | 5 | In the project directory, run: 6 | 7 | ### `npm install` 8 | 9 | This installs the required node packages 10 | 11 | ## Running 12 | 13 | In the project directory, run: 14 | ### `expo start` 15 | 16 | This runs the metro bundler in development mode.
17 | Open [http://localhost:19002](http://localhost:19002) to view the metro bundler in the browser. 18 | 19 | From there you can run the app on Android and iOS devices and simulators.
20 | 21 | ## Viewing the DOM 22 | 23 | In order to view the DOM changes, install the [React Native Debugger](https://github.com/jhen0409/react-native-debugger).
24 | Note: you should install version 0.10.x of the debugger via:
25 | `brew update && brew cask install https://raw.githubusercontent.com/caskroom/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb`.

26 | 27 | More Info on running the React Native Debugger can be found in the [Getting Started Guide](https://github.com/jhen0409/react-native-debugger/blob/master/docs/getting-started.md). 28 | 29 | ## Learn More 30 | 31 | You can learn more in the [Learn the Basics documentation](https://reactnative.dev/docs/tutorial.html). -------------------------------------------------------------------------------- /samples/react-native-app/__tests__/App.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import App from '../App'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | renderer.create(); 10 | }); 11 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.reactnativeapp", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.reactnativeapp", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format 22 | * bundleCommand: "ram-bundle", 23 | * 24 | * // whether to bundle JS and assets in debug mode 25 | * bundleInDebug: false, 26 | * 27 | * // whether to bundle JS and assets in release mode 28 | * bundleInRelease: true, 29 | * 30 | * // whether to bundle JS and assets in another build variant (if configured). 31 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 32 | * // The configuration property can be in the following formats 33 | * // 'bundleIn${productFlavor}${buildType}' 34 | * // 'bundleIn${buildType}' 35 | * // bundleInFreeDebug: true, 36 | * // bundleInPaidRelease: true, 37 | * // bundleInBeta: true, 38 | * 39 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 40 | * // for example: to disable dev mode in the staging build type (if configured) 41 | * devDisabledInStaging: true, 42 | * // The configuration property can be in the following formats 43 | * // 'devDisabledIn${productFlavor}${buildType}' 44 | * // 'devDisabledIn${buildType}' 45 | * 46 | * // the root of your project, i.e. where "package.json" lives 47 | * root: "../../", 48 | * 49 | * // where to put the JS bundle asset in debug mode 50 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 51 | * 52 | * // where to put the JS bundle asset in release mode 53 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 54 | * 55 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 56 | * // require('./image.png')), in debug mode 57 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 58 | * 59 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 60 | * // require('./image.png')), in release mode 61 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 62 | * 63 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 64 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 65 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 66 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 67 | * // for example, you might want to remove it from here. 68 | * inputExcludes: ["android/**", "ios/**"], 69 | * 70 | * // override which node gets called and with what additional arguments 71 | * nodeExecutableAndArgs: ["node"], 72 | * 73 | * // supply additional arguments to the packager 74 | * extraPackagerArgs: [] 75 | * ] 76 | */ 77 | 78 | project.ext.react = [ 79 | entryFile: "index.js", 80 | enableHermes: false 81 | ] 82 | 83 | apply from: '../../node_modules/react-native-unimodules/gradle.groovy' 84 | apply from: "../../node_modules/react-native/react.gradle" 85 | apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle" 86 | 87 | /** 88 | * Set this to true to create two separate APKs instead of one: 89 | * - An APK that only works on ARM devices 90 | * - An APK that only works on x86 devices 91 | * The advantage is the size of the APK is reduced by about 4MB. 92 | * Upload all the APKs to the Play Store and people will download 93 | * the correct one based on the CPU architecture of their device. 94 | */ 95 | def enableSeparateBuildPerCPUArchitecture = true 96 | 97 | /** 98 | * Run Proguard to shrink the Java bytecode in release builds. 99 | */ 100 | def enableProguardInReleaseBuilds = false 101 | 102 | /** 103 | * The preferred build flavor of JavaScriptCore. 104 | * 105 | * For example, to use the international variant, you can use: 106 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 107 | * 108 | * The international variant includes ICU i18n library and necessary data 109 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 110 | * give correct results when using with locales other than en-US. Note that 111 | * this variant is about 6MiB larger per architecture than default. 112 | */ 113 | def jscFlavor = 'org.webkit:android-jsc:+' 114 | 115 | /** 116 | * Whether to enable the Hermes VM. 117 | * 118 | * This should be set on project.ext.react and mirrored here. If it is not set 119 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 120 | * and the benefits of using Hermes will therefore be sharply reduced. 121 | */ 122 | def enableHermes = project.ext.react.get("enableHermes", false); 123 | 124 | android { 125 | compileSdkVersion rootProject.ext.compileSdkVersion 126 | 127 | compileOptions { 128 | sourceCompatibility JavaVersion.VERSION_1_8 129 | targetCompatibility JavaVersion.VERSION_1_8 130 | } 131 | 132 | defaultConfig { 133 | applicationId "com.reactnativeapp" 134 | minSdkVersion rootProject.ext.minSdkVersion 135 | targetSdkVersion rootProject.ext.targetSdkVersion 136 | versionCode 1 137 | versionName "1.0" 138 | } 139 | splits { 140 | abi { 141 | reset() 142 | enable enableSeparateBuildPerCPUArchitecture 143 | universalApk false // If true, also generate a universal APK 144 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 145 | } 146 | } 147 | signingConfigs { 148 | debug { 149 | storeFile file('debug.keystore') 150 | storePassword 'android' 151 | keyAlias 'androiddebugkey' 152 | keyPassword 'android' 153 | } 154 | } 155 | buildTypes { 156 | debug { 157 | signingConfig signingConfigs.debug 158 | } 159 | release { 160 | // Caution! In production, you need to generate your own keystore file. 161 | // see https://facebook.github.io/react-native/docs/signed-apk-android. 162 | signingConfig signingConfigs.debug 163 | minifyEnabled enableProguardInReleaseBuilds 164 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 165 | } 166 | } 167 | // applicationVariants are e.g. debug, release 168 | applicationVariants.all { variant -> 169 | variant.outputs.each { output -> 170 | // For each separate APK per architecture, set a unique version code as described here: 171 | // https://developer.android.com/studio/build/configure-apk-splits.html 172 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 173 | def abi = output.getFilter(OutputFile.ABI) 174 | if (abi != null) { // null for the universal-debug, universal-release variants 175 | output.versionCodeOverride = 176 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 177 | } 178 | 179 | } 180 | } 181 | } 182 | 183 | dependencies { 184 | implementation fileTree(dir: "libs", include: ["*.jar"]) 185 | implementation "com.facebook.react:react-native:+" // From node_modules 186 | addUnimodulesDependencies() 187 | 188 | if (enableHermes) { 189 | def hermesPath = "../../node_modules/hermes-engine/android/"; 190 | debugImplementation files(hermesPath + "hermes-debug.aar") 191 | releaseImplementation files(hermesPath + "hermes-release.aar") 192 | } else { 193 | implementation jscFlavor 194 | } 195 | } 196 | 197 | // Run this once to be able to run the application with BUCK 198 | // puts all compile dependencies into folder libs for BUCK to use 199 | task copyDownloadableDepsToLibs(type: Copy) { 200 | from configurations.compile 201 | into 'libs' 202 | } 203 | 204 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 205 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/java/com/reactnativeapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.reactnativeapp; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.facebook.react.ReactActivity; 6 | import com.facebook.react.ReactActivityDelegate; 7 | import com.facebook.react.ReactRootView; 8 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; 9 | 10 | import expo.modules.splashscreen.SplashScreen; 11 | import expo.modules.splashscreen.SplashScreenImageResizeMode; 12 | 13 | public class MainActivity extends ReactActivity { 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | // SplashScreen.show(...) has to be called after super.onCreate(...) 18 | // Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually 19 | SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, ReactRootView.class); 20 | } 21 | 22 | 23 | /** 24 | * Returns the name of the main component registered from JavaScript. 25 | * This is used to schedule rendering of the component. 26 | */ 27 | @Override 28 | protected String getMainComponentName() { 29 | return "main"; 30 | } 31 | 32 | @Override 33 | protected ReactActivityDelegate createReactActivityDelegate() { 34 | return new ReactActivityDelegate(this, getMainComponentName()) { 35 | @Override 36 | protected ReactRootView createRootView() { 37 | return new RNGestureHandlerEnabledRootView(MainActivity.this); 38 | } 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/java/com/reactnativeapp/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.reactnativeapp; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | 7 | import com.facebook.react.PackageList; 8 | import com.facebook.react.ReactApplication; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.react.shell.MainReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | import com.reactnativeapp.generated.BasePackageList; 14 | 15 | import org.unimodules.adapters.react.ReactAdapterPackage; 16 | import org.unimodules.adapters.react.ModuleRegistryAdapter; 17 | import org.unimodules.adapters.react.ReactModuleRegistryProvider; 18 | import org.unimodules.core.interfaces.Package; 19 | import org.unimodules.core.interfaces.SingletonModule; 20 | import expo.modules.constants.ConstantsPackage; 21 | import expo.modules.permissions.PermissionsPackage; 22 | import expo.modules.filesystem.FileSystemPackage; 23 | import expo.modules.updates.UpdatesController; 24 | 25 | import java.lang.reflect.InvocationTargetException; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import javax.annotation.Nullable; 29 | 30 | public class MainApplication extends Application implements ReactApplication { 31 | private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider( 32 | new BasePackageList().getPackageList() 33 | ); 34 | 35 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 36 | @Override 37 | public boolean getUseDeveloperSupport() { 38 | return BuildConfig.DEBUG; 39 | } 40 | 41 | @Override 42 | protected List getPackages() { 43 | List packages = new PackageList(this).getPackages(); 44 | packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider)); 45 | return packages; 46 | } 47 | 48 | @Override 49 | protected String getJSMainModuleName() { 50 | return "index"; 51 | } 52 | 53 | @Override 54 | protected @Nullable String getJSBundleFile() { 55 | if (BuildConfig.DEBUG) { 56 | return super.getJSBundleFile(); 57 | } else { 58 | return UpdatesController.getInstance().getLaunchAssetFile(); 59 | } 60 | } 61 | 62 | @Override 63 | protected @Nullable String getBundleAssetName() { 64 | if (BuildConfig.DEBUG) { 65 | return super.getBundleAssetName(); 66 | } else { 67 | return UpdatesController.getInstance().getBundleAssetName(); 68 | } 69 | } 70 | }; 71 | 72 | @Override 73 | public ReactNativeHost getReactNativeHost() { 74 | return mReactNativeHost; 75 | } 76 | 77 | @Override 78 | public void onCreate() { 79 | super.onCreate(); 80 | SoLoader.init(this, /* native exopackage */ false); 81 | initializeFlipper(this); // Remove this line if you don't want Flipper enabled 82 | 83 | if (!BuildConfig.DEBUG) { 84 | UpdatesController.initialize(this); 85 | } 86 | } 87 | 88 | /** 89 | * Loads Flipper in React Native templates. 90 | * 91 | * @param context 92 | */ 93 | private static void initializeFlipper(Context context) { 94 | if (BuildConfig.DEBUG) { 95 | try { 96 | /* 97 | We use reflection here to pick up the class that initializes Flipper, 98 | since Flipper library is not available in release mode 99 | */ 100 | Class aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper"); 101 | aClass.getMethod("initializeFlipper", Context.class).invoke(null, context); 102 | } catch (ClassNotFoundException e) { 103 | e.printStackTrace(); 104 | } catch (NoSuchMethodException e) { 105 | e.printStackTrace(); 106 | } catch (IllegalAccessException e) { 107 | e.printStackTrace(); 108 | } catch (InvocationTargetException e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/drawable/splashscreen.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/drawable/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/drawable/splashscreen_image.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FFFFFF 5 | 6 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | react-native-app 3 | 4 | -------------------------------------------------------------------------------- /samples/react-native-app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /samples/react-native-app/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.3" 6 | minSdkVersion = 21 7 | compileSdkVersion = 28 8 | targetSdkVersion = 28 9 | } 10 | repositories { 11 | google() 12 | jcenter() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle:3.5.3") 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | mavenLocal() 25 | maven { 26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 27 | url("$rootDir/../node_modules/react-native/android") 28 | } 29 | maven { 30 | // Android JSC is installed from npm 31 | url("$rootDir/../node_modules/jsc-android/dist") 32 | } 33 | 34 | google() 35 | jcenter() 36 | maven { url 'https://jitpack.io' } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/react-native-app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useAndroidX=true 21 | android.enableJetifier=true 22 | -------------------------------------------------------------------------------- /samples/react-native-app/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/react-native-app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /samples/react-native-app/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin, switch paths to Windows format before running java 129 | if $cygwin ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /samples/react-native-app/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /samples/react-native-app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reactnativeapp' 2 | 3 | apply from: '../node_modules/react-native-unimodules/gradle.groovy' 4 | includeUnimodulesProjects() 5 | 6 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); 7 | applyNativeModulesSettingsGradle(settings) 8 | 9 | include ':app' 10 | -------------------------------------------------------------------------------- /samples/react-native-app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-app", 3 | "displayName": "react-native-app", 4 | "expo": { 5 | "name": "react-native-app", 6 | "slug": "expo-template-bare", 7 | "version": "1.0.0", 8 | "platforms": [ 9 | "ios", 10 | "android", 11 | "web" 12 | ], 13 | "assetBundlePatterns": [ 14 | "**/*" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/react-native-app/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache.never() 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | // This is the local reference @fullstory/babel-plugin-annotate-react 7 | // To use in your projects you would replace '../..' with '@fullstory/babel-plugin-annotate-react' 8 | ['../..', { native: true, }] 9 | ], 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /samples/react-native-app/index.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo'; 2 | 3 | import App from './App'; 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in the Expo client or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App); 9 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | 3 | require_relative '../node_modules/react-native-unimodules/cocoapods' 4 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 5 | 6 | target 'reactnativeapp' do 7 | rnPrefix = "../node_modules/react-native" 8 | 9 | # React Native and its dependencies 10 | pod 'FBLazyVector', :path => "#{rnPrefix}/Libraries/FBLazyVector" 11 | pod 'FBReactNativeSpec', :path => "#{rnPrefix}/Libraries/FBReactNativeSpec" 12 | pod 'RCTRequired', :path => "#{rnPrefix}/Libraries/RCTRequired" 13 | pod 'RCTTypeSafety', :path => "#{rnPrefix}/Libraries/TypeSafety" 14 | pod 'React', :path => "#{rnPrefix}/" 15 | pod 'React-Core', :path => "#{rnPrefix}/" 16 | pod 'React-CoreModules', :path => "#{rnPrefix}/React/CoreModules" 17 | pod 'React-RCTActionSheet', :path => "#{rnPrefix}/Libraries/ActionSheetIOS" 18 | pod 'React-RCTAnimation', :path => "#{rnPrefix}/Libraries/NativeAnimation" 19 | pod 'React-RCTBlob', :path => "#{rnPrefix}/Libraries/Blob" 20 | pod 'React-RCTImage', :path => "#{rnPrefix}/Libraries/Image" 21 | pod 'React-RCTLinking', :path => "#{rnPrefix}/Libraries/LinkingIOS" 22 | pod 'React-RCTNetwork', :path => "#{rnPrefix}/Libraries/Network" 23 | pod 'React-RCTSettings', :path => "#{rnPrefix}/Libraries/Settings" 24 | pod 'React-RCTText', :path => "#{rnPrefix}/Libraries/Text" 25 | pod 'React-RCTVibration', :path => "#{rnPrefix}/Libraries/Vibration" 26 | pod 'React-Core/RCTWebSocket', :path => "#{rnPrefix}/" 27 | pod 'React-Core/DevSupport', :path => "#{rnPrefix}/" 28 | pod 'React-cxxreact', :path => "#{rnPrefix}/ReactCommon/cxxreact" 29 | pod 'React-jsi', :path => "#{rnPrefix}/ReactCommon/jsi" 30 | pod 'React-jsiexecutor', :path => "#{rnPrefix}/ReactCommon/jsiexecutor" 31 | pod 'React-jsinspector', :path => "#{rnPrefix}/ReactCommon/jsinspector" 32 | pod 'ReactCommon/jscallinvoker', :path => "#{rnPrefix}/ReactCommon" 33 | pod 'ReactCommon/turbomodule/core', :path => "#{rnPrefix}/ReactCommon" 34 | pod 'Yoga', :path => "#{rnPrefix}/ReactCommon/yoga" 35 | pod 'DoubleConversion', :podspec => "#{rnPrefix}/third-party-podspecs/DoubleConversion.podspec" 36 | pod 'glog', :podspec => "#{rnPrefix}/third-party-podspecs/glog.podspec" 37 | pod 'Folly', :podspec => "#{rnPrefix}/third-party-podspecs/Folly.podspec" 38 | 39 | # Other native modules 40 | 41 | # Automatically detect installed unimodules 42 | use_unimodules! 43 | 44 | # react-native-cli autolinking 45 | use_native_modules! 46 | end 47 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0731D3B824204D62009CAE85 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0731D3B524204D62009CAE85 /* Expo.plist */; }; 11 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 12 | 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 14 | 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 15 | 3DE4DAD41476765101945408 /* libPods-reactnativeapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D43FF9D506E70904424FA7E9 /* libPods-reactnativeapp.a */; }; 16 | 99EC63CFB4B549BBAF093D10 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E3ABB89D1E68496EB7D95742 /* SplashScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 21 | 0731D3B524204D62009CAE85 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Expo.plist; path = reactnativeapp/Supporting/Expo.plist; sourceTree = ""; }; 22 | 13B07F961A680F5B00A75B9A /* reactnativeapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = reactnativeapp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = reactnativeapp/AppDelegate.h; sourceTree = ""; }; 24 | 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = reactnativeapp/AppDelegate.m; sourceTree = ""; }; 25 | 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 26 | 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = reactnativeapp/Images.xcassets; sourceTree = ""; }; 27 | 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = reactnativeapp/Info.plist; sourceTree = ""; }; 28 | 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = reactnativeapp/main.m; sourceTree = ""; }; 29 | 2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 3B47C5AFCB8BDE514B7D1AC6 /* Pods-reactnativeapp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-reactnativeapp.debug.xcconfig"; path = "Target Support Files/Pods-reactnativeapp/Pods-reactnativeapp.debug.xcconfig"; sourceTree = ""; }; 31 | 8AC623DBF3A3E2CB072F81F2 /* Pods-reactnativeapp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-reactnativeapp.release.xcconfig"; path = "Target Support Files/Pods-reactnativeapp/Pods-reactnativeapp.release.xcconfig"; sourceTree = ""; }; 32 | D43FF9D506E70904424FA7E9 /* libPods-reactnativeapp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-reactnativeapp.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 34 | ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; 35 | E3ABB89D1E68496EB7D95742 /* SplashScreen.storyboard */ = {isa = PBXFileReference; name = "SplashScreen.storyboard"; path = "reactnativeapp/SplashScreen.storyboard"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = file.storyboard; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | 3DE4DAD41476765101945408 /* libPods-reactnativeapp.a in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 13B07FAE1A68108700A75B9A /* reactnativeapp */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 54 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 55 | 13B07FB01A68108700A75B9A /* AppDelegate.m */, 56 | 13B07FB51A68108700A75B9A /* Images.xcassets */, 57 | 13B07FB61A68108700A75B9A /* Info.plist */, 58 | 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 59 | 13B07FB71A68108700A75B9A /* main.m */, 60 | 0731D3B524204D62009CAE85 /* Expo.plist */, 61 | E3ABB89D1E68496EB7D95742 /* SplashScreen.storyboard */, 62 | ); 63 | name = reactnativeapp; 64 | sourceTree = ""; 65 | }; 66 | 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */, 70 | ED2971642150620600B7C4FE /* JavaScriptCore.framework */, 71 | 2D16E6891FA4F8E400B85C8A /* libReact.a */, 72 | D43FF9D506E70904424FA7E9 /* libPods-reactnativeapp.a */, 73 | ); 74 | name = Frameworks; 75 | sourceTree = ""; 76 | }; 77 | 72E5486571395D51695C2A02 /* Pods */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 3B47C5AFCB8BDE514B7D1AC6 /* Pods-reactnativeapp.debug.xcconfig */, 81 | 8AC623DBF3A3E2CB072F81F2 /* Pods-reactnativeapp.release.xcconfig */, 82 | ); 83 | name = Pods; 84 | path = Pods; 85 | sourceTree = ""; 86 | }; 87 | 832341AE1AAA6A7D00B99B32 /* Libraries */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | ); 91 | name = Libraries; 92 | sourceTree = ""; 93 | }; 94 | 83CBB9F61A601CBA00E9B192 = { 95 | isa = PBXGroup; 96 | children = ( 97 | 13B07FAE1A68108700A75B9A /* reactnativeapp */, 98 | 832341AE1AAA6A7D00B99B32 /* Libraries */, 99 | 83CBBA001A601CBA00E9B192 /* Products */, 100 | 2D16E6871FA4F8E400B85C8A /* Frameworks */, 101 | 72E5486571395D51695C2A02 /* Pods */, 102 | ); 103 | indentWidth = 2; 104 | sourceTree = ""; 105 | tabWidth = 2; 106 | usesTabs = 0; 107 | }; 108 | 83CBBA001A601CBA00E9B192 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 13B07F961A680F5B00A75B9A /* reactnativeapp.app */, 112 | ); 113 | name = Products; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXNativeTarget section */ 119 | 13B07F861A680F5B00A75B9A /* reactnativeapp */ = { 120 | isa = PBXNativeTarget; 121 | buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "reactnativeapp" */; 122 | buildPhases = ( 123 | F8BC737F2AD7A05944D9E2A1 /* [CP] Check Pods Manifest.lock */, 124 | FD4C38642228810C00325AF5 /* Start Packager */, 125 | 13B07F871A680F5B00A75B9A /* Sources */, 126 | 13B07F8C1A680F5B00A75B9A /* Frameworks */, 127 | 13B07F8E1A680F5B00A75B9A /* Resources */, 128 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = reactnativeapp; 135 | productName = "Hello World"; 136 | productReference = 13B07F961A680F5B00A75B9A /* reactnativeapp.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | 83CBB9F71A601CBA00E9B192 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | LastUpgradeCheck = 940; 146 | ORGANIZATIONNAME = Facebook; 147 | TargetAttributes = { 148 | 00E356ED1AD99517003FC87E = { 149 | CreatedOnToolsVersion = 6.2; 150 | TestTargetID = 13B07F861A680F5B00A75B9A; 151 | }; 152 | }; 153 | }; 154 | buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "reactnativeapp" */; 155 | compatibilityVersion = "Xcode 3.2"; 156 | developmentRegion = English; 157 | hasScannedForEncodings = 0; 158 | knownRegions = ( 159 | en, 160 | Base, 161 | ); 162 | mainGroup = 83CBB9F61A601CBA00E9B192; 163 | productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; 164 | projectDirPath = ""; 165 | projectRoot = ""; 166 | targets = ( 167 | 13B07F861A680F5B00A75B9A /* reactnativeapp */, 168 | ); 169 | }; 170 | /* End PBXProject section */ 171 | 172 | /* Begin PBXResourcesBuildPhase section */ 173 | 13B07F8E1A680F5B00A75B9A /* Resources */ = { 174 | isa = PBXResourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | 0731D3B824204D62009CAE85 /* Expo.plist in Resources */, 178 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 179 | 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, 180 | 99EC63CFB4B549BBAF093D10 /* SplashScreen.storyboard in Resources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXResourcesBuildPhase section */ 185 | 186 | /* Begin PBXShellScriptBuildPhase section */ 187 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { 188 | isa = PBXShellScriptBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | ); 192 | inputPaths = ( 193 | ); 194 | name = "Bundle React Native code and images"; 195 | outputPaths = ( 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | shellPath = /bin/sh; 199 | shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n../node_modules/expo-updates/scripts/create-manifest-ios.sh"; 200 | }; 201 | F8BC737F2AD7A05944D9E2A1 /* [CP] Check Pods Manifest.lock */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputFileListPaths = ( 207 | ); 208 | inputPaths = ( 209 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 210 | "${PODS_ROOT}/Manifest.lock", 211 | ); 212 | name = "[CP] Check Pods Manifest.lock"; 213 | outputFileListPaths = ( 214 | ); 215 | outputPaths = ( 216 | "$(DERIVED_FILE_DIR)/Pods-reactnativeapp-checkManifestLockResult.txt", 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | shellPath = /bin/sh; 220 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 221 | showEnvVarsInLog = 0; 222 | }; 223 | FD4C38642228810C00325AF5 /* Start Packager */ = { 224 | isa = PBXShellScriptBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | inputFileListPaths = ( 229 | ); 230 | inputPaths = ( 231 | ); 232 | name = "Start Packager"; 233 | outputFileListPaths = ( 234 | ); 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8085}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; 240 | showEnvVarsInLog = 0; 241 | }; 242 | /* End PBXShellScriptBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | 13B07F871A680F5B00A75B9A /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, 250 | 13B07FC11A68108700A75B9A /* main.m in Sources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | /* End PBXSourcesBuildPhase section */ 255 | 256 | /* Begin PBXVariantGroup section */ 257 | 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { 258 | isa = PBXVariantGroup; 259 | children = ( 260 | 13B07FB21A68108700A75B9A /* Base */, 261 | ); 262 | name = LaunchScreen.xib; 263 | path = reactnativeapp; 264 | sourceTree = ""; 265 | }; 266 | /* End PBXVariantGroup section */ 267 | 268 | /* Begin XCBuildConfiguration section */ 269 | 13B07F941A680F5B00A75B9A /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | baseConfigurationReference = 3B47C5AFCB8BDE514B7D1AC6 /* Pods-reactnativeapp.debug.xcconfig */; 272 | buildSettings = { 273 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 274 | CURRENT_PROJECT_VERSION = 1; 275 | DEAD_CODE_STRIPPING = NO; 276 | INFOPLIST_FILE = reactnativeapp/Info.plist; 277 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 278 | OTHER_LDFLAGS = ( 279 | "$(inherited)", 280 | "-ObjC", 281 | "-lc++", 282 | ); 283 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; 284 | PRODUCT_NAME = reactnativeapp; 285 | VERSIONING_SYSTEM = "apple-generic"; 286 | }; 287 | name = Debug; 288 | }; 289 | 13B07F951A680F5B00A75B9A /* Release */ = { 290 | isa = XCBuildConfiguration; 291 | baseConfigurationReference = 8AC623DBF3A3E2CB072F81F2 /* Pods-reactnativeapp.release.xcconfig */; 292 | buildSettings = { 293 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 294 | CURRENT_PROJECT_VERSION = 1; 295 | INFOPLIST_FILE = reactnativeapp/Info.plist; 296 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 297 | OTHER_LDFLAGS = ( 298 | "$(inherited)", 299 | "-ObjC", 300 | "-lc++", 301 | ); 302 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; 303 | PRODUCT_NAME = reactnativeapp; 304 | VERSIONING_SYSTEM = "apple-generic"; 305 | }; 306 | name = Release; 307 | }; 308 | 83CBBA201A601CBA00E9B192 /* Debug */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ALWAYS_SEARCH_USER_PATHS = NO; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | ENABLE_TESTABILITY = YES; 339 | GCC_C_LANGUAGE_STANDARD = gnu99; 340 | GCC_DYNAMIC_NO_PIC = NO; 341 | GCC_NO_COMMON_BLOCKS = YES; 342 | GCC_OPTIMIZATION_LEVEL = 0; 343 | GCC_PREPROCESSOR_DEFINITIONS = ( 344 | "DEBUG=1", 345 | "$(inherited)", 346 | ); 347 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 355 | MTL_ENABLE_DEBUG_INFO = YES; 356 | ONLY_ACTIVE_ARCH = YES; 357 | SDKROOT = iphoneos; 358 | }; 359 | name = Debug; 360 | }; 361 | 83CBBA211A601CBA00E9B192 /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = YES; 390 | ENABLE_NS_ASSERTIONS = NO; 391 | ENABLE_STRICT_OBJC_MSGSEND = YES; 392 | GCC_C_LANGUAGE_STANDARD = gnu99; 393 | GCC_NO_COMMON_BLOCKS = YES; 394 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 395 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 396 | GCC_WARN_UNDECLARED_SELECTOR = YES; 397 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 398 | GCC_WARN_UNUSED_FUNCTION = YES; 399 | GCC_WARN_UNUSED_VARIABLE = YES; 400 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 401 | MTL_ENABLE_DEBUG_INFO = NO; 402 | SDKROOT = iphoneos; 403 | VALIDATE_PRODUCT = YES; 404 | }; 405 | name = Release; 406 | }; 407 | /* End XCBuildConfiguration section */ 408 | 409 | /* Begin XCConfigurationList section */ 410 | 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "reactnativeapp" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | 13B07F941A680F5B00A75B9A /* Debug */, 414 | 13B07F951A680F5B00A75B9A /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "reactnativeapp" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | 83CBBA201A601CBA00E9B192 /* Debug */, 423 | 83CBBA211A601CBA00E9B192 /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | /* End XCConfigurationList section */ 429 | }; 430 | rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; 431 | } 432 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp.xcodeproj/xcshareddata/xcschemes/reactnativeapp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | #import 11 | #import 12 | 13 | #import 14 | 15 | @interface AppDelegate : UMAppDelegateWrapper 16 | 17 | @property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter; 18 | @property (nonatomic, strong) UIWindow *window; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | 13 | #import 14 | #import 15 | #import 16 | 17 | @interface AppDelegate () 18 | 19 | @property (nonatomic, strong) NSDictionary *launchOptions; 20 | 21 | @end 22 | 23 | @implementation AppDelegate 24 | 25 | @synthesize window = _window; 26 | 27 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 28 | { 29 | self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]]; 30 | self.launchOptions = launchOptions; 31 | 32 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 33 | #ifdef DEBUG 34 | [self initializeReactNativeApp]; 35 | #else 36 | EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance]; 37 | controller.delegate = self; 38 | [controller startAndShowLaunchScreen:self.window]; 39 | #endif 40 | 41 | [super application:application didFinishLaunchingWithOptions:launchOptions]; 42 | 43 | return YES; 44 | } 45 | 46 | - (RCTBridge *)initializeReactNativeApp 47 | { 48 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions]; 49 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil]; 50 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 51 | 52 | UIViewController *rootViewController = [UIViewController new]; 53 | rootViewController.view = rootView; 54 | self.window.rootViewController = rootViewController; 55 | [self.window makeKeyAndVisible]; 56 | 57 | return bridge; 58 | } 59 | 60 | - (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge 61 | { 62 | NSArray> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge]; 63 | // You can inject any extra modules that you would like here, more information at: 64 | // https://facebook.github.io/react-native/docs/native-modules-ios.html#dependency-injection 65 | return extraModules; 66 | } 67 | 68 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { 69 | #ifdef DEBUG 70 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 71 | #else 72 | return [[EXUpdatesAppController sharedInstance] launchAssetUrl]; 73 | #endif 74 | } 75 | 76 | - (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success 77 | { 78 | appController.bridge = [self initializeReactNativeApp]; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Images.xcassets/SplashScreen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "splashscreen.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "scale": "2x" 11 | }, 12 | { 13 | "idiom": "universal", 14 | "scale": "3x" 15 | } 16 | ], 17 | "info": { 18 | "version": 1, 19 | "author": "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Images.xcassets/SplashScreen.imageset/splashscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstorydev/fullstory-babel-plugin-annotate-react/0fb496f012cef23aafeec59f5dca603681418312/samples/react-native-app/ios/reactnativeapp/Images.xcassets/SplashScreen.imageset/splashscreen.png -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | react-native-app 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSLocationWhenInUseUsageDescription 28 | 29 | UILaunchStoryboardName 30 | SplashScreen 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | NSLocationWhenInUseUsageDescription 44 | 45 | NSAppTransportSecurity 46 | 47 | 48 | NSAllowsArbitraryLoads 49 | 50 | NSExceptionDomains 51 | 52 | localhost 53 | 54 | NSExceptionAllowsInsecureHTTPLoads 55 | 56 | 57 | 58 | 59 | NSCalendarsUsageDescription 60 | Allow react-native-app to access your calendar 61 | NSCameraUsageDescription 62 | Allow react-native-app to use the camera 63 | NSContactsUsageDescription 64 | Allow react-native-app experiences to access your contacts 65 | NSLocationAlwaysAndWhenInUseUsageDescription 66 | Allow react-native-app to use your location 67 | NSLocationAlwaysUsageDescription 68 | Allow react-native-app to use your location 69 | NSLocationWhenInUseUsageDescription 70 | Allow react-native-app to use your location 71 | NSMicrophoneUsageDescription 72 | Allow react-native-app to access your microphone 73 | NSMotionUsageDescription 74 | Allow react-native-app to access your device's accelerometer 75 | NSPhotoLibraryAddUsageDescription 76 | Give react-native-app periences permission to save photos 77 | NSPhotoLibraryUsageDescription 78 | Give react-native-app periences permission to access your photos 79 | NSRemindersUsageDescription 80 | Allow react-native-app to access your reminders 81 | 82 | 83 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/SplashScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/Supporting/Expo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXUpdatesSDKVersion 6 | YOUR-APP-SDK-VERSION-HERE 7 | EXUpdatesURL 8 | YOUR-APP-URL-HERE 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/react-native-app/ios/reactnativeapp/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/react-native-app/metro.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resetCache: true, 3 | transformer: { 4 | assetPlugins: ['expo-asset/tools/hashAssetFiles'], 5 | }, 6 | server: { 7 | port: 8085, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /samples/react-native-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js", 3 | "scripts": { 4 | "android": "react-native run-android", 5 | "ios": "react-native run-ios", 6 | "web": "expo start --web", 7 | "start": "react-native start", 8 | "test": "jest" 9 | }, 10 | "dependencies": { 11 | "expo": "^49.0.16", 12 | "expo-splash-screen": "^0.20.0", 13 | "expo-updates": "~0.18.16", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0", 16 | "react-native": "0.72.6", 17 | "react-native-gesture-handler": "~2.12.0", 18 | "react-native-reanimated": "~3.3.0", 19 | "react-native-screens": "~3.22.0", 20 | "react-native-unimodules": "~0.9.0", 21 | "react-native-web": "~0.19.6", 22 | "@expo/webpack-config": "^19.0.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "~7.23.2", 26 | "babel-jest": "~29.7.0", 27 | "jest": "^29.2.1", 28 | "react-test-renderer": "~16.9.0" 29 | }, 30 | "jest": { 31 | "preset": "react-native" 32 | }, 33 | "overrides": { 34 | "semver": "^7.5.3" 35 | }, 36 | "private": true 37 | } 38 | -------------------------------------------------------------------------------- /samples/single-page-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /samples/single-page-app/README.md: -------------------------------------------------------------------------------- 1 | # Single Page App Sample 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) using the "single-page-app" template. It was then "ejected" so that the configuration files are locally editable. 4 | 5 | To enable the @fullstory/babel-plugin-annotate-react plugin, edit your module rules for files matching `/\.(js|mjs|jsx|ts|tsx)$/` to include the plugin. In our sample repository we modified the `fullstory-babel-plugin-annotate-react/samples/single-page-app/config/webpack.config.js` file (around line 426). Since this sample is using the local plugin, we use `../..` as the plugin name. However, you should use the npm package name: `@fullstory/babel-plugin-annotate-react`. 6 | 7 | To test this sample: 8 | 9 | cd fullstory-babel-plugin-annotate-react/samples/single-page-app/ 10 | npm install 11 | npm run start 12 | 13 | A browser window should open and you can compare the source components in `fullstory-babel-plugin-annotate-react/samples/single-page-app/src/App.js` to the rendered DOM. Look for the `data-component`, `data-element`, and `data-source-file` attributes added by this plugin. 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/single-page-app/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | const dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | // Don't include `.env.local` for `test` environment 21 | // since normally you expect tests to produce the same 22 | // results for everyone 23 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 24 | `${paths.dotenv}.${NODE_ENV}`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | // We support configuring the sockjs pathname during development. 81 | // These settings let a developer run multiple simultaneous projects. 82 | // They are used as the connection `hostname`, `pathname` and `port` 83 | // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` 84 | // and `sockPort` options in webpack-dev-server. 85 | WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, 86 | WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, 87 | WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, 88 | // Whether or not react-refresh is enabled. 89 | // It is defined here so it is available in the webpackHotDevClient. 90 | FAST_REFRESH: process.env.FAST_REFRESH !== 'false', 91 | } 92 | ); 93 | // Stringify all values so we can feed into webpack DefinePlugin 94 | const stringified = { 95 | 'process.env': Object.keys(raw).reduce((env, key) => { 96 | env[key] = JSON.stringify(raw[key]); 97 | return env; 98 | }, {}), 99 | }; 100 | 101 | return { raw, stringified }; 102 | } 103 | 104 | module.exports = getClientEnvironment; 105 | -------------------------------------------------------------------------------- /samples/single-page-app/config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /samples/single-page-app/config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babelJest = require('babel-jest').default; 4 | 5 | const hasJsxRuntime = (() => { 6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 7 | return false; 8 | } 9 | 10 | try { 11 | require.resolve('react/jsx-runtime'); 12 | return true; 13 | } catch (e) { 14 | return false; 15 | } 16 | })(); 17 | 18 | module.exports = babelJest.createTransformer({ 19 | presets: [ 20 | [ 21 | require.resolve('babel-preset-react-app'), 22 | { 23 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 24 | }, 25 | ], 26 | ], 27 | babelrc: false, 28 | configFile: false, 29 | }); 30 | -------------------------------------------------------------------------------- /samples/single-page-app/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return { 9 | code: 'module.exports = {};' 10 | }; 11 | }, 12 | getCacheKey() { 13 | // The output is always the same. 14 | return 'cssTransform'; 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /samples/single-page-app/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return { 21 | code: `const React = require('react'); 22 | module.exports = { 23 | __esModule: true, 24 | default: ${assetFilename}, 25 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 26 | return { 27 | $$typeof: Symbol.for('react.element'), 28 | type: 'svg', 29 | ref: ref, 30 | key: null, 31 | props: Object.assign({}, props, { 32 | children: ${assetFilename} 33 | }) 34 | }; 35 | }), 36 | };` 37 | } 38 | } 39 | 40 | return { 41 | code: `module.exports = ${assetFilename};` 42 | }; 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /samples/single-page-app/config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const resolve = require('resolve'); 8 | 9 | /** 10 | * Get additional module paths based on the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl; 16 | 17 | if (!baseUrl) { 18 | return ''; 19 | } 20 | 21 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 22 | 23 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 24 | // the default behavior. 25 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 26 | return null; 27 | } 28 | 29 | // Allow the user set the `baseUrl` to `appSrc`. 30 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 31 | return [paths.appSrc]; 32 | } 33 | 34 | // If the path is equal to the root directory we ignore it here. 35 | // We don't want to allow importing from the root directly as source files are 36 | // not transpiled outside of `src`. We do allow importing them with the 37 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 38 | // an alias. 39 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 40 | return null; 41 | } 42 | 43 | // Otherwise, throw an error. 44 | throw new Error( 45 | chalk.red.bold( 46 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 47 | ' Create React App does not support other values at this time.' 48 | ) 49 | ); 50 | } 51 | 52 | /** 53 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 54 | * 55 | * @param {*} options 56 | */ 57 | function getWebpackAliases(options = {}) { 58 | const baseUrl = options.baseUrl; 59 | 60 | if (!baseUrl) { 61 | return {}; 62 | } 63 | 64 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 65 | 66 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 67 | return { 68 | src: paths.appSrc, 69 | }; 70 | } 71 | } 72 | 73 | /** 74 | * Get jest aliases based on the baseUrl of a compilerOptions object. 75 | * 76 | * @param {*} options 77 | */ 78 | function getJestAliases(options = {}) { 79 | const baseUrl = options.baseUrl; 80 | 81 | if (!baseUrl) { 82 | return {}; 83 | } 84 | 85 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 86 | 87 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 88 | return { 89 | '^src/(.*)$': '/src/$1', 90 | }; 91 | } 92 | } 93 | 94 | function getModules() { 95 | // Check if TypeScript is setup 96 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 97 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 98 | 99 | if (hasTsConfig && hasJsConfig) { 100 | throw new Error( 101 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 102 | ); 103 | } 104 | 105 | let config; 106 | 107 | // If there's a tsconfig.json we assume it's a 108 | // TypeScript project and set up the config 109 | // based on tsconfig.json 110 | if (hasTsConfig) { 111 | const ts = require(resolve.sync('typescript', { 112 | basedir: paths.appNodeModules, 113 | })); 114 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; 115 | // Otherwise we'll check if there is jsconfig.json 116 | // for non TS projects. 117 | } else if (hasJsConfig) { 118 | config = require(paths.appJsConfig); 119 | } 120 | 121 | config = config || {}; 122 | const options = config.compilerOptions || {}; 123 | 124 | const additionalModulePaths = getAdditionalModulePaths(options); 125 | 126 | return { 127 | additionalModulePaths: additionalModulePaths, 128 | webpackAliases: getWebpackAliases(options), 129 | jestAliases: getJestAliases(options), 130 | hasTsConfig, 131 | }; 132 | } 133 | 134 | module.exports = getModules(); 135 | -------------------------------------------------------------------------------- /samples/single-page-app/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right