Skip to content

Commit 3d63eb0

Browse files
feat: Add support for ignoring specific React components during annotation (#4517)
1 parent afe5fcb commit 3d63eb0

File tree

8 files changed

+263
-65
lines changed

8 files changed

+263
-65
lines changed

CHANGELOG.md

+21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@
88
99
## Unreleased
1010

11+
### Features
12+
13+
- Add `ignoredComponents` option to `annotateReactComponents` to exclude specific components from React component annotations ([#4517](https://github.com/getsentry/sentry-react-native/pull/4517))
14+
15+
```js
16+
// metro.config.js
17+
// for React Native
18+
const config = withSentryConfig(mergedConfig, {
19+
annotateReactComponents: {
20+
ignoredComponents: ['MyCustomComponent']
21+
}
22+
});
23+
24+
// for Expo
25+
const config = getSentryExpoConfig(__dirname, {
26+
annotateReactComponents: {
27+
ignoredComponents: ['MyCustomComponent'],
28+
},
29+
});
30+
```
31+
1132
### Dependencies
1233

1334
- Bump JavaScript SDK from v8.53.0 to v8.54.0 ([#4503](https://github.com/getsentry/sentry-react-native/pull/4503))

packages/core/src/js/tools/metroconfig.ts

+21-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import * as process from 'process';
55
import { env } from 'process';
66

77
import { enableLogger } from './enableLogger';
8-
import { setSentryDefaultBabelTransformerPathEnv } from './sentryBabelTransformerUtils';
8+
import {
9+
setSentryBabelTransformerOptions,
10+
setSentryDefaultBabelTransformerPathEnv,
11+
} from './sentryBabelTransformerUtils';
912
import { createSentryMetroSerializer, unstable_beforeAssetSerializationPlugin } from './sentryMetroSerializer';
1013
import type { DefaultConfigOptions } from './vendor/expo/expoconfig';
1114
export * from './sentryMetroSerializer';
@@ -18,7 +21,11 @@ export interface SentryMetroConfigOptions {
1821
* Annotates React components with Sentry data.
1922
* @default false
2023
*/
21-
annotateReactComponents?: boolean;
24+
annotateReactComponents?:
25+
| boolean
26+
| {
27+
ignoredComponents?: string[];
28+
};
2229
/**
2330
* Adds the Sentry replay package for web.
2431
* @default true
@@ -60,7 +67,7 @@ export function withSentryConfig(
6067
newConfig = withSentryDebugId(newConfig);
6168
newConfig = withSentryFramesCollapsed(newConfig);
6269
if (annotateReactComponents) {
63-
newConfig = withSentryBabelTransformer(newConfig);
70+
newConfig = withSentryBabelTransformer(newConfig, annotateReactComponents);
6471
}
6572
if (includeWebReplay === false) {
6673
newConfig = withSentryResolver(newConfig, includeWebReplay);
@@ -92,7 +99,7 @@ export function getSentryExpoConfig(
9299

93100
let newConfig = withSentryFramesCollapsed(config);
94101
if (options.annotateReactComponents) {
95-
newConfig = withSentryBabelTransformer(newConfig);
102+
newConfig = withSentryBabelTransformer(newConfig, options.annotateReactComponents);
96103
}
97104

98105
if (options.includeWebReplay === false) {
@@ -129,7 +136,10 @@ function loadExpoMetroConfigModule(): {
129136
/**
130137
* Adds Sentry Babel transformer to the Metro config.
131138
*/
132-
export function withSentryBabelTransformer(config: MetroConfig): MetroConfig {
139+
export function withSentryBabelTransformer(
140+
config: MetroConfig,
141+
annotateReactComponents: true | { ignoredComponents?: string[] },
142+
): MetroConfig {
133143
const defaultBabelTransformerPath = config.transformer && config.transformer.babelTransformerPath;
134144
logger.debug('Default Babel transformer path from `config.transformer`:', defaultBabelTransformerPath);
135145

@@ -146,6 +156,12 @@ export function withSentryBabelTransformer(config: MetroConfig): MetroConfig {
146156
setSentryDefaultBabelTransformerPathEnv(defaultBabelTransformerPath);
147157
}
148158

159+
if (typeof annotateReactComponents === 'object') {
160+
setSentryBabelTransformerOptions({
161+
annotateReactComponents,
162+
});
163+
}
164+
149165
return {
150166
...config,
151167
transformer: {

packages/core/src/js/tools/sentryBabelTransformer.ts

+1-35
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,8 @@
1-
import componentAnnotatePlugin from '@sentry/babel-plugin-component-annotate';
2-
31
import { enableLogger } from './enableLogger';
4-
import { loadDefaultBabelTransformer } from './sentryBabelTransformerUtils';
5-
import type { BabelTransformer, BabelTransformerArgs } from './vendor/metro/metroBabelTransformer';
2+
import { createSentryBabelTransformer } from './sentryBabelTransformerUtils';
63

74
enableLogger();
85

9-
/**
10-
* Creates a Babel transformer with Sentry component annotation plugin.
11-
*/
12-
function createSentryBabelTransformer(): BabelTransformer {
13-
const defaultTransformer = loadDefaultBabelTransformer();
14-
15-
// Using spread operator to avoid any conflicts with the default transformer
16-
const transform: BabelTransformer['transform'] = (...args) => {
17-
const transformerArgs = args[0];
18-
19-
addSentryComponentAnnotatePlugin(transformerArgs);
20-
21-
return defaultTransformer.transform(...args);
22-
};
23-
24-
return {
25-
...defaultTransformer,
26-
transform,
27-
};
28-
}
29-
30-
function addSentryComponentAnnotatePlugin(args: BabelTransformerArgs | undefined): void {
31-
if (!args || typeof args.filename !== 'string' || !Array.isArray(args.plugins)) {
32-
return undefined;
33-
}
34-
35-
if (!args.filename.includes('node_modules')) {
36-
args.plugins.push(componentAnnotatePlugin);
37-
}
38-
}
39-
406
const sentryBabelTransformer = createSentryBabelTransformer();
417
// With TS set to `commonjs` this will be translated to `module.exports = sentryBabelTransformer;`
428
// which will be correctly picked up by Metro

packages/core/src/js/tools/sentryBabelTransformerUtils.ts

+87-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import componentAnnotatePlugin from '@sentry/babel-plugin-component-annotate';
12
import { logger } from '@sentry/core';
23
import * as process from 'process';
34

4-
import type { BabelTransformer } from './vendor/metro/metroBabelTransformer';
5+
import type { BabelTransformer, BabelTransformerArgs } from './vendor/metro/metroBabelTransformer';
6+
7+
export type SentryBabelTransformerOptions = { annotateReactComponents?: { ignoredComponents?: string[] } };
58

69
export const SENTRY_DEFAULT_BABEL_TRANSFORMER_PATH = 'SENTRY_DEFAULT_BABEL_TRANSFORMER_PATH';
10+
export const SENTRY_BABEL_TRANSFORMER_OPTIONS = 'SENTRY_BABEL_TRANSFORMER_OPTIONS';
711

812
/**
913
* Sets default Babel transformer path to the environment variables.
@@ -35,3 +39,85 @@ export function loadDefaultBabelTransformer(): BabelTransformer {
3539
// eslint-disable-next-line @typescript-eslint/no-var-requires
3640
return require(defaultBabelTransformerPath);
3741
}
42+
43+
/**
44+
*
45+
*/
46+
export function setSentryBabelTransformerOptions(options: SentryBabelTransformerOptions): void {
47+
let optionsString: string | null = null;
48+
try {
49+
logger.debug(`Stringifying Sentry Babel transformer options`, options);
50+
optionsString = JSON.stringify(options);
51+
} catch (e) {
52+
// eslint-disable-next-line no-console
53+
console.error('Failed to stringify Sentry Babel transformer options', e);
54+
}
55+
56+
if (!optionsString) {
57+
return;
58+
}
59+
60+
logger.debug(`Sentry Babel transformer options set to ${SENTRY_BABEL_TRANSFORMER_OPTIONS}`, optionsString);
61+
process.env[SENTRY_BABEL_TRANSFORMER_OPTIONS] = optionsString;
62+
}
63+
64+
/**
65+
*
66+
*/
67+
export function getSentryBabelTransformerOptions(): SentryBabelTransformerOptions | undefined {
68+
const optionsString = process.env[SENTRY_BABEL_TRANSFORMER_OPTIONS];
69+
if (!optionsString) {
70+
logger.debug(
71+
`Sentry Babel transformer options environment variable ${SENTRY_BABEL_TRANSFORMER_OPTIONS} is not set`,
72+
);
73+
return undefined;
74+
}
75+
76+
try {
77+
logger.debug(`Parsing Sentry Babel transformer options from ${optionsString}`);
78+
return JSON.parse(optionsString);
79+
} catch (e) {
80+
// eslint-disable-next-line no-console
81+
console.error('Failed to parse Sentry Babel transformer options', e);
82+
return undefined;
83+
}
84+
}
85+
86+
/**
87+
* Creates a Babel transformer with Sentry component annotation plugin.
88+
*/
89+
export function createSentryBabelTransformer(): BabelTransformer {
90+
const defaultTransformer = loadDefaultBabelTransformer();
91+
const options = getSentryBabelTransformerOptions();
92+
93+
// Using spread operator to avoid any conflicts with the default transformer
94+
const transform: BabelTransformer['transform'] = (...args) => {
95+
const transformerArgs = args[0];
96+
97+
addSentryComponentAnnotatePlugin(transformerArgs, options?.annotateReactComponents);
98+
99+
return defaultTransformer.transform(...args);
100+
};
101+
102+
return {
103+
...defaultTransformer,
104+
transform,
105+
};
106+
}
107+
108+
function addSentryComponentAnnotatePlugin(
109+
args: BabelTransformerArgs | undefined,
110+
options: SentryBabelTransformerOptions['annotateReactComponents'] | undefined,
111+
): void {
112+
if (!args || typeof args.filename !== 'string' || !Array.isArray(args.plugins)) {
113+
return undefined;
114+
}
115+
116+
if (!args.filename.includes('node_modules')) {
117+
if (options) {
118+
args.plugins.push([componentAnnotatePlugin, options]);
119+
} else {
120+
args.plugins.push(componentAnnotatePlugin);
121+
}
122+
}
123+
}

packages/core/test/tools/metroconfig.test.ts

+61-10
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@ import {
88
withSentryFramesCollapsed,
99
withSentryResolver,
1010
} from '../../src/js/tools/metroconfig';
11-
import { SENTRY_DEFAULT_BABEL_TRANSFORMER_PATH } from '../../src/js/tools/sentryBabelTransformerUtils';
11+
import {
12+
SENTRY_BABEL_TRANSFORMER_OPTIONS,
13+
SENTRY_DEFAULT_BABEL_TRANSFORMER_PATH,
14+
} from '../../src/js/tools/sentryBabelTransformerUtils';
1215

1316
type MetroFrame = Parameters<Required<Required<MetroConfig>['symbolicator']>['customizeFrame']>[0];
1417

1518
describe('metroconfig', () => {
1619
beforeEach(() => {
1720
jest.clearAllMocks();
21+
22+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
23+
delete process.env[SENTRY_BABEL_TRANSFORMER_OPTIONS];
24+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
25+
delete process.env[SENTRY_DEFAULT_BABEL_TRANSFORMER_PATH];
1826
});
1927

2028
test('getSentryExpoConfig keeps compatible interface with Expos getDefaultConfig', () => {
@@ -55,35 +63,78 @@ describe('metroconfig', () => {
5563
test.each([[{}], [{ transformer: {} }], [{ transformer: { hermesParser: true } }]])(
5664
"does not add babel transformer none is set in the config object '%o'",
5765
input => {
58-
expect(withSentryBabelTransformer(JSON.parse(JSON.stringify(input)))).toEqual(input);
66+
expect(withSentryBabelTransformer(JSON.parse(JSON.stringify(input)), {})).toEqual(input);
5967
},
6068
);
6169

6270
test('save default babel transformer path to environment variable', () => {
6371
const defaultBabelTransformerPath = '/default/babel/transformer';
6472

65-
withSentryBabelTransformer({
66-
transformer: {
67-
babelTransformerPath: defaultBabelTransformerPath,
73+
withSentryBabelTransformer(
74+
{
75+
transformer: {
76+
babelTransformerPath: defaultBabelTransformerPath,
77+
},
6878
},
69-
});
79+
{},
80+
);
7081

7182
expect(process.env[SENTRY_DEFAULT_BABEL_TRANSFORMER_PATH]).toBe(defaultBabelTransformerPath);
7283
});
7384

7485
test('return config with sentry babel transformer path', () => {
7586
const defaultBabelTransformerPath = 'defaultBabelTransformerPath';
7687

77-
const config = withSentryBabelTransformer({
78-
transformer: {
79-
babelTransformerPath: defaultBabelTransformerPath,
88+
const config = withSentryBabelTransformer(
89+
{
90+
transformer: {
91+
babelTransformerPath: defaultBabelTransformerPath,
92+
},
8093
},
81-
});
94+
{},
95+
);
8296

8397
expect(config.transformer?.babelTransformerPath).toBe(
8498
require.resolve('../../src/js/tools/sentryBabelTransformer'),
8599
);
86100
});
101+
102+
test('save babel transformer options to environment variable', () => {
103+
withSentryBabelTransformer(
104+
{
105+
transformer: {
106+
babelTransformerPath: 'path/to/babel/transformer',
107+
},
108+
},
109+
{
110+
ignoredComponents: ['MyCustomComponent'],
111+
},
112+
);
113+
114+
expect(process.env[SENTRY_BABEL_TRANSFORMER_OPTIONS]).toBe(
115+
JSON.stringify({
116+
annotateReactComponents: {
117+
ignoredComponents: ['MyCustomComponent'],
118+
},
119+
}),
120+
);
121+
});
122+
123+
test('gracefully handle none serializable babel transformer options', () => {
124+
withSentryBabelTransformer(
125+
{
126+
transformer: {
127+
babelTransformerPath: 'path/to/babel/transformer',
128+
},
129+
},
130+
{
131+
ignoredComponents: ['MyCustomComponent'],
132+
nonSerializable: BigInt(1),
133+
} as any,
134+
);
135+
136+
expect(process.env[SENTRY_BABEL_TRANSFORMER_OPTIONS]).toBeUndefined();
137+
});
87138
});
88139

89140
describe('withSentryResolver', () => {

0 commit comments

Comments
 (0)