Skip to content

feat(solid): Remove need to pass router hooks to solid integration #12617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading