From 4bf0a46cb0038f2d13bd3374a0b1a997101c48f6 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Sun, 9 Jul 2017 16:13:03 +0430 Subject: [PATCH] chore: polish app/server.js --- lib/app/server.js | 202 ++++++++++++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 80 deletions(-) diff --git a/lib/app/server.js b/lib/app/server.js index 7bcc1eb43a..703a575b6d 100644 --- a/lib/app/server.js +++ b/lib/app/server.js @@ -1,92 +1,103 @@ -'use strict' - import Vue from 'vue' import clone from 'clone' import { stringify } from 'querystring' import { omit } from 'lodash' import middleware from './middleware' import { createApp, NuxtError } from './index' -import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils' +import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin} from './utils' + const debug = require('debug')('nuxt:render') debug.color = 4 // force blue color const isDev = <%= isDev %> +const noopApp = () => new Vue({ render: (h) => h('div') }) + +const createNext = context => opts => { + context.redirected = opts + // If nuxt generate + if (!context.res) { + context.nuxt.serverRendered = false + return + } + opts.query = stringify(opts.query) + opts.path = opts.path + (opts.query ? '?' + opts.query : '') + if (opts.path.indexOf('http') !== 0 && ('<%= router.base %>' !== '/' && opts.path.indexOf('<%= router.base %>') !== 0)) { + opts.path = urlJoin('<%= router.base %>', opts.path) + } + // Avoid loop redirect + if (opts.path === context.url) { + context.redirected = false + return + } + context.res.writeHead(opts.status, { + 'Location': opts.path + }) + context.res.end() +} + // This exported function will be called by `bundleRenderer`. // This is where we perform data-prefetching to determine the // state of our application before actually rendering it. // Since data fetching is async, this function is expected to // return a Promise that resolves to the app instance. -export default async (context) => { - // create context.next for simulate next() of beforeEach() when wanted to redirect +export default async context => { + // Create context.next for simulate next() of beforeEach() when wanted to redirect context.redirected = false - context.next = function (opts) { - context.redirected = opts - // if nuxt generate - if (!context.res) { - context.nuxt.serverRendered = false - return - } - opts.query = stringify(opts.query) - opts.path = opts.path + (opts.query ? '?' + opts.query : '') - if (opts.path.indexOf('http') !== 0 && ('<%= router.base %>' !== '/' && opts.path.indexOf('<%= router.base %>') !== 0)) { - opts.path = urlJoin('<%= router.base %>', opts.path) - } - // Avoid loop redirect - if (opts.path === context.url) { - context.redirected = false - return - } - context.res.writeHead(opts.status, { - 'Location': opts.path - }) - context.res.end() - } + context.next = createNext(context) + const { app, router<%= (store ? ', store' : '') %> } = await createApp(context) const _app = new Vue(app) - const _noopApp = new Vue({ render: (h) => h('div') }) + // Add store to the context <%= (store ? 'context.store = store' : '') %> + // Add route to the context context.route = router.currentRoute + // Components array (for dynamic components) context.hasDynamicComponents = false context.components = [] + // Nuxt object context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true } + // Add meta infos context.meta = _app.$meta() + // Error function context.error = _app.$options._nuxt.error.bind(_app) + // Keep asyncData for each matched component in context context.asyncData = {} - <%= (isDev ? 'const s = isDev && Date.now()' : '') %> - let ctx = getContext(context, app) + // Create shared ctx + const ctx = getContext(context, app) + + <% if (isDev) { %>const s = isDev && Date.now()<% } %> + + // Resolve components let Components = [] - let promises = getMatchedComponents(router.match(context.url)).map((Component) => { - return new Promise((resolve, reject) => { - if (typeof Component !== 'function' || Component.super === Vue) return resolve(sanitizeComponent(Component)) + try { + Components = await Promise.all(getMatchedComponents(router.match(context.url)).map((Component) => new Promise((resolve, reject) => { + if (typeof Component !== 'function' || Component.super === Vue) { + return resolve(sanitizeComponent(Component)) + } const _resolve = (Component) => resolve(sanitizeComponent(Component)) Component().then(_resolve).catch(reject) - }) - }) - try { - Components = await Promise.all(promises) + }))) } catch (err) { // Throw back error to renderRoute() throw err } - // nuxtServerInit - <% if (store) { %> - let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', getContext(context, app)) : null) - if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) promise = Promise.resolve() - <% } else { %> - let promise = Promise.resolve() - <% } %> - await promise - // If nuxtServerInit made a redirect - if (context.redirected) return _noopApp + + // Dispatch nuxtServerInit + if (store._actions && store._actions.nuxtServerInit) { + await store.dispatch('nuxtServerInit', ctx) + } + // ...If there is a redirect + if (context.redirected) return noopApp() + // Call global middleware (nuxt.config.js) let midd = <%= serialize(router.middleware, { isJSON: true }) %> midd = midd.map((name) => { @@ -98,32 +109,39 @@ export default async (context) => { if (!context.nuxt.error) { await middlewareSeries(midd, ctx) } - if (context.redirected) return _noopApp + // ...If there is a redirect + if (context.redirected) return noopApp() + // Set layout let layout = Components.length ? Components[0].options.layout : NuxtError.layout if (typeof layout === 'function') layout = layout(ctx) await _app.loadLayout(layout) layout = _app.setLayout(layout) - // Set layout to __NUXT__ + // ...Set layout to __NUXT__ context.nuxt.layout = _app.layoutName + // Call middleware (layout + pages) - midd = [] - 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') { - context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) - } - return middleware[name] - }) if (!context.nuxt.error) { + midd = [] + 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') { + context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) + } + return middleware[name] + }) + await middlewareSeries(midd, ctx) + + // If there is a redirect + if (context.redirected) return noopApp() } - if (context.redirected) return _noopApp + // Call .validate() let isValid = true Components.forEach((Component) => { @@ -134,7 +152,7 @@ export default async (context) => { query: context.route.query || {}<%= (store ? ', store: ctx.store' : '') %> }) }) - // If .validate() returned false + // ...If .validate() returned false if (!isValid) { // Don't server-render the page in generate mode if (context._generate) { @@ -143,71 +161,95 @@ export default async (context) => { // Call the 404 error by making the Components array empty Components = [] } + // Call asyncData & fetch hooks on components matched by the route. - let asyncDatas = await Promise.all(Components.map((Component) => { + let asyncDatas = await Promise.all(Components.map(Component => { let promises = [] + // Create this context for asyncData & fetch (used for dynamic component injection) const _this = { components: {} } - // Call asyncData + + // Call asyncData(context) if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { let promise = promisify(Component.options.asyncData.bind(_this), ctx) - // Call asyncData(context) - promise.then((asyncDataResult) => { + promise.then(asyncDataResult => { context.asyncData[Component.options.name] = asyncDataResult applyAsyncData(Component) return asyncDataResult }) promises.push(promise) - } else promises.push(null) + } else { + promises.push(null) + } + // Call fetch(context) - if (Component.options.fetch) promises.push(Component.options.fetch.call(_this, ctx)) - else promises.push(null) - return Promise.all(promises) - .then((data) => { + if (Component.options.fetch) { + promises.push(Component.options.fetch.call(_this, ctx)) + } + else { + promises.push(null) + } + + return Promise.all(promises).then(data => { // If not dyanmic component, return data directly - if (Object.keys(_this.components).length === 0) return data + if (Object.keys(_this.components).length === 0) { + return data + } + // Sanetize resolved components (Temporary workaround for vue-loader 13.0.0) Object.keys(_this.components).forEach(name => { _this.components[name] = _this.components[name].default || _this.components[name] }) + // Tell renderer that dynamic components has been added context.hasDynamicComponents = true + // Add Component on server side (clone of it) Component.options.components = { ...Component.options.components, ...clone(_this.components) // Clone it to avoid vue to overwrite references } + // Add components into __NUXT__ for client-side hydration // We clone it since vue-server-renderer will update the component definition context.components.push(sanitizeDynamicComponents(_this.components)) + // Return data to server-render them return data }) })) + // If no Components found, returns 404 if (!Components.length) { context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.' }) } - <% if (isDev) { %> - if (asyncDatas.length) debug('Data fetching ' + context.url + ': ' + (Date.now() - s) + 'ms') - <% } %> + + <% if (isDev) { %>if (asyncDatas.length) debug('Data fetching ' + context.url + ': ' + (Date.now() - s) + 'ms')<% } %> + // datas are the first row of each - context.nuxt.data = asyncDatas.map((r) => (r[0] || {})) + context.nuxt.data = asyncDatas.map(r => r[0] || {}) + // If an error occured in the execution if (_app.$options._nuxt.err) { context.nuxt.error = _app.$options._nuxt.err } - <%= (store ? '// Add the state from the vuex store' : '') %> - <%= (store ? 'context.nuxt.state = store.state' : '') %> + + <% if (store) { %> + // Add the state from the vuex store + context.nuxt.state = store.state + <% } %> + // If no error, return main app if (!context.nuxt.error) { return _app } + // Load layout for error page layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout) context.nuxt.layout = layout || '' await _app.loadLayout(layout) _app.setLayout(layout) + return _app }