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..25fca6c --- /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.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 +} + +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