Skip to content

Commit

Permalink
feat(vue-app): support app/router.scrollBehavior.js and deprecate `…
Browse files Browse the repository at this point in the history
…scrollBehavior` (#6055)
  • Loading branch information
atinux authored and Pooya Parsa committed Jul 24, 2019
1 parent ac00f7a commit f7cb3da
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 112 deletions.
58 changes: 33 additions & 25 deletions packages/builder/src/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down Expand Up @@ -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 () {
Expand Down
5 changes: 4 additions & 1 deletion packages/builder/test/__utils__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ export const createNuxt = () => ({
globalName: 'global_name',
globals: [],
build: {},
router: {}
router: {},
dir: {
app: 'app'
}
},
ready: jest.fn(),
hook: jest.fn(),
Expand Down
12 changes: 6 additions & 6 deletions packages/builder/test/builder.generate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {} }
])
})

Expand Down
4 changes: 2 additions & 2 deletions packages/builder/test/builder.watch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/config/_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default () => ({
],
dir: {
assets: 'assets',
app: 'app',
layouts: 'layouts',
middleware: 'middleware',
pages: 'pages',
Expand Down
6 changes: 6 additions & 0 deletions packages/config/src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
1 change: 1 addition & 0 deletions packages/config/test/__snapshots__/options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ Object {
"dev": false,
"devModules": Array [],
"dir": Object {
"app": "app",
"assets": "assets",
"layouts": "layouts",
"middleware": "middleware",
Expand Down
2 changes: 2 additions & 0 deletions packages/config/test/config/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Object {
"dev": false,
"devModules": Array [],
"dir": Object {
"app": "app",
"assets": "assets",
"layouts": "layouts",
"middleware": "middleware",
Expand Down Expand Up @@ -457,6 +458,7 @@ Object {
"dev": false,
"devModules": Array [],
"dir": Object {
"app": "app",
"assets": "assets",
"layouts": "layouts",
"middleware": "middleware",
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/test/serialize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/vue-app/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const template = {
'index.js',
'middleware.js',
'router.js',
'router.scrollBehavior.js',
'server.js',
'utils.js',
'empty.js',
Expand Down
76 changes: 1 addition & 75 deletions packages/vue-app/template/router.js
Original file line number Diff line number Diff line change
@@ -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 = ''
Expand Down Expand Up @@ -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 %>',
Expand Down
77 changes: 77 additions & 0 deletions packages/vue-app/template/router.scrollBehavior.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
}
<% } %>
3 changes: 2 additions & 1 deletion test/fixtures/with-config/with-config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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\''],
[{
Expand Down

0 comments on commit f7cb3da

Please sign in to comment.