Skip to content

Commit 3627b54

Browse files
committed
feat(solidstart): Add solidstart sentry vite plugin
1 parent f452423 commit 3627b54

File tree

16 files changed

+389
-115
lines changed

16 files changed

+389
-115
lines changed
Lines changed: 11 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,15 @@
1-
import * as Sentry from '@sentry/solidstart';
2-
import type { ParentProps } from 'solid-js';
3-
import { ErrorBoundary, createSignal, onMount } from 'solid-js';
4-
5-
const SentryErrorBoundary = Sentry.withSentryErrorBoundary(ErrorBoundary);
6-
7-
const [count, setCount] = createSignal(1);
8-
const [caughtError, setCaughtError] = createSignal(false);
9-
101
export default function ClientErrorPage() {
112
return (
12-
<SampleErrorBoundary>
13-
{caughtError() && (
14-
<Throw error={`Error ${count()} thrown from Sentry ErrorBoundary in Solid Start E2E test app`} />
15-
)}
16-
<section class="bg-gray-100 text-gray-700 p-8">
17-
<div class="flex flex-col items-start space-x-2">
18-
<button
19-
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
20-
id="caughtErrorBtn"
21-
onClick={() => setCaughtError(true)}
22-
>
23-
Throw caught error
24-
</button>
25-
</div>
26-
<div class="flex flex-col items-start space-x-2">
27-
<button
28-
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
29-
id="errorBtn"
30-
onClick={() => {
31-
throw new Error('Error thrown from Solid Start E2E test app');
32-
}}
33-
>
34-
Throw uncaught error
35-
</button>
36-
</div>
37-
</section>
38-
</SampleErrorBoundary>
39-
);
40-
}
41-
42-
function Throw(props: { error: string }) {
43-
onMount(() => {
44-
throw new Error(props.error);
45-
});
46-
return null;
47-
}
48-
49-
function SampleErrorBoundary(props: ParentProps) {
50-
return (
51-
<SentryErrorBoundary
52-
fallback={(error, reset) => (
53-
<section class="bg-gray-100 text-gray-700 p-8">
54-
<h1 class="text-2xl font-bold">Error Boundary Fallback</h1>
55-
<div class="flex items-center space-x-2 mb-4">
56-
<code>{error.message}</code>
57-
</div>
58-
<button
59-
id="errorBoundaryResetBtn"
60-
class="border rounded-lg px-2 border-gray-900"
61-
onClick={() => {
62-
setCount(count() + 1);
63-
setCaughtError(false);
64-
reset();
65-
}}
66-
>
67-
Reset
68-
</button>
69-
</section>
70-
)}
71-
>
72-
{props.children}
73-
</SentryErrorBoundary>
3+
<div class="flex flex-col items-start space-x-2">
4+
<button
5+
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
6+
id="errorBtn"
7+
onClick={() => {
8+
throw new Error('Uncaught error thrown from Solid Start E2E test app');
9+
}}
10+
>
11+
Throw uncaught error
12+
</button>
13+
</div>
7414
);
7515
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as Sentry from '@sentry/solidstart';
2+
import type { ParentProps } from 'solid-js';
3+
import { ErrorBoundary, createSignal, onMount } from 'solid-js';
4+
5+
const SentryErrorBoundary = Sentry.withSentryErrorBoundary(ErrorBoundary);
6+
7+
const [count, setCount] = createSignal(1);
8+
const [caughtError, setCaughtError] = createSignal(false);
9+
10+
export default function ErrorBoundaryTestPage() {
11+
return (
12+
<SampleErrorBoundary>
13+
{caughtError() && (
14+
<Throw error={`Error ${count()} thrown from Sentry ErrorBoundary in Solid Start E2E test app`} />
15+
)}
16+
<section class="bg-gray-100 text-gray-700 p-8">
17+
<div class="flex flex-col items-start space-x-2">
18+
<button
19+
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
20+
id="caughtErrorBtn"
21+
onClick={() => setCaughtError(true)}
22+
>
23+
Throw caught error
24+
</button>
25+
</div>
26+
</section>
27+
</SampleErrorBoundary>
28+
);
29+
}
30+
31+
function Throw(props: { error: string }) {
32+
onMount(() => {
33+
throw new Error(props.error);
34+
});
35+
return null;
36+
}
37+
38+
function SampleErrorBoundary(props: ParentProps) {
39+
return (
40+
<SentryErrorBoundary
41+
fallback={(error, reset) => (
42+
<section class="bg-gray-100 text-gray-700 p-8">
43+
<h1 class="text-2xl font-bold">Error Boundary Fallback</h1>
44+
<div class="flex items-center space-x-2 mb-4">
45+
<code>{error.message}</code>
46+
</div>
47+
<button
48+
id="errorBoundaryResetBtn"
49+
class="border rounded-lg px-2 border-gray-900"
50+
onClick={() => {
51+
setCount(count() + 1);
52+
setCaughtError(false);
53+
reset();
54+
}}
55+
>
56+
Reset
57+
</button>
58+
</section>
59+
)}
60+
>
61+
{props.children}
62+
</SentryErrorBoundary>
63+
);
64+
}

dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export default function Home() {
1414
<li>
1515
<A href="/server-error">Server error</A>
1616
</li>
17+
<li>
18+
<A href="/error-boundary">Error Boundary</A>
19+
</li>
1720
<li>
1821
<A id="navLink" href="/users/5">
1922
User 5

dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ test('captures an exception', async ({ page }) => {
1010
);
1111
});
1212

13-
await page.goto('/client-error');
13+
await page.goto('/error-boundary');
1414
await page.locator('#caughtErrorBtn').click();
1515
const errorEvent = await errorEventPromise;
1616

@@ -27,7 +27,7 @@ test('captures an exception', async ({ page }) => {
2727
},
2828
],
2929
},
30-
transaction: '/client-error',
30+
transaction: '/error-boundary',
3131
});
3232
});
3333

@@ -40,7 +40,8 @@ test('captures a second exception after resetting the boundary', async ({ page }
4040
);
4141
});
4242

43-
await page.goto('/client-error');
43+
await page.waitForTimeout(5000);
44+
await page.goto('/error-boundary');
4445
await page.locator('#caughtErrorBtn').click();
4546
const firstErrorEvent = await firstErrorEventPromise;
4647

@@ -57,7 +58,7 @@ test('captures a second exception after resetting the boundary', async ({ page }
5758
},
5859
],
5960
},
60-
transaction: '/client-error',
61+
transaction: '/error-boundary',
6162
});
6263

6364
const secondErrorEventPromise = waitForError('solidstart', errorEvent => {
@@ -85,6 +86,6 @@ test('captures a second exception after resetting the boundary', async ({ page }
8586
},
8687
],
8788
},
88-
transaction: '/client-error',
89+
transaction: '/error-boundary',
8990
});
9091
});

dev-packages/e2e-tests/test-applications/solidstart/tests/errors.client.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { waitForError } from '@sentry-internal/test-utils';
44
test.describe('client-side errors', () => {
55
test('captures error thrown on click', async ({ page }) => {
66
const errorPromise = waitForError('solidstart', async errorEvent => {
7-
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Solid Start E2E test app';
7+
return errorEvent?.exception?.values?.[0]?.value === 'Uncaught error thrown from Solid Start E2E test app';
88
});
99

1010
await page.goto(`/client-error`);
@@ -16,7 +16,7 @@ test.describe('client-side errors', () => {
1616
values: [
1717
{
1818
type: 'Error',
19-
value: 'Error thrown from Solid Start E2E test app',
19+
value: 'Uncaught error thrown from Solid Start E2E test app',
2020
mechanism: {
2121
type: 'instrument',
2222
handled: false,

packages/solidstart/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
files: ['src/vite/**', 'src/server/**'],
1515
rules: {
1616
'@sentry-internal/sdk/no-optional-chaining': 'off',
17+
'@sentry-internal/sdk/no-nullish-coalescing': 'off',
1718
},
1819
},
1920
],

packages/solidstart/README.md

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -157,58 +157,34 @@ render(
157157
);
158158
```
159159

160-
# Sourcemaps and Releases
160+
## Uploading Source Maps
161161

162-
To generate and upload source maps of your Solid Start app use our Vite bundler plugin.
163-
164-
1. Install the Sentry Vite plugin
165-
166-
```bash
167-
# Using npm
168-
npm install @sentry/vite-plugin --save-dev
169-
170-
# Using yarn
171-
yarn add @sentry/vite-plugin --dev
172-
```
173-
174-
2. Configure the vite plugin
175-
176-
To upload source maps you have to configure an auth token. Auth tokens can be passed to the plugin explicitly with the
177-
`authToken` option, with a `SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the
178-
working directory when building your project. We recommend you add the auth token to your CI/CD environment as an
179-
environment variable.
162+
To upload source maps, add the `sentrySolidStartVite` plugin from `@sentry/solidstart` to your `app.config.ts` and
163+
configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a
164+
`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when
165+
building your project. We recommend you add the auth token to your CI/CD environment as an environment variable.
180166

181167
Learn more about configuring the plugin in our
182168
[Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin).
183169

184-
```bash
185-
// .env.sentry-build-plugin
186-
SENTRY_AUTH_TOKEN=<your auth token>
187-
SENTRY_ORG=<your org>
188-
SENTRY_PROJECT=<your project name>
189-
```
190-
191-
3. Finally, add the plugin to your `app.config.ts` file.
192-
193-
```javascript
170+
```typescript
171+
// app.config.ts
194172
import { defineConfig } from '@solidjs/start/config';
195-
import { sentryVitePlugin } from '@sentry/vite-plugin';
173+
import { sentrySolidStartVite } from '@sentry/solidstart';
196174

197175
export default defineConfig({
198-
// rest of your config
199176
// ...
200177

201178
vite: {
202-
build: {
203-
sourcemap: true,
204-
},
205179
plugins: [
206-
sentryVitePlugin({
180+
sentrySolidStartVite({
207181
org: process.env.SENTRY_ORG,
208182
project: process.env.SENTRY_PROJECT,
209183
authToken: process.env.SENTRY_AUTH_TOKEN,
184+
debug: true,
210185
}),
211186
],
212187
},
188+
// ...
213189
});
214190
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './server';
2+
export * from './vite';

packages/solidstart/src/index.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// exports in this file - which we do below.
44
export * from './client';
55
export * from './server';
6+
export * from './vite';
67

78
import type { Integration, Options, StackParser } from '@sentry/types';
89

packages/solidstart/src/vite/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './sentrySolidStartVite';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Plugin } from 'vite';
2+
import { makeSourceMapsVitePlugin } from './sourceMaps';
3+
import type { SentrySolidStartPluginOptions } from './types';
4+
5+
/**
6+
* Various Sentry vite plugins to be used for SolidStart.
7+
*/
8+
export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions): Plugin[] => {
9+
const sentryPlugins: Plugin[] = [];
10+
11+
if (process.env.NODE_ENV !== 'development') {
12+
if (options.sourceMapsUploadOptions?.enabled ?? true) {
13+
sentryPlugins.push(...makeSourceMapsVitePlugin(options));
14+
}
15+
}
16+
17+
return sentryPlugins;
18+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { sentryVitePlugin } from '@sentry/vite-plugin';
2+
import type { Plugin } from 'vite';
3+
import type { SentrySolidStartPluginOptions } from './types';
4+
5+
/**
6+
* A Sentry plugin for SolidStart to enable source maps and use
7+
* @sentry/vite-plugin to automatically upload source maps to Sentry.
8+
* @param {SourceMapsOptions} options
9+
*/
10+
export function makeSourceMapsVitePlugin(options: SentrySolidStartPluginOptions): Plugin[] {
11+
const { authToken, debug, org, project, sourceMapsUploadOptions } = options;
12+
return [
13+
{
14+
name: 'sentry-solidstart-source-maps',
15+
apply: 'build',
16+
enforce: 'post',
17+
config(config) {
18+
const sourceMapsPreviouslyNotEnabled = !config.build?.sourcemap;
19+
if (debug && sourceMapsPreviouslyNotEnabled) {
20+
// eslint-disable-next-line no-console
21+
console.log('[Sentry SolidStart Plugin] Enabling source map generation');
22+
if (!sourceMapsUploadOptions?.filesToDeleteAfterUpload) {
23+
// eslint-disable-next-line no-console
24+
console.warn(
25+
`[Sentry SolidStart PLugin] We recommend setting the \`sourceMapsUploadOptions.filesToDeleteAfterUpload\` option to clean up source maps after uploading.
26+
[Sentry SolidStart Plugin] Otherwise, source maps might be deployed to production, depending on your configuration`,
27+
);
28+
}
29+
}
30+
return {
31+
...config,
32+
build: {
33+
...config.build,
34+
sourcemap: true,
35+
},
36+
};
37+
},
38+
},
39+
...sentryVitePlugin({
40+
org: org ?? process.env.SENTRY_ORG,
41+
project: project ?? process.env.SENTRY_PROJECT,
42+
authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN,
43+
telemetry: sourceMapsUploadOptions?.telemetry ?? true,
44+
sourcemaps: {
45+
filesToDeleteAfterUpload: sourceMapsUploadOptions?.filesToDeleteAfterUpload ?? undefined,
46+
...sourceMapsUploadOptions?.unstable_sentryVitePluginOptions?.sourcemaps,
47+
},
48+
_metaOptions: {
49+
telemetry: {
50+
metaFramework: 'solidstart',
51+
},
52+
},
53+
debug: debug ?? false,
54+
...sourceMapsUploadOptions?.unstable_sentryVitePluginOptions,
55+
}),
56+
];
57+
}

0 commit comments

Comments
 (0)