Skip to content
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

feat(solidstart): Add sentrySolidStartVite plugin to simplify source maps upload #13493

Merged
merged 15 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 14 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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ jobs:
working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
timeout-minutes: 5
run: pnpm test:assert

- name: Upload Playwright Traces
uses: actions/upload-artifact@v4
if: failure()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { sentrySolidStartVite } from '@sentry/solidstart';
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({});
export default defineConfig({
vite: {
plugins: [sentrySolidStartVite({})],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l/just personal opinion: It feels a bit awkward that users need to pass an options object but can leave it empty. Should we make the options object optional? I might be missing something but technically, I could pass all required options for source maps upload as env variables, right? (project slug and auth token)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, good point! I'll make this optional.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lms24 updated

},
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
"version": "0.0.0",
"scripts": {
"clean": "pnpx rimraf node_modules pnpm-lock.yaml .vinxi .output",
"clean:build": "pnpx rimraf .vinxi .output",
"dev": "NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi dev",
"build": "vinxi build",
"//": [
"We are using `vinxi dev` to start the server because `vinxi start` is experimental and ",
"doesn't correctly resolve modules for @sentry/solidstart/solidrouter.",
"This is currently not an issue outside of our repo. See: https://github.com/nksaraf/vinxi/issues/177"
"This is currently not an issue outside of our repo. See: https://github.com/nksaraf/vinxi/issues/177",
"We run the build command to ensure building succeeds. However, keeping",
"build output around slows down the vite dev server when using `@sentry/vite-plugin` so we clear it out",
"before actually running the tests.",
"Cleaning the build output should be removed once we can use `vinxi start`."
],
"preview": "HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi dev",
"preview": "pnpm clean:build && HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi dev",
"start": "HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi start",
"test:prod": "TEST_ENV=production playwright test",
"test:build": "pnpm install && npx playwright install && pnpm build",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,75 +1,15 @@
import * as Sentry from '@sentry/solidstart';
import type { ParentProps } from 'solid-js';
import { ErrorBoundary, createSignal, onMount } from 'solid-js';

const SentryErrorBoundary = Sentry.withSentryErrorBoundary(ErrorBoundary);

const [count, setCount] = createSignal(1);
const [caughtError, setCaughtError] = createSignal(false);

export default function ClientErrorPage() {
return (
<SampleErrorBoundary>
{caughtError() && (
<Throw error={`Error ${count()} thrown from Sentry ErrorBoundary in Solid Start E2E test app`} />
)}
<section class="bg-gray-100 text-gray-700 p-8">
<div class="flex flex-col items-start space-x-2">
<button
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
id="caughtErrorBtn"
onClick={() => setCaughtError(true)}
>
Throw caught error
</button>
</div>
<div class="flex flex-col items-start space-x-2">
<button
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
id="errorBtn"
onClick={() => {
throw new Error('Error thrown from Solid Start E2E test app');
}}
>
Throw uncaught error
</button>
</div>
</section>
</SampleErrorBoundary>
);
}

function Throw(props: { error: string }) {
onMount(() => {
throw new Error(props.error);
});
return null;
}

function SampleErrorBoundary(props: ParentProps) {
return (
<SentryErrorBoundary
fallback={(error, reset) => (
<section class="bg-gray-100 text-gray-700 p-8">
<h1 class="text-2xl font-bold">Error Boundary Fallback</h1>
<div class="flex items-center space-x-2 mb-4">
<code>{error.message}</code>
</div>
<button
id="errorBoundaryResetBtn"
class="border rounded-lg px-2 border-gray-900"
onClick={() => {
setCount(count() + 1);
setCaughtError(false);
reset();
}}
>
Reset
</button>
</section>
)}
>
{props.children}
</SentryErrorBoundary>
<div class="flex flex-col items-start space-x-2">
<button
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
id="errorBtn"
onClick={() => {
throw new Error('Uncaught error thrown from Solid Start E2E test app');
}}
>
Throw uncaught error
</button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as Sentry from '@sentry/solidstart';
import type { ParentProps } from 'solid-js';
import { ErrorBoundary, createSignal, onMount } from 'solid-js';

const SentryErrorBoundary = Sentry.withSentryErrorBoundary(ErrorBoundary);

const [count, setCount] = createSignal(1);
const [caughtError, setCaughtError] = createSignal(false);

export default function ErrorBoundaryTestPage() {
return (
<SampleErrorBoundary>
{caughtError() && (
<Throw error={`Error ${count()} thrown from Sentry ErrorBoundary in Solid Start E2E test app`} />
)}
<section class="bg-gray-100 text-gray-700 p-8">
<div class="flex flex-col items-start space-x-2">
<button
class="border rounded-lg px-2 mb-2 border-red-500 text-red-500 cursor-pointer"
id="caughtErrorBtn"
onClick={() => setCaughtError(true)}
>
Throw caught error
</button>
</div>
</section>
</SampleErrorBoundary>
);
}

function Throw(props: { error: string }) {
onMount(() => {
throw new Error(props.error);
});
return null;
}

function SampleErrorBoundary(props: ParentProps) {
return (
<SentryErrorBoundary
fallback={(error, reset) => (
<section class="bg-gray-100 text-gray-700 p-8">
<h1 class="text-2xl font-bold">Error Boundary Fallback</h1>
<div class="flex items-center space-x-2 mb-4">
<code>{error.message}</code>
</div>
<button
id="errorBoundaryResetBtn"
class="border rounded-lg px-2 border-gray-900"
onClick={() => {
setCount(count() + 1);
setCaughtError(false);
reset();
}}
>
Reset
</button>
</section>
)}
>
{props.children}
</SentryErrorBoundary>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default function Home() {
<li>
<A href="/server-error">Server error</A>
</li>
<li>
<A href="/error-boundary">Error Boundary</A>
</li>
<li>
<A id="navLink" href="/users/5">
User 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test('captures an exception', async ({ page }) => {
);
});

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

Expand All @@ -27,7 +27,7 @@ test('captures an exception', async ({ page }) => {
},
],
},
transaction: '/client-error',
transaction: '/error-boundary',
});
});

Expand All @@ -40,7 +40,7 @@ test('captures a second exception after resetting the boundary', async ({ page }
);
});

await page.goto('/client-error');
await page.goto('/error-boundary');
await page.locator('#caughtErrorBtn').click();
const firstErrorEvent = await firstErrorEventPromise;

Expand All @@ -57,7 +57,7 @@ test('captures a second exception after resetting the boundary', async ({ page }
},
],
},
transaction: '/client-error',
transaction: '/error-boundary',
});

const secondErrorEventPromise = waitForError('solidstart', errorEvent => {
Expand Down Expand Up @@ -85,6 +85,6 @@ test('captures a second exception after resetting the boundary', async ({ page }
},
],
},
transaction: '/client-error',
transaction: '/error-boundary',
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { waitForError } from '@sentry-internal/test-utils';
test.describe('client-side errors', () => {
test('captures error thrown on click', async ({ page }) => {
const errorPromise = waitForError('solidstart', async errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Solid Start E2E test app';
return errorEvent?.exception?.values?.[0]?.value === 'Uncaught error thrown from Solid Start E2E test app';
});

await page.goto(`/client-error`);
Expand All @@ -16,9 +16,8 @@ test.describe('client-side errors', () => {
values: [
{
type: 'Error',
value: 'Error thrown from Solid Start E2E test app',
value: 'Uncaught error thrown from Solid Start E2E test app',
mechanism: {
type: 'instrument',
handled: false,
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/solidstart/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
files: ['src/vite/**', 'src/server/**'],
rules: {
'@sentry-internal/sdk/no-optional-chaining': 'off',
'@sentry-internal/sdk/no-nullish-coalescing': 'off',
},
},
],
Expand Down
46 changes: 11 additions & 35 deletions packages/solidstart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,58 +157,34 @@ render(
);
```

# Sourcemaps and Releases
## Uploading Source Maps

To generate and upload source maps of your Solid Start app use our Vite bundler plugin.

1. Install the Sentry Vite plugin

```bash
# Using npm
npm install @sentry/vite-plugin --save-dev

# Using yarn
yarn add @sentry/vite-plugin --dev
```

2. Configure the vite plugin

To upload source maps you have to configure an auth token. Auth tokens can be passed to the plugin explicitly with the
`authToken` option, with a `SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the
working directory when building your project. We recommend you add the auth token to your CI/CD environment as an
environment variable.
To upload source maps, add the `sentrySolidStartVite` plugin from `@sentry/solidstart` to your `app.config.ts` and
configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a
`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when
building your project. We recommend you add the auth token to your CI/CD environment as an environment variable.

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

```bash
// .env.sentry-build-plugin
SENTRY_AUTH_TOKEN=<your auth token>
SENTRY_ORG=<your org>
SENTRY_PROJECT=<your project name>
```

3. Finally, add the plugin to your `app.config.ts` file.

```javascript
```typescript
// app.config.ts
import { defineConfig } from '@solidjs/start/config';
import { sentryVitePlugin } from '@sentry/vite-plugin';
import { sentrySolidStartVite } from '@sentry/solidstart';

export default defineConfig({
// rest of your config
// ...

vite: {
build: {
sourcemap: true,
},
plugins: [
sentryVitePlugin({
sentrySolidStartVite({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
debug: true,
}),
],
},
// ...
});
```
1 change: 1 addition & 0 deletions packages/solidstart/src/index.server.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './server';
export * from './vite';
1 change: 1 addition & 0 deletions packages/solidstart/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// exports in this file - which we do below.
export * from './client';
export * from './server';
export * from './vite';

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

Expand Down
1 change: 1 addition & 0 deletions packages/solidstart/src/vite/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sentrySolidStartVite';
18 changes: 18 additions & 0 deletions packages/solidstart/src/vite/sentrySolidStartVite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Plugin } from 'vite';
import { makeSourceMapsVitePlugin } from './sourceMaps';
import type { SentrySolidStartPluginOptions } from './types';

/**
* Various Sentry vite plugins to be used for SolidStart.
*/
export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions): Plugin[] => {
const sentryPlugins: Plugin[] = [];

if (process.env.NODE_ENV !== 'development') {
if (options.sourceMapsUploadOptions?.enabled ?? true) {
sentryPlugins.push(...makeSourceMapsVitePlugin(options));
}
}

return sentryPlugins;
};
Loading
Loading