From 3a0fcdee73d3230259d4db05517692e6ba1c9d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Sat, 24 Dec 2016 12:34:41 +0100 Subject: [PATCH] Layout with code-splitting! --- examples/custom-layout/layouts/error.vue | 16 +++- examples/custom-layout/static/logo.png | Bin 2089 -> 4252 bytes lib/app/App.vue | 28 +++++- lib/app/client.js | 117 +++++++++++++---------- lib/app/server.js | 6 +- 5 files changed, 106 insertions(+), 61 deletions(-) diff --git a/examples/custom-layout/layouts/error.vue b/examples/custom-layout/layouts/error.vue index 522cd13f4b..0a6bb332e3 100644 --- a/examples/custom-layout/layouts/error.vue +++ b/examples/custom-layout/layouts/error.vue @@ -1,5 +1,8 @@ + + diff --git a/examples/custom-layout/static/logo.png b/examples/custom-layout/static/logo.png index 9d716abf2216134ba4ddb8f24108120beb4b8660..7f238b598c85d618ae0c601fec86455a2a1ed4e1 100644 GIT binary patch literal 4252 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 TWz)6e00000NkvXXu0mjf@SEx3 diff --git a/lib/app/App.vue b/lib/app/App.vue index b3ecb7b7af..c69572212e 100644 --- a/lib/app/App.vue +++ b/lib/app/App.vue @@ -1,6 +1,6 @@ @@ -9,19 +9,37 @@ let layouts = { <% var layoutsKeys = Object.keys(layouts); layoutsKeys.forEach(function (key, i) { %> - _<%= key %>: require('<%= layouts[key] %>')<%= (i + 1) < layoutsKeys.length ? ',' : '' %> + _<%= key %>: process.BROWSER_BUILD ? () => System.import('<%= layouts[key] %>') : require('<%= layouts[key] %>')<%= (i + 1) < layoutsKeys.length ? ',' : '' %> <% }) %> } export default { data () { - return { layout: layouts._default } + return { layout: null } }, methods: { setLayout (layout) { if (!layout || !layouts['_' + layout]) layout = 'default' - this.layout = layouts['_' + layout] - return layout + let _layout = '_' + layout + if (typeof layouts[_layout] === 'function') { + return this.loadLayout(_layout) + } + this.layout = layouts[_layout] + return Promise.resolve(this.layout) + }, + loadLayout (_layout) { + return layouts[_layout]() + .then((Component) => { + layouts[_layout] = Component + this.layout = layouts[_layout] + return this.layout + }) + .catch((e) => { + if (this.$nuxt) { + return this.$nuxt.error({ statusCode: 500, message: e.message }) + } + console.error(e) + }) } } } diff --git a/lib/app/client.js b/lib/app/client.js index 7e1c226ad1..6dfd426abd 100644 --- a/lib/app/client.js +++ b/lib/app/client.js @@ -58,8 +58,13 @@ function loadAsyncComponents (to, ___, next) { function render (to, from, next) { let Components = getMatchedComponents(to) if (!Components.length) { - this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path }) - return next() + // Default layout + this.setLayout() + .then(() => { + this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path }) + return next() + }) + return } // Update ._data and other properties if hot reloaded Components.forEach(function (Component) { @@ -79,57 +84,61 @@ function render (to, from, next) { } } }) - this.setLayout(Components[0].options.layout) this.setTransitions(mapTransitions(Components, to, from)) this.error() let nextCalled = false - let isValid = true - Components.forEach((Component) => { - if (!isValid) return - if (typeof Component.options.validate !== 'function') return - isValid = Component.options.validate({ - params: to.params || {}, - query: to.query || {} - }) - }) - if (!isValid) { - this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path }) - return next() - } - Promise.all(Components.map((Component, i) => { - // Check if only children route changed - Component._path = compile(to.matched[i].path)(to.params) - if (Component._path === _lastPaths[i] && (i + 1) !== Components.length) { - return Promise.resolve() - } - let promises = [] - const _next = function (path) { - <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> - nextCalled = true - next(path) - } - const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }) - // Validate method - if (Component._data && typeof Component._data === 'function') { - var promise = promisify(Component._data, context) - promise.then((data) => { - Component.options.data = () => data || {} - Component._dataFn = Component.options.data.toString().replace(/\s/g, '') - if (Component._Ctor && Component._Ctor.options) { - Component._Ctor.options.data = Component.options.data - } - <%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %> + // Set layout + this.setLayout(Components[0].options.layout) + .then(() => { + // Pass validation? + let isValid = true + Components.forEach((Component) => { + if (!isValid) return + if (typeof Component.options.validate !== 'function') return + isValid = Component.options.validate({ + params: to.params || {}, + query: to.query || {} }) - promises.push(promise) + }) + if (!isValid) { + this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path }) + return next() } - if (Component.options.fetch) { - var p = Component.options.fetch(context) - if (!(p instanceof Promise)) { p = Promise.resolve(p) } - <%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %> - promises.push(p) - } - return Promise.all(promises) - })) + return Promise.all(Components.map((Component, i) => { + // Check if only children route changed + Component._path = compile(to.matched[i].path)(to.params) + if (Component._path === _lastPaths[i] && (i + 1) !== Components.length) { + return Promise.resolve() + } + let promises = [] + const _next = function (path) { + <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> + nextCalled = true + next(path) + } + const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }) + // Validate method + if (Component._data && typeof Component._data === 'function') { + var promise = promisify(Component._data, context) + promise.then((data) => { + Component.options.data = () => data || {} + Component._dataFn = Component.options.data.toString().replace(/\s/g, '') + if (Component._Ctor && Component._Ctor.options) { + Component._Ctor.options.data = Component.options.data + } + <%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %> + }) + promises.push(promise) + } + if (Component.options.fetch) { + var p = Component.options.fetch(context) + if (!(p instanceof Promise)) { p = Promise.resolve(p) } + <%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %> + promises.push(p) + } + return Promise.all(promises) + })) + }) .then(() => { _lastPaths = Components.map((Component, i) => compile(to.matched[i].path)(to.params)) <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> @@ -175,8 +184,8 @@ function hotReloadAPI (_app) { Component = Vue.extend(Component) Component._Ctor = Component } - _app.setLayout(Component.options.layout) let promises = [] + promises.push(_app.setLayout(Component.options.layout)) const next = function (path) { <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> router.push(path) @@ -268,6 +277,13 @@ function nuxtReady (app) { Promise.all(resolveComponents) .then((Components) => { const _app = new Vue(app) + + return _app.setLayout(Components.length ? Components[0].options.layout : '') + .then(() => { + return { _app, Components } + }) +}) +.then(({ _app, Components }) => { const mountApp = () => { _app.$mount('#__nuxt') <% if (loading) { %> @@ -285,9 +301,6 @@ Promise.all(resolveComponents) store.replaceState(NUXT.state) } <% } %> - if (Components.length) { - _app.setLayout(Components[0].options.layout) - } _app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app) if (Components.length) { _app.setTransitions(mapTransitions(Components, router.currentRoute)) diff --git a/lib/app/server.js b/lib/app/server.js index 2846e56716..0a6704de44 100644 --- a/lib/app/server.js +++ b/lib/app/server.js @@ -74,9 +74,9 @@ export default context => { return Component }) // Set layout - if (Components.length && Components[0].options.layout) { - _app.setLayout(Components[0].options.layout) - } + return _app.setLayout(Components.length ? Components[0].options.layout : '') + }) + .then(() => { // Call .validate() let isValid = true Components.forEach((Component) => {