mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 06:05:11 +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: [],
|
||||
linkActiveClass: 'nuxt-link-active',
|
||||
linkExactActiveClass: 'nuxt-link-exact-active',
|
||||
linkPrefetchedClass: false,
|
||||
extendRoutes: null,
|
||||
scrollBehavior: null,
|
||||
parseQuery: false,
|
||||
stringifyQuery: false,
|
||||
fallback: false
|
||||
fallback: false,
|
||||
prefetchLinks: true
|
||||
})
|
||||
|
@ -17,7 +17,8 @@ export const templatesFiles = [
|
||||
'components/nuxt-error.vue',
|
||||
'components/nuxt-loading.vue',
|
||||
'components/nuxt-child.js',
|
||||
'components/nuxt-link.js',
|
||||
'components/nuxt-link.server.js',
|
||||
'components/nuxt-link.client.js',
|
||||
'components/nuxt.js',
|
||||
'components/no-ssr.js',
|
||||
'views/app.template.html',
|
||||
|
@ -55,6 +55,7 @@ export default {
|
||||
])
|
||||
},
|
||||
data: () => ({
|
||||
isOnline: true,
|
||||
layout: null,
|
||||
layoutName: ''
|
||||
}),
|
||||
@ -65,8 +66,12 @@ export default {
|
||||
// Add this.$nuxt in child instances
|
||||
Vue.prototype.<%= globals.nuxt %> = this
|
||||
// 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
|
||||
this.refreshOnlineStatus()
|
||||
// Setup the listeners
|
||||
window.addEventListener('online', this.refreshOnlineStatus)
|
||||
window.addEventListener('offline', this.refreshOnlineStatus)
|
||||
}
|
||||
// Add $nuxt.error()
|
||||
this.error = this.nuxt.error
|
||||
@ -79,7 +84,24 @@ export default {
|
||||
'nuxt.err': 'errorChanged'
|
||||
},
|
||||
<% } %>
|
||||
computed: {
|
||||
isOffline() {
|
||||
return !this.isOnline
|
||||
}
|
||||
},
|
||||
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) { %>
|
||||
errorChanged() {
|
||||
if (this.nuxt.err && this.$loading) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import middleware from './middleware'
|
||||
import middleware from './middleware.js'
|
||||
import {
|
||||
applyAsyncData,
|
||||
sanitizeComponent,
|
||||
@ -14,8 +14,13 @@ import {
|
||||
compile,
|
||||
getQueryDiff,
|
||||
globalHandleError
|
||||
} from './utils'
|
||||
import { createApp, NuxtError } from './index'
|
||||
} from './utils.js'
|
||||
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 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 NoSsr from './components/no-ssr.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 Nuxt from './components/nuxt.js'
|
||||
import App from '<%= appPath %>'
|
||||
@ -23,9 +22,7 @@ Vue.component(NoSsr.name, NoSsr)
|
||||
Vue.component(NuxtChild.name, NuxtChild)
|
||||
Vue.component('NChild', NuxtChild)
|
||||
|
||||
// Component: <NuxtLink
|
||||
Vue.component(NuxtLink.name, NuxtLink)
|
||||
Vue.component('NLink', NuxtLink)
|
||||
// Component NuxtLink is imported in server.js or client.js
|
||||
|
||||
// Component: <Nuxt>`
|
||||
Vue.component(Nuxt.name, Nuxt)
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { stringify } from 'querystring'
|
||||
import Vue from 'vue'
|
||||
import omit from 'lodash/omit'
|
||||
import middleware from './middleware'
|
||||
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
|
||||
import { createApp, NuxtError } from './index'
|
||||
import middleware from './middleware.js'
|
||||
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils.js'
|
||||
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')
|
||||
debug.color = 4 // force blue color
|
||||
|
@ -566,4 +566,3 @@ function formatQuery(query) {
|
||||
return key + '=' + val
|
||||
}).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;
|
||||
}
|
||||
|
||||
export interface LoadingObject {
|
||||
start(): void;
|
||||
finish(): void;
|
||||
export interface NuxtApp extends Vue {
|
||||
isOffline: boolean;
|
||||
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 { Route } from "vue-router";
|
||||
import { MetaInfo } from "vue-meta";
|
||||
import { Context, Middleware, Transition, LoadingObject } from "./index";
|
||||
import { Context, Middleware, Transition, NuxtApp } from "./index";
|
||||
|
||||
declare module "vue/types/options" {
|
||||
interface ComponentOptions<V extends Vue> {
|
||||
@ -24,8 +24,6 @@ declare module "vue/types/options" {
|
||||
|
||||
declare module "vue/types/vue" {
|
||||
interface Vue {
|
||||
$nuxt: {
|
||||
$loading: LoadingObject;
|
||||
};
|
||||
$nuxt: NuxtApp;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"attributes": [
|
||||
"nuxtChildKey"
|
||||
],
|
||||
"description": "The nuxt component."
|
||||
"description": "Component to render the current nuxt page."
|
||||
},
|
||||
"n-child": {
|
||||
"description": "Component for displaying the children components in a nested route."
|
||||
@ -23,7 +23,7 @@
|
||||
"exact-active-class",
|
||||
"no-prefetch"
|
||||
],
|
||||
"description": "Component for routing. Same as <router-link> now."
|
||||
"description": "Component for navigating between Nuxt pages."
|
||||
},
|
||||
"nuxt-link": {
|
||||
"attributes": [
|
||||
@ -37,7 +37,7 @@
|
||||
"exact-active-class",
|
||||
"no-prefetch"
|
||||
],
|
||||
"description": "Component for routing. Same as <router-link> now."
|
||||
"description": "Component for navigating between Nuxt pages."
|
||||
},
|
||||
"no-ssr": {
|
||||
"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 responseSizeKilobytes = Math.ceil(responseSizeBytes / 1024)
|
||||
// Without gzip!
|
||||
expect(responseSizeKilobytes).toBeLessThanOrEqual(171)
|
||||
expect(responseSizeKilobytes).toBeLessThanOrEqual(180)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user