From b6856928db7e41989ceb2505bd05d2f60589c62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 17 Mar 2017 18:02:58 +0100 Subject: [PATCH] Dynamic layout feature --- lib/app/client.js | 136 ++++++++++++++++++++++++++++++---------------- lib/app/server.js | 24 ++++++-- 2 files changed, 109 insertions(+), 51 deletions(-) diff --git a/lib/app/client.js b/lib/app/client.js index d3882de0e8..8a04cc59c8 100644 --- a/lib/app/client.js +++ b/lib/app/client.js @@ -55,22 +55,28 @@ function loadAsyncComponents (to, from, next) { } function callMiddleware (Components, context, layout) { - // Call middleware + // if layout is undefined, only call global middleware let midd = <%= serialize(router.middleware, { isJSON: true }) %> - if (layout.middleware) { - midd = midd.concat(layout.middleware) - } - Components.forEach((Component) => { - if (Component.options.middleware) { - midd = midd.concat(Component.options.middleware) + let unknownMiddleware = false + if (typeof layout !== 'undefined') { + midd = [] // exclude global middleware if layout defined (already called before) + if (layout.middleware) { + midd = midd.concat(layout.middleware) } - }) + Components.forEach((Component) => { + if (Component.options.middleware) { + midd = midd.concat(Component.options.middleware) + } + }) + } midd = midd.map((name) => { if (typeof middleware[name] !== 'function') { + unknownMiddleware = true this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) } return middleware[name] }) + if (unknownMiddleware) return return promiseSeries(midd, context) } @@ -81,13 +87,15 @@ function render (to, from, next) { nextCalled = true next(path) } - const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }) + let context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }) let Components = getMatchedComponents(to) + this._context = context this._dateLastError = this.$options._nuxt.dateErr this._hadError = !!this.$options._nuxt.err if (!Components.length) { // Default layout - this.loadLayout(NuxtError.layout) + callMiddleware.call(this, Components, context) + .then(() => this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout)) .then(callMiddleware.bind(this, Components, context)) .then(() => { this.error({ statusCode: 404, message: 'This page could not be found.' }) @@ -105,7 +113,14 @@ function render (to, from, next) { this.setTransitions(mapTransitions(Components, to, from)) let nextCalled = false // Set layout - this.loadLayout(Components[0].options.layout) + callMiddleware.call(this, Components, context) + .then(() => { + let layout = Components[0].options.layout + if (typeof layout === 'function') { + layout = layout(context) + } + return this.loadLayout(layout) + }) .then(callMiddleware.bind(this, Components, context)) .then(() => { // Pass validation? @@ -169,7 +184,11 @@ function render (to, from, next) { .catch((error) => { _lastPaths = [] error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500 - this.loadLayout(NuxtError.layout) + let layout = NuxtError.layout + if (typeof layout === 'function') { + layout = layout(context) + } + this.loadLayout(layout) .then(() => { this.error(error) next(false) @@ -211,7 +230,11 @@ function fixPrepatch (to, ___) { this.error() } // Set layout - this.setLayout(this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout) + let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout + if (typeof layout === 'function') { + layout = layout(this._context) + } + this.setLayout(layout) // hot reloading Vue.nextTick(() => hotReloadAPI(this)) }) @@ -246,7 +269,8 @@ function addHotReload ($component, depth) { $component.$vnode.data._hasHotReload = true var _forceUpdate = $component.$forceUpdate.bind($component.$parent) $component.$vnode.context.$forceUpdate = () => { - let Component = getMatchedComponents(router.currentRoute)[depth] + let Components = getMatchedComponents(router.currentRoute) + let Component = Components[depth] if (!Component) return _forceUpdate() if (typeof Component === 'object' && !Component.options) { // Updated via vue-router resolveAsyncComponents() @@ -255,41 +279,51 @@ function addHotReload ($component, depth) { } this.error() let promises = [] - if (depth === 0) { - // If layout changed - Component.options.layout = Component.options.layout || 'default' - if (this.layoutName !== Component.options.layout) { - let promise = this.loadLayout(Component.options.layout) - promise.then(() => { - this.setLayout(Component.options.layout) - Vue.nextTick(() => hotReloadAPI(this)) - }) - promises.push(promise) - } - } const next = function (path) { <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> router.push(path) } - const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: this.error }) - // Call asyncData() - let pAsyncData = promisify(Component.options.asyncData || noopData, context) - pAsyncData.then((asyncDataResult) => { - let data = (typeof Component.options.data === 'function' ? Component.options.data() : noopData()) - data = Object.assign(data, asyncDataResult) - Component.options.data = () => data - Component._Ctor.options.data = Component.options.data - <%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %> - }) - promises.push(pAsyncData) - // Call fetch() - Component.options.fetch = Component.options.fetch || noopFetch - let pFetch = Component.options.fetch(context) - if (!(pFetch instanceof Promise)) { pFetch = Promise.resolve(pFetch) } - <%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %> - promises.push(pFetch) + let context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: this.error }) <%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %> - return Promise.all(promises).then(() => { + callMiddleware.call(this, Components, context) + .then(() => { + // If layout changed + if (depth !== 0) return Promise.resolve() + let layout = Component.options.layout || 'default' + if (typeof layout === 'function') { + layout = layout(context) + } + if (this.layoutName === layout) return Promise.resolve() + let promise = this.loadLayout(layout) + promise.then(() => { + this.setLayout(layout) + Vue.nextTick(() => hotReloadAPI(this)) + }) + return promise + }) + .then(() => { + return callMiddleware.call(this, Components, context, this.layout) + }) + .then(() => { + // Call asyncData() + let pAsyncData = promisify(Component.options.asyncData || noopData, context) + pAsyncData.then((asyncDataResult) => { + let data = (typeof Component.options.data === 'function' ? Component.options.data() : noopData()) + data = Object.assign(data, asyncDataResult) + Component.options.data = () => data + Component._Ctor.options.data = Component.options.data + <%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %> + }) + promises.push(pAsyncData) + // Call fetch() + Component.options.fetch = Component.options.fetch || noopFetch + let pFetch = Component.options.fetch(context) + if (!(pFetch instanceof Promise)) { pFetch = Promise.resolve(pFetch) } + <%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %> + promises.push(pFetch) + return Promise.all(promises) + }) + .then(() => { <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> _forceUpdate() setTimeout(() => hotReloadAPI(this), 100) @@ -352,10 +386,18 @@ Promise.all(resolveComponents) .then((Components) => { const _app = new Vue(app) - let layout = Components.length ? Components[0].options.layout : NuxtError.layout - return _app.loadLayout(layout) + let context = getContext({ to: router.currentRoute, isClient: true }) + let layoutName = 'default' + return callMiddleware.call(_app, Components, context) .then(() => { - _app.setLayout(layout) + layoutName = Components.length ? Components[0].options.layout : NuxtError.layout + if (typeof layoutName === 'function') { + layoutName = layoutName(context) + } + return _app.loadLayout(layoutName) + }) + .then(() => { + _app.setLayout(layoutName) return { _app, Components } }) }) diff --git a/lib/app/server.js b/lib/app/server.js index 1bdae940ec..16965bf96a 100644 --- a/lib/app/server.js +++ b/lib/app/server.js @@ -50,7 +50,7 @@ export default context => { context.error = _app.$options._nuxt.error.bind(_app) <%= (isDev ? 'const s = isDev && Date.now()' : '') %> - const ctx = getContext(context) + let ctx = getContext(context) let Components = getMatchedComponents(context.route) <% if (store) { %> let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null) @@ -72,12 +72,28 @@ export default context => { } return Component }) + // Call global middleware (nuxt.config.js) + let midd = <%= serialize(router.middleware, { isJSON: true }) %> + midd = midd.map((name) => { + if (typeof middleware[name] !== 'function') { + context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) + } + return middleware[name] + }) + if (context.nuxt.error) return + return promiseSeries(midd, ctx) + }) + .then(() => { // Set layout - return _app.setLayout(Components.length ? Components[0].options.layout : NuxtError.layout) + let layout = Components.length ? Components[0].options.layout : NuxtError.layout + if (typeof layout === 'function') { + layout = layout(ctx) + } + return _app.setLayout(layout) }) .then((layout) => { - // Call middleware - let midd = <%= serialize(router.middleware, { isJSON: true }) %> + // Call middleware (layout + pages) + let midd = [] if (layout.middleware) { midd = midd.concat(layout.middleware) }