From 7b3155347cb81a96b9f5080340615161a024312f Mon Sep 17 00:00:00 2001 From: Louis-Marie Michelin <33673240+lmichelin@users.noreply.github.com> Date: Wed, 18 Sep 2019 17:06:46 +0200 Subject: [PATCH] feat(vue-app): add `prefetch` prop to `` (#6292) --- packages/vue-app/template/client.js | 2 +- .../template/components/nuxt-link.client.js | 10 +- .../template/components/nuxt-link.server.js | 4 + packages/vue-app/test/__utils__/index.js | 2 +- .../test/nuxt-link.client.prefetch.test.js | 163 ++++++++++++++++++ packages/vue-app/vetur/nuxt-attributes.json | 4 + packages/vue-app/vetur/nuxt-tags.json | 2 + 7 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 packages/vue-app/test/nuxt-link.client.prefetch.test.js diff --git a/packages/vue-app/template/client.js b/packages/vue-app/template/client.js index 4dc2f6e9d8..9aa4b66766 100644 --- a/packages/vue-app/template/client.js +++ b/packages/vue-app/template/client.js @@ -17,7 +17,7 @@ import { globalHandleError } from './utils.js' import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js' -import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch && router.prefetchLinks ? "client" : "server" %>.js' // should be included after ./index.js +import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch ? "client" : "server" %>.js' // should be included after ./index.js <% if (isDev) { %>import consola from 'consola'<% } %> <% if (isDev) { %>consola.wrapConsole() diff --git a/packages/vue-app/template/components/nuxt-link.client.js b/packages/vue-app/template/components/nuxt-link.client.js index bb344255d6..fe3fdb1bf2 100644 --- a/packages/vue-app/template/components/nuxt-link.client.js +++ b/packages/vue-app/template/components/nuxt-link.client.js @@ -29,6 +29,10 @@ export default { name: 'NuxtLink', extends: Vue.component('RouterLink'), props: { + prefetch: { + type: Boolean, + default: <%= router.prefetchLinks ? 'true' : 'false' %> + }, noPrefetch: { type: Boolean, default: false @@ -39,7 +43,7 @@ export default { }<% } %> }, mounted () { - if (!this.noPrefetch) { + if (this.prefetch && !this.noPrefetch) { this.handleId = requestIdleCallback(this.observe, { timeout: 2e3 }) } }, @@ -59,7 +63,7 @@ export default { } // Add to observer if (this.shouldPrefetch()) { - this.$el.__prefetch = this.prefetch.bind(this) + this.$el.__prefetch = this.prefetchLink.bind(this) observer.observe(this.$el) this.__observed = true }<% if (router.linkPrefetchedClass) { %> else { @@ -81,7 +85,7 @@ export default { return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched) }, - prefetch () { + prefetchLink () { if (!this.canPrefetch()) { return } diff --git a/packages/vue-app/template/components/nuxt-link.server.js b/packages/vue-app/template/components/nuxt-link.server.js index bd00f7ed31..9a228f9409 100644 --- a/packages/vue-app/template/components/nuxt-link.server.js +++ b/packages/vue-app/template/components/nuxt-link.server.js @@ -5,6 +5,10 @@ export default { name: 'NuxtLink', extends: Vue.component('RouterLink'), props: { + prefetch: { + type: Boolean, + default: <%= router.prefetchLinks ? 'true' : 'false' %> + }, noPrefetch: { type: Boolean, default: false diff --git a/packages/vue-app/test/__utils__/index.js b/packages/vue-app/test/__utils__/index.js index 63afe3db5f..2621114faa 100644 --- a/packages/vue-app/test/__utils__/index.js +++ b/packages/vue-app/test/__utils__/index.js @@ -32,7 +32,7 @@ export async function compileTemplate (template, destination, options = {}) { if (typeof template === 'string') { return { src: path.resolve(rootDir, '../template', template), - dst: path.join(rootDir, '.nuxt', path.basename(template)), + dst: path.join(rootDir, '.nuxt', destination || path.basename(template)), custom: false } } diff --git a/packages/vue-app/test/nuxt-link.client.prefetch.test.js b/packages/vue-app/test/nuxt-link.client.prefetch.test.js new file mode 100644 index 0000000000..1f4330b6f2 --- /dev/null +++ b/packages/vue-app/test/nuxt-link.client.prefetch.test.js @@ -0,0 +1,163 @@ +/** + * @jest-environment jsdom + */ +import { mount, RouterLinkStub } from '@vue/test-utils' +import { vmTick, compileTemplate, importComponent } from './__utils__' + +/* eslint-disable no-console */ +describe('nuxt-link prefetch', () => { + beforeAll(() => { + jest.useFakeTimers() + + jest.spyOn(console, 'warn') + jest.spyOn(console, 'error') + }) + + afterAll(() => jest.restoreAllMocks()) + + test('when router.prefetchLinks is set to false, link with no prop should not be prefetched', + async () => { + const compiledTemplatePath = await compileTemplate( + 'components/nuxt-link.client.js', + 'nuxt-link.client.prefetch.0.js', + { router: { prefetchLinks: false } } + ) + + const Component = await importComponent(compiledTemplatePath) + Component.extends = RouterLinkStub + + const methods = { observe: jest.fn() } + + const wrapper = mount(Component, { + propsData: { to: '/link' }, + methods + }) + + jest.runAllTimers() + await vmTick(wrapper.vm) + + expect(console.warn).not.toHaveBeenCalled() + expect(console.error).not.toHaveBeenCalled() + + expect(wrapper.props('prefetch')).toBe(false) + expect(wrapper.props('noPrefetch')).toBe(false) + expect(methods.observe).not.toHaveBeenCalled() + }) + + test('when router.prefetchLinks is set to false, link with prefetch prop set to true should be prefetched', + async () => { + const compiledTemplatePath = await compileTemplate( + 'components/nuxt-link.client.js', + 'nuxt-link.client.prefetch.1.js', + { router: { prefetchLinks: false } } + ) + + const Component = await importComponent(compiledTemplatePath) + Component.extends = RouterLinkStub + + const methods = { observe: jest.fn() } + + const wrapper = mount(Component, { + propsData: { to: '/link', prefetch: true }, + methods + }) + + jest.runAllTimers() + await vmTick(wrapper.vm) + + expect(console.warn).not.toHaveBeenCalled() + expect(console.error).not.toHaveBeenCalled() + + expect(wrapper.props('prefetch')).toBe(true) + expect(wrapper.props('noPrefetch')).toBe(false) + expect(methods.observe).toHaveBeenCalled() + }) + + test('when router.prefetchLinks is set to true (default), link with no prop should be prefetched', + async () => { + const compiledTemplatePath = await compileTemplate( + 'components/nuxt-link.client.js', + 'nuxt-link.client.prefetch.2.js', + {} + ) + + const Component = await importComponent(compiledTemplatePath) + Component.extends = RouterLinkStub + + const methods = { observe: jest.fn() } + + const wrapper = mount(Component, { + propsData: { to: '/link' }, + methods + }) + + jest.runAllTimers() + await vmTick(wrapper.vm) + + expect(console.warn).not.toHaveBeenCalled() + expect(console.error).not.toHaveBeenCalled() + + expect(wrapper.props('prefetch')).toBe(true) + expect(wrapper.props('noPrefetch')).toBe(false) + expect(methods.observe).toHaveBeenCalled() + }) + + test('when router.prefetchLinks is set to true (default), link with prefetch prop set to false should not be prefetched', + async () => { + const compiledTemplatePath = await compileTemplate( + 'components/nuxt-link.client.js', + 'nuxt-link.client.prefetch.3.js', + {} + ) + + const Component = await importComponent(compiledTemplatePath) + Component.extends = RouterLinkStub + + const methods = { observe: jest.fn() } + + const wrapper = mount(Component, { + propsData: { to: '/link', prefetch: false }, + methods + }) + + jest.runAllTimers() + await vmTick(wrapper.vm) + + expect(console.warn).not.toHaveBeenCalled() + expect(console.error).not.toHaveBeenCalled() + + expect(wrapper.props('prefetch')).toBe(false) + expect(wrapper.props('noPrefetch')).toBe(false) + expect(methods.observe).not.toHaveBeenCalled() + }) + + test('when router.prefetchLinks is set to true (default), link with noPrefetch prop should not be prefetched', + async () => { + const compiledTemplatePath = await compileTemplate( + 'components/nuxt-link.client.js', + 'nuxt-link.client.prefetch.4.js', + {} + ) + + const Component = await importComponent(compiledTemplatePath) + Component.extends = RouterLinkStub + + const methods = { observe: jest.fn() } + + const wrapper = mount(Component, { + propsData: { to: '/link', noPrefetch: true }, + methods + }) + + jest.runAllTimers() + await vmTick(wrapper.vm) + + expect(console.warn).not.toHaveBeenCalled() + expect(console.error).not.toHaveBeenCalled() + + expect(wrapper.props('prefetch')).toBe(true) + expect(wrapper.props('noPrefetch')).toBe(true) + expect(methods.observe).not.toHaveBeenCalled() + }) +}) +/* eslint-enable no-console */ diff --git a/packages/vue-app/vetur/nuxt-attributes.json b/packages/vue-app/vetur/nuxt-attributes.json index a95944b881..d6bbfa21fb 100644 --- a/packages/vue-app/vetur/nuxt-attributes.json +++ b/packages/vue-app/vetur/nuxt-attributes.json @@ -5,6 +5,10 @@ "to": { "description": "Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a location descriptor object." }, + "prefetch": { + "type": "boolean", + "description": "Prefetch route target (overrides router.prefetchLinks value in nuxt.config.js)." + }, "no-prefetch": { "description": "Avoid prefetching route target." }, diff --git a/packages/vue-app/vetur/nuxt-tags.json b/packages/vue-app/vetur/nuxt-tags.json index a2d05df3f0..3fc2c28194 100644 --- a/packages/vue-app/vetur/nuxt-tags.json +++ b/packages/vue-app/vetur/nuxt-tags.json @@ -21,6 +21,7 @@ "exact", "event", "exact-active-class", + "prefetch", "no-prefetch" ], "description": "Component for navigating between Nuxt pages." @@ -35,6 +36,7 @@ "exact", "event", "exact-active-class", + "prefetch", "no-prefetch" ], "description": "Component for navigating between Nuxt pages."