mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 06:05:11 +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
|
||||
next: NextFunction
|
||||
beforeRenderFns: Array<() => any>
|
||||
fetchCounters: Record<string, number>
|
||||
nuxt: {
|
||||
layout: string
|
||||
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> {
|
||||
asyncData?(ctx: Context): Promise<object | void> | object | void
|
||||
fetch?(ctx: Context): Promise<void> | void
|
||||
fetchKey?: string | ((getKey: (id: string) => number) => string)
|
||||
fetchDelay?: number
|
||||
fetchOnServer?: boolean | (() => boolean)
|
||||
head?: MetaInfo | (() => MetaInfo)
|
||||
|
@ -317,7 +317,7 @@ export default {
|
||||
<% } %>
|
||||
setPagePayload(payload) {
|
||||
this._pagePayload = payload
|
||||
this._payloadFetchIndex = 0
|
||||
this._fetchCounters = {}
|
||||
},
|
||||
async fetchPayload(route) {
|
||||
<% if (nuxtOptions.generate.manifest) { %>
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 nuxtState = window.<%= globals.context %>
|
||||
@ -38,7 +38,7 @@ function created() {
|
||||
|
||||
// Hydrate component
|
||||
this._hydrated = true
|
||||
this._fetchKey = +this.$vnode.elm.dataset.fetchKey
|
||||
this._fetchKey = this.$vnode.elm.dataset.fetchKey
|
||||
const data = nuxtState.fetch[this._fetchKey]
|
||||
|
||||
// If fetch error
|
||||
@ -64,7 +64,17 @@ function createdFullStatic() {
|
||||
return
|
||||
}
|
||||
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]
|
||||
|
||||
// If fetch error
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import { hasFetch, normalizeError, addLifecycleHook, purifyData } from '../utils'
|
||||
import { hasFetch, normalizeError, addLifecycleHook, purifyData, createGetCounter } from '../utils'
|
||||
|
||||
async function serverPrefetch() {
|
||||
if (!this._fetchOnServer) {
|
||||
@ -19,14 +19,20 @@ async function serverPrefetch() {
|
||||
|
||||
|
||||
// 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
|
||||
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {}
|
||||
attrs['data-fetch-key'] = this._fetchKey
|
||||
|
||||
// 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 {
|
||||
@ -41,6 +47,16 @@ export default {
|
||||
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
|
||||
this.$fetch = () => {} // issue #8043
|
||||
Vue.util.defineReactive(this, '$fetchState', {
|
||||
|
@ -73,7 +73,11 @@ export default async (ssrContext) => {
|
||||
// Used for beforeNuxtRender({ Components, nuxtState })
|
||||
ssrContext.beforeRenderFns = []
|
||||
// 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
|
||||
if (process.static && ssrContext.url) {
|
||||
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 globalHandleError (error) {
|
||||
|
@ -24,6 +24,9 @@ describe('basic generate', () => {
|
||||
generate: {
|
||||
static: false,
|
||||
dir: '.nuxt-generate'
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
generated: true
|
||||
}
|
||||
})
|
||||
const nuxt = new Nuxt(config)
|
||||
@ -142,6 +145,13 @@ describe('basic generate', () => {
|
||||
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 () => {
|
||||
const window = await generator.nuxt.server.renderAndGetWindow(url('/тест雨'))
|
||||
const html = window.document.body.innerHTML
|
||||
|
@ -76,6 +76,59 @@ describe('basic browser', () => {
|
||||
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 () => {
|
||||
const page = await browser.page(url('/fetch-root'))
|
||||
expect(await page.$text('button')).toContain('has fetch')
|
||||
@ -132,7 +185,7 @@ describe('basic browser', () => {
|
||||
// Fragments
|
||||
const { data, fetch } = await page.evaluate(() => window.__NUXT__)
|
||||
expect(data.length).toBe(1)
|
||||
expect(fetch.length).toBe(1)
|
||||
expect(Object.keys(fetch).length).toBe(2)
|
||||
|
||||
// asyncData mutations
|
||||
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">
|
||||
Reload
|
||||
</button>
|
||||
<code>{{ fetched }}</code>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -18,10 +19,25 @@ const getData = () => fetch(`${baseURL}/test`)
|
||||
.then(r => r + ` (From ${name})`)
|
||||
|
||||
export default {
|
||||
async asyncData () {
|
||||
async asyncData ({ $config }) {
|
||||
if ($config.generated) { return }
|
||||
|
||||
const data = await getData()
|
||||
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: {
|
||||
async update () {
|
||||
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>
|
||||
export default {
|
||||
fetchKey: 'team',
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
|
23
test/fixtures/fetch/layouts/default.vue
vendored
23
test/fixtures/fetch/layouts/default.vue
vendored
@ -51,7 +51,30 @@
|
||||
Deprecated fetch
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/nested/item">
|
||||
Nested fetch
|
||||
</n-link>
|
||||
</li>
|
||||
</ul>
|
||||
foo-bar-{{ foo }}
|
||||
<nuxt />
|
||||
</div>
|
||||
</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