getsentry / getsentry/sentry-javascript

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

Abierto
#23,375 1 comentario 1 reacción 1 asignado Reclamado por @s1gr1d Ver en GitHub
javascript
Lenguaje dominante
TypeScript
Estrellas
8.7k
Forks
1.8k
Merge medio
1 d 17 h
PR fusionados (30 d)
515

Descripción

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

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.