Skip to content

Commit

Permalink
feat: fetch improvements (#366)
Browse files Browse the repository at this point in the history
* feat: support new fetchKey (Nuxt 2.15+) - closes #364, nuxt/nuxt#8781
* fix: fix issue with refetching/hydration in full static mode - closes #292
* fix: align fetch keys with 2.15 - see nuxt/nuxt#8809
  • Loading branch information
danielroe authored Feb 14, 2021
1 parent f1fcec7 commit d4bdbfb
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 23 deletions.
96 changes: 78 additions & 18 deletions src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Vue from 'vue'
import {
isRef,
nextTick,
onBeforeMount,
onServerPrefetch,
set,
Expand All @@ -11,8 +12,10 @@ import type { NuxtApp } from '@nuxt/types/app'

import { getCurrentInstance, ComponentInstance } from './utils'

const nuxtState = process.client && (window as any)[globalContext]

function normalizeError(err: any) {
let message
let message: string
if (!(err.message || typeof err === 'string')) {
try {
message = JSON.stringify(err, null, 2)
Expand All @@ -33,6 +36,15 @@ function normalizeError(err: any) {
}
}

function createGetCounter(counterObject: Record<string, any>, defaultKey = '') {
return function getCounter(id = defaultKey) {
if (counterObject[id] === undefined) {
counterObject[id] = 0
}
return counterObject[id]++
}
}

interface Fetch {
(context: ComponentInstance): void | Promise<void>
}
Expand All @@ -42,10 +54,9 @@ const fetchPromises = new Map<Fetch, Promise<any>>()

const isSsrHydration = (vm: ComponentInstance) =>
(vm.$vnode?.elm as any)?.dataset?.fetchKey
const nuxtState = process.client && (window as any)[globalContext]

interface AugmentedComponentInstance extends ComponentInstance {
_fetchKey?: number
_fetchKey?: number | string
_data?: any
_hydrated?: boolean
_fetchDelay?: number
Expand Down Expand Up @@ -87,6 +98,9 @@ async function callFetches(this: AugmentedComponentInstance) {
})
)
} catch (err) {
if ((process as any).dev) {
console.error('Error in fetch():', err)
}
error = normalizeError(err)
}

Expand All @@ -113,6 +127,7 @@ const setFetchState = (vm: AugmentedComponentInstance) => {
}

const loadFullStatic = (vm: AugmentedComponentInstance) => {
vm._fetchKey = getKey(vm)
// Check if component has been fetched on server
const { fetchOnServer } = vm.$options
const fetchedOnServer =
Expand All @@ -125,20 +140,20 @@ const loadFullStatic = (vm: AugmentedComponentInstance) => {
return
}
vm._hydrated = true
nuxt._payloadFetchIndex = (nuxt._payloadFetchIndex || 0) + 1
vm._fetchKey = nuxt._payloadFetchIndex
const data = nuxt._pagePayload.fetch[vm._fetchKey]
const data = nuxt._pagePayload.fetch[vm._fetchKey!]

// If fetch error
if (data && data._error) {
vm.$fetchState.error = data._error
return
}

// Merge data
for (const key in data) {
set(vm.$data, key, data[key])
}
onBeforeMount(() => {
// Merge data
for (const key in data) {
set(vm, key, data[key])
}
})
}

async function serverPrefetch(vm: AugmentedComponentInstance) {
Expand All @@ -150,12 +165,19 @@ async function serverPrefetch(vm: AugmentedComponentInstance) {
try {
await callFetches.call(vm)
} catch (err) {
if ((process as any).dev) {
console.error('Error in fetch():', err)
}
vm.$fetchState.error = normalizeError(err)
}
vm.$fetchState.pending = false

// Define an ssrKey for hydration
vm._fetchKey = vm.$ssrContext.nuxt.fetch.length
vm._fetchKey =
// Nuxt 2.15+ uses a different format - an object rather than an array
'push' in vm.$ssrContext.nuxt.fetch
? vm.$ssrContext.nuxt.fetch.length
: vm._fetchKey || vm.$ssrContext.fetchCounters['']++

// Add data-fetch-key on parent element of Component
if (!vm.$vnode.data) vm.$vnode.data = {}
Expand All @@ -172,11 +194,45 @@ async function serverPrefetch(vm: AugmentedComponentInstance) {
)

// Add to ssrContext for window.__NUXT__.fetch
vm.$ssrContext.nuxt.fetch.push(
vm.$fetchState.error
? { _error: vm.$fetchState.error }
: JSON.parse(JSON.stringify(data))
const content = vm.$fetchState.error
? { _error: vm.$fetchState.error }
: JSON.parse(JSON.stringify(data))
if ('push' in vm.$ssrContext.nuxt.fetch) {
vm.$ssrContext.nuxt.fetch.push(content)
} else {
vm.$ssrContext.nuxt.fetch[vm._fetchKey!] = content
}
}

function getKey(vm: AugmentedComponentInstance) {
const nuxtState = vm[globalNuxt] as any
if (process.server && 'push' in vm.$ssrContext.nuxt.fetch) {
return undefined
} else if (process.client && '_payloadFetchIndex' in nuxtState) {
nuxtState._payloadFetchIndex = nuxtState._payloadFetchIndex || 0
return nuxtState._payloadFetchIndex++
}
const defaultKey = (vm.$options as any)._scopeId || vm.$options.name || ''
const getCounter = createGetCounter(
process.server
? vm.$ssrContext.fetchCounters
: (vm[globalNuxt] as any)._fetchCounters,
defaultKey
)

const options: {
fetchKey:
| ((getCounter: ReturnType<typeof createGetCounter>) => string)
| string
} = vm.$options as any

if (typeof options.fetchKey === 'function') {
return options.fetchKey.call(vm, getCounter)
} else {
const key =
'string' === typeof options.fetchKey ? options.fetchKey : defaultKey
return key ? key + ':' + getCounter(key) : String(getCounter(key))
}
}

/**
Expand Down Expand Up @@ -220,6 +276,10 @@ export const useFetch = (callback: Fetch) => {
vm._fetchOnServer = vm.$options.fetchOnServer !== false
}

if (process.server) {
vm._fetchKey = getKey(vm)
}

setFetchState(vm)

onServerPrefetch(() => serverPrefetch(vm))
Expand All @@ -241,14 +301,14 @@ export const useFetch = (callback: Fetch) => {
onBeforeMount(() => !vm._hydrated && callFetches.call(vm))

if (process.server || !isSsrHydration(vm)) {
if (isFullStatic) onBeforeMount(() => loadFullStatic(vm))
if (process.client && isFullStatic) return loadFullStatic(vm)
return result()
}

// Hydrate component
vm._hydrated = true
vm._fetchKey = +(vm.$vnode.elm as any)?.dataset.fetchKey
const data = nuxtState.fetch[vm._fetchKey]
vm._fetchKey = (vm.$vnode.elm as any)?.dataset.fetchKey || getKey(vm)
const data = nuxtState.fetch[vm._fetchKey!]

// If fetch error
if (data && data._error) {
Expand Down
2 changes: 1 addition & 1 deletion src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export const globalContext = '<%= options.globalContext %>'.includes('options')

export const isFullStatic = '<%= options.isFullStatic %>'.includes('options')
? false
: ('<%= options.isFullStatic %>' as unknown) === 'true'
: JSON.parse('<%= options.isFullStatic %>')
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ const compositionApiModule: Module<any> = function compositionApiModule() {
src: resolve(libRoot, 'lib', 'entrypoint.es.js'),
fileName: join('composition-api', 'index.js'),
options: {
isFullStatic: 'isFullStatic' in utils && utils.isFullStatic(this.options),
isFullStatic:
'isFullStatic' in utils && utils.isFullStatic(this.nuxt.options),
staticPath: staticPath,
publicPath: isUrl(publicPath) ? publicPath : routerBase,
globalContext,
Expand Down
10 changes: 7 additions & 3 deletions test/e2e/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ test('Shows loading state', async t => {
await navigateTo('/other')
await t.click(Selector('a'))
await expectPathnameToBe('/')
await expectOnPage('loading email')
await t.wait(4000)
await expectOnPage('long@load.com')
await expectNotOnPage('loading email')
if (process.env.GENERATE) {
await expectNotOnPage('loading email')
await expectNotOnPage('long@load.com')
} else {
await expectOnPage('long@load.com')
await expectNotOnPage('loading email')
}
})

test('Refetches with $fetch', async t => {
Expand Down

1 comment on commit d4bdbfb

@vercel
Copy link

@vercel vercel bot commented on d4bdbfb Feb 14, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.