nuxt-link.client.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import Vue from 'vue'
  2. const requestIdleCallback = window.requestIdleCallback ||
  3. function (cb) {
  4. const start = Date.now()
  5. return setTimeout(function () {
  6. cb({
  7. didTimeout: false,
  8. timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
  9. })
  10. }, 1)
  11. }
  12. const cancelIdleCallback = window.cancelIdleCallback || function (id) {
  13. clearTimeout(id)
  14. }
  15. const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => {
  16. entries.forEach(({ intersectionRatio, target: link }) => {
  17. if (intersectionRatio <= 0 || !link.__prefetch) {
  18. return
  19. }
  20. link.__prefetch()
  21. })
  22. })
  23. export default {
  24. name: 'NuxtLink',
  25. extends: Vue.component('RouterLink'),
  26. props: {
  27. prefetch: {
  28. type: Boolean,
  29. default: true
  30. },
  31. noPrefetch: {
  32. type: Boolean,
  33. default: false
  34. }
  35. },
  36. mounted () {
  37. if (this.prefetch && !this.noPrefetch) {
  38. this.handleId = requestIdleCallback(this.observe, { timeout: 2e3 })
  39. }
  40. },
  41. beforeDestroy () {
  42. cancelIdleCallback(this.handleId)
  43. if (this.__observed) {
  44. observer.unobserve(this.$el)
  45. delete this.$el.__prefetch
  46. }
  47. },
  48. methods: {
  49. observe () {
  50. // If no IntersectionObserver, avoid prefetching
  51. if (!observer) {
  52. return
  53. }
  54. // Add to observer
  55. if (this.shouldPrefetch()) {
  56. this.$el.__prefetch = this.prefetchLink.bind(this)
  57. observer.observe(this.$el)
  58. this.__observed = true
  59. }
  60. },
  61. shouldPrefetch () {
  62. const ref = this.$router.resolve(this.to, this.$route, this.append)
  63. const Components = ref.resolved.matched.map(r => r.components.default)
  64. return Components.filter(Component => ref.href || (typeof Component === 'function' && !Component.options && !Component.__prefetched)).length
  65. },
  66. canPrefetch () {
  67. const conn = navigator.connection
  68. const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData))
  69. return !hasBadConnection
  70. },
  71. getPrefetchComponents () {
  72. const ref = this.$router.resolve(this.to, this.$route, this.append)
  73. const Components = ref.resolved.matched.map(r => r.components.default)
  74. return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched)
  75. },
  76. prefetchLink () {
  77. if (!this.canPrefetch()) {
  78. return
  79. }
  80. // Stop observing this link (in case of internet connection changes)
  81. observer.unobserve(this.$el)
  82. const Components = this.getPrefetchComponents()
  83. for (const Component of Components) {
  84. const componentOrPromise = Component()
  85. if (componentOrPromise instanceof Promise) {
  86. componentOrPromise.catch(() => {})
  87. }
  88. Component.__prefetched = true
  89. }
  90. // Preload the data only if not in preview mode
  91. if (!this.$root.isPreview) {
  92. const { href } = this.$router.resolve(this.to, this.$route, this.append)
  93. if (this.$nuxt)
  94. this.$nuxt.fetchPayload(href, true).catch(() => {})
  95. }
  96. }
  97. }
  98. }