Skip to content

Commit 121ae07

Browse files
authored
feat(nuxt): Add option autoInjectServerSentry (no default import()) (#14553)
As injecting the dynamic `import()` came with a bunch of challenges related to how the build output looks like, this default is changed to make users use the `--import` CLI flag. This PR basically reverts this: #13958
1 parent 3778482 commit 121ae07

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1303
-62
lines changed

Diff for: CHANGELOG.md

+21
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@
1010

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

13+
### Important Changes
14+
15+
- **feat(nuxt): Add option autoInjectServerSentry (no default import()) ([#14553](https://github.com/getsentry/sentry-javascript/pull/14553))**
16+
17+
Using the dynamic `import()` as the default behavior for initializing the SDK on the server-side did not work for every project.
18+
The default behavior of the SDK has been changed, and you now need to **use the `--import` flag to initialize Sentry on the server-side** to leverage full functionality.
19+
20+
Example with `--import`:
21+
22+
```bash
23+
node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs
24+
```
25+
26+
In case you are not able to use the `--import` flag, you can enable auto-injecting Sentry in the `nuxt.config.ts` (comes with limitations):
27+
28+
```json
29+
sentry: {
30+
autoInjectServerSentry: 'top-level-import', // or 'experimental_dynamic-import'
31+
},
32+
```
33+
1334
Work in this release was contributed by @lsmurray. Thank you for your contribution!
1435

1536
## 8.42.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Nuxt dev/build outputs
2+
.output
3+
.data
4+
.nuxt
5+
.nitro
6+
.cache
7+
dist
8+
9+
# Node dependencies
10+
node_modules
11+
12+
# Logs
13+
logs
14+
*.log
15+
16+
# Misc
17+
.DS_Store
18+
.fleet
19+
.idea
20+
21+
# Local env files
22+
.env
23+
.env.*
24+
!.env.example
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<NuxtLayout>
3+
<header>
4+
<nav>
5+
<ul>
6+
<li><NuxtLink to="/fetch-server-error">Fetch Server Error</NuxtLink></li>
7+
<li><NuxtLink to="/test-param/1234">Fetch Param</NuxtLink></li>
8+
<li><NuxtLink to="/client-error">Client Error</NuxtLink></li>
9+
</ul>
10+
</nav>
11+
</header>
12+
<NuxtPage />
13+
</NuxtLayout>
14+
</template>
15+
16+
<script setup>
17+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup>
2+
import { defineProps } from 'vue';
3+
4+
const props = defineProps({
5+
errorText: {
6+
type: String,
7+
required: true
8+
},
9+
id: {
10+
type: String,
11+
required: true
12+
}
13+
})
14+
15+
const triggerError = () => {
16+
throw new Error(props.errorText);
17+
};
18+
</script>
19+
20+
<template>
21+
<button :id="props.id" @click="triggerError">Trigger Error</button>
22+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This script copies the `import-in-the-middle` content of the E2E test project root `node_modules` to the build output `node_modules`
2+
# For some reason, some files are missing in the output (like `hook.mjs`) and this is not reproducible in external, standalone projects.
3+
#
4+
# Things we tried (that did not fix the problem):
5+
# - Adding a resolution for `@vercel/nft` v0.27.0 (this worked in the standalone project)
6+
# - Also adding `@vercel/nft` v0.27.0 to pnpm `peerDependencyRules`
7+
cp -r node_modules/.pnpm/import-in-the-middle@1.*/node_modules/import-in-the-middle .output/server/node_modules/import-in-the-middle
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// https://nuxt.com/docs/api/configuration/nuxt-config
2+
export default defineNuxtConfig({
3+
modules: ['@sentry/nuxt/module'],
4+
imports: {
5+
autoImport: false,
6+
},
7+
runtimeConfig: {
8+
public: {
9+
sentry: {
10+
dsn: 'https://[email protected]/1337',
11+
},
12+
},
13+
},
14+
nitro: {
15+
rollupConfig: {
16+
// @sentry/... is set external to prevent bundling all of Sentry into the `runtime.mjs` file in the build output
17+
external: [/@sentry\/.*/],
18+
},
19+
},
20+
sentry: {
21+
autoInjectServerSentry: 'experimental_dynamic-import',
22+
},
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "nuxt-3-dynamic-import",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"build": "nuxt build && bash ./copyIITM.bash",
7+
"dev": "nuxt dev",
8+
"generate": "nuxt generate",
9+
"preview": "nuxt preview",
10+
"start": "node .output/server/index.mjs",
11+
"clean": "npx nuxi cleanup",
12+
"test": "playwright test",
13+
"test:build": "pnpm install && npx playwright install && pnpm build",
14+
"test:assert": "pnpm test"
15+
},
16+
"dependencies": {
17+
"@sentry/nuxt": "latest || *",
18+
"nuxt": "^3.14.0"
19+
},
20+
"devDependencies": {
21+
"@nuxt/test-utils": "^3.14.1",
22+
"@playwright/test": "^1.44.1",
23+
"@sentry-internal/test-utils": "link:../../../test-utils"
24+
},
25+
"overrides": {
26+
"nitropack": "~2.9.7",
27+
"ofetch": "^1.4.0"
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script setup>
2+
import ErrorButton from '../components/ErrorButton.vue';
3+
</script>
4+
5+
<template>
6+
<ErrorButton id="errorBtn" error-text="Error thrown from Nuxt-3 E2E test app"/>
7+
<ErrorButton id="errorBtn2" error-text="Another Error thrown from Nuxt-3 E2E test app"/>
8+
</template>
9+
10+
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<template>
2+
<div>
3+
<button @click="fetchData">Fetch Server Data</button>
4+
</div>
5+
</template>
6+
7+
<script setup lang="ts">
8+
import { useFetch} from '#imports'
9+
10+
const fetchData = async () => {
11+
await useFetch('/api/server-error');
12+
}
13+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<h1>Hello!</h1>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import { useRoute, useFetch } from '#imports'
3+
4+
const route = useRoute();
5+
const param = route.params.param;
6+
7+
const fetchError = async () => {
8+
await useFetch(`/api/param-error/${param}`);
9+
}
10+
11+
const fetchData = async () => {
12+
await useFetch(`/api/test-param/${param}`);
13+
};
14+
</script>
15+
16+
<template>
17+
<p>Param: {{ $route.params.param }}</p>
18+
19+
<ErrorButton id="errorBtn" errorText="Error thrown from Param Route Button" />
20+
<button @click="fetchData">Fetch Server Data</button>
21+
<button @click="fetchError">Fetch Server Error</button>
22+
</template>
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { fileURLToPath } from 'node:url';
2+
import type { ConfigOptions } from '@nuxt/test-utils/playwright';
3+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
4+
5+
const nuxtConfigOptions: ConfigOptions = {
6+
nuxt: {
7+
rootDir: fileURLToPath(new URL('.', import.meta.url)),
8+
},
9+
};
10+
11+
/* Make sure to import from '@nuxt/test-utils/playwright' in the tests
12+
* Like this: import { expect, test } from '@nuxt/test-utils/playwright' */
13+
14+
const config = getPlaywrightConfig({
15+
startCommand: `pnpm start`,
16+
use: { ...nuxtConfigOptions },
17+
});
18+
19+
export default config;
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/nuxt';
2+
import { useRuntimeConfig } from '#imports';
3+
4+
Sentry.init({
5+
environment: 'qa', // dynamic sampling bias to keep transactions
6+
dsn: useRuntimeConfig().public.sentry.dsn,
7+
tunnel: `http://localhost:3031/`, // proxy server
8+
tracesSampleRate: 1.0,
9+
integrations: [
10+
Sentry.vueIntegration({
11+
tracingOptions: {
12+
trackComponents: true,
13+
},
14+
}),
15+
],
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/nuxt';
2+
3+
Sentry.init({
4+
dsn: 'https://[email protected]/1337',
5+
environment: 'qa', // dynamic sampling bias to keep transactions
6+
tracesSampleRate: 1.0, // Capture 100% of the transactions
7+
tunnel: 'http://localhost:3031/', // proxy server
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { defineEventHandler } from '#imports';
2+
3+
export default defineEventHandler(_e => {
4+
throw new Error('Nuxt 3 Param Server error');
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { defineEventHandler } from '#imports';
2+
3+
export default defineEventHandler(event => {
4+
throw new Error('Nuxt 3 Server error');
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineEventHandler, getRouterParam } from '#imports';
2+
3+
export default defineEventHandler(event => {
4+
const param = getRouterParam(event, 'param');
5+
6+
return `Param: ${param}!`;
7+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../.nuxt/tsconfig.server.json"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { startEventProxyServer } from '@sentry-internal/test-utils';
2+
3+
startEventProxyServer({
4+
port: 3031,
5+
proxyServerName: 'nuxt-3-dynamic-import',
6+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { expect, test } from '@nuxt/test-utils/playwright';
2+
import { waitForError } from '@sentry-internal/test-utils';
3+
4+
test.describe('client-side errors', async () => {
5+
test('captures error thrown on click', async ({ page }) => {
6+
const errorPromise = waitForError('nuxt-3-dynamic-import', async errorEvent => {
7+
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Nuxt-3 E2E test app';
8+
});
9+
10+
await page.goto(`/client-error`);
11+
await page.locator('#errorBtn').click();
12+
13+
const error = await errorPromise;
14+
15+
expect(error.transaction).toEqual('/client-error');
16+
expect(error).toMatchObject({
17+
exception: {
18+
values: [
19+
{
20+
type: 'Error',
21+
value: 'Error thrown from Nuxt-3 E2E test app',
22+
mechanism: {
23+
handled: false,
24+
},
25+
},
26+
],
27+
},
28+
});
29+
});
30+
31+
test('shows parametrized route on button error', async ({ page }) => {
32+
const errorPromise = waitForError('nuxt-3-dynamic-import', async errorEvent => {
33+
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Param Route Button';
34+
});
35+
36+
await page.goto(`/test-param/1234`);
37+
await page.locator('#errorBtn').click();
38+
39+
const error = await errorPromise;
40+
41+
expect(error.sdk.name).toEqual('sentry.javascript.nuxt');
42+
expect(error.transaction).toEqual('/test-param/:param()');
43+
expect(error.request.url).toMatch(/\/test-param\/1234/);
44+
expect(error).toMatchObject({
45+
exception: {
46+
values: [
47+
{
48+
type: 'Error',
49+
value: 'Error thrown from Param Route Button',
50+
mechanism: {
51+
handled: false,
52+
},
53+
},
54+
],
55+
},
56+
});
57+
});
58+
59+
test('page is still interactive after client error', async ({ page }) => {
60+
const error1Promise = waitForError('nuxt-3-dynamic-import', async errorEvent => {
61+
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Nuxt-3 E2E test app';
62+
});
63+
64+
await page.goto(`/client-error`);
65+
await page.locator('#errorBtn').click();
66+
67+
const error1 = await error1Promise;
68+
69+
const error2Promise = waitForError('nuxt-3-dynamic-import', async errorEvent => {
70+
return errorEvent?.exception?.values?.[0]?.value === 'Another Error thrown from Nuxt-3 E2E test app';
71+
});
72+
73+
await page.locator('#errorBtn2').click();
74+
75+
const error2 = await error2Promise;
76+
77+
expect(error1).toMatchObject({
78+
exception: {
79+
values: [
80+
{
81+
type: 'Error',
82+
value: 'Error thrown from Nuxt-3 E2E test app',
83+
mechanism: {
84+
handled: false,
85+
},
86+
},
87+
],
88+
},
89+
});
90+
91+
expect(error2).toMatchObject({
92+
exception: {
93+
values: [
94+
{
95+
type: 'Error',
96+
value: 'Another Error thrown from Nuxt-3 E2E test app',
97+
mechanism: {
98+
handled: false,
99+
},
100+
},
101+
],
102+
},
103+
});
104+
});
105+
});

0 commit comments

Comments
 (0)