Skip to content

Commit

Permalink
feat(solid): Remove need to pass router hooks to solid integration (#…
Browse files Browse the repository at this point in the history
…12617)

⚠️ This PR introduces **breaking changes** to `@sentry/solid`⚠️

Previously, we had to pass `useBeforeLeave` and `useLocation` to
`solidRouterBrowserTracingIntegration`. This has now been removed in
favor of importing the router hooks directly within the sdk as needed.

Import `solidRouterBrowserTracingIntegration` from
`@sentry/solid/solidrouter` and add it to `Sentry.init`:

```js
import * as Sentry from '@sentry/solid';
import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '@sentry/solid/solidrouter';
import { Router } from '@solidjs/router';

Sentry.init({
  dsn: '__PUBLIC_DSN__',
  integrations: [solidRouterBrowserTracingIntegration()],
  tracesSampleRate: 1.0, //  Capture 100% of the transactions
});

const SentryRouter = withSentryRouterRouting(Router);
```

As a result, the SDK has an optional peer dependency on
`@solidjs/router` `v0.13.4+` when using
`solidRouterBrowserTracingIntegration`.

**Note to maintainers**
This package outputs types at the `build` root instead of `build/types`
to better support projects that don't use `moduleResolution: "bundler"`.

---------

Co-authored-by: Francesco Novy <[email protected]>
Co-authored-by: Luca Forstner <[email protected]>
  • Loading branch information
3 people authored Jun 25, 2024
1 parent c49c9f3 commit dd4a7d7
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 109 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(solid): Remove need to pass router hooks to solid integration** (breaking)

This release introduces breaking changes to the `@sentry/solid` package (which is currently out in alpha).

We've made it easier to get started with the solid router integration by removing the need to pass **use\*** hooks
explicitly to `solidRouterBrowserTracingIntegration`. Import `solidRouterBrowserTracingIntegration` from
`@sentry/solid/solidrouter` and add it to `Sentry.init`

```js
import * as Sentry from '@sentry/solid';
import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '@sentry/solid/solidrouter';
import { Router } from '@solidjs/router';

Sentry.init({
dsn: '__PUBLIC_DSN__',
integrations: [solidRouterBrowserTracingIntegration()],
tracesSampleRate: 1.0, // Capture 100% of the transactions
});

const SentryRouter = withSentryRouterRouting(Router);
```

## 8.11.0

### Important Changes
Expand Down
7 changes: 4 additions & 3 deletions dev-packages/e2e-tests/test-applications/solid/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* @refresh reload */
import * as Sentry from '@sentry/solid';
import { Router, useBeforeLeave, useLocation } from '@solidjs/router';
import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '@sentry/solid/solidrouter';
import { Router } from '@solidjs/router';
import { render } from 'solid-js/web';
import './index.css';
import PageRoot from './pageroot';
Expand All @@ -10,12 +11,12 @@ Sentry.init({
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
debug: true,
environment: 'qa', // dynamic sampling bias to keep transactions
integrations: [Sentry.solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })],
integrations: [solidRouterBrowserTracingIntegration()],
release: 'e2e-test',
tunnel: 'http://localhost:3031/', // proxy server
tracesSampleRate: 1.0,
});

const SentryRouter = Sentry.withSentryRouterRouting(Router);
const SentryRouter = withSentryRouterRouting(Router);

render(() => <SentryRouter root={PageRoot}>{routes}</SentryRouter>, document.getElementById('root'));
25 changes: 11 additions & 14 deletions packages/solid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
</a>
</p>

# Official Sentry SDK for Solid
# Official Sentry SDK for Solid (EXPERIMENTAL)

[![npm version](https://img.shields.io/npm/v/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid)
[![npm dm](https://img.shields.io/npm/dm/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid)
[![npm dt](https://img.shields.io/npm/dt/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid)

This SDK is considered **experimental and in an alpha state**. It may experience breaking changes. Please reach out on
[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback or concerns. This
This SDK is considered ⚠️ **experimental and in an alpha state**. It may experience breaking changes. Please reach out
on [GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback or concerns. This
SDK currently only supports [Solid](https://www.solidjs.com/) and is not yet officially compatible with
[Solid Start](https://start.solidjs.com/).

Expand All @@ -20,25 +20,22 @@ SDK currently only supports [Solid](https://www.solidjs.com/) and is not yet off
The Solid Router instrumentation uses the Solid Router library to create navigation spans to ensure you collect
meaningful performance data about the health of your page loads and associated requests.

Add `Sentry.solidRouterBrowserTracingIntegration` instead of the regular `Sentry.browserTracingIntegration` and provide
the hooks it needs to enable performance tracing:
Add `solidRouterBrowserTracingIntegration` instead of the regular `Sentry.browserTracingIntegration`.

`useBeforeLeave` from `@solidjs/router`
`useLocation` from `@solidjs/router`
Make sure `solidRouterBrowserTracingIntegration` is initialized by your `Sentry.init` call. Otherwise, the routing
instrumentation will not work properly.

Make sure `Sentry.solidRouterBrowserTracingIntegration` is initialized by your `Sentry.init` call, before you wrap
`Router`. Otherwise, the routing instrumentation may not work properly.

Wrap `Router`, `MemoryRouter` or `HashRouter` from `@solidjs/router` using `Sentry.withSentryRouterRouting`. This
creates a higher order component, which will enable Sentry to reach your router context.
Wrap `Router`, `MemoryRouter` or `HashRouter` from `@solidjs/router` using `withSentryRouterRouting`. This creates a
higher order component, which will enable Sentry to reach your router context.

```js
import * as Sentry from '@sentry/solid';
import { Route, Router, useBeforeLeave, useLocation } from '@solidjs/router';
import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '@sentry/solid/solidrouter';
import { Route, Router } from '@solidjs/router';

Sentry.init({
dsn: '__PUBLIC_DSN__',
integrations: [Sentry.solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })],
integrations: [solidRouterBrowserTracingIntegration()],
tracesSampleRate: 1.0, // Capture 100% of the transactions
});

Expand Down
40 changes: 25 additions & 15 deletions packages/solid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,35 @@
"files": [
"cjs",
"esm",
"types",
"types-ts3.8"
"index.d.ts",
"index.d.ts.map",
"solidrouter.d.ts",
"solidrouter.d.ts.map"
],
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/types/index.d.ts",
"types": "build/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./build/types/index.d.ts",
"types": "./build/index.d.ts",
"default": "./build/esm/index.js"
},
"require": {
"types": "./build/types/index.d.ts",
"types": "./build/index.d.ts",
"default": "./build/cjs/index.js"
}
}
},
"typesVersions": {
"<4.9": {
"build/types/index.d.ts": [
"build/types-ts3.8/index.d.ts"
]
},
"./solidrouter": {
"import": {
"types": "./build/solidrouter.d.ts",
"default": "./build/esm/solidrouter.js"
},
"require": {
"types": "./build/solidrouter.d.ts",
"default": "./build/cjs/solidrouter.js"
}
}
},
"publishConfig": {
Expand All @@ -48,10 +53,16 @@
"@sentry/utils": "8.11.0"
},
"peerDependencies": {
"@solidjs/router": "^0.13.4",
"solid-js": "^1.8.4"
},
"peerDependenciesMeta": {
"@solidjs/router": {
"optional": true
}
},
"devDependencies": {
"@solidjs/router": "^0.13.5",
"@solidjs/router": "^0.13.4",
"@solidjs/testing-library": "0.8.5",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/user-event": "^14.5.2",
Expand All @@ -62,9 +73,8 @@
"build": "run-p build:transpile build:types",
"build:dev": "yarn build",
"build:transpile": "rollup -c rollup.npm.config.mjs",
"build:types": "run-s build:types:core build:types:downlevel",
"build:types": "run-s build:types:core",
"build:types:core": "tsc -p tsconfig.types.json",
"build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8",
"build:watch": "run-p build:transpile:watch build:types:watch",
"build:dev:watch": "yarn build:watch",
"build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch",
Expand Down
6 changes: 5 additions & 1 deletion packages/solid/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';

export default makeNPMConfigVariants(makeBaseNPMConfig());
export default makeNPMConfigVariants(
makeBaseNPMConfig({
entrypoints: ['src/index.ts', 'src/solidrouter.ts'],
}),
);
1 change: 0 additions & 1 deletion packages/solid/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ export * from '@sentry/browser';

export { init } from './sdk';

export * from './solidrouter';
export * from './errorboundary';
78 changes: 17 additions & 61 deletions packages/solid/src/solidrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,21 @@ import {
getClient,
} from '@sentry/core';
import type { Client, Integration, Span } from '@sentry/types';
import { logger } from '@sentry/utils';
import type {
BeforeLeaveEventArgs,
HashRouter,
MemoryRouter,
RouteSectionProps,
Router as BaseRouter,
StaticRouter,
} from '@solidjs/router';
import { useBeforeLeave, useLocation } from '@solidjs/router';
import { createEffect, mergeProps, splitProps } from 'solid-js';
import type { Component, JSX, ParentProps } from 'solid-js';
import { createComponent } from 'solid-js/web';
import { DEBUG_BUILD } from './debug-build';

// Vendored solid router types so that we don't need to depend on solid router.
// These are not exhaustive and loose on purpose.
interface Location {
pathname: string;
}

interface BeforeLeaveEventArgs {
from: Location;
to: string | number;
}

interface RouteSectionProps<T = unknown> {
location: Location;
data?: T;
children?: JSX.Element;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type RouteDefinition<S extends string | string[] = any, T = unknown> = {
path?: S;
children?: RouteDefinition | RouteDefinition[];
component?: Component<RouteSectionProps<T>>;
};

interface RouterProps {
base?: string;
root?: Component<RouteSectionProps>;
children?: JSX.Element | RouteDefinition | RouteDefinition[];
}

interface SolidRouterOptions {
useBeforeLeave: UserBeforeLeave;
useLocation: UseLocation;
}

type UserBeforeLeave = (listener: (e: BeforeLeaveEventArgs) => void) => void;
type UseLocation = () => Location;

const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet<Client>();

let _useBeforeLeave: UserBeforeLeave;
let _useLocation: UseLocation;

function handleNavigation(location: string): void {
const client = getClient();
if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) {
Expand Down Expand Up @@ -98,12 +64,12 @@ function withSentryRouterRoot(Root: Component<RouteSectionProps>): Component<Rou
// - use query params
// - parameterize the route

_useBeforeLeave(({ to }: BeforeLeaveEventArgs) => {
useBeforeLeave(({ to }: BeforeLeaveEventArgs) => {
// `to` could be `-1` if the browser back-button was used
handleNavigation(to.toString());
});

const location = _useLocation();
const location = useLocation();
createEffect(() => {
const name = location.pathname;
const rootSpan = getActiveRootSpan();
Expand All @@ -130,21 +96,17 @@ function withSentryRouterRoot(Root: Component<RouteSectionProps>): Component<Rou
* A browser tracing integration that uses Solid Router to instrument navigations.
*/
export function solidRouterBrowserTracingIntegration(
options: Parameters<typeof browserTracingIntegration>[0] & SolidRouterOptions,
options: Parameters<typeof browserTracingIntegration>[0] = {},
): Integration {
const integration = browserTracingIntegration({
...options,
instrumentNavigation: false,
});

const { instrumentNavigation = true, useBeforeLeave, useLocation } = options;
const { instrumentNavigation = true } = options;

return {
...integration,
setup() {
_useBeforeLeave = useBeforeLeave;
_useLocation = useLocation;
},
afterAllSetup(client) {
integration.afterAllSetup(client);

Expand All @@ -155,19 +117,13 @@ export function solidRouterBrowserTracingIntegration(
};
}

type RouterType = typeof BaseRouter | typeof HashRouter | typeof MemoryRouter | typeof StaticRouter;

/**
* A higher-order component to instrument Solid Router to create navigation spans.
*/
export function withSentryRouterRouting(Router: Component<RouterProps>): Component<RouterProps> {
if (!_useBeforeLeave || !_useLocation) {
DEBUG_BUILD &&
logger.warn(`solidRouterBrowserTracingIntegration was unable to wrap Solid Router because of one or more missing hooks.
useBeforeLeave: ${_useBeforeLeave}. useLocation: ${_useLocation}.`);

return Router;
}

const SentryRouter = (props: RouterProps): JSX.Element => {
export function withSentryRouterRouting(Router: RouterType): RouterType {
const SentryRouter = (props: Parameters<RouterType>[0]): JSX.Element => {
const [local, others] = splitProps(props, ['root']);
// We need to wrap root here in case the user passed in their own root
const Root = withSentryRouterRoot(local.root ? local.root : SentryDefaultRoot);
Expand Down
15 changes: 6 additions & 9 deletions packages/solid/test/solidrouter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
getCurrentScope,
setCurrentClient,
} from '@sentry/core';
import { MemoryRouter, Navigate, Route, createMemoryHistory, useBeforeLeave, useLocation } from '@solidjs/router';
import type { MemoryHistory } from '@solidjs/router';
import { MemoryRouter, Navigate, Route, createMemoryHistory } from '@solidjs/router';
import { render } from '@solidjs/testing-library';
import { vi } from 'vitest';

Expand All @@ -17,7 +18,7 @@ import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '.
// solid router uses `window.scrollTo` when navigating
vi.spyOn(global, 'scrollTo').mockImplementation(() => {});

const renderRouter = (SentryRouter, history) =>
const renderRouter = (SentryRouter: typeof MemoryRouter, history: MemoryHistory) =>
render(() => (
<SentryRouter history={history}>
<Route path="/" component={() => <div>Home</div>} />
Expand Down Expand Up @@ -58,7 +59,7 @@ describe('solidRouterBrowserTracingIntegration', () => {
setCurrentClient(client);

client.on('spanStart', span => spanStartMock(spanToJSON(span)));
client.addIntegration(solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation }));
client.addIntegration(solidRouterBrowserTracingIntegration());

const history = createMemoryHistory();
history.set({ value: '/' });
Expand Down Expand Up @@ -86,8 +87,6 @@ describe('solidRouterBrowserTracingIntegration', () => {
client.addIntegration(
solidRouterBrowserTracingIntegration({
instrumentPageLoad: false,
useBeforeLeave,
useLocation,
}),
);
const SentryRouter = withSentryRouterRouting(MemoryRouter);
Expand Down Expand Up @@ -124,7 +123,7 @@ describe('solidRouterBrowserTracingIntegration', () => {
client.on('spanStart', span => {
spanStartMock(spanToJSON(span));
});
client.addIntegration(solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation }));
client.addIntegration(solidRouterBrowserTracingIntegration());
const SentryRouter = withSentryRouterRouting(MemoryRouter);

const history = createMemoryHistory();
Expand Down Expand Up @@ -155,8 +154,6 @@ describe('solidRouterBrowserTracingIntegration', () => {
client.addIntegration(
solidRouterBrowserTracingIntegration({
instrumentNavigation: false,
useBeforeLeave,
useLocation,
}),
);
const SentryRouter = withSentryRouterRouting(MemoryRouter);
Expand Down Expand Up @@ -188,7 +185,7 @@ describe('solidRouterBrowserTracingIntegration', () => {
client.on('spanStart', span => {
spanStartMock(spanToJSON(span));
});
client.addIntegration(solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation }));
client.addIntegration(solidRouterBrowserTracingIntegration());
const SentryRouter = withSentryRouterRouting(MemoryRouter);

const history = createMemoryHistory();
Expand Down
2 changes: 1 addition & 1 deletion packages/solid/tsconfig.types.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "build/types"
"outDir": "build"
}
}
Loading

0 comments on commit dd4a7d7

Please sign in to comment.