mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 23:52:06 +00:00
feat(nuxt-link): Smart prefetching and $nuxt.isOffline (#4574)
* feat(nuxt-link): Improve <n-link> and add automatic prefetch * Update packages/vue-app/template/components/nuxt-link.js Co-Authored-By: Atinux <seb@orion.sh> * add missing space * feat(nuxt-link): Split in two components for smaller bundle * fix(vue-app): Use requestIdleCallback * chore(vue-app): Improve nuxt prefetch strategy for nuxt links * chore(vue-app): Add .isOnline and handle it for prefetch * chore(vue-app): Add .isOffline and use it * chore(vue-app): Add .isOffline * chore(server): Check is options.modern is given in dev mode * chore(vue-app): Add intersection-observer polyfill if router.prefetchLinks is 'polyfill' * chore(vue-app): Remove polyfill * chore(vue-app): Use only process.client * chore(vue-app): Add TS typings for .isOnline and isOffline * chore(vue-app): Update typings by @kevinmarrec * chore(vue-app): Reorder names * examples(nuxt-prefetch): Add Nuxt prefetching example * chore(vue-app): Add router.linkPrefetchedClass * lint(vue-app): Fix lint * chore(vue-app): Use intersectionRatio, recommend by @maoberlehner * fix(lint): Fix linting issues * lint(vue-app): Fix again (lol) * types(vue-app): Update TS typings * chore(vue-app): Update Vetur tags description * fix(vue-app): Use prefetchClass * chore(vue-app): Disable linkPrefetchedClass by default
This commit is contained in:
parent
35151150fd
commit
f319033928
3
examples/nuxt-prefetch/README.md
Normal file
3
examples/nuxt-prefetch/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Example of Nuxt.js prefetching
|
||||||
|
|
||||||
|
Learn more at https://github.com/nuxt/nuxt.js/pull/4574
|
1
examples/nuxt-prefetch/assets/check.svg
Normal file
1
examples/nuxt-prefetch/assets/check.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#41b883" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
After Width: | Height: | Size: 258 B |
57
examples/nuxt-prefetch/layouts/default.vue
Normal file
57
examples/nuxt-prefetch/layouts/default.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="$nuxt.isOffline" class="offline">
|
||||||
|
You are offline
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ $route.name }}</h1>
|
||||||
|
<Nuxt />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding: 10px 20px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
.offline {
|
||||||
|
background: #3B8070;
|
||||||
|
color: white;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 10px 10px 0px;
|
||||||
|
margin: 20px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
border-bottom: 2px #ddd solid;
|
||||||
|
color: #3B8070;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-bottom-color 0.3s linear;
|
||||||
|
}
|
||||||
|
a:hover,
|
||||||
|
a.nuxt-link-exact-active {
|
||||||
|
background-color: rgb(245, 245, 245);
|
||||||
|
}
|
||||||
|
a.nuxt-link-prefetched:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
background: url('../assets/check.svg') no-repeat;
|
||||||
|
background-size: 14px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
position: relative;
|
||||||
|
right: -3px;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
12
examples/nuxt-prefetch/nuxt.config.js
Normal file
12
examples/nuxt-prefetch/nuxt.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default {
|
||||||
|
head: {
|
||||||
|
titleTemplate: '%s - NuxtJS Prefetching'
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
// To disable prefetching, uncomment the line
|
||||||
|
// prefetchLinks: false
|
||||||
|
|
||||||
|
// Activate prefetched class (default: false)
|
||||||
|
linkPrefetchedClass: 'nuxt-link-prefetched'
|
||||||
|
}
|
||||||
|
}
|
11
examples/nuxt-prefetch/package.json
Normal file
11
examples/nuxt-prefetch/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "example-hello-world",
|
||||||
|
"dependencies": {
|
||||||
|
"nuxt": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nuxt",
|
||||||
|
"build": "nuxt build",
|
||||||
|
"start": "nuxt start"
|
||||||
|
}
|
||||||
|
}
|
17
examples/nuxt-prefetch/pages/accelerated.vue
Executable file
17
examples/nuxt-prefetch/pages/accelerated.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/active.vue
Executable file
17
examples/nuxt-prefetch/pages/active.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/agile.vue
Executable file
17
examples/nuxt-prefetch/pages/agile.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/brisk.vue
Executable file
17
examples/nuxt-prefetch/pages/brisk.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/dashing.vue
Executable file
17
examples/nuxt-prefetch/pages/dashing.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/electric.vue
Executable file
17
examples/nuxt-prefetch/pages/electric.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/flashing.vue
Executable file
17
examples/nuxt-prefetch/pages/flashing.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/fleet.vue
Executable file
17
examples/nuxt-prefetch/pages/fleet.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/fleeting.vue
Executable file
17
examples/nuxt-prefetch/pages/fleeting.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/flying.vue
Executable file
17
examples/nuxt-prefetch/pages/flying.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/hot.vue
Executable file
17
examples/nuxt-prefetch/pages/hot.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/hurried.vue
Executable file
17
examples/nuxt-prefetch/pages/hurried.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/index.vue
Executable file
17
examples/nuxt-prefetch/pages/index.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/nimble.vue
Executable file
17
examples/nuxt-prefetch/pages/nimble.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/quick.vue
Executable file
17
examples/nuxt-prefetch/pages/quick.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/racing.vue
Executable file
17
examples/nuxt-prefetch/pages/racing.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/rapid.vue
Executable file
17
examples/nuxt-prefetch/pages/rapid.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/ready.vue
Executable file
17
examples/nuxt-prefetch/pages/ready.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/snap.vue
Executable file
17
examples/nuxt-prefetch/pages/snap.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/swift.vue
Executable file
17
examples/nuxt-prefetch/pages/swift.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
17
examples/nuxt-prefetch/pages/winged.vue
Executable file
17
examples/nuxt-prefetch/pages/winged.vue
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink v-for="link of $store.state.links" :key="link" :to="{ name: link }">
|
||||||
|
/{{ link }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$route.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
25
examples/nuxt-prefetch/store/index.js
Normal file
25
examples/nuxt-prefetch/store/index.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export const state = () => ({
|
||||||
|
links: [
|
||||||
|
'index',
|
||||||
|
'agile',
|
||||||
|
'brisk',
|
||||||
|
'hot',
|
||||||
|
'nimble',
|
||||||
|
'quick',
|
||||||
|
'rapid',
|
||||||
|
'swift',
|
||||||
|
'accelerated',
|
||||||
|
'active',
|
||||||
|
'dashing',
|
||||||
|
'electric',
|
||||||
|
'flashing',
|
||||||
|
'fleet',
|
||||||
|
'fleeting',
|
||||||
|
'flying',
|
||||||
|
'hurried',
|
||||||
|
'racing',
|
||||||
|
'ready',
|
||||||
|
'snap',
|
||||||
|
'winged'
|
||||||
|
]
|
||||||
|
})
|
@ -6,9 +6,11 @@ export default () => ({
|
|||||||
middleware: [],
|
middleware: [],
|
||||||
linkActiveClass: 'nuxt-link-active',
|
linkActiveClass: 'nuxt-link-active',
|
||||||
linkExactActiveClass: 'nuxt-link-exact-active',
|
linkExactActiveClass: 'nuxt-link-exact-active',
|
||||||
|
linkPrefetchedClass: false,
|
||||||
extendRoutes: null,
|
extendRoutes: null,
|
||||||
scrollBehavior: null,
|
scrollBehavior: null,
|
||||||
parseQuery: false,
|
parseQuery: false,
|
||||||
stringifyQuery: false,
|
stringifyQuery: false,
|
||||||
fallback: false
|
fallback: false,
|
||||||
|
prefetchLinks: true
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,8 @@ export const templatesFiles = [
|
|||||||
'components/nuxt-error.vue',
|
'components/nuxt-error.vue',
|
||||||
'components/nuxt-loading.vue',
|
'components/nuxt-loading.vue',
|
||||||
'components/nuxt-child.js',
|
'components/nuxt-child.js',
|
||||||
'components/nuxt-link.js',
|
'components/nuxt-link.server.js',
|
||||||
|
'components/nuxt-link.client.js',
|
||||||
'components/nuxt.js',
|
'components/nuxt.js',
|
||||||
'components/no-ssr.js',
|
'components/no-ssr.js',
|
||||||
'views/app.template.html',
|
'views/app.template.html',
|
||||||
|
@ -55,6 +55,7 @@ export default {
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
isOnline: true,
|
||||||
layout: null,
|
layout: null,
|
||||||
layoutName: ''
|
layoutName: ''
|
||||||
}),
|
}),
|
||||||
@ -65,8 +66,12 @@ export default {
|
|||||||
// Add this.$nuxt in child instances
|
// Add this.$nuxt in child instances
|
||||||
Vue.prototype.<%= globals.nuxt %> = this
|
Vue.prototype.<%= globals.nuxt %> = this
|
||||||
// add to window so we can listen when ready
|
// add to window so we can listen when ready
|
||||||
if (typeof window !== 'undefined') {
|
if (process.client) {
|
||||||
window.<%= globals.nuxt %> = <%= (globals.nuxt !== '$nuxt' ? 'window.$nuxt = ' : '') %>this
|
window.<%= globals.nuxt %> = <%= (globals.nuxt !== '$nuxt' ? 'window.$nuxt = ' : '') %>this
|
||||||
|
this.refreshOnlineStatus()
|
||||||
|
// Setup the listeners
|
||||||
|
window.addEventListener('online', this.refreshOnlineStatus)
|
||||||
|
window.addEventListener('offline', this.refreshOnlineStatus)
|
||||||
}
|
}
|
||||||
// Add $nuxt.error()
|
// Add $nuxt.error()
|
||||||
this.error = this.nuxt.error
|
this.error = this.nuxt.error
|
||||||
@ -79,7 +84,24 @@ export default {
|
|||||||
'nuxt.err': 'errorChanged'
|
'nuxt.err': 'errorChanged'
|
||||||
},
|
},
|
||||||
<% } %>
|
<% } %>
|
||||||
|
computed: {
|
||||||
|
isOffline() {
|
||||||
|
return !this.isOnline
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
refreshOnlineStatus() {
|
||||||
|
if (process.client) {
|
||||||
|
if (typeof window.navigator.onLine === 'undefined') {
|
||||||
|
// If the browser doesn't support connection status reports
|
||||||
|
// assume that we are online because most apps' only react
|
||||||
|
// when they now that the connection has been interrupted
|
||||||
|
this.isOnline = true
|
||||||
|
} else {
|
||||||
|
this.isOnline = window.navigator.onLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
<% if (loading) { %>
|
<% if (loading) { %>
|
||||||
errorChanged() {
|
errorChanged() {
|
||||||
if (this.nuxt.err && this.$loading) {
|
if (this.nuxt.err && this.$loading) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import middleware from './middleware'
|
import middleware from './middleware.js'
|
||||||
import {
|
import {
|
||||||
applyAsyncData,
|
applyAsyncData,
|
||||||
sanitizeComponent,
|
sanitizeComponent,
|
||||||
@ -14,8 +14,13 @@ import {
|
|||||||
compile,
|
compile,
|
||||||
getQueryDiff,
|
getQueryDiff,
|
||||||
globalHandleError
|
globalHandleError
|
||||||
} from './utils'
|
} from './utils.js'
|
||||||
import { createApp, NuxtError } from './index'
|
import { createApp, NuxtError } from './index.js'
|
||||||
|
import NuxtLink from './components/nuxt-link.<%= router.prefetchLinks ? "client" : "server" %>.js' // should be included after ./index.js
|
||||||
|
|
||||||
|
// Component: <NuxtLink>
|
||||||
|
Vue.component(NuxtLink.name, NuxtLink)
|
||||||
|
Vue.component('NLink', NuxtLink)
|
||||||
|
|
||||||
const noopData = () => { return {} }
|
const noopData = () => { return {} }
|
||||||
const noopFetch = () => {}
|
const noopFetch = () => {}
|
||||||
|
101
packages/vue-app/template/components/nuxt-link.client.js
Normal file
101
packages/vue-app/template/components/nuxt-link.client.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<%= isTest ? '// @vue/component' : '' %>
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
const requestIdleCallback = window.requestIdleCallback ||
|
||||||
|
function (cb) {
|
||||||
|
const start = Date.now()
|
||||||
|
return setTimeout(function () {
|
||||||
|
cb({
|
||||||
|
didTimeout: false,
|
||||||
|
timeRemaining: function () {
|
||||||
|
return Math.max(0, 50 - (Date.now() - start))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
const observer = window.IntersectionObserver && new window.IntersectionObserver(entries => {
|
||||||
|
entries.forEach(({ intersectionRatio, target: link }) => {
|
||||||
|
if (intersectionRatio <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
link.__prefetch()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: Vue.component('RouterLink'),
|
||||||
|
name: 'NuxtLink',
|
||||||
|
props: {
|
||||||
|
noPrefetch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}<% if (router.linkPrefetchedClass) { %>,
|
||||||
|
prefetchedClass: {
|
||||||
|
type: String,
|
||||||
|
default: '<%= router.linkPrefetchedClass %>'
|
||||||
|
}<% } %>
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.noPrefetch) {
|
||||||
|
requestIdleCallback(this.observe, { timeout: 2e3 })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.__observed) {
|
||||||
|
observer.unobserve(this.$el)
|
||||||
|
delete this.$el.__prefetch
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observe() {
|
||||||
|
// If no IntersectionObserver, avoid prefetching
|
||||||
|
if (!observer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Add to observer
|
||||||
|
if (this.shouldPrefetch()) {
|
||||||
|
this.$el.__prefetch = this.prefetch.bind(this)
|
||||||
|
observer.observe(this.$el)
|
||||||
|
this.__observed = true
|
||||||
|
}<% if (router.linkPrefetchedClass) { %> else {
|
||||||
|
this.addPrefetchedClass()
|
||||||
|
}<% } %>
|
||||||
|
},
|
||||||
|
shouldPrefetch() {
|
||||||
|
return this.getPrefetchComponents().length > 0
|
||||||
|
},
|
||||||
|
canPrefetch() {
|
||||||
|
const conn = navigator.connection
|
||||||
|
const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData))
|
||||||
|
|
||||||
|
return !hasBadConnection
|
||||||
|
},
|
||||||
|
getPrefetchComponents() {
|
||||||
|
const ref = this.$router.resolve(this.to, this.$route, this.append)
|
||||||
|
const Components = ref.resolved.matched.map((r) => r.components.default)
|
||||||
|
|
||||||
|
return Components.filter((Component) => typeof Component === 'function' && !Component.options && !Component.__prefetched)
|
||||||
|
},
|
||||||
|
prefetch() {
|
||||||
|
if (!this.canPrefetch()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Stop obersing this link (in case of internet connection changes)
|
||||||
|
observer.unobserve(this.$el)
|
||||||
|
const Components = this.getPrefetchComponents()
|
||||||
|
|
||||||
|
for (const Component of Components) {
|
||||||
|
try {
|
||||||
|
Component()
|
||||||
|
Component.__prefetched = true
|
||||||
|
} catch (e) {}
|
||||||
|
}<% if (router.linkPrefetchedClass) { %>
|
||||||
|
this.addPrefetchedClass()<% } %>
|
||||||
|
}<% if (router.linkPrefetchedClass) { %>,
|
||||||
|
addPrefetchedClass() {
|
||||||
|
if (this.prefetchedClass !== 'false') {
|
||||||
|
this.$el.className += (this.$el.className + ' ' + this.prefetchedClass).trim()
|
||||||
|
}
|
||||||
|
}<% } %>
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
<%= isTest ? '// @vue/component' : '' %>
|
|
||||||
export default {
|
|
||||||
name: 'NuxtLink',
|
|
||||||
functional: true,
|
|
||||||
render(h, { data, children }) {
|
|
||||||
return h('router-link', data, children)
|
|
||||||
}
|
|
||||||
}
|
|
13
packages/vue-app/template/components/nuxt-link.server.js
Normal file
13
packages/vue-app/template/components/nuxt-link.server.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<%= isTest ? '// @vue/component' : '' %>
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: Vue.component('RouterLink'),
|
||||||
|
name: 'NuxtLink',
|
||||||
|
props: {
|
||||||
|
noPrefetch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import Meta from 'vue-meta'
|
|||||||
import { createRouter } from './router.js'
|
import { createRouter } from './router.js'
|
||||||
import NoSsr from './components/no-ssr.js'
|
import NoSsr from './components/no-ssr.js'
|
||||||
import NuxtChild from './components/nuxt-child.js'
|
import NuxtChild from './components/nuxt-child.js'
|
||||||
import NuxtLink from './components/nuxt-link.js'
|
|
||||||
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
|
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
|
||||||
import Nuxt from './components/nuxt.js'
|
import Nuxt from './components/nuxt.js'
|
||||||
import App from '<%= appPath %>'
|
import App from '<%= appPath %>'
|
||||||
@ -23,9 +22,7 @@ Vue.component(NoSsr.name, NoSsr)
|
|||||||
Vue.component(NuxtChild.name, NuxtChild)
|
Vue.component(NuxtChild.name, NuxtChild)
|
||||||
Vue.component('NChild', NuxtChild)
|
Vue.component('NChild', NuxtChild)
|
||||||
|
|
||||||
// Component: <NuxtLink
|
// Component NuxtLink is imported in server.js or client.js
|
||||||
Vue.component(NuxtLink.name, NuxtLink)
|
|
||||||
Vue.component('NLink', NuxtLink)
|
|
||||||
|
|
||||||
// Component: <Nuxt>`
|
// Component: <Nuxt>`
|
||||||
Vue.component(Nuxt.name, Nuxt)
|
Vue.component(Nuxt.name, Nuxt)
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
import { stringify } from 'querystring'
|
import { stringify } from 'querystring'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import omit from 'lodash/omit'
|
import omit from 'lodash/omit'
|
||||||
import middleware from './middleware'
|
import middleware from './middleware.js'
|
||||||
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
|
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils.js'
|
||||||
import { createApp, NuxtError } from './index'
|
import { createApp, NuxtError } from './index.js'
|
||||||
|
import NuxtLink from './components/nuxt-link.server.js'
|
||||||
|
|
||||||
|
// Component: <NuxtLink>
|
||||||
|
Vue.component(NuxtLink.name, NuxtLink)
|
||||||
|
Vue.component('NLink', NuxtLink)
|
||||||
|
|
||||||
const debug = require('debug')('nuxt:render')
|
const debug = require('debug')('nuxt:render')
|
||||||
debug.color = 4 // force blue color
|
debug.color = 4 // force blue color
|
||||||
|
@ -566,4 +566,3 @@ function formatQuery(query) {
|
|||||||
return key + '=' + val
|
return key + '=' + val
|
||||||
}).filter(Boolean).join('&')
|
}).filter(Boolean).join('&')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
packages/vue-app/types/index.d.ts
vendored
10
packages/vue-app/types/index.d.ts
vendored
@ -59,7 +59,11 @@ export interface ErrorParams {
|
|||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadingObject {
|
export interface NuxtApp extends Vue {
|
||||||
start(): void;
|
isOffline: boolean;
|
||||||
finish(): void;
|
isOnline: boolean;
|
||||||
|
$loading: {
|
||||||
|
start(): void;
|
||||||
|
finish(): void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
6
packages/vue-app/types/vue.d.ts
vendored
6
packages/vue-app/types/vue.d.ts
vendored
@ -5,7 +5,7 @@
|
|||||||
import Vue, { ComponentOptions } from "vue";
|
import Vue, { ComponentOptions } from "vue";
|
||||||
import { Route } from "vue-router";
|
import { Route } from "vue-router";
|
||||||
import { MetaInfo } from "vue-meta";
|
import { MetaInfo } from "vue-meta";
|
||||||
import { Context, Middleware, Transition, LoadingObject } from "./index";
|
import { Context, Middleware, Transition, NuxtApp } from "./index";
|
||||||
|
|
||||||
declare module "vue/types/options" {
|
declare module "vue/types/options" {
|
||||||
interface ComponentOptions<V extends Vue> {
|
interface ComponentOptions<V extends Vue> {
|
||||||
@ -24,8 +24,6 @@ declare module "vue/types/options" {
|
|||||||
|
|
||||||
declare module "vue/types/vue" {
|
declare module "vue/types/vue" {
|
||||||
interface Vue {
|
interface Vue {
|
||||||
$nuxt: {
|
$nuxt: NuxtApp;
|
||||||
$loading: LoadingObject;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"attributes": [
|
"attributes": [
|
||||||
"nuxtChildKey"
|
"nuxtChildKey"
|
||||||
],
|
],
|
||||||
"description": "The nuxt component."
|
"description": "Component to render the current nuxt page."
|
||||||
},
|
},
|
||||||
"n-child": {
|
"n-child": {
|
||||||
"description": "Component for displaying the children components in a nested route."
|
"description": "Component for displaying the children components in a nested route."
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"exact-active-class",
|
"exact-active-class",
|
||||||
"no-prefetch"
|
"no-prefetch"
|
||||||
],
|
],
|
||||||
"description": "Component for routing. Same as <router-link> now."
|
"description": "Component for navigating between Nuxt pages."
|
||||||
},
|
},
|
||||||
"nuxt-link": {
|
"nuxt-link": {
|
||||||
"attributes": [
|
"attributes": [
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"exact-active-class",
|
"exact-active-class",
|
||||||
"no-prefetch"
|
"no-prefetch"
|
||||||
],
|
],
|
||||||
"description": "Component for routing. Same as <router-link> now."
|
"description": "Component for navigating between Nuxt pages."
|
||||||
},
|
},
|
||||||
"no-ssr": {
|
"no-ssr": {
|
||||||
"description": "Component for excluding a part of your app from server-side rendering."
|
"description": "Component for excluding a part of your app from server-side rendering."
|
||||||
|
@ -34,6 +34,6 @@ describe('size-limit test', () => {
|
|||||||
const responseSizeBytes = responseSizes.reduce((bytes, responseLength) => bytes + responseLength, 0)
|
const responseSizeBytes = responseSizes.reduce((bytes, responseLength) => bytes + responseLength, 0)
|
||||||
const responseSizeKilobytes = Math.ceil(responseSizeBytes / 1024)
|
const responseSizeKilobytes = Math.ceil(responseSizeBytes / 1024)
|
||||||
// Without gzip!
|
// Without gzip!
|
||||||
expect(responseSizeKilobytes).toBeLessThanOrEqual(171)
|
expect(responseSizeKilobytes).toBeLessThanOrEqual(180)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user