mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 23:52:06 +00:00
feat(vue-app): support custom fetchKey
for full static generation (#8466)
[release]
This commit is contained in:
parent
5a5161aa52
commit
92018e586b
1
packages/types/app/index.d.ts
vendored
1
packages/types/app/index.d.ts
vendored
@ -61,6 +61,7 @@ export interface Context {
|
|||||||
redirected: boolean
|
redirected: boolean
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
beforeRenderFns: Array<() => any>
|
beforeRenderFns: Array<() => any>
|
||||||
|
fetchCounters: Record<string, number>
|
||||||
nuxt: {
|
nuxt: {
|
||||||
layout: string
|
layout: string
|
||||||
data: Array<Record<string, any>>
|
data: Array<Record<string, any>>
|
||||||
|
1
packages/types/app/vue.d.ts
vendored
1
packages/types/app/vue.d.ts
vendored
@ -12,6 +12,7 @@ declare module 'vue/types/options' {
|
|||||||
interface ComponentOptions<V extends Vue> {
|
interface ComponentOptions<V extends Vue> {
|
||||||
asyncData?(ctx: Context): Promise<object | void> | object | void
|
asyncData?(ctx: Context): Promise<object | void> | object | void
|
||||||
fetch?(ctx: Context): Promise<void> | void
|
fetch?(ctx: Context): Promise<void> | void
|
||||||
|
fetchKey?: string | ((getKey: (id: string) => number) => string)
|
||||||
fetchDelay?: number
|
fetchDelay?: number
|
||||||
fetchOnServer?: boolean | (() => boolean)
|
fetchOnServer?: boolean | (() => boolean)
|
||||||
head?: MetaInfo | (() => MetaInfo)
|
head?: MetaInfo | (() => MetaInfo)
|
||||||
|
@ -317,7 +317,7 @@ export default {
|
|||||||
<% } %>
|
<% } %>
|
||||||
setPagePayload(payload) {
|
setPagePayload(payload) {
|
||||||
this._pagePayload = payload
|
this._pagePayload = payload
|
||||||
this._payloadFetchIndex = 0
|
this._fetchCounters = {}
|
||||||
},
|
},
|
||||||
async fetchPayload(route) {
|
async fetchPayload(route) {
|
||||||
<% if (nuxtOptions.generate.manifest) { %>
|
<% if (nuxtOptions.generate.manifest) { %>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
|
import { hasFetch, normalizeError, addLifecycleHook, createGetCounter } from '../utils'
|
||||||
|
|
||||||
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.fetchKey
|
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.fetchKey
|
||||||
const nuxtState = window.<%= globals.context %>
|
const nuxtState = window.<%= globals.context %>
|
||||||
@ -38,7 +38,7 @@ function created() {
|
|||||||
|
|
||||||
// Hydrate component
|
// Hydrate component
|
||||||
this._hydrated = true
|
this._hydrated = true
|
||||||
this._fetchKey = +this.$vnode.elm.dataset.fetchKey
|
this._fetchKey = this.$vnode.elm.dataset.fetchKey
|
||||||
const data = nuxtState.fetch[this._fetchKey]
|
const data = nuxtState.fetch[this._fetchKey]
|
||||||
|
|
||||||
// If fetch error
|
// If fetch error
|
||||||
@ -64,7 +64,17 @@ function createdFullStatic() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this._hydrated = true
|
this._hydrated = true
|
||||||
this._fetchKey = this.<%= globals.nuxt %>._payloadFetchIndex++
|
|
||||||
|
const defaultKey = this.$options._scopeId || this.$options.name || ''
|
||||||
|
const getCounter = createGetCounter(this.<%= globals.nuxt %>._fetchCounters, defaultKey)
|
||||||
|
|
||||||
|
if (typeof this.$options.fetchKey === 'function') {
|
||||||
|
this._fetchKey = this.$options.fetchKey.call(this, getCounter)
|
||||||
|
} else {
|
||||||
|
const key = 'string' === typeof this.$options.fetchKey ? this.$options.fetchKey : defaultKey
|
||||||
|
this._fetchKey = key + ':' + getCounter(key)
|
||||||
|
}
|
||||||
|
|
||||||
const data = this.<%= globals.nuxt %>._pagePayload.fetch[this._fetchKey]
|
const data = this.<%= globals.nuxt %>._pagePayload.fetch[this._fetchKey]
|
||||||
|
|
||||||
// If fetch error
|
// If fetch error
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { hasFetch, normalizeError, addLifecycleHook, purifyData } from '../utils'
|
import { hasFetch, normalizeError, addLifecycleHook, purifyData, createGetCounter } from '../utils'
|
||||||
|
|
||||||
async function serverPrefetch() {
|
async function serverPrefetch() {
|
||||||
if (!this._fetchOnServer) {
|
if (!this._fetchOnServer) {
|
||||||
@ -19,14 +19,20 @@ async function serverPrefetch() {
|
|||||||
|
|
||||||
|
|
||||||
// Define an ssrKey for hydration
|
// Define an ssrKey for hydration
|
||||||
this._fetchKey = this.$ssrContext.nuxt.fetch.length
|
this._fetchKey = this._fetchKey || this.$ssrContext.fetchCounters['']++
|
||||||
|
|
||||||
// Add data-fetch-key on parent element of Component
|
// Add data-fetch-key on parent element of Component
|
||||||
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {}
|
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {}
|
||||||
attrs['data-fetch-key'] = this._fetchKey
|
attrs['data-fetch-key'] = this._fetchKey
|
||||||
|
|
||||||
// Add to ssrContext for window.__NUXT__.fetch
|
// Add to ssrContext for window.__NUXT__.fetch
|
||||||
this.$ssrContext.nuxt.fetch.push(this.$fetchState.error ? { _error: this.$fetchState.error } : purifyData(this._data))
|
<% if (debug) { %>
|
||||||
|
if (this.$ssrContext.nuxt.fetch[this._fetchKey] !== undefined) {
|
||||||
|
console.warn(`Duplicate fetch key detected (${this._fetchKey}). This may lead to unexpected results.`)
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
|
this.$ssrContext.nuxt.fetch[this._fetchKey] =
|
||||||
|
this.$fetchState.error ? { _error: this.$fetchState.error } : purifyData(this._data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -41,6 +47,16 @@ export default {
|
|||||||
this._fetchOnServer = this.$options.fetchOnServer !== false
|
this._fetchOnServer = this.$options.fetchOnServer !== false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultKey = this.$options._scopeId || this.$options.name || ''
|
||||||
|
const getCounter = createGetCounter(this.$ssrContext.fetchCounters, defaultKey)
|
||||||
|
|
||||||
|
if (typeof this.$options.fetchKey === 'function') {
|
||||||
|
this._fetchKey = this.$options.fetchKey.call(this, getCounter)
|
||||||
|
} else {
|
||||||
|
const key = 'string' === typeof this.$options.fetchKey ? this.$options.fetchKey : defaultKey
|
||||||
|
this._fetchKey = key + getCounter(key)
|
||||||
|
}
|
||||||
|
|
||||||
// Added for remove vue undefined warning while ssr
|
// Added for remove vue undefined warning while ssr
|
||||||
this.$fetch = () => {} // issue #8043
|
this.$fetch = () => {} // issue #8043
|
||||||
Vue.util.defineReactive(this, '$fetchState', {
|
Vue.util.defineReactive(this, '$fetchState', {
|
||||||
|
@ -73,7 +73,11 @@ export default async (ssrContext) => {
|
|||||||
// Used for beforeNuxtRender({ Components, nuxtState })
|
// Used for beforeNuxtRender({ Components, nuxtState })
|
||||||
ssrContext.beforeRenderFns = []
|
ssrContext.beforeRenderFns = []
|
||||||
// Nuxt object (window.{{globals.context}}, defaults to window.__NUXT__)
|
// Nuxt object (window.{{globals.context}}, defaults to window.__NUXT__)
|
||||||
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: [], <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
|
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: {}, <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
|
||||||
|
<% if (features.fetch) { %>
|
||||||
|
ssrContext.fetchCounters = {}
|
||||||
|
<% } %>
|
||||||
|
|
||||||
// Remove query from url is static target
|
// Remove query from url is static target
|
||||||
if (process.static && ssrContext.url) {
|
if (process.static && ssrContext.url) {
|
||||||
ssrContext.url = ssrContext.url.split('?')[0]
|
ssrContext.url = ssrContext.url.split('?')[0]
|
||||||
|
@ -10,6 +10,15 @@ if (process.client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createGetCounter (counterObject, defaultKey = '') {
|
||||||
|
return function getCounter (id = defaultKey) {
|
||||||
|
if (counterObject[id] === undefined) {
|
||||||
|
counterObject[id] = 0
|
||||||
|
}
|
||||||
|
return counterObject[id]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function empty () {}
|
export function empty () {}
|
||||||
|
|
||||||
export function globalHandleError (error) {
|
export function globalHandleError (error) {
|
||||||
|
@ -24,6 +24,9 @@ describe('basic generate', () => {
|
|||||||
generate: {
|
generate: {
|
||||||
static: false,
|
static: false,
|
||||||
dir: '.nuxt-generate'
|
dir: '.nuxt-generate'
|
||||||
|
},
|
||||||
|
publicRuntimeConfig: {
|
||||||
|
generated: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const nuxt = new Nuxt(config)
|
const nuxt = new Nuxt(config)
|
||||||
@ -142,6 +145,13 @@ describe('basic generate', () => {
|
|||||||
expect(html).toContain('<p>Nuxt</p>')
|
expect(html).toContain('<p>Nuxt</p>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('/fetch', async () => {
|
||||||
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/fetch'))
|
||||||
|
const html = window.document.body.innerHTML
|
||||||
|
expect(html).toContain('<code>true</code>')
|
||||||
|
expect(window.__NUXT__.fetch.custom100.fetched).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test('/тест雨 (test non ascii route)', async () => {
|
test('/тест雨 (test non ascii route)', async () => {
|
||||||
const window = await generator.nuxt.server.renderAndGetWindow(url('/тест雨'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/тест雨'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
|
@ -76,6 +76,59 @@ describe('basic browser', () => {
|
|||||||
expect(await page.$text('pre')).toContain('kevinmarrec')
|
expect(await page.$text('pre')).toContain('kevinmarrec')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('/nested', async () => {
|
||||||
|
await page.nuxt.navigate('/nested')
|
||||||
|
const fetchKeys = await page.evaluate(() => Object.keys(window.__NUXT__.fetch))
|
||||||
|
expect(fetchKeys).toEqual([
|
||||||
|
'0',
|
||||||
|
'DefaultLayout0'
|
||||||
|
])
|
||||||
|
expect(await page.$text('div')).toContain('foo-bar-baz')
|
||||||
|
expect(await page.$text('div')).toContain('fizz-buzz')
|
||||||
|
expect(await page.$text('button')).toContain('fetch')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/nested/child', async () => {
|
||||||
|
await page.nuxt.navigate('/nested/child')
|
||||||
|
await page.waitForSelector('pre')
|
||||||
|
expect(await page.$text('pre')).toContain('Atinux')
|
||||||
|
const fetchKeys = await page.evaluate(() => Object.keys(window.__NUXT__.fetch))
|
||||||
|
expect(fetchKeys).toEqual([
|
||||||
|
'0',
|
||||||
|
'DefaultLayout0'
|
||||||
|
])
|
||||||
|
expect(await page.$text('div')).toContain('foo-bar-baz')
|
||||||
|
expect(await page.$text('div')).toContain('fizz-buzz')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ssr: /nested', async () => {
|
||||||
|
page = await browser.page(url('/nested'))
|
||||||
|
expect(await page.$text('div')).toContain('foo-bar-baz')
|
||||||
|
expect(await page.$text('div')).toContain('fizz-buzz')
|
||||||
|
const fetchKeys = await page.evaluate(() => Object.keys(window.__NUXT__.fetch))
|
||||||
|
expect(fetchKeys).toEqual([
|
||||||
|
'0',
|
||||||
|
'DefaultLayout0',
|
||||||
|
'ie0'
|
||||||
|
])
|
||||||
|
expect(await page.$text('button')).toContain('has fetch')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ssr: /nested/child', async () => {
|
||||||
|
page = await browser.page(url('/nested/child'))
|
||||||
|
expect(await page.$text('pre')).toContain('Atinux')
|
||||||
|
const fetchKeys = await page.evaluate(() => Object.keys(window.__NUXT__.fetch))
|
||||||
|
expect(fetchKeys).toEqual([
|
||||||
|
'0',
|
||||||
|
'DefaultLayout0',
|
||||||
|
'team0'
|
||||||
|
])
|
||||||
|
const team = await page.evaluate(() => window.__NUXT__.fetch.team0.team)
|
||||||
|
expect(team.includes('Atinux'))
|
||||||
|
expect(await page.$text('div')).toContain('foo-bar-baz')
|
||||||
|
expect(await page.$text('div')).toContain('fizz-buzz')
|
||||||
|
})
|
||||||
|
|
||||||
test('ssr: /fetch-root', async () => {
|
test('ssr: /fetch-root', async () => {
|
||||||
const page = await browser.page(url('/fetch-root'))
|
const page = await browser.page(url('/fetch-root'))
|
||||||
expect(await page.$text('button')).toContain('has fetch')
|
expect(await page.$text('button')).toContain('has fetch')
|
||||||
@ -132,7 +185,7 @@ describe('basic browser', () => {
|
|||||||
// Fragments
|
// Fragments
|
||||||
const { data, fetch } = await page.evaluate(() => window.__NUXT__)
|
const { data, fetch } = await page.evaluate(() => window.__NUXT__)
|
||||||
expect(data.length).toBe(1)
|
expect(data.length).toBe(1)
|
||||||
expect(fetch.length).toBe(1)
|
expect(Object.keys(fetch).length).toBe(2)
|
||||||
|
|
||||||
// asyncData mutations
|
// asyncData mutations
|
||||||
expect(data[0]).toMatchObject({ async: 'data', async2: 'data2' })
|
expect(data[0]).toMatchObject({ async: 'data', async2: 'data2' })
|
||||||
|
18
test/fixtures/basic/pages/fetch.vue
vendored
18
test/fixtures/basic/pages/fetch.vue
vendored
@ -7,6 +7,7 @@
|
|||||||
<button @click="reload">
|
<button @click="reload">
|
||||||
Reload
|
Reload
|
||||||
</button>
|
</button>
|
||||||
|
<code>{{ fetched }}</code>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -18,10 +19,25 @@ const getData = () => fetch(`${baseURL}/test`)
|
|||||||
.then(r => r + ` (From ${name})`)
|
.then(r => r + ` (From ${name})`)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData () {
|
async asyncData ({ $config }) {
|
||||||
|
if ($config.generated) { return }
|
||||||
|
|
||||||
const data = await getData()
|
const data = await getData()
|
||||||
return { data }
|
return { data }
|
||||||
},
|
},
|
||||||
|
data: () => ({
|
||||||
|
num: 10,
|
||||||
|
fetched: false
|
||||||
|
}),
|
||||||
|
async fetch () {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this.fetched = true
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fetchKey (getCounter) {
|
||||||
|
return 'custom' + this.num + getCounter('custom')
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async update () {
|
async update () {
|
||||||
this.data = await getData()
|
this.data = await getData()
|
||||||
|
1
test/fixtures/fetch/components/Team.vue
vendored
1
test/fixtures/fetch/components/Team.vue
vendored
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
fetchKey: 'team',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
team: []
|
team: []
|
||||||
|
23
test/fixtures/fetch/layouts/default.vue
vendored
23
test/fixtures/fetch/layouts/default.vue
vendored
@ -51,7 +51,30 @@
|
|||||||
Deprecated fetch
|
Deprecated fetch
|
||||||
</n-link>
|
</n-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<n-link to="/nested/item">
|
||||||
|
Nested fetch
|
||||||
|
</n-link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
foo-bar-{{ foo }}
|
||||||
<nuxt />
|
<nuxt />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DefaultLayout',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
foo: 'bar'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetch () {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this.foo = 'baz'
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
22
test/fixtures/fetch/pages/nested.vue
vendored
Normal file
22
test/fixtures/fetch/pages/nested.vue
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
fizz-{{ foo }}
|
||||||
|
<NuxtChild />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
foo: 'bar'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetch () {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this.foo = 'buzz'
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
13
test/fixtures/fetch/pages/nested/_slug.vue
vendored
Normal file
13
test/fixtures/fetch/pages/nested/_slug.vue
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<team />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Team from '@/components/Team.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Team
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
26
test/fixtures/fetch/pages/nested/index.vue
vendored
Normal file
26
test/fixtures/fetch/pages/nested/index.vue
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button @click="$fetch">
|
||||||
|
fetch {{ foo }}
|
||||||
|
</button>
|
||||||
|
<NuxtChild />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
fetchKey (getCounter) {
|
||||||
|
return 'ie' + getCounter('ie')
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
foo: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetch () {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
|
||||||
|
this.foo = this.$fetch ? 'has fetch' : 'hasn\'t fetch'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user