@truewatchtech/react-native-mobile-babel-plugin 0.4.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -0
- package/lib/commonjs/actions/global.js +19 -0
- package/lib/commonjs/actions/global.js.map +1 -0
- package/lib/commonjs/actions/rum/components.js +81 -0
- package/lib/commonjs/actions/rum/components.js.map +1 -0
- package/lib/commonjs/actions/rum/content.js +168 -0
- package/lib/commonjs/actions/rum/content.js.map +1 -0
- package/lib/commonjs/actions/rum/index.js +68 -0
- package/lib/commonjs/actions/rum/index.js.map +1 -0
- package/lib/commonjs/actions/rum/memoization.js +88 -0
- package/lib/commonjs/actions/rum/memoization.js.map +1 -0
- package/lib/commonjs/actions/rum/metadata.js +63 -0
- package/lib/commonjs/actions/rum/metadata.js.map +1 -0
- package/lib/commonjs/actions/rum/tap.js +45 -0
- package/lib/commonjs/actions/rum/tap.js.map +1 -0
- package/lib/commonjs/constants.js +25 -0
- package/lib/commonjs/constants.js.map +1 -0
- package/lib/commonjs/index.js +48 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/options.js +23 -0
- package/lib/commonjs/options.js.map +1 -0
- package/lib/commonjs/state.js +32 -0
- package/lib/commonjs/state.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/jsx.js +43 -0
- package/lib/commonjs/utils/jsx.js.map +1 -0
- package/lib/module/actions/global.js +13 -0
- package/lib/module/actions/global.js.map +1 -0
- package/lib/module/actions/rum/components.js +73 -0
- package/lib/module/actions/rum/components.js.map +1 -0
- package/lib/module/actions/rum/content.js +161 -0
- package/lib/module/actions/rum/content.js.map +1 -0
- package/lib/module/actions/rum/index.js +61 -0
- package/lib/module/actions/rum/index.js.map +1 -0
- package/lib/module/actions/rum/memoization.js +82 -0
- package/lib/module/actions/rum/memoization.js.map +1 -0
- package/lib/module/actions/rum/metadata.js +56 -0
- package/lib/module/actions/rum/metadata.js.map +1 -0
- package/lib/module/actions/rum/tap.js +38 -0
- package/lib/module/actions/rum/tap.js.map +1 -0
- package/lib/module/constants.js +19 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +42 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/options.js +17 -0
- package/lib/module/options.js.map +1 -0
- package/lib/module/state.js +25 -0
- package/lib/module/state.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/jsx.js +36 -0
- package/lib/module/utils/jsx.js.map +1 -0
- package/lib/typescript/actions/global.d.ts +2 -0
- package/lib/typescript/actions/rum/components.d.ts +5 -0
- package/lib/typescript/actions/rum/content.d.ts +6 -0
- package/lib/typescript/actions/rum/index.d.ts +4 -0
- package/lib/typescript/actions/rum/memoization.d.ts +4 -0
- package/lib/typescript/actions/rum/metadata.d.ts +4 -0
- package/lib/typescript/actions/rum/tap.d.ts +6 -0
- package/lib/typescript/constants.d.ts +4 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/options.d.ts +2 -0
- package/lib/typescript/state.d.ts +5 -0
- package/lib/typescript/types.d.ts +56 -0
- package/lib/typescript/utils/jsx.d.ts +4 -0
- package/package.json +68 -0
- package/src/actions/global.ts +48 -0
- package/src/actions/rum/components.ts +128 -0
- package/src/actions/rum/content.ts +274 -0
- package/src/actions/rum/index.ts +154 -0
- package/src/actions/rum/memoization.ts +136 -0
- package/src/actions/rum/metadata.ts +147 -0
- package/src/actions/rum/tap.ts +148 -0
- package/src/constants.ts +21 -0
- package/src/index.ts +50 -0
- package/src/options.ts +26 -0
- package/src/state.ts +39 -0
- package/src/types.ts +72 -0
- package/src/utils/jsx.ts +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# TrueWatch React Native Babel plugin
|
|
2
|
+
|
|
3
|
+
This plugin wraps supported React Native interaction handlers at build time so
|
|
4
|
+
the TrueWatch SDK can write a stable, readable value to RUM `action_name`. Use
|
|
5
|
+
the same release version as `@truewatchtech/react-native-mobile`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
yarn add -D @truewatchtech/react-native-mobile-babel-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Add the plugin after the React Native preset:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
module.exports = {
|
|
17
|
+
presets: ['module:metro-react-native-babel-preset'],
|
|
18
|
+
plugins: ['@truewatchtech/react-native-mobile-babel-plugin'],
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Keep `enableAutoTrackUserAction: true` in `FTRUMConfig`. When the Babel plugin
|
|
23
|
+
is present, the SDK uses Babel-based interaction tracking instead of patching
|
|
24
|
+
React at runtime. Applications that do not install the plugin keep the legacy
|
|
25
|
+
runtime behavior. Both paths call `startAction`; Babel-based handlers use their
|
|
26
|
+
configured `actionType` or `click` by default. Both honor `ft-enable-track` and
|
|
27
|
+
`ft-extra-property` when the interaction event provides a React Native Fiber
|
|
28
|
+
target.
|
|
29
|
+
|
|
30
|
+
## Action name priority
|
|
31
|
+
|
|
32
|
+
For a tracked component, the action name is selected in this order:
|
|
33
|
+
|
|
34
|
+
1. Static `ft-action-name` attribute.
|
|
35
|
+
2. Static custom attribute configured through `actionNameAttribute`.
|
|
36
|
+
3. Static `accessibilityLabel`.
|
|
37
|
+
4. Content from `trackingLabel`, `title`, `label`, `text`, a custom content
|
|
38
|
+
prop, or children.
|
|
39
|
+
5. JSX component name.
|
|
40
|
+
|
|
41
|
+
By default a label is prefixed with the component name, for example
|
|
42
|
+
`Button ("Submit")`.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Button ft-action-name="checkout" title="Pay now" onPress={submit} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Content extraction follows Datadog's click-time naming behavior. The generated
|
|
49
|
+
`getContent` closure reads variables and member expressions, evaluates content
|
|
50
|
+
props and children, and extracts text from the resulting React elements. This
|
|
51
|
+
supports dynamic titles, fragments, conditional children, arrays, and zero-argument
|
|
52
|
+
render functions. For example, each invocation of this helper uses its own title:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
const renderButton = (title, onPress) => (
|
|
56
|
+
<Pressable onPress={onPress}>
|
|
57
|
+
<Text>{title}</Text>
|
|
58
|
+
</Pressable>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
renderButton('Bind User', bindUser); // Pressable ("Bind User")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The getter reconstructs content elements with `React.createElement`, preserving
|
|
65
|
+
React 16.13.1 compatibility. It reads click-time values, not a snapshot of the last
|
|
66
|
+
render. Content expressions, including calls and property getters, can therefore
|
|
67
|
+
run again on each tracked interaction. Keep them free of side effects, or provide
|
|
68
|
+
a static `ft-action-name` / `accessibilityLabel` to bypass content extraction.
|
|
69
|
+
`useContent: false` also disables it. Business handlers still run once per call,
|
|
70
|
+
including when content extraction or reporting throws; a content-extraction error
|
|
71
|
+
skips that Action.
|
|
72
|
+
|
|
73
|
+
Render functions with parameters, such as
|
|
74
|
+
`{({ pressed }) => <Text>{title}</Text>}`, are not invoked for naming. Without
|
|
75
|
+
another label, they fall back to the component name (`Pressable`). A zero-argument
|
|
76
|
+
render function can supply text. Spread props are not replayed for naming, and
|
|
77
|
+
props before a spread are ignored because the spread may override them. If no
|
|
78
|
+
non-empty name or content is available, the JSX component name is used.
|
|
79
|
+
|
|
80
|
+
Memoized handlers keep their `useCallback` / `useMemo` identity. If dynamic
|
|
81
|
+
content depends on a narrower scope than the handler definition, such as an
|
|
82
|
+
`item` declared inside `items.map`, the plugin omits that unsafe content getter
|
|
83
|
+
and falls back to the component name. Add a static `ft-action-name` or
|
|
84
|
+
`accessibilityLabel` when that pattern needs a descriptive action name.
|
|
85
|
+
|
|
86
|
+
Upgrade the Babel plugin and rebuild the application to apply these build-time
|
|
87
|
+
changes. Previously transformed bundles keep their existing generated code.
|
|
88
|
+
|
|
89
|
+
## Custom components
|
|
90
|
+
|
|
91
|
+
All configuration is optional. `useContent` and `useNamePrefix` default to
|
|
92
|
+
`true`; component-level values override the shared defaults.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
interface PluginOptions {
|
|
96
|
+
actionNameAttribute?: string;
|
|
97
|
+
components?: {
|
|
98
|
+
useContent?: boolean;
|
|
99
|
+
useNamePrefix?: boolean;
|
|
100
|
+
tracked?: Array<{
|
|
101
|
+
name: string;
|
|
102
|
+
useContent?: boolean;
|
|
103
|
+
useNamePrefix?: boolean;
|
|
104
|
+
contentProp?: string;
|
|
105
|
+
handlers: Array<{
|
|
106
|
+
event: string;
|
|
107
|
+
actionType?: string;
|
|
108
|
+
mode?: 'default' | 'delayed';
|
|
109
|
+
}>;
|
|
110
|
+
}>;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
module.exports = {
|
|
117
|
+
presets: ['module:metro-react-native-babel-preset'],
|
|
118
|
+
plugins: [
|
|
119
|
+
[
|
|
120
|
+
'@truewatchtech/react-native-mobile-babel-plugin',
|
|
121
|
+
{
|
|
122
|
+
actionNameAttribute: 'analytics-name',
|
|
123
|
+
components: {
|
|
124
|
+
useContent: true,
|
|
125
|
+
useNamePrefix: true,
|
|
126
|
+
tracked: [
|
|
127
|
+
{
|
|
128
|
+
name: 'CustomButton',
|
|
129
|
+
contentProp: 'caption',
|
|
130
|
+
handlers: [{ event: 'onPress', actionType: 'submit' }],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The plugin automatically tracks named imports of `Button`, `Pressable`, the
|
|
141
|
+
Touchable components, `Switch`, and `TextInput` from `react-native`. Import
|
|
142
|
+
aliases are supported. Namespace imports are not auto-discovered, but their
|
|
143
|
+
full JSX names can be configured through `components.tracked`.
|
|
144
|
+
|
|
145
|
+
`actionType` is written to RUM `action_type` through `startAction`; it defaults
|
|
146
|
+
to `click` when omitted, empty, or whitespace-only. Keep custom values stable and
|
|
147
|
+
low-cardinality. Explicit action-name attributes must be static strings; content
|
|
148
|
+
props and children may be expressions. Web builds and files under `node_modules`
|
|
149
|
+
are left unchanged. A handler configured with `mode: 'delayed'` is treated as a
|
|
150
|
+
factory whose return value is the actual interaction handler. Direct arrow
|
|
151
|
+
functions, function expressions, identifiers, member expressions, and conditional
|
|
152
|
+
expressions are supported.
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
The plugin is organized by responsibility:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
src/
|
|
160
|
+
index.ts Plugin declaration and visitor orchestration
|
|
161
|
+
constants.ts Runtime names, attributes, and supported native events
|
|
162
|
+
options.ts Configuration defaults
|
|
163
|
+
types.ts Public options and internal transform types
|
|
164
|
+
state.ts File filtering and per-file state initialization
|
|
165
|
+
actions/
|
|
166
|
+
global.ts Compatible plugin-enabled flag injection
|
|
167
|
+
rum/
|
|
168
|
+
index.ts JSX action processing and runtime imports
|
|
169
|
+
components.ts Component discovery and required TextInput handlers
|
|
170
|
+
content.ts JSX conversion and click-time content getters
|
|
171
|
+
metadata.ts Action-name attributes and runtime target objects
|
|
172
|
+
tap.ts Immediate, delayed, and conditional handler wrappers
|
|
173
|
+
memoization.ts useCallback / useMemo handling
|
|
174
|
+
utils/
|
|
175
|
+
jsx.ts JSX name resolution and attribute paths
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The visitor initializes state for each file, delegates tracked JSX elements to
|
|
179
|
+
`actions/rum`, and inserts the required runtime imports on program exit. Runtime
|
|
180
|
+
action-name selection and reporting live in `@truewatchtech/react-native-mobile`.
|
|
181
|
+
The package entry continues to export the plugin and its public option types.
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
Apache 2.0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.insertPluginEnabledFlag = insertPluginEnabledFlag;
|
|
7
|
+
var _constants = require("../constants");
|
|
8
|
+
/*
|
|
9
|
+
* Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
|
|
10
|
+
* interaction tracking implementation, licensed under Apache-2.0.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function insertPluginEnabledFlag(t, programPath) {
|
|
14
|
+
const createFlagAssignment = globalIdentifier => t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.cloneNode(globalIdentifier), t.identifier(_constants.PLUGIN_ENABLED_FLAG)), t.booleanLiteral(true)));
|
|
15
|
+
const globalThisIdentifier = t.identifier('globalThis');
|
|
16
|
+
const reactNativeGlobalIdentifier = t.identifier('global');
|
|
17
|
+
programPath.unshiftContainer('body', t.ifStatement(t.binaryExpression('!==', t.unaryExpression('typeof', t.cloneNode(globalThisIdentifier)), t.stringLiteral('undefined')), t.blockStatement([createFlagAssignment(globalThisIdentifier)]), t.ifStatement(t.binaryExpression('!==', t.unaryExpression('typeof', t.cloneNode(reactNativeGlobalIdentifier)), t.stringLiteral('undefined')), t.blockStatement([createFlagAssignment(reactNativeGlobalIdentifier)]))));
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_constants","require","insertPluginEnabledFlag","t","programPath","createFlagAssignment","globalIdentifier","expressionStatement","assignmentExpression","memberExpression","cloneNode","identifier","PLUGIN_ENABLED_FLAG","booleanLiteral","globalThisIdentifier","reactNativeGlobalIdentifier","unshiftContainer","ifStatement","binaryExpression","unaryExpression","stringLiteral","blockStatement"],"sources":["global.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\nimport { PLUGIN_ENABLED_FLAG } from '../constants';\n\nexport function insertPluginEnabledFlag(\n t: typeof Babel.types,\n programPath: Babel.NodePath<Babel.types.Program>\n): void {\n const createFlagAssignment = (\n globalIdentifier: Babel.types.Identifier\n ): Babel.types.ExpressionStatement =>\n t.expressionStatement(\n t.assignmentExpression(\n '=',\n t.memberExpression(\n t.cloneNode(globalIdentifier),\n t.identifier(PLUGIN_ENABLED_FLAG)\n ),\n t.booleanLiteral(true)\n )\n );\n const globalThisIdentifier = t.identifier('globalThis');\n const reactNativeGlobalIdentifier = t.identifier('global');\n\n programPath.unshiftContainer(\n 'body',\n t.ifStatement(\n t.binaryExpression(\n '!==',\n t.unaryExpression('typeof', t.cloneNode(globalThisIdentifier)),\n t.stringLiteral('undefined')\n ),\n t.blockStatement([createFlagAssignment(globalThisIdentifier)]),\n t.ifStatement(\n t.binaryExpression(\n '!==',\n t.unaryExpression('typeof', t.cloneNode(reactNativeGlobalIdentifier)),\n t.stringLiteral('undefined')\n ),\n t.blockStatement([createFlagAssignment(reactNativeGlobalIdentifier)])\n )\n )\n );\n}\n"],"mappings":";;;;;;AAMA,IAAAA,UAAA,GAAAC,OAAA;AANA;AACA;AACA;AACA;;AAKO,SAASC,uBAAuBA,CACrCC,CAAqB,EACrBC,WAAgD,EAC1C;EACN,MAAMC,oBAAoB,GACxBC,gBAAwC,IAExCH,CAAC,CAACI,mBAAmB,CACnBJ,CAAC,CAACK,oBAAoB,CACpB,GAAG,EACHL,CAAC,CAACM,gBAAgB,CAChBN,CAAC,CAACO,SAAS,CAACJ,gBAAgB,CAAC,EAC7BH,CAAC,CAACQ,UAAU,CAACC,8BAAmB,CAClC,CAAC,EACDT,CAAC,CAACU,cAAc,CAAC,IAAI,CACvB,CACF,CAAC;EACH,MAAMC,oBAAoB,GAAGX,CAAC,CAACQ,UAAU,CAAC,YAAY,CAAC;EACvD,MAAMI,2BAA2B,GAAGZ,CAAC,CAACQ,UAAU,CAAC,QAAQ,CAAC;EAE1DP,WAAW,CAACY,gBAAgB,CAC1B,MAAM,EACNb,CAAC,CAACc,WAAW,CACXd,CAAC,CAACe,gBAAgB,CAChB,KAAK,EACLf,CAAC,CAACgB,eAAe,CAAC,QAAQ,EAAEhB,CAAC,CAACO,SAAS,CAACI,oBAAoB,CAAC,CAAC,EAC9DX,CAAC,CAACiB,aAAa,CAAC,WAAW,CAC7B,CAAC,EACDjB,CAAC,CAACkB,cAAc,CAAC,CAAChB,oBAAoB,CAACS,oBAAoB,CAAC,CAAC,CAAC,EAC9DX,CAAC,CAACc,WAAW,CACXd,CAAC,CAACe,gBAAgB,CAChB,KAAK,EACLf,CAAC,CAACgB,eAAe,CAAC,QAAQ,EAAEhB,CAAC,CAACO,SAAS,CAACK,2BAA2B,CAAC,CAAC,EACrEZ,CAAC,CAACiB,aAAa,CAAC,WAAW,CAC7B,CAAC,EACDjB,CAAC,CAACkB,cAAc,CAAC,CAAChB,oBAAoB,CAACU,2BAA2B,CAAC,CAAC,CACtE,CACF,CACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.addRequiredTextInputHandler = addRequiredTextInputHandler;
|
|
7
|
+
exports.createTrackedComponents = createTrackedComponents;
|
|
8
|
+
exports.isInsideConfiguredComponent = isInsideConfiguredComponent;
|
|
9
|
+
var _constants = require("../../constants");
|
|
10
|
+
var _jsx = require("../../utils/jsx");
|
|
11
|
+
/*
|
|
12
|
+
* Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
|
|
13
|
+
* interaction tracking implementation, licensed under Apache-2.0.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
function createTrackedComponents(t, programPath, options) {
|
|
17
|
+
const tracked = {};
|
|
18
|
+
for (const statement of programPath.node.body) {
|
|
19
|
+
if (!t.isImportDeclaration(statement) || statement.source.value !== 'react-native') {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
for (const specifier of statement.specifiers) {
|
|
23
|
+
if (!t.isImportSpecifier(specifier)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const importedName = (0, _jsx.getNodeName)(t, specifier.imported);
|
|
27
|
+
const localName = (0, _jsx.getNodeName)(t, specifier.local);
|
|
28
|
+
const events = importedName ? _constants.NATIVE_COMPONENT_HANDLERS[importedName] : undefined;
|
|
29
|
+
if (!events || !importedName || !localName) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
tracked[localName] = {
|
|
33
|
+
handlers: events.map(event => ({
|
|
34
|
+
event
|
|
35
|
+
})),
|
|
36
|
+
importedName,
|
|
37
|
+
isCustom: false,
|
|
38
|
+
useContent: options.components.useContent,
|
|
39
|
+
useNamePrefix: options.components.useNamePrefix
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const component of options.components.tracked) {
|
|
44
|
+
tracked[component.name] = {
|
|
45
|
+
...(component.contentProp ? {
|
|
46
|
+
contentProp: component.contentProp
|
|
47
|
+
} : {}),
|
|
48
|
+
handlers: component.handlers || [],
|
|
49
|
+
isCustom: true,
|
|
50
|
+
useContent: component.useContent === undefined ? options.components.useContent : component.useContent,
|
|
51
|
+
useNamePrefix: component.useNamePrefix === undefined ? options.components.useNamePrefix : component.useNamePrefix
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return tracked;
|
|
55
|
+
}
|
|
56
|
+
function isInsideConfiguredComponent(t, path, customComponentNames) {
|
|
57
|
+
let current = path.parentPath;
|
|
58
|
+
while (current && !current.isProgram()) {
|
|
59
|
+
if (current.isFunctionDeclaration() || current.isClassDeclaration() || current.isVariableDeclarator()) {
|
|
60
|
+
const name = (0, _jsx.getNodeName)(t, current.node.id);
|
|
61
|
+
if (name && customComponentNames.has(name)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
current = current.parentPath;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
function addRequiredTextInputHandler(t, path, component, componentName) {
|
|
70
|
+
if (component.isCustom || component.importedName !== 'TextInput' || componentName !== 'TextInput') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const attributes = (0, _jsx.getDirectAttributePaths)(path);
|
|
74
|
+
const hasSpread = attributes.some(attribute => attribute.isJSXSpreadAttribute());
|
|
75
|
+
const hasOnFocus = attributes.some(attribute => attribute.isJSXAttribute() && (0, _jsx.getNodeName)(t, attribute.node.name) === 'onFocus');
|
|
76
|
+
if (!hasSpread && !hasOnFocus) {
|
|
77
|
+
const openingElementPath = path.get('openingElement');
|
|
78
|
+
openingElementPath.pushContainer('attributes', t.jsxAttribute(t.jsxIdentifier('onFocus'), t.jsxExpressionContainer(t.arrowFunctionExpression([], t.blockStatement([])))));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_constants","require","_jsx","createTrackedComponents","t","programPath","options","tracked","statement","node","body","isImportDeclaration","source","value","specifier","specifiers","isImportSpecifier","importedName","getNodeName","imported","localName","local","events","NATIVE_COMPONENT_HANDLERS","undefined","handlers","map","event","isCustom","useContent","components","useNamePrefix","component","name","contentProp","isInsideConfiguredComponent","path","customComponentNames","current","parentPath","isProgram","isFunctionDeclaration","isClassDeclaration","isVariableDeclarator","id","has","addRequiredTextInputHandler","componentName","attributes","getDirectAttributePaths","hasSpread","some","attribute","isJSXSpreadAttribute","hasOnFocus","isJSXAttribute","openingElementPath","get","pushContainer","jsxAttribute","jsxIdentifier","jsxExpressionContainer","arrowFunctionExpression","blockStatement"],"sources":["components.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\nimport { NATIVE_COMPONENT_HANDLERS } from '../../constants';\nimport type {\n NormalizedPluginOptions,\n TrackedComponentData,\n} from '../../types';\nimport { getDirectAttributePaths, getNodeName } from '../../utils/jsx';\n\nexport function createTrackedComponents(\n t: typeof Babel.types,\n programPath: Babel.NodePath<Babel.types.Program>,\n options: NormalizedPluginOptions\n): Record<string, TrackedComponentData> {\n const tracked: Record<string, TrackedComponentData> = {};\n\n for (const statement of programPath.node.body) {\n if (\n !t.isImportDeclaration(statement) ||\n statement.source.value !== 'react-native'\n ) {\n continue;\n }\n\n for (const specifier of statement.specifiers) {\n if (!t.isImportSpecifier(specifier)) {\n continue;\n }\n const importedName = getNodeName(t, specifier.imported);\n const localName = getNodeName(t, specifier.local);\n const events = importedName\n ? NATIVE_COMPONENT_HANDLERS[importedName]\n : undefined;\n if (!events || !importedName || !localName) {\n continue;\n }\n tracked[localName] = {\n handlers: events.map((event) => ({ event })),\n importedName,\n isCustom: false,\n useContent: options.components.useContent,\n useNamePrefix: options.components.useNamePrefix,\n };\n }\n }\n\n for (const component of options.components.tracked) {\n tracked[component.name] = {\n ...(component.contentProp ? { contentProp: component.contentProp } : {}),\n handlers: component.handlers || [],\n isCustom: true,\n useContent:\n component.useContent === undefined\n ? options.components.useContent\n : component.useContent,\n useNamePrefix:\n component.useNamePrefix === undefined\n ? options.components.useNamePrefix\n : component.useNamePrefix,\n };\n }\n\n return tracked;\n}\n\nexport function isInsideConfiguredComponent(\n t: typeof Babel.types,\n path: Babel.NodePath<Babel.types.JSXElement>,\n customComponentNames: Set<string>\n): boolean {\n let current: Babel.NodePath | null = path.parentPath;\n while (current && !current.isProgram()) {\n if (\n current.isFunctionDeclaration() ||\n current.isClassDeclaration() ||\n current.isVariableDeclarator()\n ) {\n const name = getNodeName(t, current.node.id);\n if (name && customComponentNames.has(name)) {\n return true;\n }\n }\n current = current.parentPath;\n }\n return false;\n}\n\nexport function addRequiredTextInputHandler(\n t: typeof Babel.types,\n path: Babel.NodePath<Babel.types.JSXElement>,\n component: TrackedComponentData,\n componentName: string\n): void {\n if (\n component.isCustom ||\n component.importedName !== 'TextInput' ||\n componentName !== 'TextInput'\n ) {\n return;\n }\n const attributes = getDirectAttributePaths(path);\n const hasSpread = attributes.some((attribute) =>\n attribute.isJSXSpreadAttribute()\n );\n const hasOnFocus = attributes.some(\n (attribute) =>\n attribute.isJSXAttribute() &&\n getNodeName(t, attribute.node.name) === 'onFocus'\n );\n if (!hasSpread && !hasOnFocus) {\n const openingElementPath = path.get(\n 'openingElement'\n ) as Babel.NodePath<Babel.types.JSXOpeningElement>;\n openingElementPath.pushContainer(\n 'attributes',\n t.jsxAttribute(\n t.jsxIdentifier('onFocus'),\n t.jsxExpressionContainer(\n t.arrowFunctionExpression([], t.blockStatement([]))\n )\n )\n );\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.buildContentGetter = buildContentGetter;
|
|
7
|
+
exports.isContentVisibleFromScope = isContentVisibleFromScope;
|
|
8
|
+
var _jsx = require("../../utils/jsx");
|
|
9
|
+
/*
|
|
10
|
+
* Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
|
|
11
|
+
* interaction tracking implementation, licensed under Apache-2.0.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const CONTENT_PROPS = ['trackingLabel', 'title', 'label', 'text'];
|
|
15
|
+
function contentCandidateNames(component) {
|
|
16
|
+
return component.contentProp && !CONTENT_PROPS.includes(component.contentProp) ? [...CONTENT_PROPS, component.contentProp] : CONTENT_PROPS;
|
|
17
|
+
}
|
|
18
|
+
function elementType(t, name) {
|
|
19
|
+
if (t.isJSXMemberExpression(name)) {
|
|
20
|
+
// Lowercase member receivers (e.g. ui.Text) are variables, not host tags.
|
|
21
|
+
const receiver = t.isJSXIdentifier(name.object) ? t.identifier(name.object.name) : elementType(t, name.object);
|
|
22
|
+
return t.memberExpression(receiver, t.identifier(name.property.name));
|
|
23
|
+
}
|
|
24
|
+
const value = (0, _jsx.getNodeName)(t, name) || '';
|
|
25
|
+
return t.isJSXIdentifier(name) && !/^[a-z]/.test(value) ? t.identifier(value) : t.stringLiteral(value);
|
|
26
|
+
}
|
|
27
|
+
function effectiveAttributes(t, attributes) {
|
|
28
|
+
const result = new Map();
|
|
29
|
+
for (const attribute of attributes) {
|
|
30
|
+
if (t.isJSXSpreadAttribute(attribute)) {
|
|
31
|
+
// Do not replay spreads or trust props they may have overridden.
|
|
32
|
+
result.clear();
|
|
33
|
+
} else {
|
|
34
|
+
result.set((0, _jsx.getNodeName)(t, attribute.name) || '', attribute);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function contentSourcePaths(t, path, component) {
|
|
40
|
+
const candidateNames = contentCandidateNames(component);
|
|
41
|
+
const attributes = new Map();
|
|
42
|
+
for (const attributePath of (0, _jsx.getDirectAttributePaths)(path)) {
|
|
43
|
+
if (!attributePath.isJSXAttribute()) {
|
|
44
|
+
attributes.clear();
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
attributes.set((0, _jsx.getNodeName)(t, attributePath.node.name) || '', attributePath);
|
|
48
|
+
}
|
|
49
|
+
const sources = [];
|
|
50
|
+
for (const name of candidateNames) {
|
|
51
|
+
const attributePath = attributes.get(name);
|
|
52
|
+
const valuePath = attributePath === null || attributePath === void 0 ? void 0 : attributePath.get('value');
|
|
53
|
+
if (valuePath && !Array.isArray(valuePath) && valuePath.node) {
|
|
54
|
+
sources.push(valuePath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return sources.concat(path.get('children'));
|
|
58
|
+
}
|
|
59
|
+
function containsPath(root, candidate) {
|
|
60
|
+
let current = candidate;
|
|
61
|
+
while (current) {
|
|
62
|
+
if (current === root || current.node === root.node) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
current = current.parentPath;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Checks whether moving content into a memo callback preserves every binding. */
|
|
71
|
+
function isContentVisibleFromScope(t, path, component, destinationScope) {
|
|
72
|
+
const sources = contentSourcePaths(t, path, component);
|
|
73
|
+
let visible = true;
|
|
74
|
+
const checkReference = (referencePath, name) => {
|
|
75
|
+
const sourceBinding = referencePath.scope.getBinding(name);
|
|
76
|
+
if (sourceBinding && sources.some(source => containsPath(source, sourceBinding.path))) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const destinationBinding = destinationScope.getBinding(name);
|
|
80
|
+
if ((sourceBinding === null || sourceBinding === void 0 ? void 0 : sourceBinding.identifier) !== (destinationBinding === null || destinationBinding === void 0 ? void 0 : destinationBinding.identifier)) {
|
|
81
|
+
visible = false;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
for (const source of sources) {
|
|
85
|
+
source.traverse({
|
|
86
|
+
ReferencedIdentifier(referencePath) {
|
|
87
|
+
checkReference(referencePath, referencePath.node.name);
|
|
88
|
+
if (!visible) {
|
|
89
|
+
referencePath.stop();
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
JSXOpeningElement(openingPath) {
|
|
93
|
+
let name = openingPath.node.name;
|
|
94
|
+
let isMember = false;
|
|
95
|
+
while (t.isJSXMemberExpression(name)) {
|
|
96
|
+
isMember = true;
|
|
97
|
+
name = name.object;
|
|
98
|
+
}
|
|
99
|
+
if (t.isJSXIdentifier(name) && (isMember || !/^[a-z]/.test(name.name))) {
|
|
100
|
+
checkReference(openingPath, name.name);
|
|
101
|
+
}
|
|
102
|
+
if (!visible) {
|
|
103
|
+
openingPath.stop();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
JSXSpreadAttribute(spreadPath) {
|
|
107
|
+
// Spread attributes are not copied into the content getter.
|
|
108
|
+
spreadPath.skip();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (!visible) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Converts JSX anywhere in an expression, including render functions and maps. */
|
|
119
|
+
function toRuntimeNode(t, node, react) {
|
|
120
|
+
if (t.isJSXElement(node) || t.isJSXFragment(node)) {
|
|
121
|
+
const properties = [];
|
|
122
|
+
if (t.isJSXElement(node)) {
|
|
123
|
+
for (const [name, attribute] of effectiveAttributes(t, node.openingElement.attributes)) {
|
|
124
|
+
properties.push(t.objectProperty(t.stringLiteral(name), attribute.value ? toRuntimeNode(t, attribute.value, react) : t.booleanLiteral(true)));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const children = node.children.filter(child => !(t.isJSXText(child) && !child.value.trim()) && !(t.isJSXExpressionContainer(child) && t.isJSXEmptyExpression(child.expression))).map(child => toRuntimeNode(t, child, react));
|
|
128
|
+
return t.callExpression(t.memberExpression(t.cloneNode(react), t.identifier('createElement')), [t.isJSXElement(node) ? elementType(t, node.openingElement.name) : t.memberExpression(t.cloneNode(react), t.identifier('Fragment')), properties.length ? t.objectExpression(properties) : t.nullLiteral(), ...children]);
|
|
129
|
+
}
|
|
130
|
+
if (t.isJSXText(node)) {
|
|
131
|
+
return t.stringLiteral(node.value.replace(/\s+/g, ' ').trim());
|
|
132
|
+
}
|
|
133
|
+
if (t.isJSXExpressionContainer(node)) {
|
|
134
|
+
return toRuntimeNode(t, node.expression, react);
|
|
135
|
+
}
|
|
136
|
+
// Recurse through all expression shapes so cloned JSX cannot survive Babel's
|
|
137
|
+
// JSX visitor or be instrumented a second time inside the content getter.
|
|
138
|
+
const cloned = t.cloneNode(node, false);
|
|
139
|
+
const fields = cloned;
|
|
140
|
+
for (const key of t.VISITOR_KEYS[node.type] || []) {
|
|
141
|
+
const value = fields[key];
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
fields[key] = value.map(child => child ? toRuntimeNode(t, child, react) : child);
|
|
144
|
+
} else if (value) {
|
|
145
|
+
fields[key] = toRuntimeNode(t, value, react);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return cloned;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Matches Datadog's click-time evaluation of content props and JSX children. */
|
|
152
|
+
function buildContentGetter(t, path, component, state) {
|
|
153
|
+
if (!component.useContent) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const candidateNames = contentCandidateNames(component);
|
|
157
|
+
const attributes = effectiveAttributes(t, path.node.openingElement.attributes);
|
|
158
|
+
const candidates = [];
|
|
159
|
+
for (const name of candidateNames) {
|
|
160
|
+
const attribute = attributes.get(name);
|
|
161
|
+
if (attribute !== null && attribute !== void 0 && attribute.value) {
|
|
162
|
+
candidates.push(toRuntimeNode(t, attribute.value, state._ftReactIdentifier));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const fragment = t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), path.node.children);
|
|
166
|
+
return t.arrowFunctionExpression([], t.callExpression(t.cloneNode(state._ftExtractTextIdentifier), [toRuntimeNode(t, fragment, state._ftReactIdentifier), t.arrayExpression(candidates)]));
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_jsx","require","CONTENT_PROPS","contentCandidateNames","component","contentProp","includes","elementType","t","name","isJSXMemberExpression","receiver","isJSXIdentifier","object","identifier","memberExpression","property","value","getNodeName","test","stringLiteral","effectiveAttributes","attributes","result","Map","attribute","isJSXSpreadAttribute","clear","set","contentSourcePaths","path","candidateNames","attributePath","getDirectAttributePaths","isJSXAttribute","node","sources","get","valuePath","Array","isArray","push","concat","containsPath","root","candidate","current","parentPath","isContentVisibleFromScope","destinationScope","visible","checkReference","referencePath","sourceBinding","scope","getBinding","some","source","destinationBinding","traverse","ReferencedIdentifier","stop","JSXOpeningElement","openingPath","isMember","JSXSpreadAttribute","spreadPath","skip","toRuntimeNode","react","isJSXElement","isJSXFragment","properties","openingElement","objectProperty","booleanLiteral","children","filter","child","isJSXText","trim","isJSXExpressionContainer","isJSXEmptyExpression","expression","map","callExpression","cloneNode","length","objectExpression","nullLiteral","replace","cloned","fields","key","VISITOR_KEYS","type","buildContentGetter","state","useContent","candidates","_ftReactIdentifier","fragment","jsxFragment","jsxOpeningFragment","jsxClosingFragment","arrowFunctionExpression","_ftExtractTextIdentifier","arrayExpression"],"sources":["content.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\nimport type { PluginState, TrackedComponentData } from '../../types';\nimport { getDirectAttributePaths, getNodeName } from '../../utils/jsx';\n\nconst CONTENT_PROPS = ['trackingLabel', 'title', 'label', 'text'];\n\nfunction contentCandidateNames(component: TrackedComponentData): string[] {\n return component.contentProp && !CONTENT_PROPS.includes(component.contentProp)\n ? [...CONTENT_PROPS, component.contentProp]\n : CONTENT_PROPS;\n}\n\nfunction elementType(\n t: typeof Babel.types,\n name: Babel.types.JSXOpeningElement['name']\n): Babel.types.Expression {\n if (t.isJSXMemberExpression(name)) {\n // Lowercase member receivers (e.g. ui.Text) are variables, not host tags.\n const receiver = t.isJSXIdentifier(name.object)\n ? t.identifier(name.object.name)\n : elementType(t, name.object);\n return t.memberExpression(receiver, t.identifier(name.property.name));\n }\n const value = getNodeName(t, name) || '';\n return t.isJSXIdentifier(name) && !/^[a-z]/.test(value)\n ? t.identifier(value)\n : t.stringLiteral(value);\n}\n\nfunction effectiveAttributes(\n t: typeof Babel.types,\n attributes: Babel.types.JSXOpeningElement['attributes']\n): Map<string, Babel.types.JSXAttribute> {\n const result = new Map<string, Babel.types.JSXAttribute>();\n for (const attribute of attributes) {\n if (t.isJSXSpreadAttribute(attribute)) {\n // Do not replay spreads or trust props they may have overridden.\n result.clear();\n } else {\n result.set(getNodeName(t, attribute.name) || '', attribute);\n }\n }\n return result;\n}\n\nfunction contentSourcePaths(\n t: typeof Babel.types,\n path: Babel.NodePath<Babel.types.JSXElement>,\n component: TrackedComponentData\n): Babel.NodePath[] {\n const candidateNames = contentCandidateNames(component);\n const attributes = new Map<\n string,\n Babel.NodePath<Babel.types.JSXAttribute>\n >();\n for (const attributePath of getDirectAttributePaths(path)) {\n if (!attributePath.isJSXAttribute()) {\n attributes.clear();\n continue;\n }\n attributes.set(\n getNodeName(t, attributePath.node.name) || '',\n attributePath\n );\n }\n const sources: Babel.NodePath[] = [];\n for (const name of candidateNames) {\n const attributePath = attributes.get(name);\n const valuePath = attributePath?.get('value');\n if (valuePath && !Array.isArray(valuePath) && valuePath.node) {\n sources.push(valuePath as Babel.NodePath<Babel.types.Node>);\n }\n }\n return sources.concat(path.get('children') as Babel.NodePath[]);\n}\n\nfunction containsPath(\n root: Babel.NodePath,\n candidate: Babel.NodePath\n): boolean {\n let current: Babel.NodePath | null = candidate;\n while (current) {\n if (current === root || current.node === root.node) {\n return true;\n }\n current = current.parentPath;\n }\n return false;\n}\n\n/** Checks whether moving content into a memo callback preserves every binding. */\nexport function isContentVisibleFromScope(\n t: typeof Babel.types,\n path: Babel.NodePath<Babel.types.JSXElement>,\n component: TrackedComponentData,\n destinationScope: Babel.NodePath['scope']\n): boolean {\n const sources = contentSourcePaths(t, path, component);\n let visible = true;\n const checkReference = (referencePath: Babel.NodePath, name: string) => {\n const sourceBinding = referencePath.scope.getBinding(name);\n if (\n sourceBinding &&\n sources.some((source) => containsPath(source, sourceBinding.path))\n ) {\n return;\n }\n const destinationBinding = destinationScope.getBinding(name);\n if (sourceBinding?.identifier !== destinationBinding?.identifier) {\n visible = false;\n }\n };\n\n for (const source of sources) {\n source.traverse({\n ReferencedIdentifier(referencePath) {\n checkReference(referencePath, referencePath.node.name);\n if (!visible) {\n referencePath.stop();\n }\n },\n JSXOpeningElement(openingPath) {\n let name = openingPath.node.name;\n let isMember = false;\n while (t.isJSXMemberExpression(name)) {\n isMember = true;\n name = name.object;\n }\n if (\n t.isJSXIdentifier(name) &&\n (isMember || !/^[a-z]/.test(name.name))\n ) {\n checkReference(openingPath, name.name);\n }\n if (!visible) {\n openingPath.stop();\n }\n },\n JSXSpreadAttribute(spreadPath) {\n // Spread attributes are not copied into the content getter.\n spreadPath.skip();\n },\n });\n if (!visible) {\n return false;\n }\n }\n return true;\n}\n\n/** Converts JSX anywhere in an expression, including render functions and maps. */\nfunction toRuntimeNode(\n t: typeof Babel.types,\n node: Babel.types.Node,\n react: Babel.types.Identifier\n): Babel.types.Node {\n if (t.isJSXElement(node) || t.isJSXFragment(node)) {\n const properties: Babel.types.ObjectProperty[] = [];\n if (t.isJSXElement(node)) {\n for (const [name, attribute] of effectiveAttributes(\n t,\n node.openingElement.attributes\n )) {\n properties.push(\n t.objectProperty(\n t.stringLiteral(name),\n attribute.value\n ? (toRuntimeNode(\n t,\n attribute.value,\n react\n ) as Babel.types.Expression)\n : t.booleanLiteral(true)\n )\n );\n }\n }\n const children = node.children\n .filter(\n (child) =>\n !(t.isJSXText(child) && !child.value.trim()) &&\n !(\n t.isJSXExpressionContainer(child) &&\n t.isJSXEmptyExpression(child.expression)\n )\n )\n .map((child) => toRuntimeNode(t, child, react) as Babel.types.Expression);\n return t.callExpression(\n t.memberExpression(t.cloneNode(react), t.identifier('createElement')),\n [\n t.isJSXElement(node)\n ? elementType(t, node.openingElement.name)\n : t.memberExpression(t.cloneNode(react), t.identifier('Fragment')),\n properties.length ? t.objectExpression(properties) : t.nullLiteral(),\n ...children,\n ]\n );\n }\n if (t.isJSXText(node)) {\n return t.stringLiteral(node.value.replace(/\\s+/g, ' ').trim());\n }\n if (t.isJSXExpressionContainer(node)) {\n return toRuntimeNode(t, node.expression, react);\n }\n // Recurse through all expression shapes so cloned JSX cannot survive Babel's\n // JSX visitor or be instrumented a second time inside the content getter.\n const cloned = t.cloneNode(node, false);\n const fields = cloned as unknown as Record<\n string,\n Babel.types.Node | Babel.types.Node[] | null\n >;\n for (const key of t.VISITOR_KEYS[node.type] || []) {\n const value = fields[key];\n if (Array.isArray(value)) {\n fields[key] = value.map((child) =>\n child ? toRuntimeNode(t, child, react) : child\n );\n } else if (value) {\n fields[key] = toRuntimeNode(t, value, react);\n }\n }\n return cloned;\n}\n\n/** Matches Datadog's click-time evaluation of content props and JSX children. */\nexport function buildContentGetter(\n t: typeof Babel.types,\n path: Babel.NodePath<Babel.types.JSXElement>,\n component: TrackedComponentData,\n state: PluginState\n): Babel.types.ArrowFunctionExpression | null {\n if (!component.useContent) {\n return null;\n }\n const candidateNames = contentCandidateNames(component);\n const attributes = effectiveAttributes(\n t,\n path.node.openingElement.attributes\n );\n const candidates: Babel.types.Expression[] = [];\n for (const name of candidateNames) {\n const attribute = attributes.get(name);\n if (attribute?.value) {\n candidates.push(\n toRuntimeNode(\n t,\n attribute.value,\n state._ftReactIdentifier\n ) as Babel.types.Expression\n );\n }\n }\n const fragment = t.jsxFragment(\n t.jsxOpeningFragment(),\n t.jsxClosingFragment(),\n path.node.children\n );\n return t.arrowFunctionExpression(\n [],\n t.callExpression(t.cloneNode(state._ftExtractTextIdentifier), [\n toRuntimeNode(\n t,\n fragment,\n state._ftReactIdentifier\n ) as Babel.types.Expression,\n t.arrayExpression(candidates),\n ])\n );\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.handleJSXElement = handleJSXElement;
|
|
7
|
+
exports.insertRuntimeImports = insertRuntimeImports;
|
|
8
|
+
var _constants = require("../../constants");
|
|
9
|
+
var _jsx = require("../../utils/jsx");
|
|
10
|
+
var _components = require("./components");
|
|
11
|
+
var _metadata = require("./metadata");
|
|
12
|
+
var _memoization = require("./memoization");
|
|
13
|
+
var _tap = require("./tap");
|
|
14
|
+
/*
|
|
15
|
+
* Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
|
|
16
|
+
* interaction tracking implementation, licensed under Apache-2.0.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function handleJSXElement(t, path, state, options) {
|
|
20
|
+
const componentName = (0, _jsx.getNodeName)(t, path.node.openingElement.name);
|
|
21
|
+
const component = componentName ? state._ftTrackedComponents[componentName] : undefined;
|
|
22
|
+
if (!componentName || !component || (0, _components.isInsideConfiguredComponent)(t, path, state._ftCustomNames)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
(0, _components.addRequiredTextInputHandler)(t, path, component, componentName);
|
|
26
|
+
let metadata;
|
|
27
|
+
for (const attributePath of (0, _jsx.getDirectAttributePaths)(path)) {
|
|
28
|
+
var _attributePath$node$e;
|
|
29
|
+
if (!attributePath.isJSXAttribute() || (_attributePath$node$e = attributePath.node.extra) !== null && _attributePath$node$e !== void 0 && _attributePath$node$e.__ftWrappedAction) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const attributeName = (0, _jsx.getNodeName)(t, attributePath.node.name);
|
|
33
|
+
const handler = component.handlers.find(item => item.event === attributeName);
|
|
34
|
+
if (!handler) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const value = attributePath.node.value;
|
|
38
|
+
if (!t.isJSXExpressionContainer(value)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const expression = value.expression;
|
|
42
|
+
if (!t.isArrowFunctionExpression(expression) && !t.isFunctionExpression(expression) && !t.isIdentifier(expression) && !t.isMemberExpression(expression) && !t.isConditionalExpression(expression)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
metadata || (metadata = (0, _metadata.buildMetadata)(t, path, componentName, component, options, state));
|
|
46
|
+
if (!(0, _memoization.wrapMemoizedHandler)(t, attributePath, expression, metadata, state, handler.mode, handler.actionType)) {
|
|
47
|
+
const wrappedHandler = t.isConditionalExpression(expression) ? (0, _tap.buildGuardedHandlerWrapper)(t, expression, metadata, state, attributePath.scope, handler.mode, handler.actionType) : (0, _tap.buildHandlerWrapper)(t, expression, metadata, state, attributePath.scope, handler.mode, handler.actionType);
|
|
48
|
+
attributePath.node.value = t.jsxExpressionContainer(wrappedHandler);
|
|
49
|
+
state._ftHasWrappedAction = true;
|
|
50
|
+
}
|
|
51
|
+
attributePath.node.extra = {
|
|
52
|
+
...attributePath.node.extra,
|
|
53
|
+
__ftWrappedAction: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function insertRuntimeImports(t, programPath, state) {
|
|
58
|
+
if (!state._ftHasWrappedAction) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const imports = [t.importSpecifier(t.cloneNode(state._ftTrackingIdentifier), t.identifier('FTBabelInteractionTracking'))];
|
|
62
|
+
if (state._ftNeedsContentRuntime) {
|
|
63
|
+
imports.push(t.importSpecifier(t.cloneNode(state._ftExtractTextIdentifier), t.identifier('__ftExtractText')));
|
|
64
|
+
programPath.unshiftContainer('body', t.importDeclaration([t.importNamespaceSpecifier(t.cloneNode(state._ftReactIdentifier))], t.stringLiteral('react')));
|
|
65
|
+
}
|
|
66
|
+
programPath.unshiftContainer('body', t.importDeclaration(imports, t.stringLiteral(_constants.RUNTIME_PACKAGE)));
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_constants","require","_jsx","_components","_metadata","_memoization","_tap","handleJSXElement","t","path","state","options","componentName","getNodeName","node","openingElement","name","component","_ftTrackedComponents","undefined","isInsideConfiguredComponent","_ftCustomNames","addRequiredTextInputHandler","metadata","attributePath","getDirectAttributePaths","_attributePath$node$e","isJSXAttribute","extra","__ftWrappedAction","attributeName","handler","handlers","find","item","event","value","isJSXExpressionContainer","expression","isArrowFunctionExpression","isFunctionExpression","isIdentifier","isMemberExpression","isConditionalExpression","buildMetadata","wrapMemoizedHandler","mode","actionType","wrappedHandler","buildGuardedHandlerWrapper","scope","buildHandlerWrapper","jsxExpressionContainer","_ftHasWrappedAction","insertRuntimeImports","programPath","imports","importSpecifier","cloneNode","_ftTrackingIdentifier","identifier","_ftNeedsContentRuntime","push","_ftExtractTextIdentifier","unshiftContainer","importDeclaration","importNamespaceSpecifier","_ftReactIdentifier","stringLiteral","RUNTIME_PACKAGE"],"sources":["index.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\nimport { RUNTIME_PACKAGE } from '../../constants';\nimport type {\n ActionMetadata,\n NormalizedPluginOptions,\n PluginState,\n} from '../../types';\nimport { getDirectAttributePaths, getNodeName } from '../../utils/jsx';\nimport {\n addRequiredTextInputHandler,\n isInsideConfiguredComponent,\n} from './components';\nimport { buildMetadata } from './metadata';\nimport { wrapMemoizedHandler } from './memoization';\nimport { buildGuardedHandlerWrapper, buildHandlerWrapper } from './tap';\n\nexport function handleJSXElement(\n t: typeof Babel.types,\n path: Babel.NodePath<Babel.types.JSXElement>,\n state: PluginState,\n options: NormalizedPluginOptions\n): void {\n const componentName = getNodeName(t, path.node.openingElement.name);\n const component = componentName\n ? state._ftTrackedComponents[componentName]\n : undefined;\n if (\n !componentName ||\n !component ||\n isInsideConfiguredComponent(t, path, state._ftCustomNames)\n ) {\n return;\n }\n\n addRequiredTextInputHandler(t, path, component, componentName);\n let metadata: ActionMetadata | undefined;\n\n for (const attributePath of getDirectAttributePaths(path)) {\n if (\n !attributePath.isJSXAttribute() ||\n attributePath.node.extra?.__ftWrappedAction\n ) {\n continue;\n }\n const attributeName = getNodeName(t, attributePath.node.name);\n const handler = component.handlers.find(\n (item) => item.event === attributeName\n );\n if (!handler) {\n continue;\n }\n const value = attributePath.node.value;\n if (!t.isJSXExpressionContainer(value)) {\n continue;\n }\n const expression = value.expression;\n if (\n !t.isArrowFunctionExpression(expression) &&\n !t.isFunctionExpression(expression) &&\n !t.isIdentifier(expression) &&\n !t.isMemberExpression(expression) &&\n !t.isConditionalExpression(expression)\n ) {\n continue;\n }\n\n metadata ||= buildMetadata(\n t,\n path,\n componentName,\n component,\n options,\n state\n );\n\n if (\n !wrapMemoizedHandler(\n t,\n attributePath,\n expression,\n metadata,\n state,\n handler.mode,\n handler.actionType\n )\n ) {\n const wrappedHandler = t.isConditionalExpression(expression)\n ? buildGuardedHandlerWrapper(\n t,\n expression,\n metadata,\n state,\n attributePath.scope,\n handler.mode,\n handler.actionType\n )\n : buildHandlerWrapper(\n t,\n expression,\n metadata,\n state,\n attributePath.scope,\n handler.mode,\n handler.actionType\n );\n attributePath.node.value = t.jsxExpressionContainer(wrappedHandler);\n state._ftHasWrappedAction = true;\n }\n attributePath.node.extra = {\n ...attributePath.node.extra,\n __ftWrappedAction: true,\n };\n }\n}\n\nexport function insertRuntimeImports(\n t: typeof Babel.types,\n programPath: Babel.NodePath<Babel.types.Program>,\n state: PluginState\n): void {\n if (!state._ftHasWrappedAction) {\n return;\n }\n const imports = [\n t.importSpecifier(\n t.cloneNode(state._ftTrackingIdentifier),\n t.identifier('FTBabelInteractionTracking')\n ),\n ];\n if (state._ftNeedsContentRuntime) {\n imports.push(\n t.importSpecifier(\n t.cloneNode(state._ftExtractTextIdentifier),\n t.identifier('__ftExtractText')\n )\n );\n programPath.unshiftContainer(\n 'body',\n t.importDeclaration(\n [t.importNamespaceSpecifier(t.cloneNode(state._ftReactIdentifier))],\n t.stringLiteral('react')\n )\n );\n }\n programPath.unshiftContainer(\n 'body',\n t.importDeclaration(imports, t.stringLiteral(RUNTIME_PACKAGE))\n );\n}\n"],"mappings":"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","ignoreList":[]}
|