Merge branch 'dev' of github.com:Atinux/nuxt.js into dev

This commit is contained in:
Sébastien Chopin 2017-07-09 16:31:25 +02:00
commit 78400042ef
3 changed files with 136 additions and 84 deletions

View File

@ -1,5 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
// Node Source Map Support
// https://github.com/evanw/node-source-map-support
require('source-map-support').install()
const join = require('path').join const join = require('path').join
const defaultCommand = 'dev' const defaultCommand = 'dev'
@ -21,4 +25,11 @@ if (commands.has(cmd)) {
const bin = join(__dirname, 'nuxt-' + cmd) const bin = join(__dirname, 'nuxt-' + cmd)
// Console error unhandled promises
process.on('unhandledRejection', function (err) {
/* eslint-disable no-console */
console.error(err)
console.error('[nuxt] Unhandled promise rejection: ' + err)
})
require(bin) require(bin)

View File

@ -54,11 +54,6 @@ const nuxtConfigFile = resolve(rootDir, argv['config-file'])
const nuxtConfig = loadNuxtConfig() const nuxtConfig = loadNuxtConfig()
_.defaultsDeep(nuxtConfig, { watchers: { chokidar: { ignoreInitial: true } } }) _.defaultsDeep(nuxtConfig, { watchers: { chokidar: { ignoreInitial: true } } })
// Fail if an error happened
process.on('unhandledRejection', function (err) {
throw err
})
// Start dev // Start dev
let dev = startDev() let dev = startDev()

View File

@ -1,28 +1,21 @@
'use strict'
import Vue from 'vue' import Vue from 'vue'
import clone from 'clone' import clone from 'clone'
import { stringify } from 'querystring' import { stringify } from 'querystring'
import { omit } from 'lodash' import { omit } from 'lodash'
import middleware from './middleware' import middleware from './middleware'
import { createApp, NuxtError } from './index' 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') const debug = require('debug')('nuxt:render')
debug.color = 4 // force blue color debug.color = 4 // force blue color
const isDev = <%= isDev %> const isDev = <%= isDev %>
// This exported function will be called by `bundleRenderer`. const noopApp = () => new Vue({ render: (h) => h('div') })
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it. const createNext = context => opts => {
// 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
context.redirected = false
context.next = function (opts) {
context.redirected = opts context.redirected = opts
// if nuxt generate // If nuxt generate
if (!context.res) { if (!context.res) {
context.nuxt.serverRendered = false context.nuxt.serverRendered = false
return return
@ -41,52 +34,74 @@ export default async (context) => {
'Location': opts.path 'Location': opts.path
}) })
context.res.end() 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
context.redirected = false
context.next = createNext(context)
const { app, router<%= (store ? ', store' : '') %> } = await createApp(context) const { app, router<%= (store ? ', store' : '') %> } = await createApp(context)
const _app = new Vue(app) const _app = new Vue(app)
const _noopApp = new Vue({ render: (h) => h('div') })
<% if (store) { %>
// Add store to the context // Add store to the context
<%= (store ? 'context.store = store' : '') %> context.store = store
<% } %>
// Add route to the context // Add route to the context
context.route = router.currentRoute context.route = router.currentRoute
// Components array (for dynamic components) // Components array (for dynamic components)
context.hasDynamicComponents = false context.hasDynamicComponents = false
context.components = [] context.components = []
// Nuxt object // Nuxt object
context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true } context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
// Add meta infos // Add meta infos
context.meta = _app.$meta() context.meta = _app.$meta()
// Error function // Error function
context.error = _app.$options._nuxt.error.bind(_app) context.error = _app.$options._nuxt.error.bind(_app)
// Keep asyncData for each matched component in context // Keep asyncData for each matched component in context
context.asyncData = {} context.asyncData = {}
<%= (isDev ? 'const s = isDev && Date.now()' : '') %> // Create shared ctx
let ctx = getContext(context, app) const ctx = getContext(context, app)
<% if (isDev) { %>const s = isDev && Date.now()<% } %>
// Resolve components
let Components = [] let Components = []
let promises = getMatchedComponents(router.match(context.url)).map((Component) => { try {
return new Promise((resolve, reject) => { 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)) if (typeof Component !== 'function' || Component.super === Vue) {
return resolve(sanitizeComponent(Component))
}
const _resolve = (Component) => resolve(sanitizeComponent(Component)) const _resolve = (Component) => resolve(sanitizeComponent(Component))
Component().then(_resolve).catch(reject) Component().then(_resolve).catch(reject)
}) })))
})
try {
Components = await Promise.all(promises)
} catch (err) { } catch (err) {
// Throw back error to renderRoute() // Throw back error to renderRoute()
throw err throw err
} }
// nuxtServerInit
<% if (store) { %> <% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', getContext(context, app)) : null) // Dispatch store nuxtServerInit
if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) promise = Promise.resolve() if (store._actions && store._actions.nuxtServerInit) {
<% } else { %> await store.dispatch('nuxtServerInit', ctx)
let promise = Promise.resolve() }
// ...If there is a redirect
if (context.redirected) return noopApp()
<% } %> <% } %>
await promise
// If nuxtServerInit made a redirect
if (context.redirected) return _noopApp
// Call global middleware (nuxt.config.js) // Call global middleware (nuxt.config.js)
let midd = <%= serialize(router.middleware, { isJSON: true }) %> let midd = <%= serialize(router.middleware, { isJSON: true }) %>
midd = midd.map((name) => { midd = midd.map((name) => {
@ -98,15 +113,19 @@ export default async (context) => {
if (!context.nuxt.error) { if (!context.nuxt.error) {
await middlewareSeries(midd, ctx) await middlewareSeries(midd, ctx)
} }
if (context.redirected) return _noopApp // ...If there is a redirect
if (context.redirected) return noopApp()
// Set layout // Set layout
let layout = 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) if (typeof layout === 'function') layout = layout(ctx)
await _app.loadLayout(layout) await _app.loadLayout(layout)
layout = _app.setLayout(layout) layout = _app.setLayout(layout)
// Set layout to __NUXT__ // ...Set layout to __NUXT__
context.nuxt.layout = _app.layoutName context.nuxt.layout = _app.layoutName
// Call middleware (layout + pages) // Call middleware (layout + pages)
if (!context.nuxt.error) {
midd = [] midd = []
if (layout.middleware) midd = midd.concat(layout.middleware) if (layout.middleware) midd = midd.concat(layout.middleware)
Components.forEach((Component) => { Components.forEach((Component) => {
@ -120,10 +139,13 @@ export default async (context) => {
} }
return middleware[name] return middleware[name]
}) })
if (!context.nuxt.error) {
await middlewareSeries(midd, ctx) await middlewareSeries(midd, ctx)
// If there is a redirect
if (context.redirected) return noopApp()
} }
if (context.redirected) return _noopApp
// Call .validate() // Call .validate()
let isValid = true let isValid = true
Components.forEach((Component) => { Components.forEach((Component) => {
@ -134,7 +156,7 @@ export default async (context) => {
query: context.route.query || {}<%= (store ? ', store: ctx.store' : '') %> query: context.route.query || {}<%= (store ? ', store: ctx.store' : '') %>
}) })
}) })
// If .validate() returned false // ...If .validate() returned false
if (!isValid) { if (!isValid) {
// Don't server-render the page in generate mode // Don't server-render the page in generate mode
if (context._generate) { if (context._generate) {
@ -143,71 +165,95 @@ export default async (context) => {
// Call the 404 error by making the Components array empty // Call the 404 error by making the Components array empty
Components = [] Components = []
} }
// Call asyncData & fetch hooks on components matched by the route. // 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 = [] let promises = []
// Create this context for asyncData & fetch (used for dynamic component injection) // Create this context for asyncData & fetch (used for dynamic component injection)
const _this = { components: {} } const _this = { components: {} }
// Call asyncData
// Call asyncData(context)
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
let promise = promisify(Component.options.asyncData.bind(_this), ctx) let promise = promisify(Component.options.asyncData.bind(_this), ctx)
// Call asyncData(context) promise.then(asyncDataResult => {
promise.then((asyncDataResult) => {
context.asyncData[Component.options.name] = asyncDataResult context.asyncData[Component.options.name] = asyncDataResult
applyAsyncData(Component) applyAsyncData(Component)
return asyncDataResult return asyncDataResult
}) })
promises.push(promise) promises.push(promise)
} else promises.push(null) } else {
promises.push(null)
}
// Call fetch(context) // Call fetch(context)
if (Component.options.fetch) promises.push(Component.options.fetch.call(_this, ctx)) if (Component.options.fetch) {
else promises.push(null) promises.push(Component.options.fetch.call(_this, ctx))
return Promise.all(promises) }
.then((data) => { else {
promises.push(null)
}
return Promise.all(promises).then(data => {
// If not dyanmic component, return data directly // 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) // Sanetize resolved components (Temporary workaround for vue-loader 13.0.0)
Object.keys(_this.components).forEach(name => { Object.keys(_this.components).forEach(name => {
_this.components[name] = _this.components[name].default || _this.components[name] _this.components[name] = _this.components[name].default || _this.components[name]
}) })
// Tell renderer that dynamic components has been added // Tell renderer that dynamic components has been added
context.hasDynamicComponents = true context.hasDynamicComponents = true
// Add Component on server side (clone of it) // Add Component on server side (clone of it)
Component.options.components = { Component.options.components = {
...Component.options.components, ...Component.options.components,
...clone(_this.components) // Clone it to avoid vue to overwrite references ...clone(_this.components) // Clone it to avoid vue to overwrite references
} }
// Add components into __NUXT__ for client-side hydration // Add components into __NUXT__ for client-side hydration
// We clone it since vue-server-renderer will update the component definition // We clone it since vue-server-renderer will update the component definition
context.components.push(sanitizeDynamicComponents(_this.components)) context.components.push(sanitizeDynamicComponents(_this.components))
// Return data to server-render them // Return data to server-render them
return data return data
}) })
})) }))
// If no Components found, returns 404 // If no Components found, returns 404
if (!Components.length) { if (!Components.length) {
context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.' }) 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 // 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 an error occured in the execution
if (_app.$options._nuxt.err) { if (_app.$options._nuxt.err) {
context.nuxt.error = _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 no error, return main app
if (!context.nuxt.error) { if (!context.nuxt.error) {
return _app return _app
} }
// Load layout for error page // Load layout for error page
layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout) layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout)
context.nuxt.layout = layout || '' context.nuxt.layout = layout || ''
await _app.loadLayout(layout) await _app.loadLayout(layout)
_app.setLayout(layout) _app.setLayout(layout)
return _app return _app
} }