mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(vue-app): support app/router.scrollBehavior.js
and deprecate scrollBehavior
(#6055)
This commit is contained in:
parent
ac00f7a627
commit
f7cb3dae0f
@ -405,42 +405,47 @@ export default class Builder {
|
||||
}
|
||||
|
||||
async resolveCustomTemplates (templateContext) {
|
||||
// Resolve template files
|
||||
// Sanitize custom template files
|
||||
this.options.build.templates = this.options.build.templates.map((t) => {
|
||||
const src = t.src || t
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
src: r(this.options.srcDir, src),
|
||||
dst: t.dst || path.basename(src),
|
||||
custom: true
|
||||
},
|
||||
typeof t === 'object' ? t : undefined
|
||||
)
|
||||
})
|
||||
const customTemplateFiles = this.options.build.templates.map(
|
||||
t => t.dst || path.basename(t.src || t)
|
||||
)
|
||||
const templatePaths = uniq([
|
||||
// Modules & user provided templates
|
||||
// first custom to keep their index
|
||||
...customTemplateFiles,
|
||||
// @nuxt/vue-app templates
|
||||
...templateContext.templateFiles
|
||||
])
|
||||
|
||||
const templateFiles = await Promise.all(templateContext.templateFiles.map(async (file) => {
|
||||
// Skip if custom file was already provided in build.templates[]
|
||||
if (customTemplateFiles.includes(file)) {
|
||||
return
|
||||
}
|
||||
templateContext.templateFiles = await Promise.all(templatePaths.map(async (file) => {
|
||||
// Use custom file if provided in build.templates[]
|
||||
const customTemplateIndex = customTemplateFiles.indexOf(file)
|
||||
const customTemplate = customTemplateIndex !== -1 ? this.options.build.templates[customTemplateIndex] : null
|
||||
let src = customTemplate ? (customTemplate.src || customTemplate) : r(this.template.dir, file)
|
||||
// Allow override templates using a file with same name in ${srcDir}/app
|
||||
const customPath = r(this.options.srcDir, 'app', file)
|
||||
const customPath = r(this.options.srcDir, this.options.dir.app, file)
|
||||
const customFileExists = await fsExtra.exists(customPath)
|
||||
src = customFileExists ? customPath : src
|
||||
|
||||
return {
|
||||
src: customFileExists ? customPath : r(this.template.dir, file),
|
||||
src,
|
||||
dst: file,
|
||||
custom: customFileExists
|
||||
custom: Boolean(customFileExists || customTemplate),
|
||||
options: (customTemplate && customTemplate.options) || {}
|
||||
}
|
||||
}))
|
||||
|
||||
templateContext.templateFiles = templateFiles
|
||||
.filter(Boolean)
|
||||
// Add custom template files
|
||||
.concat(
|
||||
this.options.build.templates.map((t) => {
|
||||
return Object.assign(
|
||||
{
|
||||
src: r(this.options.srcDir, t.src || t),
|
||||
dst: t.dst || path.basename(t.src || t),
|
||||
custom: true
|
||||
},
|
||||
typeof t === 'object' ? t : undefined
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async resolveLoadingIndicator ({ templateFiles }) {
|
||||
@ -628,6 +633,9 @@ export default class Builder {
|
||||
}
|
||||
|
||||
this.createFileWatcher(customPatterns, ['change'], refreshFiles, this.assignWatcher('custom'))
|
||||
|
||||
// Watch for app/ files
|
||||
this.createFileWatcher([r(this.options.srcDir, this.options.dir.app)], ['add', 'change', 'unlink'], refreshFiles, this.assignWatcher('app'))
|
||||
}
|
||||
|
||||
getServerMiddlewarePaths () {
|
||||
|
@ -3,7 +3,10 @@ export const createNuxt = () => ({
|
||||
globalName: 'global_name',
|
||||
globals: [],
|
||||
build: {},
|
||||
router: {}
|
||||
router: {},
|
||||
dir: {
|
||||
app: 'app'
|
||||
}
|
||||
},
|
||||
ready: jest.fn(),
|
||||
hook: jest.fn(),
|
||||
|
@ -209,12 +209,12 @@ describe('builder: builder generate', () => {
|
||||
await builder.resolveCustomTemplates(templateContext)
|
||||
|
||||
expect(templateContext.templateFiles).toEqual([
|
||||
{ custom: true, dst: 'router.js', src: 'r(/var/nuxt/src, app, router.js)' },
|
||||
{ custom: undefined, dst: 'store.js', src: 'r(/var/nuxt/templates, store.js)' },
|
||||
{ custom: undefined, dst: 'middleware.js', src: 'r(/var/nuxt/templates, middleware.js)' },
|
||||
{ custom: true, dst: 'foo.js', src: 'r(/var/nuxt/src, /var/nuxt/templates/foo.js)' },
|
||||
{ custom: true, dst: 'bar.js', src: '/var/nuxt/templates/bar.js' },
|
||||
{ custom: true, dst: 'baz.js', src: '/var/nuxt/templates/baz.js' }
|
||||
{ custom: true, dst: 'foo.js', src: 'r(/var/nuxt/src, app, foo.js)', options: {} },
|
||||
{ custom: true, dst: 'bar.js', src: '/var/nuxt/templates/bar.js', options: {} },
|
||||
{ custom: true, dst: 'baz.js', src: '/var/nuxt/templates/baz.js', options: {} },
|
||||
{ custom: false, dst: 'router.js', src: 'r(/var/nuxt/templates, router.js)', options: {} },
|
||||
{ custom: false, dst: 'store.js', src: 'r(/var/nuxt/templates, store.js)', options: {} },
|
||||
{ custom: false, dst: 'middleware.js', src: 'r(/var/nuxt/templates, middleware.js)', options: {} }
|
||||
])
|
||||
})
|
||||
|
||||
|
@ -189,9 +189,9 @@ describe('builder: builder watch', () => {
|
||||
'/var/nuxt/src/style'
|
||||
]
|
||||
|
||||
expect(builder.createFileWatcher).toBeCalledTimes(2)
|
||||
expect(builder.createFileWatcher).toBeCalledTimes(3)
|
||||
expect(builder.createFileWatcher).toBeCalledWith(patterns, ['change'], expect.any(Function), expect.any(Function))
|
||||
expect(builder.assignWatcher).toBeCalledTimes(2)
|
||||
expect(builder.assignWatcher).toBeCalledTimes(3)
|
||||
})
|
||||
|
||||
test('should invoke chokidar to create watcher', () => {
|
||||
|
@ -35,6 +35,7 @@ export default () => ({
|
||||
],
|
||||
dir: {
|
||||
assets: 'assets',
|
||||
app: 'app',
|
||||
layouts: 'layouts',
|
||||
middleware: 'middleware',
|
||||
pages: 'pages',
|
||||
|
@ -35,6 +35,12 @@ export function getNuxtConfig (_options) {
|
||||
options._routerBaseSpecified = true
|
||||
}
|
||||
|
||||
// TODO: Remove for Nuxt 3
|
||||
// router.scrollBehavior -> app/router.scrollBehavior.js
|
||||
if (options.router && typeof options.router.scrollBehavior !== 'undefined') {
|
||||
consola.warn('`router.scrollBehavior` property is deprecated in favor of using `~/app/router.scrollBehavior.js` file, learn more: https://nuxtjs.org/api/configuration-router#scrollbehavior')
|
||||
}
|
||||
|
||||
// TODO: Remove for Nuxt 3
|
||||
// transition -> pageTransition
|
||||
if (typeof options.transition !== 'undefined') {
|
||||
|
@ -151,6 +151,7 @@ Object {
|
||||
"dev": false,
|
||||
"devModules": Array [],
|
||||
"dir": Object {
|
||||
"app": "app",
|
||||
"assets": "assets",
|
||||
"layouts": "layouts",
|
||||
"middleware": "middleware",
|
||||
|
@ -131,6 +131,7 @@ Object {
|
||||
"dev": false,
|
||||
"devModules": Array [],
|
||||
"dir": Object {
|
||||
"app": "app",
|
||||
"assets": "assets",
|
||||
"layouts": "layouts",
|
||||
"middleware": "middleware",
|
||||
@ -457,6 +458,7 @@ Object {
|
||||
"dev": false,
|
||||
"devModules": Array [],
|
||||
"dir": Object {
|
||||
"app": "app",
|
||||
"assets": "assets",
|
||||
"layouts": "layouts",
|
||||
"middleware": "middleware",
|
||||
|
@ -26,9 +26,9 @@ describe('util: serialize', () => {
|
||||
|
||||
test('should serialize normal function', () => {
|
||||
const obj = {
|
||||
fn () {}
|
||||
fn: function () {} // eslint-disable-line object-shorthand
|
||||
}
|
||||
expect(serializeFunction(obj.fn)).toEqual('function() {}')
|
||||
expect(serializeFunction(obj.fn)).toEqual('function () {}')
|
||||
})
|
||||
|
||||
test('should serialize shorthand function', () => {
|
||||
|
@ -10,6 +10,7 @@ export const template = {
|
||||
'index.js',
|
||||
'middleware.js',
|
||||
'router.js',
|
||||
'router.scrollBehavior.js',
|
||||
'server.js',
|
||||
'utils.js',
|
||||
'empty.js',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import { interopDefault } from './utils'<%= isTest ? '// eslint-disable-line no-unused-vars' : '' %>
|
||||
import scrollBehavior from './router.scrollBehavior.js'
|
||||
|
||||
<% function recursiveRoutes(routes, tab, components, indentCount) {
|
||||
let res = ''
|
||||
@ -83,81 +84,6 @@ const _routes = recursiveRoutes(router.routes, ' ', _components, 2)
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
<% if (router.scrollBehavior) { %>
|
||||
const scrollBehavior = <%= serializeFunction(router.scrollBehavior) %>
|
||||
<% } else { %>
|
||||
if (process.client) {
|
||||
if ('scrollRestoration' in window.history) {
|
||||
window.history.scrollRestoration = 'manual'
|
||||
|
||||
// reset scrollRestoration to auto when leaving page, allowing page reload
|
||||
// and back-navigation from other pages to use the browser to restore the
|
||||
// scrolling position.
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.history.scrollRestoration = 'auto'
|
||||
})
|
||||
|
||||
// Setting scrollRestoration to manual again when returning to this page.
|
||||
window.addEventListener('load', () => {
|
||||
window.history.scrollRestoration = 'manual'
|
||||
})
|
||||
}
|
||||
}
|
||||
const scrollBehavior = function (to, from, savedPosition) {
|
||||
// if the returned position is falsy or an empty object,
|
||||
// will retain current scroll position.
|
||||
let position = false
|
||||
|
||||
// if no children detected and scrollToTop is not explicitly disabled
|
||||
if (
|
||||
to.matched.length < 2 &&
|
||||
to.matched.every(r => r.components.default.options.scrollToTop !== false)
|
||||
) {
|
||||
// scroll to the top of the page
|
||||
position = { x: 0, y: 0 }
|
||||
} else if (to.matched.some(r => r.components.default.options.scrollToTop)) {
|
||||
// if one of the children has scrollToTop option set to true
|
||||
position = { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
// savedPosition is only available for popstate navigations (back button)
|
||||
if (savedPosition) {
|
||||
position = savedPosition
|
||||
}
|
||||
|
||||
const nuxt = window.<%= globals.nuxt %>
|
||||
|
||||
// triggerScroll is only fired when a new component is loaded
|
||||
if (to.path === from.path && to.hash !== from.hash) {
|
||||
nuxt.$nextTick(() => nuxt.$emit('triggerScroll'))
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// wait for the out transition to complete (if necessary)
|
||||
nuxt.$once('triggerScroll', () => {
|
||||
// coords will be used if no selector is provided,
|
||||
// or if the selector didn't match any element.
|
||||
if (to.hash) {
|
||||
let hash = to.hash
|
||||
// CSS.escape() is not supported with IE and Edge.
|
||||
if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') {
|
||||
hash = '#' + window.CSS.escape(hash.substr(1))
|
||||
}
|
||||
try {
|
||||
if (document.querySelector(hash)) {
|
||||
// scroll to anchor by returning the selector
|
||||
position = { selector: hash }
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).')
|
||||
}
|
||||
}
|
||||
resolve(position)
|
||||
})
|
||||
})
|
||||
}
|
||||
<% } %>
|
||||
|
||||
export function createRouter() {
|
||||
return new Router({
|
||||
mode: '<%= router.mode %>',
|
||||
|
77
packages/vue-app/template/router.scrollBehavior.js
Normal file
77
packages/vue-app/template/router.scrollBehavior.js
Normal file
@ -0,0 +1,77 @@
|
||||
<% if (router.scrollBehavior) { %>
|
||||
export default <%= serializeFunction(router.scrollBehavior) %>
|
||||
<% } else { %>import { getMatchedComponents } from './utils'
|
||||
|
||||
if (process.client) {
|
||||
if ('scrollRestoration' in window.history) {
|
||||
window.history.scrollRestoration = 'manual'
|
||||
|
||||
// reset scrollRestoration to auto when leaving page, allowing page reload
|
||||
// and back-navigation from other pages to use the browser to restore the
|
||||
// scrolling position.
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.history.scrollRestoration = 'auto'
|
||||
})
|
||||
|
||||
// Setting scrollRestoration to manual again when returning to this page.
|
||||
window.addEventListener('load', () => {
|
||||
window.history.scrollRestoration = 'manual'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default function (to, from, savedPosition) {
|
||||
// if the returned position is falsy or an empty object,
|
||||
// will retain current scroll position.
|
||||
let position = false
|
||||
|
||||
// if no children detected and scrollToTop is not explicitly disabled
|
||||
const Pages = getMatchedComponents(to)
|
||||
if (
|
||||
Pages.length < 2 &&
|
||||
Pages.every(Page => Page.options.scrollToTop !== false)
|
||||
) {
|
||||
// scroll to the top of the page
|
||||
position = { x: 0, y: 0 }
|
||||
} else if (Pages.some(Page => Page.options.scrollToTop)) {
|
||||
// if one of the children has scrollToTop option set to true
|
||||
position = { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
// savedPosition is only available for popstate navigations (back button)
|
||||
if (savedPosition) {
|
||||
position = savedPosition
|
||||
}
|
||||
|
||||
const nuxt = window.<%= globals.nuxt %>
|
||||
|
||||
// triggerScroll is only fired when a new component is loaded
|
||||
if (to.path === from.path && to.hash !== from.hash) {
|
||||
nuxt.$nextTick(() => nuxt.$emit('triggerScroll'))
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// wait for the out transition to complete (if necessary)
|
||||
nuxt.$once('triggerScroll', () => {
|
||||
// coords will be used if no selector is provided,
|
||||
// or if the selector didn't match any element.
|
||||
if (to.hash) {
|
||||
let hash = to.hash
|
||||
// CSS.escape() is not supported with IE and Edge.
|
||||
if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') {
|
||||
hash = '#' + window.CSS.escape(hash.substr(1))
|
||||
}
|
||||
try {
|
||||
if (document.querySelector(hash)) {
|
||||
// scroll to anchor by returning the selector
|
||||
position = { selector: hash }
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).')
|
||||
}
|
||||
}
|
||||
resolve(position)
|
||||
})
|
||||
})
|
||||
}
|
||||
<% } %>
|
@ -14,9 +14,10 @@ const hooks = [
|
||||
|
||||
describe('with-config', () => {
|
||||
buildFixture('with-config', () => {
|
||||
expect(consola.warn).toHaveBeenCalledTimes(6)
|
||||
expect(consola.warn).toHaveBeenCalledTimes(7)
|
||||
expect(consola.fatal).toHaveBeenCalledTimes(0)
|
||||
expect(consola.warn.mock.calls).toMatchObject([
|
||||
['`router.scrollBehavior` property is deprecated in favor of using `~/app/router.scrollBehavior.js` file, learn more: https://nuxtjs.org/api/configuration-router#scrollbehavior'],
|
||||
['Unknown mode: unknown. Falling back to universal'],
|
||||
['Invalid plugin mode (server/client/all): \'abc\'. Falling back to \'all\''],
|
||||
[{
|
||||
|
Loading…
Reference in New Issue
Block a user