Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions playground/plugins/memleak.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineNuxtPlugin, ref } from '#imports'

ref('memory-leak-warning-shown')

export default defineNuxtPlugin(() => {
// Show memory leak warning
})
Comment on lines +2 to +7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just for my own understanding - if you wouldn't mind explaining - how does this create a memory leak?
I thought that the ref would be created once and obviously kept in memory, but not consume more memory with subsequent requests.

Isn't it just unsafe in terms of cross-request state pollution?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually, when users uses ref outside a vue context, they tends to attach theses references to request events and array and so on.
It's also unsafe in term of XRSP. I admit i had a bit of a tunnel vision here since i'm a bit traumatized by memleaks 🀣

Let's change the wording to XRSP instead. It's way more accurate

10 changes: 9 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -38,6 +38,14 @@ export default defineNuxtModule<ModuleOptions>({
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'),
Expand Down
19 changes: 19 additions & 0 deletions src/runtime/composables/vue.ts
Original file line number Diff line number Diff line change
@@ -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<Fn extends (...args: any[]) => 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
Loading