Skip to content

Commit 4086800

Browse files
pi0atinux
andauthored
fix(vue-app): fix asyncData memory leak on client-side (#4966)
Co-authored-by: Sébastien Chopin <seb@chopin.io>
1 parent 17cc12f commit 4086800

File tree

3 files changed

+49
-6
lines changed

3 files changed

+49
-6
lines changed

packages/vue-app/template/utils.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import Vue from 'vue'
22

3-
const noopData = () => ({})
4-
53
// window.{{globals.loadedCallback}} hook
64
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
75
if (process.client) {
@@ -24,19 +22,27 @@ export function interopDefault(promise) {
2422
}
2523

2624
export function applyAsyncData(Component, asyncData) {
27-
const ComponentData = Component.options.data || noopData
28-
// Prevent calling this method for each request on SSR context
29-
if (!asyncData && Component.options.hasAsyncData) {
25+
if (
26+
// For SSR, we once all this function without second param to just apply asyncData
27+
// Prevent doing this for each SSR request
28+
!asyncData && Component.options.__hasNuxtData
29+
) {
3030
return
3131
}
32-
Component.options.hasAsyncData = true
32+
33+
const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} }
34+
Component.options._originDataFn = ComponentData
35+
3336
Component.options.data = function () {
3437
const data = ComponentData.call(this)
3538
if (this.$ssrContext) {
3639
asyncData = this.$ssrContext.asyncData[Component.cid]
3740
}
3841
return { ...data, ...asyncData }
3942
}
43+
44+
Component.options.__hasNuxtData = true
45+
4046
if (Component._Ctor && Component._Ctor.options) {
4147
Component._Ctor.options.data = Component.options.data
4248
}

test/fixtures/spa/pages/async.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<template>
2+
<pre>
3+
{{ debug }}
4+
</pre>
5+
</template>
6+
7+
<script>
8+
export default {
9+
asyncData() {
10+
return {
11+
[Math.random()]: true
12+
}
13+
},
14+
computed: {
15+
debug() {
16+
return JSON.stringify(this.$data, null, 2)
17+
}
18+
}
19+
}
20+
</script>

test/unit/spa.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ describe('spa', () => {
8282
expect(consola.log).toHaveBeenCalledWith('mounted')
8383
consola.log.mockClear()
8484
})
85+
86+
test('/async no asyncData leak', async () => {
87+
const window = await nuxt.server.renderAndGetWindow(url('/async'))
88+
89+
const navigate = url => new Promise((resolve, reject) => {
90+
window.$nuxt.$router.push(url, resolve, reject)
91+
})
92+
93+
for (let i = 0; i < 3; i++) {
94+
await navigate('/')
95+
await navigate('/async')
96+
}
97+
98+
const { $data } = window.$nuxt.$route.matched[0].instances.default
99+
expect(Object.keys($data).length).toBe(1)
100+
})
101+
85102
// Close server and ask nuxt to stop listening to file changes
86103
afterAll(async () => {
87104
await nuxt.close()

0 commit comments

Comments
 (0)