@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.
Files changed (80) hide show
  1. package/README.md +185 -0
  2. package/lib/commonjs/actions/global.js +19 -0
  3. package/lib/commonjs/actions/global.js.map +1 -0
  4. package/lib/commonjs/actions/rum/components.js +81 -0
  5. package/lib/commonjs/actions/rum/components.js.map +1 -0
  6. package/lib/commonjs/actions/rum/content.js +168 -0
  7. package/lib/commonjs/actions/rum/content.js.map +1 -0
  8. package/lib/commonjs/actions/rum/index.js +68 -0
  9. package/lib/commonjs/actions/rum/index.js.map +1 -0
  10. package/lib/commonjs/actions/rum/memoization.js +88 -0
  11. package/lib/commonjs/actions/rum/memoization.js.map +1 -0
  12. package/lib/commonjs/actions/rum/metadata.js +63 -0
  13. package/lib/commonjs/actions/rum/metadata.js.map +1 -0
  14. package/lib/commonjs/actions/rum/tap.js +45 -0
  15. package/lib/commonjs/actions/rum/tap.js.map +1 -0
  16. package/lib/commonjs/constants.js +25 -0
  17. package/lib/commonjs/constants.js.map +1 -0
  18. package/lib/commonjs/index.js +48 -0
  19. package/lib/commonjs/index.js.map +1 -0
  20. package/lib/commonjs/options.js +23 -0
  21. package/lib/commonjs/options.js.map +1 -0
  22. package/lib/commonjs/state.js +32 -0
  23. package/lib/commonjs/state.js.map +1 -0
  24. package/lib/commonjs/types.js +6 -0
  25. package/lib/commonjs/types.js.map +1 -0
  26. package/lib/commonjs/utils/jsx.js +43 -0
  27. package/lib/commonjs/utils/jsx.js.map +1 -0
  28. package/lib/module/actions/global.js +13 -0
  29. package/lib/module/actions/global.js.map +1 -0
  30. package/lib/module/actions/rum/components.js +73 -0
  31. package/lib/module/actions/rum/components.js.map +1 -0
  32. package/lib/module/actions/rum/content.js +161 -0
  33. package/lib/module/actions/rum/content.js.map +1 -0
  34. package/lib/module/actions/rum/index.js +61 -0
  35. package/lib/module/actions/rum/index.js.map +1 -0
  36. package/lib/module/actions/rum/memoization.js +82 -0
  37. package/lib/module/actions/rum/memoization.js.map +1 -0
  38. package/lib/module/actions/rum/metadata.js +56 -0
  39. package/lib/module/actions/rum/metadata.js.map +1 -0
  40. package/lib/module/actions/rum/tap.js +38 -0
  41. package/lib/module/actions/rum/tap.js.map +1 -0
  42. package/lib/module/constants.js +19 -0
  43. package/lib/module/constants.js.map +1 -0
  44. package/lib/module/index.js +42 -0
  45. package/lib/module/index.js.map +1 -0
  46. package/lib/module/options.js +17 -0
  47. package/lib/module/options.js.map +1 -0
  48. package/lib/module/state.js +25 -0
  49. package/lib/module/state.js.map +1 -0
  50. package/lib/module/types.js +2 -0
  51. package/lib/module/types.js.map +1 -0
  52. package/lib/module/utils/jsx.js +36 -0
  53. package/lib/module/utils/jsx.js.map +1 -0
  54. package/lib/typescript/actions/global.d.ts +2 -0
  55. package/lib/typescript/actions/rum/components.d.ts +5 -0
  56. package/lib/typescript/actions/rum/content.d.ts +6 -0
  57. package/lib/typescript/actions/rum/index.d.ts +4 -0
  58. package/lib/typescript/actions/rum/memoization.d.ts +4 -0
  59. package/lib/typescript/actions/rum/metadata.d.ts +4 -0
  60. package/lib/typescript/actions/rum/tap.d.ts +6 -0
  61. package/lib/typescript/constants.d.ts +4 -0
  62. package/lib/typescript/index.d.ts +5 -0
  63. package/lib/typescript/options.d.ts +2 -0
  64. package/lib/typescript/state.d.ts +5 -0
  65. package/lib/typescript/types.d.ts +56 -0
  66. package/lib/typescript/utils/jsx.d.ts +4 -0
  67. package/package.json +68 -0
  68. package/src/actions/global.ts +48 -0
  69. package/src/actions/rum/components.ts +128 -0
  70. package/src/actions/rum/content.ts +274 -0
  71. package/src/actions/rum/index.ts +154 -0
  72. package/src/actions/rum/memoization.ts +136 -0
  73. package/src/actions/rum/metadata.ts +147 -0
  74. package/src/actions/rum/tap.ts +148 -0
  75. package/src/constants.ts +21 -0
  76. package/src/index.ts +50 -0
  77. package/src/options.ts +26 -0
  78. package/src/state.ts +39 -0
  79. package/src/types.ts +72 -0
  80. package/src/utils/jsx.ts +50 -0
@@ -0,0 +1,148 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+ import type { ActionMetadata, PluginState, TrackedHandler } from '../../types';
8
+ import { buildTargetObject } from './metadata';
9
+
10
+ const DEFAULT_ACTION_TYPE = 'click';
11
+
12
+ function normalizeActionType(actionType: TrackedHandler['actionType']): string {
13
+ return typeof actionType === 'string' && actionType.trim()
14
+ ? actionType.trim()
15
+ : DEFAULT_ACTION_TYPE;
16
+ }
17
+
18
+ function buildOriginalInvocation(
19
+ t: typeof Babel.types,
20
+ originalExpression: Babel.types.Expression,
21
+ argsIdentifier: Babel.types.Identifier
22
+ ): Babel.types.OptionalCallExpression {
23
+ return t.optionalCallExpression(
24
+ t.cloneNode(originalExpression, true),
25
+ [t.spreadElement(t.cloneNode(argsIdentifier))],
26
+ true
27
+ );
28
+ }
29
+
30
+ function buildImmediateHandlerWrapper(
31
+ t: typeof Babel.types,
32
+ originalExpression: Babel.types.Expression,
33
+ metadata: ActionMetadata,
34
+ state: PluginState,
35
+ scope: Babel.NodePath['scope'],
36
+ actionType: TrackedHandler['actionType']
37
+ ): Babel.types.ArrowFunctionExpression {
38
+ const handlerArgsIdentifier = scope.generateUidIdentifier('ftHandlerArgs');
39
+ const originalArgsIdentifier = scope.generateUidIdentifier('ftOriginalArgs');
40
+ const originalInvoker = t.arrowFunctionExpression(
41
+ [t.restElement(originalArgsIdentifier)],
42
+ buildOriginalInvocation(t, originalExpression, originalArgsIdentifier)
43
+ );
44
+ const instanceCall = t.callExpression(
45
+ t.memberExpression(
46
+ t.cloneNode(state._ftTrackingIdentifier),
47
+ t.identifier('getInstance')
48
+ ),
49
+ []
50
+ );
51
+ const wrappedHandler = t.callExpression(
52
+ t.memberExpression(instanceCall, t.identifier('wrapRumAction')),
53
+ [
54
+ originalInvoker,
55
+ t.stringLiteral(normalizeActionType(actionType)),
56
+ buildTargetObject(t, metadata, handlerArgsIdentifier, state),
57
+ ]
58
+ );
59
+ return t.arrowFunctionExpression(
60
+ [t.restElement(handlerArgsIdentifier)],
61
+ t.callExpression(wrappedHandler, [
62
+ t.spreadElement(t.cloneNode(handlerArgsIdentifier)),
63
+ ])
64
+ );
65
+ }
66
+
67
+ /** Wraps a handler, or a handler factory when delayed mode is configured. */
68
+ export function buildHandlerWrapper(
69
+ t: typeof Babel.types,
70
+ originalExpression: Babel.types.Expression,
71
+ metadata: ActionMetadata,
72
+ state: PluginState,
73
+ scope: Babel.NodePath['scope'],
74
+ mode: TrackedHandler['mode'] = 'default',
75
+ actionType?: TrackedHandler['actionType']
76
+ ): Babel.types.ArrowFunctionExpression {
77
+ if (mode !== 'delayed') {
78
+ return buildImmediateHandlerWrapper(
79
+ t,
80
+ originalExpression,
81
+ metadata,
82
+ state,
83
+ scope,
84
+ actionType
85
+ );
86
+ }
87
+
88
+ const factoryArgsIdentifier = scope.generateUidIdentifier('ftFactoryArgs');
89
+ const handlerIdentifier = scope.generateUidIdentifier('ftDelayedHandler');
90
+ return t.arrowFunctionExpression(
91
+ [t.restElement(factoryArgsIdentifier)],
92
+ t.blockStatement([
93
+ t.variableDeclaration('const', [
94
+ t.variableDeclarator(
95
+ handlerIdentifier,
96
+ buildOriginalInvocation(t, originalExpression, factoryArgsIdentifier)
97
+ ),
98
+ ]),
99
+ t.returnStatement(
100
+ buildImmediateHandlerWrapper(
101
+ t,
102
+ handlerIdentifier,
103
+ metadata,
104
+ state,
105
+ scope,
106
+ actionType
107
+ )
108
+ ),
109
+ ])
110
+ );
111
+ }
112
+
113
+ /** Resolves a conditional handler once at render time, preserving non-functions. */
114
+ export function buildGuardedHandlerWrapper(
115
+ t: typeof Babel.types,
116
+ originalExpression: Babel.types.Expression,
117
+ metadata: ActionMetadata,
118
+ state: PluginState,
119
+ scope: Babel.NodePath['scope'],
120
+ mode: TrackedHandler['mode'] = 'default',
121
+ actionType?: TrackedHandler['actionType']
122
+ ): Babel.types.CallExpression {
123
+ const resolvedHandlerIdentifier =
124
+ scope.generateUidIdentifier('ftResolvedHandler');
125
+ return t.callExpression(
126
+ t.arrowFunctionExpression(
127
+ [resolvedHandlerIdentifier],
128
+ t.conditionalExpression(
129
+ t.binaryExpression(
130
+ '===',
131
+ t.unaryExpression('typeof', t.cloneNode(resolvedHandlerIdentifier)),
132
+ t.stringLiteral('function')
133
+ ),
134
+ buildHandlerWrapper(
135
+ t,
136
+ resolvedHandlerIdentifier,
137
+ metadata,
138
+ state,
139
+ scope,
140
+ mode,
141
+ actionType
142
+ ),
143
+ t.cloneNode(resolvedHandlerIdentifier)
144
+ )
145
+ ),
146
+ [t.cloneNode(originalExpression, true)]
147
+ );
148
+ }
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ export const RUNTIME_PACKAGE = '@truewatchtech/react-native-mobile';
7
+
8
+ export const ACTION_NAME_ATTRIBUTE = 'ft-action-name';
9
+
10
+ export const PLUGIN_ENABLED_FLAG = '__FT_RN_BABEL_PLUGIN_ENABLED__';
11
+
12
+ export const NATIVE_COMPONENT_HANDLERS: Record<string, string[]> = {
13
+ Button: ['onPress'],
14
+ Pressable: ['onPress', 'onLongPress'],
15
+ TouchableOpacity: ['onPress'],
16
+ TouchableHighlight: ['onPress'],
17
+ TouchableWithoutFeedback: ['onPress'],
18
+ TouchableNativeFeedback: ['onPress'],
19
+ Switch: ['onValueChange'],
20
+ TextInput: ['onFocus'],
21
+ };
package/src/index.ts ADDED
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+ import { declare } from '@babel/helper-plugin-utils';
8
+ import { insertPluginEnabledFlag } from './actions/global';
9
+ import { handleJSXElement, insertRuntimeImports } from './actions/rum';
10
+ import { mergeOptions } from './options';
11
+ import { initializePluginState, isSkippedFile } from './state';
12
+ import type { PluginAPI, PluginOptions, PluginState } from './types';
13
+
14
+ export type { PluginOptions, TrackedComponent, TrackedHandler } from './types';
15
+
16
+ const plugin = declare(
17
+ (api: PluginAPI, rawOptions: PluginOptions): Babel.PluginObj<PluginState> => {
18
+ api.assertVersion(7);
19
+ const t = api.types;
20
+ const options = mergeOptions(rawOptions);
21
+
22
+ return {
23
+ name: 'truewatch-react-native-action-name',
24
+ visitor: {
25
+ Program: {
26
+ enter(programPath, state) {
27
+ state._ftSkip = isSkippedFile(state);
28
+ if (state._ftSkip) {
29
+ return;
30
+ }
31
+ initializePluginState(t, programPath, state, options);
32
+ insertPluginEnabledFlag(t, programPath);
33
+ },
34
+ exit(programPath, state) {
35
+ if (!state._ftSkip) {
36
+ insertRuntimeImports(t, programPath, state);
37
+ }
38
+ },
39
+ },
40
+ JSXElement(path, state) {
41
+ if (!state._ftSkip) {
42
+ handleJSXElement(t, path, state, options);
43
+ }
44
+ },
45
+ },
46
+ };
47
+ }
48
+ );
49
+
50
+ export default plugin;
package/src/options.ts ADDED
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type { NormalizedPluginOptions, PluginOptions } from './types';
7
+
8
+ export function mergeOptions(
9
+ options: PluginOptions = {}
10
+ ): NormalizedPluginOptions {
11
+ const componentOptions = options.components || {};
12
+ return {
13
+ actionNameAttribute: options.actionNameAttribute,
14
+ components: {
15
+ tracked: componentOptions.tracked || [],
16
+ useContent:
17
+ componentOptions.useContent === undefined
18
+ ? true
19
+ : componentOptions.useContent,
20
+ useNamePrefix:
21
+ componentOptions.useNamePrefix === undefined
22
+ ? true
23
+ : componentOptions.useNamePrefix,
24
+ },
25
+ };
26
+ }
package/src/state.ts ADDED
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+ import { createTrackedComponents } from './actions/rum/components';
8
+ import type { NormalizedPluginOptions, PluginState } from './types';
9
+
10
+ export function isSkippedFile(state: PluginState): boolean {
11
+ const caller = state.file?.opts?.caller as { platform?: string } | undefined;
12
+ const filename = state.filename || state.file?.opts?.filename;
13
+ return (
14
+ caller?.platform === 'web' ||
15
+ Boolean(filename && filename.includes('node_modules'))
16
+ );
17
+ }
18
+
19
+ /** Initializes per-file state; identifiers and memoization never leak across files. */
20
+ export function initializePluginState(
21
+ t: typeof Babel.types,
22
+ programPath: Babel.NodePath<Babel.types.Program>,
23
+ state: PluginState,
24
+ options: NormalizedPluginOptions
25
+ ): void {
26
+ state._ftTrackedComponents = createTrackedComponents(t, programPath, options);
27
+ state._ftCustomNames = new Set(
28
+ options.components.tracked.map((component) => component.name)
29
+ );
30
+ state._ftMemoizedHandlers = new Set();
31
+ state._ftHasWrappedAction = false;
32
+ state._ftNeedsContentRuntime = false;
33
+ state._ftExtractTextIdentifier =
34
+ programPath.scope.generateUidIdentifier('ftExtractText');
35
+ state._ftReactIdentifier = programPath.scope.generateUidIdentifier('FTReact');
36
+ state._ftTrackingIdentifier = programPath.scope.generateUidIdentifier(
37
+ 'FTBabelInteractionTracking'
38
+ );
39
+ }
package/src/types.ts ADDED
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+
8
+ export type TrackedHandler = {
9
+ event: string;
10
+ actionType?: string;
11
+ mode?: 'default' | 'delayed';
12
+ };
13
+
14
+ export type TrackedComponent = {
15
+ name: string;
16
+ useContent?: boolean;
17
+ useNamePrefix?: boolean;
18
+ contentProp?: string;
19
+ handlers: TrackedHandler[];
20
+ };
21
+
22
+ export type PluginOptions = {
23
+ actionNameAttribute?: string;
24
+ components?: {
25
+ useContent?: boolean;
26
+ useNamePrefix?: boolean;
27
+ tracked?: TrackedComponent[];
28
+ };
29
+ };
30
+
31
+ export type NormalizedPluginOptions = {
32
+ actionNameAttribute?: string;
33
+ components: {
34
+ useContent: boolean;
35
+ useNamePrefix: boolean;
36
+ tracked: TrackedComponent[];
37
+ };
38
+ };
39
+
40
+ export type TrackedComponentData = Omit<TrackedComponent, 'name'> & {
41
+ importedName?: string;
42
+ isCustom: boolean;
43
+ useContent: boolean;
44
+ useNamePrefix: boolean;
45
+ };
46
+
47
+ export type ActionMetadata = {
48
+ accessibilityLabels: Babel.types.StringLiteral[];
49
+ actionNames: Babel.types.StringLiteral[];
50
+ component: TrackedComponentData;
51
+ componentName: string;
52
+ customActionNames: Babel.types.StringLiteral[];
53
+ getContent: Babel.types.ArrowFunctionExpression | null;
54
+ };
55
+
56
+ export type PluginState = Babel.PluginPass & {
57
+ _ftCustomNames: Set<string>;
58
+ _ftExtractTextIdentifier: Babel.types.Identifier;
59
+ _ftHasWrappedAction: boolean;
60
+ _ftMemoizedHandlers: Set<Babel.types.Node>;
61
+ _ftNeedsContentRuntime: boolean;
62
+ _ftReactIdentifier: Babel.types.Identifier;
63
+ _ftSkip: boolean;
64
+ _ftTrackedComponents: Record<string, TrackedComponentData>;
65
+ _ftTrackingIdentifier: Babel.types.Identifier;
66
+ };
67
+
68
+ export type PluginAPI = typeof Babel & Babel.ConfigAPI;
69
+
70
+ export type JSXAttributePath = Babel.NodePath<
71
+ Babel.types.JSXAttribute | Babel.types.JSXSpreadAttribute
72
+ >;
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+ import type { JSXAttributePath } from '../types';
8
+
9
+ export function getNodeName(
10
+ t: typeof Babel.types,
11
+ node: Babel.types.Node | null | undefined
12
+ ): string | null {
13
+ if (!node) {
14
+ return null;
15
+ }
16
+ if (t.isIdentifier(node) || t.isJSXIdentifier(node)) {
17
+ return node.name;
18
+ }
19
+ if (t.isStringLiteral(node)) {
20
+ return node.value;
21
+ }
22
+ if (t.isJSXNamespacedName(node)) {
23
+ return `${node.namespace.name}:${node.name.name}`;
24
+ }
25
+ if (t.isJSXMemberExpression(node)) {
26
+ let nodeName = node.property.name;
27
+ let nodeTracker:
28
+ | Babel.types.JSXIdentifier
29
+ | Babel.types.JSXMemberExpression = node.object;
30
+
31
+ while (t.isJSXMemberExpression(nodeTracker)) {
32
+ nodeName = `${nodeTracker.property.name}.${nodeName}`;
33
+ nodeTracker = nodeTracker.object;
34
+ }
35
+
36
+ return t.isJSXIdentifier(nodeTracker)
37
+ ? `${nodeTracker.name}.${nodeName}`
38
+ : null;
39
+ }
40
+ if ('name' in node && node.name && typeof node.name !== 'string') {
41
+ return getNodeName(t, node.name);
42
+ }
43
+ return null;
44
+ }
45
+
46
+ export function getDirectAttributePaths(
47
+ path: Babel.NodePath<Babel.types.JSXElement>
48
+ ): JSXAttributePath[] {
49
+ return path.get('openingElement.attributes') as JSXAttributePath[];
50
+ }