Skip to content

Commit

Permalink
Add RUM's react plugin injection
Browse files Browse the repository at this point in the history
  • Loading branch information
yoannmoinet committed Jan 16, 2025
1 parent d66990f commit 61cce9f
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 6 deletions.
Binary file not shown.
1 change: 1 addition & 0 deletions LICENSES-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Component,Origin,Licence,Copyright
@datadog/browser-core,npm,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-core)
@datadog/browser-rum,virtual,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum)
@datadog/browser-rum-core,npm,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum-core)
@datadog/browser-rum-react,virtual,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum-react)
@esbuild/darwin-arm64,npm,MIT,(https://www.npmjs.com/package/@esbuild/darwin-arm64)
@esbuild/linux-x64,npm,MIT,(https://www.npmjs.com/package/@esbuild/linux-x64)
@eslint-community/eslint-utils,virtual,MIT,Toru Nagashima (https://github.com/eslint-community/eslint-utils#readme)
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ datadogWebpackPlugin({
datadogWebpackPlugin({
rum?: {
disabled?: boolean,
react?: {
router?: boolean,
},
sdk?: {
actionNameAttribute?: string,
allowedTracingUrls?: string[],
Expand Down Expand Up @@ -355,6 +358,9 @@ datadogWebpackPlugin({
};
rum?: {
disabled?: boolean;
react?: {
router?: boolean;
};
sdk?: {
actionNameAttribute?: string;
allowedTracingUrls?: string[];
Expand Down
24 changes: 24 additions & 0 deletions packages/plugins/rum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Interact with Real User Monitoring (RUM) directly from your build system.

<!-- #toc -->
- [Configuration](#configuration)
- [React instrumentation](#react-instrumentation)
- [rum.react.router (alpha)](#rumreactrouter-alpha)
- [Browser SDK Injection](#browser-sdk-injection)
- [rum.sdk.applicationId](#rumsdkapplicationid)
- [rum.sdk.clientToken](#rumsdkclienttoken)
Expand Down Expand Up @@ -48,6 +50,9 @@ Interact with Real User Monitoring (RUM) directly from your build system.
```ts
rum?: {
disabled?: boolean;
react?: {
router?: boolean;
};
sdk?: {
actionNameAttribute?: string;
allowedTracingUrls?: string[];
Expand Down Expand Up @@ -92,6 +97,25 @@ rum: {
}
```

## React instrumentation

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

### rum.react.router (alpha)

> default: false
It will:

1. inject `@datadog/browser-rum-react` into your bundle.
2. enable the plugin in the RUM SDK.
3. automatically instrument your React Router routes.
a. For now, it only instruments `createBrowserRouter`.

> [!IMPORTANT]
> - You need to have `react`, `react-dom` and `react-router-dom` into your dependencies.
> - This feature is in alpha and may not work as expected in all cases.
## Browser SDK Injection

Automatically inject the RUM SDK into your application.
Expand Down
10 changes: 9 additions & 1 deletion packages/plugins/rum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
"toBuild": {
"rum-browser-sdk": {
"entry": "./src/built/rum-browser-sdk.ts"
},
"rum-react-plugin": {
"entry": "./src/built/rum-react-plugin.ts",
"external": [
"react",
"react-router-dom"
]
}
},
"exports": {
Expand All @@ -28,6 +35,7 @@
"chalk": "2.3.1"
},
"devDependencies": {
"@datadog/browser-rum": "6.0.0"
"@datadog/browser-rum": "6.0.0",
"@datadog/browser-rum-react": "6.0.0"
}
}
18 changes: 18 additions & 0 deletions packages/plugins/rum/src/built/rum-react-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import { createBrowserRouter } from '@datadog/browser-rum-react/react-router-v6';
import { reactPlugin } from '@datadog/browser-rum-react';

// To please TypeScript.
const globalAny: any = global;

// Have them globally available.
globalAny.reactPlugin = reactPlugin;
globalAny.createBrowserRouter = createBrowserRouter;

// Also them to the global DD_RUM object.
globalAny.DD_RUM = globalAny.DD_RUM || {};
globalAny.DD_RUM.reactPlugin = reactPlugin;
globalAny.DD_RUM.createBrowserRouter = createBrowserRouter;
17 changes: 16 additions & 1 deletion packages/plugins/rum/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { InjectPosition } from '@dd/core/types';
import path from 'path';

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

if (options.react?.router) {
// Inject the rum-react-plugin.
context.inject({
type: 'file',
// It's MIDDLE in order to be able to import "react", "react-dom" and "react-router-dom".
// If put in BEFORE, it would not have access to the dependencies of the user's project.
position: InjectPosition.MIDDLE,
// This file is being built alongside the bundler plugin.
value: path.join(__dirname, './rum-react-plugin.js'),
});

plugins.push(getReactPlugin());
}

// Inject the SDK Initialization.
context.inject({
type: 'code',
Expand Down
37 changes: 37 additions & 0 deletions packages/plugins/rum/src/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import type { PluginOptions } from '@dd/core/types';

export const getReactPlugin = (): PluginOptions => {
return {
name: 'datadog-rum-react-plugin',
transform(code) {
let updatedCode = code;
const createBrowserRouterImportRegExp = new RegExp(
/(import \{.*)createBrowserRouter[,]?(.*\} from "react-router-dom")/g,
);
const hasCreateBrowserRouterImport =
code.match(createBrowserRouterImportRegExp) !== null;

if (hasCreateBrowserRouterImport) {
// Remove the import of createBrowserRouter
updatedCode = updatedCode.replace(createBrowserRouterImportRegExp, (_, p1, p2) => {
return `${p1}${p2}`;
});

// replace all occurences of `createBrowserRouter` with `DD_RUM.createBrowserRouter`
updatedCode = updatedCode.replace(
new RegExp(/createBrowserRouter/g),
'DD_RUM.createBrowserRouter',
);
}

return updatedCode;
},
transformInclude(id) {
return id.match(new RegExp(/.*\.(js|jsx|ts|tsx)$/)) !== null;
},
};
};
3 changes: 2 additions & 1 deletion packages/plugins/rum/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ type RumAppResponse = {
};

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

Expand Down
12 changes: 12 additions & 0 deletions packages/plugins/rum/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { CONFIG_KEY } from './constants';
export type RumOptions = {
disabled?: boolean;
sdk?: SDKOptions;
react?: ReactOptions;
};

export type SDKOptions = {
Expand Down Expand Up @@ -59,12 +60,23 @@ export type SDKOptionsWithDefaults = Assign<
}
>;

export type ReactOptions = {
router?: boolean;
};

export type ReactOptionsWithDefaults = Required<ReactOptions>;

export type RumOptionsWithDefaults = {
disabled?: boolean;
sdk?: SDKOptionsWithDefaults;
react?: ReactOptionsWithDefaults;
};

export type RumOptionsWithSdk = Assign<RumOptionsWithDefaults, { sdk: SDKOptionsWithDefaults }>;
export type RumOptionsWithReact = Assign<
RumOptionsWithDefaults,
{ react: ReactOptionsWithDefaults }
>;

export interface OptionsWithRum extends GetPluginsOptions {
[CONFIG_KEY]: RumOptions;
Expand Down
40 changes: 39 additions & 1 deletion packages/plugins/rum/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import chalk from 'chalk';
import { CONFIG_KEY, PLUGIN_NAME } from './constants';
import type {
OptionsWithRum,
ReactOptionsWithDefaults,
RumOptions,
RumOptionsWithDefaults,
SDKOptionsWithDefaults,
Expand All @@ -21,8 +22,9 @@ export const validateOptions = (

// Validate and add defaults sub-options.
const sdkResults = validateSDKOptions(options);
const reactResults = validateReactOptions(options);

errors.push(...sdkResults.errors);
errors.push(...sdkResults.errors, ...reactResults.errors);

// Throw if there are any errors.
if (errors.length) {
Expand All @@ -34,13 +36,18 @@ export const validateOptions = (
const toReturn: RumOptionsWithDefaults = {
...options[CONFIG_KEY],
sdk: undefined,
react: undefined,
};

// Fill in the defaults.
if (sdkResults.config) {
toReturn.sdk = sdkResults.config;
}

if (reactResults.config) {
toReturn.react = reactResults.config;
}

return toReturn;
};

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

export const validateReactOptions = (
options: Partial<OptionsWithRum>,
): ToReturn<ReactOptionsWithDefaults> => {
const red = chalk.bold.red;
const validatedOptions: RumOptions = options[CONFIG_KEY] || {};
const toReturn: ToReturn<ReactOptionsWithDefaults> = {
errors: [],
};

if (validatedOptions.react) {
if (!options.rum?.sdk?.applicationId && options.rum?.react?.router) {
toReturn.errors.push(
`You must provide ${red('"rum.sdk.applicationId"')} to use ${red('"rum.react.router"')}.`,
);
}

const reactWithDefault: ReactOptionsWithDefaults = {
router: false,
...validatedOptions.react,
};

// Save the config.
toReturn.config = {
...reactWithDefault,
...validatedOptions.react,
};
}

return toReturn;
};

export const validateSDKOptions = (
options: Partial<OptionsWithRum>,
): ToReturn<SDKOptionsWithDefaults> => {
Expand Down
1 change: 1 addition & 0 deletions packages/tests/src/_jest/helpers/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export const getFullPluginConfig = (overrides: Partial<Options> = {}): Options =
applicationId: '123',
clientToken: '123',
},
react: { router: true },
},
telemetry: getTelemetryConfiguration(),
...overrides,
Expand Down
15 changes: 13 additions & 2 deletions packages/tests/src/plugins/rum/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('RUM Plugin', () => {
const injections = {
'browser-sdk': path.resolve('../plugins/rum/src/rum-browser-sdk.js'),
'sdk-init': injectionValue,
'rum-react-plugin': path.resolve('../plugins/rum/src/rum-react-plugin.js'),
};

const expectations: {
Expand All @@ -25,15 +26,25 @@ describe('RUM Plugin', () => {
should: { inject?: (keyof typeof injections)[]; throw?: boolean };
}[] = [
{
type: 'no sdk',
type: 'no sdk and no react',
config: {},
should: { inject: [] },
},
{
type: 'sdk',
type: 'sdk and no react',
config: { sdk: { applicationId: 'app-id' } },
should: { inject: ['browser-sdk', 'sdk-init'] },
},
{
type: 'sdk and react',
config: { sdk: { applicationId: 'app-id' }, react: { router: true } },
should: { inject: ['browser-sdk', 'sdk-init', 'rum-react-plugin'] },
},
{
type: 'no sdk and react',
config: { react: { router: true } },
should: { throw: true },
},
];

test.each(expectations)(
Expand Down
23 changes: 23 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,28 @@ __metadata:
languageName: node
linkType: hard

"@datadog/browser-rum-react@npm:6.0.0":
version: 6.0.0
resolution: "@datadog/browser-rum-react@npm:6.0.0"
dependencies:
"@datadog/browser-core": "npm:6.0.0"
"@datadog/browser-rum-core": "npm:6.0.0"
peerDependencies:
react: 18
react-router-dom: 6
peerDependenciesMeta:
"@datadog/browser-rum":
optional: true
"@datadog/browser-rum-slim":
optional: true
react:
optional: true
react-router-dom:
optional: true
checksum: 10/f13aec89dea182f0aeab70ab6e58d8e34da96699f6b6e62e2490f50d048015f018ea28bfd4f773582627fda198f49b1c623b11c4b197cd0df97da94974e85f55
languageName: node
linkType: hard

"@datadog/browser-rum@npm:6.0.0":
version: 6.0.0
resolution: "@datadog/browser-rum@npm:6.0.0"
Expand Down Expand Up @@ -1728,6 +1750,7 @@ __metadata:
resolution: "@dd/rum-plugin@workspace:packages/plugins/rum"
dependencies:
"@datadog/browser-rum": "npm:6.0.0"
"@datadog/browser-rum-react": "npm:6.0.0"
"@dd/core": "workspace:*"
chalk: "npm:2.3.1"
languageName: unknown
Expand Down

0 comments on commit 61cce9f

Please sign in to comment.