feat(vue-app): new fetch syntax (#6880)

This commit is contained in:
Sébastien Chopin 2020-02-04 19:36:22 +01:00 committed by GitHub
parent e271aa0a0a
commit 6db325c321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 893 additions and 27 deletions

View File

@ -0,0 +1,5 @@
# New fetch() with Nuxt.js
Nuxt.js `v2.12` introduces a new hook called `fetch` in any of your Vue components.
See [live demo](https://nuxt-new-fetch.surge.sh) and [documentation](https://nuxtjs.org/api/pages-fetch).

View File

@ -0,0 +1,30 @@
<template>
<p v-if="$fetchState.error">
Could not fetch Author
</p>
<p v-else>
Written by {{ $fetchState.pending ? '...' : user.name }} <button @click="$fetch">
Refresh
</button>
</p>
</template>
<script>
export default {
props: {
userId: {
type: Number,
required: true
}
},
async fetch () {
this.user = await this.$http.$get(`https://jsonplaceholder.typicode.com/users/${this.userId}`)
},
data () {
return {
user: {}
}
},
fetchOnServer: false
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<div>
<!-- <p>Fetching: {{ $nuxt.isFetching }} ({{ $nuxt.nbFetching }})</p> -->
<nuxt />
</div>
</template>
<script>
export default {
head: {
link: [
{ rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/gh/kognise/water.css@1.4.0/dist/light.min.css' }
]
}
}
</script>
<style>
body {
padding: 20px 30px;
}
.page-enter-active, .page-leave-active {
transition: opacity .3s;
}
.page-enter, .page-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,18 @@
const fetch = require('node-fetch')
export default {
plugins: [
'@/plugins/vue-placeholders.js'
],
modules: [
'@nuxt/http'
],
generate: {
async routes () {
const posts = await fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json()).then(d => d.slice(0, 20))
const routes = posts.map(post => `/posts/${post.id}`)
return ['/'].concat(routes)
}
}
}

View File

@ -0,0 +1,18 @@
{
"name": "example-hello-world",
"dependencies": {
"@nuxt/http": "^0.3.8",
"nuxt-start": "latest",
"vue-content-placeholders": "^0.2.1"
},
"devDependencies": {
"nuxt": "latest"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"post-update": "yarn upgrade --latest"
}
}

View File

@ -0,0 +1,43 @@
<template>
<div>
<h1>Blog posts</h1>
<template v-if="$fetchState.pending">
<content-placeholders>
<content-placeholders-text :lines="20" />
</content-placeholders>
</template>
<template v-else-if="$fetchState.error">
<p>
Error while fetching posts: {{ error }}
</p>
</template>
<template v-else>
<ul>
<li v-for="post of posts" :key="post.id">
<n-link :to="`/posts/${post.id}`">
{{ post.title }}
</n-link>
</li>
<li>
<n-link to="/posts/404">
404 post
</n-link>
</li>
</ul>
</template>
</div>
</template>
<script>
export default {
async fetch () {
this.posts = await this.$http.$get('https://jsonplaceholder.typicode.com/posts')
.then(posts => posts.slice(0, 20))
},
data () {
return {
posts: null
}
}
}
</script>

View File

@ -0,0 +1,54 @@
<template>
<div>
<button @click="$fetch">
Refresh
</button>
<template v-if="$fetchState.pending">
<content-placeholders>
<content-placeholders-heading />
<content-placeholders-text :lines="10" />
</content-placeholders>
</template>
<template v-else-if="$fetchState.error">
<h1>
Post #{{ $route.params.id }} not found
</h1>
</template>
<template v-else>
<h1>{{ post.title }}</h1>
<author :user-id="post.userId" />
<pre>{{ post.body }}</pre>
<p>
<n-link :to="{ name: 'posts-id', params: { id: (post.id + 1) } }">
Next article
</n-link>
</p>
</template>
<p>
<n-link to="/">
Home
</n-link>
</p>
</div>
</template>
<script>
import Author from '~/components/Author.vue'
export default {
components: {
Author
},
async fetch () {
this.post = await this.$http.$get(`https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}`)
},
data () {
return {
post: {}
}
},
head () {
return { title: this.post.title }
}
}
</script>

View File

@ -0,0 +1,4 @@
import Vue from 'vue'
import VueContentPlaceholders from 'vue-content-placeholders'
Vue.use(VueContentPlaceholders)

View File

@ -13,6 +13,8 @@ export const template = {
'server.js',
'utils.js',
'empty.js',
'mixins/fetch.server.js',
'mixins/fetch.client.js',
'components/nuxt-error.vue',
'components/nuxt-child.js',
'components/nuxt-link.server.js',

View File

@ -2,6 +2,7 @@ import Vue from 'vue'
<% if (features.asyncData || features.fetch) { %>
import {
getMatchedComponentsInstances,
getChildrenComponentInstancesUsingFetch,
promisify,
globalHandleError
} from './utils'
@ -88,9 +89,12 @@ export default {
<% } %>
<% if (features.layouts) { %>
layout: null,
layoutName: ''
layoutName: '',
<% } %>
}),
<% if (features.fetch) { %>
nbFetching: 0
<% } %>
}),
<% } %>
beforeCreate () {
Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt)
@ -125,7 +129,12 @@ export default {
computed: {
isOffline () {
return !this.isOnline
},
<% if (features.fetch) { %>
isFetching() {
return this.nbFetching > 0
}
<% } %>
},
<% } %>
methods: {
@ -157,9 +166,18 @@ export default {
const p = []
<% if (features.fetch) { %>
if (page.$options.fetch) {
// Old fetch
if (page.$options.fetch && page.$options.fetch.length) {
p.push(promisify(page.$options.fetch, this.context))
}
if (page.$fetch) {
p.push(page.$fetch())
} else {
// Get all component instance to call $fetch
for (const component of getChildrenComponentInstancesUsingFetch(page.$vnode.componentInstance)) {
p.push(component.$fetch())
}
}
<% } %>
<% if (features.asyncData) { %>
if (page.$options.asyncData) {

View File

@ -17,8 +17,17 @@ import {
globalHandleError
} from './utils.js'
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.client'<% } %>
import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch ? "client" : "server" %>.js' // should be included after ./index.js
<% if (features.fetch) { %>
// Fetch mixin
if (!Vue.__nuxt__fetch__mixin__) {
Vue.mixin(fetchMixin)
Vue.__nuxt__fetch__mixin__ = true
}
<% } %>
// Component: <NuxtLink>
Vue.component(NuxtLink.name, NuxtLink)
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
@ -458,7 +467,10 @@ async function render (to, from, next) {
<% } %>
<% if (features.fetch) { %>
const hasFetch = Boolean(Component.options.fetch)
const hasFetch = Boolean(Component.options.fetch) && Component.options.fetch.length
if (hasFetch) {
console.warn('fetch(context) has been deprecated, please use middleware(context)')
}
<% } else { %>
const hasFetch = false
<% } %>
@ -738,7 +750,7 @@ function addHotReload ($component, depth) {
<% if (features.fetch) { %>
// Call fetch()
Component.options.fetch = Component.options.fetch || noopFetch
let pFetch = Component.options.fetch(context)
let pFetch = Component.options.fetch.length && Component.options.fetch(context)
if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) }
<%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
promises.push(pFetch)

View File

@ -13,7 +13,8 @@ export default {
default: undefined
}
},
render (h, { parent, data, props }) {
render (_, { parent, data, props }) {
const h = parent.$createElement
<% if (features.transitions) { %>
data.nuxtChild = true
const _parent = parent
@ -42,15 +43,17 @@ export default {
listeners[key] = transition[key].bind(_parent)
}
})
// Add triggerScroll event on beforeEnter (fix #1376)
const beforeEnter = listeners.beforeEnter
listeners.beforeEnter = (el) => {
// Ensure to trigger scroll event after calling scrollBehavior
window.<%= globals.nuxt %>.$nextTick(() => {
window.<%= globals.nuxt %>.$emit('triggerScroll')
})
if (beforeEnter) {
return beforeEnter.call(_parent, el)
if (process.client) {
// Add triggerScroll event on beforeEnter (fix #1376)
const beforeEnter = listeners.beforeEnter
listeners.beforeEnter = (el) => {
// Ensure to trigger scroll event after calling scrollBehavior
window.<%= globals.nuxt %>.$nextTick(() => {
window.<%= globals.nuxt %>.$emit('triggerScroll')
})
if (beforeEnter) {
return beforeEnter.call(_parent, el)
}
}
}

View File

@ -0,0 +1,80 @@
import Vue from 'vue'
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.ssrKey
const nuxtState = window.<%= globals.context %>
export default {
beforeCreate () {
if (!hasFetch(this)) {
return
}
this._fetchDelay = typeof this.$options.fetchDelay === 'number' ? this.$options.fetchDelay : 200
Vue.util.defineReactive(this, '$fetchState', {
pending: false,
error: null,
timestamp: Date.now()
})
this.$fetch = $fetch.bind(this)
addLifecycleHook(this, 'created', created)
addLifecycleHook(this, 'beforeMount', beforeMount)
}
}
function beforeMount() {
if (!this._hydrated) {
return this.$fetch()
}
}
function created() {
if (!isSsrHydration(this)) {
return
}
// Hydrate component
this._hydrated = true
this._ssrKey = +this.$vnode.elm.dataset.ssrKey
const data = nuxtState.fetch[this._ssrKey]
// If fetch error
if (data && data._error) {
this.$fetchState.error = data._error
return
}
// Merge data
for (const key in data) {
Vue.set(this.$data, key, data[key])
}
}
async function $fetch() {
this.$nuxt.nbFetching++
this.$fetchState.pending = true
this.$fetchState.error = null
this._hydrated = false
let error = null
const startTime = Date.now()
try {
await this.$options.fetch.call(this)
} catch (err) {
error = normalizeError(err)
}
const delayLeft = this._fetchDelay - (Date.now() - startTime)
if (delayLeft > 0) {
await new Promise(resolve => setTimeout(resolve, delayLeft))
}
this.$fetchState.error = error
this.$fetchState.pending = false
this.$fetchState.timestamp = Date.now()
this.$nextTick(() => this.$nuxt.nbFetching--)
}

View File

@ -0,0 +1,49 @@
import Vue from 'vue'
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
async function serverPrefetch() {
if (!this._fetchOnServer) {
return
}
// Call and await on $fetch
try {
await this.$options.fetch.call(this)
} catch (err) {
this.$fetchState.error = normalizeError(err)
}
this.$fetchState.pending = false
// Define an ssrKey for hydration
this._ssrKey = this.$ssrContext.nuxt.fetch.length
// Add data-ssr-key on parent element of Component
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {}
attrs['data-ssr-key'] = this._ssrKey
// Call asyncData & add to ssrContext for window.__NUXT__.fetch
this.$ssrContext.nuxt.fetch.push(this.$fetchState.error ? { _error: this.$fetchState.error } : this._data)
}
export default {
beforeCreate() {
if (!hasFetch(this)) {
return
}
if (typeof this.$options.fetchOnServer === 'function') {
this._fetchOnServer = this.$options.fetchOnServer.call(this) !== false
} else {
this._fetchOnServer = this.$options.fetchOnServer !== false
}
Vue.util.defineReactive(this, '$fetchState', {
pending: true,
error: null,
timestamp: Date.now()
})
addLifecycleHook(this, 'serverPrefetch', serverPrefetch)
}
}

View File

@ -9,9 +9,21 @@ import {
getMatchedComponents,
promisify
} from './utils.js'
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.server'<% } %>
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js
<% if (features.fetch) { %>
// Update serverPrefetch strategy
Vue.config.optionMergeStrategies.serverPrefetch = Vue.config.optionMergeStrategies.created
// Fetch mixin
if (!Vue.__nuxt__fetch__mixin__) {
Vue.mixin(fetchMixin)
Vue.__nuxt__fetch__mixin__ = true
}
<% } %>
// Component: <NuxtLink>
Vue.component(NuxtLink.name, NuxtLink)
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
@ -60,7 +72,7 @@ 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: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: [], <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
// Create the app definition and the instance (created for each request)
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
const _app = new Vue(app)
@ -267,7 +279,8 @@ export default async (ssrContext) => {
<% if (features.fetch) { %>
// Call fetch(context)
if (Component.options.fetch) {
if (Component.options.fetch && Component.options.fetch.length) {
console.warn('fetch(context) has been deprecated, please use middleware(context)')
promises.push(Component.options.fetch(app.context))
} else {
promises.push(null)

View File

@ -21,6 +21,24 @@ export function interopDefault (promise) {
return promise.then(m => m.default || m)
}
<% if (features.fetch) { %>
export function hasFetch(vm) {
return vm.$options && typeof vm.$options.fetch === 'function' && !vm.$options.fetch.length
}
export function getChildrenComponentInstancesUsingFetch(vm, instances = []) {
const children = vm.$children || []
for (const child of children) {
if (child.$fetch) {
instances.push(child)
continue; // Don't get the children since it will reload the template
}
if (child.$children) {
getChildrenComponentInstancesUsingFetch(child, instances)
}
}
return instances
}
<% } %>
<% if (features.asyncData) { %>
export function applyAsyncData (Component, asyncData) {
if (
@ -615,3 +633,10 @@ function formatQuery (query) {
}).filter(Boolean).join('&')
}
<% } %>
export function addLifecycleHook(vm, hook, fn) {
if (!vm.$options[hook]) {
vm.$options[hook] = []
}
vm.$options[hook].push(fn)
}

View File

@ -0,0 +1,159 @@
import Browser from '../utils/browser'
import { loadFixture, getPort, Nuxt } from '../utils'
let port
const browser = new Browser()
const url = route => 'http://localhost:' + port + route
let nuxt = null
let page = null
describe('basic browser', () => {
beforeAll(async () => {
const config = await loadFixture('fetch')
nuxt = new Nuxt(config)
await nuxt.ready()
port = await getPort()
await nuxt.server.listen(port, 'localhost')
await browser.start({
// slowMo: 50,
// headless: false
})
})
test('Open /', async () => {
page = await browser.page(url('/'))
expect(await page.$text('pre')).toContain('Atinux')
})
test('/fetch-client', async () => {
await page.nuxt.navigate('/fetch-client')
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('pre')
expect(await page.$text('pre')).toContain('pi0')
})
test('/fetch-error', async () => {
await page.nuxt.navigate('/fetch-error')
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('#error')
expect(await page.$text('#error')).toContain('fetch-error')
})
test('/fetch-component', async () => {
await page.nuxt.navigate('/fetch-component')
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('pre')
expect(await page.$text('pre')).toContain('clarkdo')
})
test('/fetch-delay', async () => {
const now = Date.now()
await page.nuxt.navigate('/fetch-delay')
expect(await page.$text('p')).toContain('Fetching for 1 second')
await page.waitForSelector('pre')
const delay = Date.now() - now
expect(await page.$text('pre')).toContain('alexchopin')
expect(delay).toBeGreaterThanOrEqual(1000)
})
test('/fetch-button', async () => {
await page.nuxt.navigate('/fetch-button')
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('pre')
expect(await page.$text('pre')).toContain('kevinmarrec')
await page.click('button')
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('pre')
expect(await page.$text('pre')).toContain('kevinmarrec')
})
test('/old-fetch', async () => {
const msg = new Promise(resolve =>
page.on('console', msg => resolve(msg.text()))
)
await page.nuxt.navigate('/old-fetch')
expect(await msg).toBe('fetch(context) has been deprecated, please use middleware(context)')
})
test('ssr: /fetch-client', async () => {
const page = await browser.page(url('/fetch-client'))
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('pre')
expect(await page.$text('pre')).toContain('pi0')
page.close()
})
test('ssr: /fetch-conditional', async () => {
const page = await browser.page(url('/fetch-conditional'))
expect(await page.$text('pre')).toContain('galvez')
page.close()
})
test('ssr: /fetch-conditional?fetch_client=true', async () => {
const page = await browser.page(url('/fetch-conditional?fetch_client=true'))
expect(await page.$text('p')).toContain('Fetching...')
await page.waitForSelector('pre')
expect(await page.$text('pre')).toContain('pimlie')
page.close()
})
test('ssr: /fetch-error', async () => {
const page = await browser.page(url('/fetch-error'))
expect(await page.$text('#error')).toContain('fetch-error')
page.close()
})
test('ssr: /fetch-deep', async () => {
const page = await browser.page(url('/fetch-deep'))
const expectedState = {
foo: 'barbar',
user: {
name: 'Potato',
inventory: {
type: 'green',
items: ['A', 'B']
}
},
async: 'data',
async2: 'data2fetch'
}
// Hydrated HTML
const renderedData = await page.$text('#data').then(t => JSON.parse(t))
expect(renderedData).toMatchObject(expectedState)
// Fragments
const { data, fetch } = await page.evaluate(() => window.__NUXT__)
expect(data.length).toBe(1)
expect(fetch.length).toBe(1)
// asyncData mutations
expect(data[0]).toMatchObject({ async: 'data', async2: 'data2' })
// fetch mutations
expect(fetch[0]).toMatchObject({
user: {
inventory: { items: ['A', 'B'] },
name: 'Potato'
},
foo: 'barbar',
async2: 'data2fetch'
})
page.close()
})
// Close server and ask nuxt to stop listening to file changes
afterAll(async () => {
await nuxt.close()
})
// Stop browser
afterAll(async () => {
await page.close()
await browser.close()
})
})

View File

@ -1,7 +1,7 @@
<script>
export default {
fetch () {
middleware (context) {
throw { error: 'fetch error!' } // eslint-disable-line
}
}

View File

@ -1,7 +1,7 @@
<script>
export default {
fetch () {
middleware (context) {
throw 'fetch error!' // eslint-disable-line
}
}

View File

@ -4,7 +4,7 @@
<script>
export default {
fetch ({ redirect }) {
middleware ({ redirect }) {
return redirect('https://nuxtjs.org/')
}
}

View File

@ -4,7 +4,7 @@
<script>
export default {
fetch ({ redirect }) {
middleware ({ redirect }) {
return redirect({ name: 'stateless' })
}
}

View File

@ -4,7 +4,7 @@
<script>
export default {
fetch ({ redirect }) {
middleware ({ redirect }) {
return redirect('/')
}
}

View File

@ -4,7 +4,7 @@
<script>
export default {
fetch ({ beforeNuxtRender }) {
middleware ({ beforeNuxtRender }) {
if (process.server) {
beforeNuxtRender(({ nuxtState }) => {
nuxtState.test = true

View File

@ -4,7 +4,7 @@
<script>
export default {
fetch ({ store }) {
middleware ({ store }) {
store.dispatch('simpleModule/mutate')
}
}

26
test/fixtures/fetch/components/Team.vue vendored Normal file
View File

@ -0,0 +1,26 @@
<template>
<div>
<p v-if="$fetchState.pending">
Fetching...
</p>
<p v-else-if="$fetchState.error">
{{ $fetchState.error }}
</p>
<pre v-else>{{ team }}</pre>
</div>
</template>
<script>
export default {
async fetch () {
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
this.team = await fetch(`${url}/team.json`).then(res => res.json())
},
data () {
return {
team: []
}
}
}
</script>

3
test/fixtures/fetch/fetch.test.js vendored Normal file
View File

@ -0,0 +1,3 @@
import { buildFixture } from '../../utils/build'
buildFixture('fetch')

52
test/fixtures/fetch/layouts/default.vue vendored Normal file
View File

@ -0,0 +1,52 @@
<template>
<div>
<ul>
<li>
<n-link to="/">
Fetch
</n-link>
</li>
<li>
<n-link to="/fetch-client">
Fetch on client
</n-link>
</li>
<li>
<n-link to="/fetch-conditional">
Fetch conditional
</n-link>
</li>
<li>
<n-link to="/fetch-error">
Fetch error
</n-link>
</li>
<li>
<n-link to="/fetch-delay">
Fetch delay
</n-link>
</li>
<li>
<n-link to="/fetch-button">
Fetch button
</n-link>
</li>
<li>
<n-link to="/fetch-component">
Fetch in component
</n-link>
</li>
<li>
<n-link to="/fetch-deep">
Fetch with deep update updates
</n-link>
</li>
<li>
<n-link to="/old-fetch">
Deprecated fetch
</n-link>
</li>
</ul>
<nuxt />
</div>
</template>

1
test/fixtures/fetch/nuxt.config.js vendored Normal file
View File

@ -0,0 +1 @@
export default {}

View File

@ -0,0 +1,26 @@
<template>
<div>
<p v-if="$fetchState.pending">
Fetching...
</p>
<pre v-else>{{ team }}</pre>
<button @click="$fetch">
Refresh
</button>
</div>
</template>
<script>
export default {
async fetch () {
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
this.team = await fetch(`${url}/team.json`).then(res => res.json())
},
data () {
return {
team: []
}
}
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<div>
<p v-if="$fetchState.pending">
Fetching...
</p>
<pre v-else>{{ team }}</pre>
</div>
</template>
<script>
export default {
async fetch () {
this.team = await fetch('/team.json').then(res => res.json())
},
data () {
return {
team: []
}
},
fetchOnServer: false
}
</script>

View File

@ -0,0 +1,13 @@
<template>
<team />
</template>
<script>
import Team from '@/components/Team.vue'
export default {
components: {
Team
}
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<div>
<nuxt-link to="/fetch-conditional?fetch_client=true">Fetch on client</nuxt-link>
<p v-if="$fetchState.pending">
Fetching...
</p>
<pre v-else>{{ team }}</pre>
</div>
</template>
<script>
export default {
async fetch () {
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
this.team = await fetch(`${url}/team.json`).then(res => res.json())
},
data () {
return {
team: []
}
},
fetchOnServer () {
return !this.$route.query.fetch_client
}
}
</script>

View File

@ -0,0 +1,35 @@
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<pre id="data" v-html="JSON.stringify($data)" />
</template>
<script>
export default {
async fetch () {
await new Promise(resolve => setTimeout(resolve, 100))
this.user.inventory.items.push('B')
this.user.name = 'Potato'
this.foo = 'barbar'
this.async2 = 'data2fetch'
},
async asyncData () {
await new Promise(resolve => setTimeout(resolve, 100))
return {
async: 'data',
async2: 'data2'
}
},
data () {
return {
foo: 'bar',
user: {
name: 'Baz',
inventory: {
type: 'green',
items: ['A']
}
}
}
}
}
</script>

View File

@ -0,0 +1,24 @@
<template>
<div>
<p v-if="$fetchState.pending">
Fetching for 1 second
</p>
<pre v-else>{{ team }}</pre>
</div>
</template>
<script>
export default {
async fetch () {
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
this.team = await fetch(`${url}/team.json`).then(res => res.json())
},
data () {
return {
team: []
}
},
fetchDelay: 1000
}
</script>

View File

@ -0,0 +1,24 @@
<template>
<div>
<p v-if="$fetchState.pending">
Fetching...
</p>
<p v-else-if="$fetchState.error" id="error">
{{ $fetchState.error.message }}
</p>
<pre v-else>{{ team }}</pre>
</div>
</template>
<script>
export default {
async fetch () {
await new Promise((resolve, reject) => reject(new Error('fetch-error')))
},
data () {
return {
team: []
}
}
}
</script>

23
test/fixtures/fetch/pages/index.vue vendored Normal file
View File

@ -0,0 +1,23 @@
<template>
<div>
<p v-if="$fetchState.pending">
Fetching...
</p>
<pre v-else>{{ team }}</pre>
</div>
</template>
<script>
export default {
async fetch () {
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
this.team = await fetch(`${url}/team.json`).then(res => res.json())
},
data () {
return {
team: []
}
}
}
</script>

18
test/fixtures/fetch/pages/old-fetch.vue vendored Normal file
View File

@ -0,0 +1,18 @@
<template>
<div>
Display a warning in the console
</div>
</template>
<script>
export default {
async fetch (context) {
// Should display a warning
},
data () {
return {
team: []
}
}
}
</script>

11
test/fixtures/fetch/static/team.json vendored Normal file
View File

@ -0,0 +1,11 @@
[
"Atinux",
"alexchopin",
"pi0",
"clarkdo",
"manniL",
"galvez",
"aldarund",
"kevinmarrec",
"pimlie"
]

View File

@ -1,7 +1,7 @@
<script>
export default {
fetch () {
fetch (context) {
throw { message: 'fetch error!' } // eslint-disable-line
}
}

View File

@ -1,7 +1,7 @@
<script>
export default {
fetch () {
fetch (context) {
throw 'fetch error!' // eslint-disable-line
}
}

View File

@ -1,7 +1,7 @@
<script>
export default {
fetch () {
fetch (context) {
throw new Error('fetch error!')
}
}