Skip to content

Commit ad05939

Browse files
committed
Add RUM's react plugin injection
1 parent 5f3ea86 commit ad05939

14 files changed

+201
-6
lines changed
Binary file not shown.

Diff for: LICENSES-3rdparty.csv

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Component,Origin,Licence,Copyright
118118
@datadog/browser-core,npm,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-core)
119119
@datadog/browser-rum,virtual,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum)
120120
@datadog/browser-rum-core,npm,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum-core)
121+
@datadog/browser-rum-react,virtual,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum-react)
121122
@esbuild/darwin-arm64,npm,MIT,(https://www.npmjs.com/package/@esbuild/darwin-arm64)
122123
@esbuild/linux-x64,npm,MIT,(https://www.npmjs.com/package/@esbuild/linux-x64)
123124
@eslint-community/eslint-utils,virtual,MIT,Toru Nagashima (https://github.com/eslint-community/eslint-utils#readme)

Diff for: README.md

+6
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ Follow the specific documentation for each bundler:
104104
};
105105
rum?: {
106106
disabled?: boolean;
107+
react?: {
108+
router?: boolean;
109+
};
107110
sdk?: {
108111
actionNameAttribute?: string;
109112
allowedTracingUrls?: string[];
@@ -327,6 +330,9 @@ datadogWebpackPlugin({
327330
datadogWebpackPlugin({
328331
rum?: {
329332
disabled?: boolean,
333+
react?: {
334+
router?: boolean,
335+
},
330336
sdk?: {
331337
actionNameAttribute?: string,
332338
allowedTracingUrls?: string[],

Diff for: packages/plugins/rum/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Interact with Real User Monitoring (RUM) directly from your build system.
1010

1111
<!-- #toc -->
1212
- [Configuration](#configuration)
13+
- [React instrumentation](#react-instrumentation)
14+
- [rum.react.router (alpha)](#rumreactrouter-alpha)
1315
- [Browser SDK Injection](#browser-sdk-injection)
1416
- [rum.sdk.applicationId](#rumsdkapplicationid)
1517
- [rum.sdk.clientToken](#rumsdkclienttoken)
@@ -48,6 +50,9 @@ Interact with Real User Monitoring (RUM) directly from your build system.
4850
```ts
4951
rum?: {
5052
disabled?: boolean;
53+
react?: {
54+
router?: boolean;
55+
};
5156
sdk?: {
5257
actionNameAttribute?: string;
5358
allowedTracingUrls?: string[];
@@ -92,6 +97,25 @@ rum: {
9297
}
9398
```
9499

100+
## React instrumentation
101+
102+
Automatically inject and instrument [RUM's React and React Router integrations](https://github.com/DataDog/browser-sdk/tree/main/packages/rum-react#react-router-integration).
103+
104+
### rum.react.router (alpha)
105+
106+
> default: false
107+
108+
It will:
109+
110+
1. inject `@datadog/browser-rum-react` into your bundle.
111+
2. enable the plugin in the RUM SDK.
112+
3. automatically instrument your React Router routes.
113+
a. For now, it only instruments `createBrowserRouter`.
114+
115+
> [!IMPORTANT]
116+
> - You need to have `react`, `react-dom` and `react-router-dom` into your dependencies.
117+
> - This feature is in alpha and may not work as expected in all cases.
118+
95119
## Browser SDK Injection
96120

97121
Automatically inject the RUM SDK into your application.

Diff for: packages/plugins/rum/package.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
"toBuild": {
1515
"rum-browser-sdk": {
1616
"entry": "./src/built/rum-browser-sdk.ts"
17+
},
18+
"rum-react-plugin": {
19+
"entry": "./src/built/rum-react-plugin.ts",
20+
"external": [
21+
"react",
22+
"react-router-dom"
23+
]
1724
}
1825
},
1926
"exports": {
@@ -28,6 +35,7 @@
2835
"chalk": "2.3.1"
2936
},
3037
"devDependencies": {
31-
"@datadog/browser-rum": "6.0.0"
38+
"@datadog/browser-rum": "6.0.0",
39+
"@datadog/browser-rum-react": "6.0.0"
3240
}
3341
}

Diff for: packages/plugins/rum/src/built/rum-react-plugin.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
import { createBrowserRouter } from '@datadog/browser-rum-react/react-router-v6';
6+
import { reactPlugin } from '@datadog/browser-rum-react';
7+
8+
// To please TypeScript.
9+
const globalAny: any = global;
10+
11+
// Have them globally available.
12+
globalAny.reactPlugin = reactPlugin;
13+
globalAny.createBrowserRouter = createBrowserRouter;
14+
15+
// Also them to the global DD_RUM object.
16+
globalAny.DD_RUM = globalAny.DD_RUM || {};
17+
globalAny.DD_RUM.reactPlugin = reactPlugin;
18+
globalAny.DD_RUM.createBrowserRouter = createBrowserRouter;

Diff for: packages/plugins/rum/src/index.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { InjectPosition } from '@dd/core/types';
77
import path from 'path';
88

99
import { CONFIG_KEY, PLUGIN_NAME } from './constants';
10+
import { getReactPlugin } from './react';
1011
import { getInjectionValue } from './sdk';
1112
import type { OptionsWithRum, RumOptions, RumOptionsWithSdk } from './types';
1213
import { validateOptions } from './validate';
@@ -37,12 +38,26 @@ export const getPlugins: GetPlugins<OptionsWithRum> = (
3738
// Inject the SDK from the CDN.
3839
context.inject({
3940
type: 'file',
40-
// Using MIDDLE otherwise it's not executed in context.
41+
// Using MIDDLE otherwise it's not executed before the rum react plugin injection.
4142
position: InjectPosition.MIDDLE,
4243
// This file is being built alongside the bundler plugin.
4344
value: path.join(__dirname, './rum-browser-sdk.js'),
4445
});
4546

47+
if (options.react?.router) {
48+
// Inject the rum-react-plugin.
49+
context.inject({
50+
type: 'file',
51+
// It's MIDDLE in order to be able to import "react", "react-dom" and "react-router-dom".
52+
// If put in BEFORE, it would not have access to the dependencies of the user's project.
53+
position: InjectPosition.MIDDLE,
54+
// This file is being built alongside the bundler plugin.
55+
value: path.join(__dirname, './rum-react-plugin.js'),
56+
});
57+
58+
plugins.push(getReactPlugin());
59+
}
60+
4661
// Inject the SDK Initialization.
4762
context.inject({
4863
type: 'code',

Diff for: packages/plugins/rum/src/react.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
import type { PluginOptions } from '@dd/core/types';
6+
7+
export const getReactPlugin = (): PluginOptions => {
8+
return {
9+
name: 'datadog-rum-react-plugin',
10+
transform(code) {
11+
let updatedCode = code;
12+
const createBrowserRouterImportRegExp = new RegExp(
13+
/(import \{.*)createBrowserRouter[,]?(.*\} from "react-router-dom")/g,
14+
);
15+
const hasCreateBrowserRouterImport =
16+
code.match(createBrowserRouterImportRegExp) !== null;
17+
18+
if (hasCreateBrowserRouterImport) {
19+
// Remove the import of createBrowserRouter
20+
updatedCode = updatedCode.replace(createBrowserRouterImportRegExp, (_, p1, p2) => {
21+
return `${p1}${p2}`;
22+
});
23+
24+
// replace all occurences of `createBrowserRouter` with `DD_RUM.createBrowserRouter`
25+
updatedCode = updatedCode.replace(
26+
new RegExp(/createBrowserRouter/g),
27+
'DD_RUM.createBrowserRouter',
28+
);
29+
}
30+
31+
return updatedCode;
32+
},
33+
transformInclude(id) {
34+
return id.match(new RegExp(/.*\.(js|jsx|ts|tsx)$/)) !== null;
35+
},
36+
};
37+
};

Diff for: packages/plugins/rum/src/sdk.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ type RumAppResponse = {
1616
};
1717

1818
const getContent = (opts: RumOptionsWithDefaults) => {
19-
return `global.DD_RUM.init({${JSON.stringify(opts.sdk).replace(/(^{|}$)/g, '')}});
19+
const pluginContent = opts.react?.router ? ',plugins:[reactPlugin({router:true})]' : '';
20+
return `global.DD_RUM.init({${JSON.stringify(opts.sdk).replace(/(^{|}$)/g, '')}${pluginContent}});
2021
`;
2122
};
2223

Diff for: packages/plugins/rum/src/types.ts

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { CONFIG_KEY } from './constants';
99
export type RumOptions = {
1010
disabled?: boolean;
1111
sdk?: SDKOptions;
12+
react?: ReactOptions;
1213
};
1314

1415
export type SDKOptions = {
@@ -59,12 +60,23 @@ export type SDKOptionsWithDefaults = Assign<
5960
}
6061
>;
6162

63+
export type ReactOptions = {
64+
router?: boolean;
65+
};
66+
67+
export type ReactOptionsWithDefaults = Required<ReactOptions>;
68+
6269
export type RumOptionsWithDefaults = {
6370
disabled?: boolean;
6471
sdk?: SDKOptionsWithDefaults;
72+
react?: ReactOptionsWithDefaults;
6573
};
6674

6775
export type RumOptionsWithSdk = Assign<RumOptionsWithDefaults, { sdk: SDKOptionsWithDefaults }>;
76+
export type RumOptionsWithReact = Assign<
77+
RumOptionsWithDefaults,
78+
{ react: ReactOptionsWithDefaults }
79+
>;
6880

6981
export interface OptionsWithRum extends GetPluginsOptions {
7082
[CONFIG_KEY]: RumOptions;

Diff for: packages/plugins/rum/src/validate.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import chalk from 'chalk';
88
import { CONFIG_KEY, PLUGIN_NAME } from './constants';
99
import type {
1010
OptionsWithRum,
11+
ReactOptionsWithDefaults,
1112
RumOptions,
1213
RumOptionsWithDefaults,
1314
SDKOptionsWithDefaults,
@@ -21,8 +22,9 @@ export const validateOptions = (
2122

2223
// Validate and add defaults sub-options.
2324
const sdkResults = validateSDKOptions(options);
25+
const reactResults = validateReactOptions(options);
2426

25-
errors.push(...sdkResults.errors);
27+
errors.push(...sdkResults.errors, ...reactResults.errors);
2628

2729
// Throw if there are any errors.
2830
if (errors.length) {
@@ -34,13 +36,18 @@ export const validateOptions = (
3436
const toReturn: RumOptionsWithDefaults = {
3537
...options[CONFIG_KEY],
3638
sdk: undefined,
39+
react: undefined,
3740
};
3841

3942
// Fill in the defaults.
4043
if (sdkResults.config) {
4144
toReturn.sdk = sdkResults.config;
4245
}
4346

47+
if (reactResults.config) {
48+
toReturn.react = reactResults.config;
49+
}
50+
4451
return toReturn;
4552
};
4653

@@ -49,6 +56,37 @@ type ToReturn<T> = {
4956
config?: T;
5057
};
5158

59+
export const validateReactOptions = (
60+
options: Partial<OptionsWithRum>,
61+
): ToReturn<ReactOptionsWithDefaults> => {
62+
const red = chalk.bold.red;
63+
const validatedOptions: RumOptions = options[CONFIG_KEY] || {};
64+
const toReturn: ToReturn<ReactOptionsWithDefaults> = {
65+
errors: [],
66+
};
67+
68+
if (validatedOptions.react) {
69+
if (!options.rum?.sdk?.applicationId && options.rum?.react?.router) {
70+
toReturn.errors.push(
71+
`You must provide ${red('"rum.sdk.applicationId"')} to use ${red('"rum.react.router"')}.`,
72+
);
73+
}
74+
75+
const reactWithDefault: ReactOptionsWithDefaults = {
76+
router: false,
77+
...validatedOptions.react,
78+
};
79+
80+
// Save the config.
81+
toReturn.config = {
82+
...reactWithDefault,
83+
...validatedOptions.react,
84+
};
85+
}
86+
87+
return toReturn;
88+
};
89+
5290
export const validateSDKOptions = (
5391
options: Partial<OptionsWithRum>,
5492
): ToReturn<SDKOptionsWithDefaults> => {

Diff for: packages/tests/src/_jest/helpers/mocks.ts

+1
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ export const getFullPluginConfig = (overrides: Partial<Options> = {}): Options =
233233
applicationId: '123',
234234
clientToken: '123',
235235
},
236+
react: { router: true },
236237
},
237238
telemetry: getTelemetryConfiguration(),
238239
...overrides,

Diff for: packages/tests/src/plugins/rum/index.test.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ describe('RUM Plugin', () => {
1717
const injections = {
1818
'browser-sdk': path.resolve('../plugins/rum/src/rum-browser-sdk.js'),
1919
'sdk-init': injectionValue,
20+
'rum-react-plugin': path.resolve('../plugins/rum/src/rum-react-plugin.js'),
2021
};
2122

2223
const expectations: {
@@ -25,15 +26,25 @@ describe('RUM Plugin', () => {
2526
should: { inject?: (keyof typeof injections)[]; throw?: boolean };
2627
}[] = [
2728
{
28-
type: 'no sdk',
29+
type: 'no sdk and no react',
2930
config: {},
3031
should: { inject: [] },
3132
},
3233
{
33-
type: 'sdk',
34+
type: 'sdk and no react',
3435
config: { sdk: { applicationId: 'app-id' } },
3536
should: { inject: ['browser-sdk', 'sdk-init'] },
3637
},
38+
{
39+
type: 'sdk and react',
40+
config: { sdk: { applicationId: 'app-id' }, react: { router: true } },
41+
should: { inject: ['browser-sdk', 'sdk-init', 'rum-react-plugin'] },
42+
},
43+
{
44+
type: 'no sdk and react',
45+
config: { react: { router: true } },
46+
should: { throw: true },
47+
},
3748
];
3849

3950
test.each(expectations)(

Diff for: yarn.lock

+23
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,28 @@ __metadata:
14331433
languageName: node
14341434
linkType: hard
14351435

1436+
"@datadog/browser-rum-react@npm:6.0.0":
1437+
version: 6.0.0
1438+
resolution: "@datadog/browser-rum-react@npm:6.0.0"
1439+
dependencies:
1440+
"@datadog/browser-core": "npm:6.0.0"
1441+
"@datadog/browser-rum-core": "npm:6.0.0"
1442+
peerDependencies:
1443+
react: 18
1444+
react-router-dom: 6
1445+
peerDependenciesMeta:
1446+
"@datadog/browser-rum":
1447+
optional: true
1448+
"@datadog/browser-rum-slim":
1449+
optional: true
1450+
react:
1451+
optional: true
1452+
react-router-dom:
1453+
optional: true
1454+
checksum: 10/f13aec89dea182f0aeab70ab6e58d8e34da96699f6b6e62e2490f50d048015f018ea28bfd4f773582627fda198f49b1c623b11c4b197cd0df97da94974e85f55
1455+
languageName: node
1456+
linkType: hard
1457+
14361458
"@datadog/browser-rum@npm:6.0.0":
14371459
version: 6.0.0
14381460
resolution: "@datadog/browser-rum@npm:6.0.0"
@@ -1728,6 +1750,7 @@ __metadata:
17281750
resolution: "@dd/rum-plugin@workspace:packages/plugins/rum"
17291751
dependencies:
17301752
"@datadog/browser-rum": "npm:6.0.0"
1753+
"@datadog/browser-rum-react": "npm:6.0.0"
17311754
"@dd/core": "workspace:*"
17321755
chalk: "npm:2.3.1"
17331756
languageName: unknown

0 commit comments

Comments
 (0)