Nuxt/packages/vue-app/template/components/nuxt-link.client.js
Sébastien Chopin f319033928
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
2018-12-28 17:27:03 +01:00

102 lines
2.8 KiB
JavaScript

<%= 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()
}
}<% } %>
}
}