|
|
@@ -1,5 +1,5 @@
|
|
|
import Vue from 'vue'
|
|
|
-import middleware from './middleware'
|
|
|
+import middleware from './middleware.js'
|
|
|
import {
|
|
|
applyAsyncData,
|
|
|
sanitizeComponent,
|
|
|
@@ -14,11 +14,13 @@ import {
|
|
|
compile,
|
|
|
getQueryDiff,
|
|
|
globalHandleError
|
|
|
-} from './utils'
|
|
|
-import { createApp, NuxtError } from './index'
|
|
|
+} from './utils.js'
|
|
|
+import { createApp, NuxtError } from './index.js'
|
|
|
+import NuxtLink from './components/nuxt-link.client.js' // should be included after ./index.js
|
|
|
|
|
|
-const noopData = () => { return {} }
|
|
|
-const noopFetch = () => {}
|
|
|
+// Component: <NuxtLink>
|
|
|
+Vue.component(NuxtLink.name, NuxtLink)
|
|
|
+Vue.component('NLink', NuxtLink)
|
|
|
|
|
|
// Global shared references
|
|
|
let _lastPaths = []
|
|
|
@@ -28,7 +30,7 @@ let router
|
|
|
// Try to rehydrate SSR data from window
|
|
|
const NUXT = window.__NUXT__ || {}
|
|
|
|
|
|
-Object.assign(Vue.config, {"silent":true,"performance":false})
|
|
|
+Object.assign(Vue.config, {"silent":false,"performance":true})
|
|
|
|
|
|
// Setup global Vue error handler
|
|
|
if (!Vue.config.$nuxt) {
|
|
|
@@ -68,11 +70,14 @@ if (!Vue.config.$nuxt) {
|
|
|
}
|
|
|
Vue.config.$nuxt.$nuxt = true
|
|
|
|
|
|
+const errorHandler = Vue.config.errorHandler || console.error
|
|
|
+
|
|
|
// Create and mount App
|
|
|
createApp()
|
|
|
.then(mountApp)
|
|
|
.catch((err) => {
|
|
|
- console.error('[nuxt] Error while initializing app', err)
|
|
|
+ err.message = '[nuxt] Error while mounting app: ' + err.message
|
|
|
+ errorHandler(err)
|
|
|
})
|
|
|
|
|
|
function componentOption(component, key, ...args) {
|
|
|
@@ -138,10 +143,21 @@ async function loadAsyncComponents(to, from, next) {
|
|
|
|
|
|
// Call next()
|
|
|
next()
|
|
|
- } catch (err) {
|
|
|
- this.error(err)
|
|
|
- this.$nuxt.$emit('routeChanged', to, from, error)
|
|
|
- next(false)
|
|
|
+ } catch (error) {
|
|
|
+ const err = error || {}
|
|
|
+ const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500
|
|
|
+ const message = err.message || ''
|
|
|
+
|
|
|
+ // Handle chunk loading errors
|
|
|
+ // This may be due to a new deployment or a network problem
|
|
|
+ if (/^Loading chunk (\d)+ failed\./.test(message)) {
|
|
|
+ window.location.reload(true /* skip cache */)
|
|
|
+ return // prevent error page blinking for user
|
|
|
+ }
|
|
|
+
|
|
|
+ this.error({ statusCode, message })
|
|
|
+ this.$nuxt.$emit('routeChanged', to, from, err)
|
|
|
+ next()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -176,8 +192,9 @@ function callMiddleware(Components, context, layout) {
|
|
|
// If layout is undefined, only call global middleware
|
|
|
if (typeof layout !== 'undefined') {
|
|
|
midd = [] // Exclude global middleware if layout defined (already called before)
|
|
|
- if (layout.middleware) {
|
|
|
- midd = midd.concat(layout.middleware)
|
|
|
+ layout = sanitizeComponent(layout)
|
|
|
+ if (layout.options.middleware) {
|
|
|
+ midd = midd.concat(layout.options.middleware)
|
|
|
}
|
|
|
Components.forEach((Component) => {
|
|
|
if (Component.options.middleware) {
|
|
|
@@ -405,7 +422,7 @@ async function render(to, from, next) {
|
|
|
|
|
|
this.error(error)
|
|
|
this.$nuxt.$emit('routeChanged', to, from, error)
|
|
|
- next(false)
|
|
|
+ next()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -468,6 +485,9 @@ function fixPrepatch(to, ___) {
|
|
|
}
|
|
|
})
|
|
|
showNextPage.call(this, to)
|
|
|
+
|
|
|
+ // Hot reloading
|
|
|
+ setTimeout(() => hotReloadAPI(this), 100)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -488,6 +508,104 @@ function nuxtReady(_app) {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+const noopData = () => { return {} }
|
|
|
+const noopFetch = () => {}
|
|
|
+
|
|
|
+// Special hot reload with asyncData(context)
|
|
|
+function getNuxtChildComponents($parent, $components = []) {
|
|
|
+ $parent.$children.forEach(($child) => {
|
|
|
+ if ($child.$vnode && $child.$vnode.data.nuxtChild && !$components.find(c =>(c.$options.__file === $child.$options.__file))) {
|
|
|
+ $components.push($child)
|
|
|
+ }
|
|
|
+ if ($child.$children && $child.$children.length) {
|
|
|
+ getNuxtChildComponents($child, $components)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return $components
|
|
|
+}
|
|
|
+
|
|
|
+function hotReloadAPI(_app) {
|
|
|
+ if (!module.hot) return
|
|
|
+
|
|
|
+ let $components = getNuxtChildComponents(_app.$nuxt, [])
|
|
|
+
|
|
|
+ $components.forEach(addHotReload.bind(_app))
|
|
|
+}
|
|
|
+
|
|
|
+function addHotReload($component, depth) {
|
|
|
+ if ($component.$vnode.data._hasHotReload) return
|
|
|
+ $component.$vnode.data._hasHotReload = true
|
|
|
+
|
|
|
+ var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
|
|
|
+
|
|
|
+ $component.$vnode.context.$forceUpdate = async () => {
|
|
|
+ let Components = getMatchedComponents(router.currentRoute)
|
|
|
+ let Component = Components[depth]
|
|
|
+ if (!Component) return _forceUpdate()
|
|
|
+ if (typeof Component === 'object' && !Component.options) {
|
|
|
+ // Updated via vue-router resolveAsyncComponents()
|
|
|
+ Component = Vue.extend(Component)
|
|
|
+ Component._Ctor = Component
|
|
|
+ }
|
|
|
+ this.error()
|
|
|
+ let promises = []
|
|
|
+ const next = function (path) {
|
|
|
+ this.$loading.finish && this.$loading.finish()
|
|
|
+ router.push(path)
|
|
|
+ }
|
|
|
+ await setContext(app, {
|
|
|
+ route: router.currentRoute,
|
|
|
+ isHMR: true,
|
|
|
+ next: next.bind(this)
|
|
|
+ })
|
|
|
+ const context = app.context
|
|
|
+
|
|
|
+ if (this.$loading.start && !this.$loading.manual) this.$loading.start()
|
|
|
+
|
|
|
+ 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(context)
|
|
|
+ let pAsyncData = promisify(Component.options.asyncData || noopData, context)
|
|
|
+ pAsyncData.then((asyncDataResult) => {
|
|
|
+ applyAsyncData(Component, asyncDataResult)
|
|
|
+ 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 || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) }
|
|
|
+ pFetch.then(() => this.$loading.increase && this.$loading.increase(30))
|
|
|
+ promises.push(pFetch)
|
|
|
+ return Promise.all(promises)
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ this.$loading.finish && this.$loading.finish()
|
|
|
+ _forceUpdate()
|
|
|
+ setTimeout(() => hotReloadAPI(this), 100)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
async function mountApp(__app) {
|
|
|
// Set global variables
|
|
|
app = __app.app
|
|
|
@@ -507,6 +625,9 @@ async function mountApp(__app) {
|
|
|
Vue.nextTick(() => {
|
|
|
// Call window.{{globals.readyCallback}} callbacks
|
|
|
nuxtReady(_app)
|
|
|
+
|
|
|
+ // Enable hot reloading
|
|
|
+ hotReloadAPI(_app)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -547,7 +668,7 @@ async function mountApp(__app) {
|
|
|
// Push the path and then mount app
|
|
|
router.push(path, () => mount(), (err) => {
|
|
|
if (!err) return mount()
|
|
|
- console.error(err)
|
|
|
+ errorHandler(err)
|
|
|
})
|
|
|
})
|
|
|
}
|