npm package: @fullstory/babel-plugin-annotate-react
Description: A Babel plugin that annotates React components, making them easier to target with FullStory search
Author: FullStory
Latest version: 2.4.0

# Babel Plugin: Annotate React
[![CircleCI](https://circleci.com/gh/fullstorydev/fullstory-babel-plugin-annotate-react.svg?style=svg)](https://circleci.com/gh/fullstorydev/fullstory-babel-plugin-annotate-react)

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.

# Getting started

## with npm

```
npm i @fullstory/babel-plugin-annotate-react --save
```

## with yarn

```
yarn add @fullstory/babel-plugin-annotate-react
```

## Babel configuration

Add the `@fullstory/babel-plugin-annotate-react` plugin to your babel plugin configuration (e.g., **babel.config.js**).

```
module.exports = {
  /* ... */
  plugins: [
    /* ... */
    "@fullstory/babel-plugin-annotate-react"
  ]
};
```

# Description

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`.

> 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***.

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`.

Example input:

    class HelloComponent extends Component {
      render() {
        return <div>
          <h1>Hello world</h1>
        </div>;
      }
    }

Example JS output:

    class HelloComponent extends Component {
      render() {
        return React.createElement("div", {
          "data-component": "HelloComponent",
          "data-file-source": "hello-component.js"
        }, React.createElement("h1", {
          null
        }, "Hello world"));
      }
    }

Final render:

    <div data-component="HelloComponent" data-file-source="hello-component.js">
      <h1>Hello world</h1>
    </div>

# Configuration options

## React Native

To activate React Native support you must pass in a `native` plugin option like so:

    plugins: [
      ["@fullstory/babel-plugin-annotate-react", { native: true }]
    ]

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.

### `setFSTagName` setting

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.

Example:
* Before `RCTSafeAreaView[data-source-file="App.tsx"][data-element="SafeAreaView"][data-component="App"]`
* After `App[data-source-file="App.tsx"]`

```
plugins: [
  '@fullstory/react-native',
  ["@fullstory/annotate-react", {
    native: true,
    setFSTagName: true,
  }]
]
```

⚠️ Important: Existing FullStory privacy selectors and defined elements may need to be updated if the app was previously published without `setFSTagName: true`.
<!-- todo: write up a KB article to walk customers through transitioning to `fsTagName` if they have pre-existing privacy selectors or defined elements; link to it here -->


## Fragments

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.

An example with no child element:

    const componentName = () => (
      <Fragment>Hello, there.</Fragment>
    );

An example with child elements:

    const componentName = () => (
      <Fragment>
        Some text
        <h1>Hello, there.</h1> /* This one could be annotated */
        <a href="#foo">Click me</a>
      </Fragment>
    );


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:

    plugins: [
      ["@fullstory/babel-plugin-annotate-react", { "annotate-fragments": true }]
    ]

## React Compiler

If your project uses the [React Compiler](https://react.dev/learn/react-compiler), enable the `reactCompiler` option:

```javascript
plugins: [
  ["@fullstory/babel-plugin-annotate-react", { reactCompiler: true }]
]
```

The React Compiler optimizes function components by extracting JSX subtrees out of `return` statements into intermediate cached variables. This means a compiled component may look like:

```javascript
// Before React Compiler
function MyComponent() {
  return <div className="foo">Hello</div>;
}

// After React Compiler (simplified)
function MyComponent() {
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = <div className="foo">Hello</div>;
    $[0] = t0;
  }
  return t0;
}
```

Without `reactCompiler: true`, the plugin would see a non-JSX `return` statement and skip the component entirely, producing no annotations. With the option enabled, the plugin traverses the entire function body to find and annotate all JSX nodes, regardless of where they appear.

⚠️ **Important:** Because the React Compiler restructures how JSX is emitted, enabling `reactCompiler: true` may change which JSX nodes get annotated. Selectors and named elements previously defined in Fullstory are not guaranteed to match after enabling this option. Review your existing segments, metrics, and defined elements after activating this setting.

## Ignoring Components

If you would like the plugin to skip the annotation for certain components, use the `ignoreComponents` option:

```javascript
  plugins: [
      [
        "@fullstory/annotate-react",
        {
          ignoreComponents:[
            // each item must be a string array containing three items: file name, component name, element name
            // corresponding to the values for data-source-file, data-component, data-element
            // use wild card (*) to match anything
            ["myBoxComponent.jsx","MyBox","Box"],
            ["App.jsx", "*", "ThemeProvider"], // use wild-card to match anything
            ["App.jsx", "App", "*"],
          ]
        }
      ],
  ]
```

## Sample Apps

We have a few samples to demonstrate this plugin:

- [Single Page App](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tree/master/samples/single-page-app/)
- [styled-components](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tree/master/samples/styled-components/)
- [React native](https://github.com/fullstorydev/fullstory-babel-plugin-annotate-react/tree/master/samples/react-native-app/)

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.

## Getting Help

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.

### React Native

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.