getsentry / getsentry/sentry-javascript

[Nuxt 5] Replace the Vue integration’s reliance on `app.mixin()`

Đang mở
#23,375 1 bình luận 1 reaction 1 người được giao Được @s1gr1d nhận Xem trên GitHub
javascript
Ngôn ngữ chính
TypeScript
Star
8.7k
Fork
1.8k
Merge trung bình
1 ngày 17 giờ
Pull request đã merge (30 ngày)
515

Mô tả

Nuxt 5 disables Vue’s Options API by default
Refernce: [https://github.com/nuxt/nuxt/pull/35791]()

For the time being, it can be enabled in Nuxt with:

```ts
export default defineNuxtConfig({
vue: {
optionsApi: true
}
})
```
---

### What `app.mixin()` captures today

`vueInit()` in `packages/vue/src/integration.ts` registers one mixin when tracing is on. That mixin produces every Vue UI span we emit:

| Span | Op | Operation | Mixin hooks (start / end) | On by default |
|---|---|---|---|---|
| `Application Render` | `ui.render` | any | the root's first hook starts it, a `timeout` debounce (2000 ms) ends it | yes |
| `Vue ` | `ui.mount` | `mount` | `beforeMount` / `mounted` | yes |
| `Vue ` | `ui.mount` | `mount` | `beforeMount` / `mounted` | no (`trackComponents`) |
| `Vue ` | `ui.mount` | `activate` | `activated` / `deactivated` | no (`trackComponents`) |
| `Vue ` | `ui.mount` | `create` | `beforeCreate` / `created` | no (`trackComponents` + `hooks: ['create']`) |
| `Vue ` | `ui.update` | `update` | `beforeUpdate` / `updated` | no (`trackComponents` + `hooks: ['update']`) |
| `Vue ` | `ui.unmount` | `unmount` (Vue 3) | `beforeUnmount` / `unmounted` | no (`trackComponents` + `hooks: ['unmount']`) |
| `Vue ` | `ui.unmount` | `destroy` (Vue 2) | `beforeDestroy` / `destroyed` | no (`trackComponents` + `hooks: ['destroy']`) |

**Not affected**: Pageload and navigation transactions come from `browserTracingIntegration` and the router. Error capture uses `app.config.errorHandler`, which Vue does not gate. Pinia and TanStack Router never touch mixins.

### The alternative: register Composition API hooks in `setup`

Composition API lifecycle hooks are not gated by `__VUE_OPTIONS_API__`. They write to `instance.m`, `instance.u`, and so on, and the renderer invokes those arrays unconditionally. The question is only how to reach every component's `setup`.

Two mechanisms, and the split runs between the root component and everything else.

**Runtime, for the root.** `createApp` stores the root component as `app._component`, a plain writable property, and shallow-copies it first, so mutating `app._component.setup` in `vueInit()` does not touch the user's module. Function root components are not copied, so replace the property instead of mutating it. This covers both default-on spans with no plugin and no new install step.

**Build time, for `trackComponents`.** A Vite, Rollup, or webpack plugin adds a `setup` to each `.vue` module's default export that registers the hooks and then calls the original `setup`. Wrap by adding to the existing options object rather than returning a new component, so `name` and `__file` survive.

| Operation | Composition API hook | Runtime root wrap | Build-time plugin |
|---|---|---|---|
| `mount` (default on) | `onBeforeMount` / `onMounted` | `Application Render` and `Vue `, identical to the mixin | every `.vue` the plugin transforms |
| `activate` (default on) | `onActivated` / `onDeactivated` | never fires, the root is not inside `` | every `.vue` the plugin transforms |
| `update` (opt-in) | `onBeforeUpdate` / `onUpdated` | root re-renders only | every `.vue` the plugin transforms |
| `unmount` (opt-in) | `onBeforeUnmount` / `onUnmounted` | `app.unmount()` only | every `.vue` the plugin transforms |
| `create` (opt-in) | none, `setup` is the create phase | approximate, brackets the original `setup` | approximate, same |

Component names stay correct. `compiler-sfc` emits `__name` in production builds, and `formatComponentName` already reads it.

What it misses: render function and (non SFC) JSX components, runtime compiled templates, CDN builds, and pre-compiled components in `node_modules`, including Nuxt's own `` and ``, unless the plugin also transforms `node_modules`.

Nuxt can get the plugin through the existing `addVitePlugin` call in `packages/nuxt/src/module.ts`. Plain `@sentry/vue` users need a new install step.

### Vue 2 keeps the mixin

We cannot wrap `setup` at runtime in Vue 2, and Vue 2.7 does not change that.

The runtime wrap needs a handle on the root component's options object before Vue instantiates it. In Vue 3, `createApp(App)` gives us exactly that as `app._component`. In Vue 2 the integration receives the `Vue` constructor, the user calls `new Vue({...})` afterwards, and that options object never reaches us. Vue 2.7 backported the Composition API but explicitly did not port `createApp`, and 2.7 is the last Vue 2 minor.

### Warning users on missing mixins

`app.mixin()` is a silent no-op when `__VUE_OPTIONS_API__` is `false`, and Vue only warns in dev.

We can detect if mixins are disabled and we warn the users: https://github.com/getsentry/sentry-javascript/pull/23568

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.