nuxt-link.client.js 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  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. return this.getPrefetchComponents().length > 0
  63. },
  64. canPrefetch () {
  65. const conn = navigator.connection
  66. const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData))
  67. return !hasBadConnection
  68. },
  69. getPrefetchComponents () {
  70. const ref = this.$router.resolve(this.to, this.$route, this.append)
  71. const Components = ref.resolved.matched.map(r => r.components.default)
  72. return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched)
  73. },
  74. prefetchLink () {
  75. if (!this.canPrefetch()) {
  76. return
  77. }
  78. // Stop observing this link (in case of internet connection changes)
  79. observer.unobserve(this.$el)
  80. const Components = this.getPrefetchComponents()
  81. for (const Component of Components) {
  82. const componentOrPromise = Component()
  83. if (componentOrPromise instanceof Promise) {
  84. componentOrPromise.catch(() => {})
  85. }
  86. Component.__prefetched = true
  87. }
  88. }
  89. }
  90. }