From cea65eeeb03eea2443c053652e7b13d95fc85fe7 Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Sun, 12 Oct 2025 19:53:46 +0200 Subject: [PATCH 1/3] feat: add outside vue/nuxt context usage detection of ref/reactive --- playground/plugins/memleak.ts | 7 +++++++ src/module.ts | 10 +++++++++- src/runtime/composables/vue.ts | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 playground/plugins/memleak.ts create mode 100644 src/runtime/composables/vue.ts diff --git a/playground/plugins/memleak.ts b/playground/plugins/memleak.ts new file mode 100644 index 0000000..e416065 --- /dev/null +++ b/playground/plugins/memleak.ts @@ -0,0 +1,7 @@ +import { defineNuxtPlugin, ref } from '#imports' + +ref('memory-leak-warning-shown') + +export default defineNuxtPlugin(() => { + // Show memory leak warning +}) diff --git a/src/module.ts b/src/module.ts index 9099e36..46e9dd0 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,4 +1,4 @@ -import { defineNuxtModule, addPlugin, createResolver, addBuildPlugin, addComponent, addServerPlugin } from '@nuxt/kit' +import { defineNuxtModule, addPlugin, createResolver, addBuildPlugin, addComponent, addServerPlugin, addImports } from '@nuxt/kit' import { setupDevToolsUI } from './devtools' import { InjectHydrationPlugin } from './plugins/hydration' @@ -38,6 +38,14 @@ export default defineNuxtModule({ addPlugin(resolver.resolve('./runtime/plugins/third-party-scripts/plugin.client')) addServerPlugin(resolver.resolve('./runtime/plugins/third-party-scripts/nitro.plugin')) + // Imports for server side misusage detection + addImports([ + { from: resolver.resolve('./runtime/composables/vue'), name: 'ref' }, + { from: resolver.resolve('./runtime/composables/vue'), name: 'shallowRef' }, + { from: resolver.resolve('./runtime/composables/vue'), name: 'reactive' }, + { from: resolver.resolve('./runtime/composables/vue'), name: 'shallowReactive' }, + ]) + nuxt.hook('prepare:types', ({ references }) => { references.push({ types: resolver.resolve('./runtime/types.d.ts'), diff --git a/src/runtime/composables/vue.ts b/src/runtime/composables/vue.ts new file mode 100644 index 0000000..1c3badf --- /dev/null +++ b/src/runtime/composables/vue.ts @@ -0,0 +1,19 @@ +import { ref as _ref, reactive as _reactive, shallowReactive as _shallowReactive, shallowRef as _shallowRef, getCurrentInstance } from 'vue' +import { tryUseNuxtApp } from '#app' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function wrapWithWarning any>(fn: Fn, name: string): Fn { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (this: unknown, ...args: any[]) { + const nuxtApp = tryUseNuxtApp() + if (!nuxtApp && !getCurrentInstance()) { + console.warn(`[@nuxt/hints] ${name}() called outside of setup() or without Nuxt app context. This may lead to Server side memory leaks.`) + } + return fn.call(this, ...args) + } as Fn +} + +export const ref = wrapWithWarning(_ref, 'ref') +export const reactive = wrapWithWarning(_reactive, 'reactive') +export const shallowReactive = wrapWithWarning(_shallowReactive, 'shallowReactive') +export const shallowRef = wrapWithWarning(_shallowRef, 'shallowRef') From 20186172cfd10c4699ba923bfb0a5e0dfb1d2ba4 Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Sun, 12 Oct 2025 19:54:24 +0200 Subject: [PATCH 2/3] perf: import.meta flag --- src/runtime/composables/vue.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/composables/vue.ts b/src/runtime/composables/vue.ts index 1c3badf..f040368 100644 --- a/src/runtime/composables/vue.ts +++ b/src/runtime/composables/vue.ts @@ -13,7 +13,7 @@ function wrapWithWarning any>(fn: Fn, name: strin } as Fn } -export const ref = wrapWithWarning(_ref, 'ref') -export const reactive = wrapWithWarning(_reactive, 'reactive') -export const shallowReactive = wrapWithWarning(_shallowReactive, 'shallowReactive') -export const shallowRef = wrapWithWarning(_shallowRef, 'shallowRef') +export const ref = import.meta.server ? wrapWithWarning(_ref, 'ref') : _ref +export const reactive = import.meta.server ? wrapWithWarning(_reactive, 'reactive') : _reactive +export const shallowReactive = import.meta.server ? wrapWithWarning(_shallowReactive, 'shallowReactive') : _shallowReactive +export const shallowRef = import.meta.server ? wrapWithWarning(_shallowRef, 'shallowRef') : _shallowRef From 84392f37a09b23639a98cc78d8a654f633e88f60 Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Sun, 12 Oct 2025 19:55:28 +0200 Subject: [PATCH 3/3] feat: log as error --- src/runtime/composables/vue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/composables/vue.ts b/src/runtime/composables/vue.ts index f040368..25fca6c 100644 --- a/src/runtime/composables/vue.ts +++ b/src/runtime/composables/vue.ts @@ -7,7 +7,7 @@ function wrapWithWarning any>(fn: Fn, name: strin return function (this: unknown, ...args: any[]) { const nuxtApp = tryUseNuxtApp() if (!nuxtApp && !getCurrentInstance()) { - console.warn(`[@nuxt/hints] ${name}() called outside of setup() or without Nuxt app context. This may lead to Server side memory leaks.`) + console.error(new Error(`[@nuxt/hints] ${name}() called outside of setup() or without Nuxt app context. This may lead to Server side memory leaks.`)) } return fn.call(this, ...args) } as Fn