client.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. import Vue from 'vue'
  2. import fetch from 'unfetch'
  3. import middleware from './middleware.js'
  4. import {
  5. applyAsyncData,
  6. promisify,
  7. middlewareSeries,
  8. sanitizeComponent,
  9. resolveRouteComponents,
  10. getMatchedComponents,
  11. getMatchedComponentsInstances,
  12. flatMapComponents,
  13. setContext,
  14. getLocation,
  15. compile,
  16. getQueryDiff,
  17. globalHandleError,
  18. isSamePath,
  19. urlJoin
  20. } from './utils.js'
  21. import { createApp, NuxtError } from './index.js'
  22. import fetchMixin from './mixins/fetch.client'
  23. import NuxtLink from './components/nuxt-link.client.js' // should be included after ./index.js
  24. // Fetch mixin
  25. if (!Vue.__nuxt__fetch__mixin__) {
  26. Vue.mixin(fetchMixin)
  27. Vue.__nuxt__fetch__mixin__ = true
  28. }
  29. // Component: <NuxtLink>
  30. Vue.component(NuxtLink.name, NuxtLink)
  31. Vue.component('NLink', NuxtLink)
  32. if (!global.fetch) { global.fetch = fetch }
  33. // Global shared references
  34. let _lastPaths = []
  35. let app
  36. let router
  37. // Try to rehydrate SSR data from window
  38. const NUXT = window.__NUXT__ || {}
  39. const $config = NUXT.config || {}
  40. if ($config._app) {
  41. __webpack_public_path__ = urlJoin($config._app.cdnURL, $config._app.assetsPath)
  42. }
  43. Object.assign(Vue.config, {"silent":false,"performance":true})
  44. const logs = NUXT.logs || []
  45. if (logs.length > 0) {
  46. const ssrLogStyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;'
  47. console.group && console.group ('%cNuxt SSR', ssrLogStyle)
  48. logs.forEach(logObj => (console[logObj.type] || console.log)(...logObj.args))
  49. delete NUXT.logs
  50. console.groupEnd && console.groupEnd()
  51. }
  52. // Setup global Vue error handler
  53. if (!Vue.config.$nuxt) {
  54. const defaultErrorHandler = Vue.config.errorHandler
  55. Vue.config.errorHandler = async (err, vm, info, ...rest) => {
  56. // Call other handler if exist
  57. let handled = null
  58. if (typeof defaultErrorHandler === 'function') {
  59. handled = defaultErrorHandler(err, vm, info, ...rest)
  60. }
  61. if (handled === true) {
  62. return handled
  63. }
  64. if (vm && vm.$root) {
  65. const nuxtApp = Object.keys(Vue.config.$nuxt)
  66. .find(nuxtInstance => vm.$root[nuxtInstance])
  67. // Show Nuxt Error Page
  68. if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') {
  69. const currentApp = vm.$root[nuxtApp]
  70. // Load error layout
  71. let layout = (NuxtError.options || NuxtError).layout
  72. if (typeof layout === 'function') {
  73. layout = layout(currentApp.context)
  74. }
  75. if (layout) {
  76. await currentApp.loadLayout(layout).catch(() => {})
  77. }
  78. currentApp.setLayout(layout)
  79. currentApp.error(err)
  80. }
  81. }
  82. if (typeof defaultErrorHandler === 'function') {
  83. return handled
  84. }
  85. // Log to console
  86. if (process.env.NODE_ENV !== 'production') {
  87. console.error(err)
  88. } else {
  89. console.error(err.message || err)
  90. }
  91. }
  92. Vue.config.$nuxt = {}
  93. }
  94. Vue.config.$nuxt.$nuxt = true
  95. const errorHandler = Vue.config.errorHandler || console.error
  96. // Create and mount App
  97. createApp(null, NUXT.config).then(mountApp).catch(errorHandler)
  98. function componentOption (component, key, ...args) {
  99. if (!component || !component.options || !component.options[key]) {
  100. return {}
  101. }
  102. const option = component.options[key]
  103. if (typeof option === 'function') {
  104. return option(...args)
  105. }
  106. return option
  107. }
  108. function mapTransitions (toComponents, to, from) {
  109. const componentTransitions = (component) => {
  110. const transition = componentOption(component, 'transition', to, from) || {}
  111. return (typeof transition === 'string' ? { name: transition } : transition)
  112. }
  113. const fromComponents = from ? getMatchedComponents(from) : []
  114. const maxDepth = Math.max(toComponents.length, fromComponents.length)
  115. const mergedTransitions = []
  116. for (let i=0; i<maxDepth; i++) {
  117. // Clone original objects to prevent overrides
  118. const toTransitions = Object.assign({}, componentTransitions(toComponents[i]))
  119. const transitions = Object.assign({}, componentTransitions(fromComponents[i]))
  120. // Combine transitions & prefer `leave` properties of "from" route
  121. Object.keys(toTransitions)
  122. .filter(key => typeof toTransitions[key] !== 'undefined' && !key.toLowerCase().includes('leave'))
  123. .forEach((key) => { transitions[key] = toTransitions[key] })
  124. mergedTransitions.push(transitions)
  125. }
  126. return mergedTransitions
  127. }
  128. async function loadAsyncComponents (to, from, next) {
  129. // Check if route changed (this._routeChanged), only if the page is not an error (for validate())
  130. this._routeChanged = Boolean(app.nuxt.err) || from.name !== to.name
  131. this._paramChanged = !this._routeChanged && from.path !== to.path
  132. this._queryChanged = !this._paramChanged && from.fullPath !== to.fullPath
  133. this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : [])
  134. if ((this._routeChanged || this._paramChanged) && this.$loading.start && !this.$loading.manual) {
  135. this.$loading.start()
  136. }
  137. try {
  138. if (this._queryChanged) {
  139. const Components = await resolveRouteComponents(
  140. to,
  141. (Component, instance) => ({ Component, instance })
  142. )
  143. // Add a marker on each component that it needs to refresh or not
  144. const startLoader = Components.some(({ Component, instance }) => {
  145. const watchQuery = Component.options.watchQuery
  146. if (watchQuery === true) {
  147. return true
  148. }
  149. if (Array.isArray(watchQuery)) {
  150. return watchQuery.some(key => this._diffQuery[key])
  151. }
  152. if (typeof watchQuery === 'function') {
  153. return watchQuery.apply(instance, [to.query, from.query])
  154. }
  155. return false
  156. })
  157. if (startLoader && this.$loading.start && !this.$loading.manual) {
  158. this.$loading.start()
  159. }
  160. }
  161. // Call next()
  162. next()
  163. } catch (error) {
  164. const err = error || {}
  165. const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500
  166. const message = err.message || ''
  167. // Handle chunk loading errors
  168. // This may be due to a new deployment or a network problem
  169. if (/^Loading( CSS)? chunk (\d)+ failed\./.test(message)) {
  170. window.location.reload(true /* skip cache */)
  171. return // prevent error page blinking for user
  172. }
  173. this.error({ statusCode, message })
  174. this.$nuxt.$emit('routeChanged', to, from, err)
  175. next()
  176. }
  177. }
  178. function applySSRData (Component, ssrData) {
  179. if (NUXT.serverRendered && ssrData) {
  180. applyAsyncData(Component, ssrData)
  181. }
  182. Component._Ctor = Component
  183. return Component
  184. }
  185. // Get matched components
  186. function resolveComponents (route) {
  187. return flatMapComponents(route, async (Component, _, match, key, index) => {
  188. // If component is not resolved yet, resolve it
  189. if (typeof Component === 'function' && !Component.options) {
  190. Component = await Component()
  191. }
  192. // Sanitize it and save it
  193. const _Component = applySSRData(sanitizeComponent(Component), NUXT.data ? NUXT.data[index] : null)
  194. match.components[key] = _Component
  195. return _Component
  196. })
  197. }
  198. function callMiddleware (Components, context, layout) {
  199. let midd = []
  200. let unknownMiddleware = false
  201. // If layout is undefined, only call global middleware
  202. if (typeof layout !== 'undefined') {
  203. midd = [] // Exclude global middleware if layout defined (already called before)
  204. layout = sanitizeComponent(layout)
  205. if (layout.options.middleware) {
  206. midd = midd.concat(layout.options.middleware)
  207. }
  208. Components.forEach((Component) => {
  209. if (Component.options.middleware) {
  210. midd = midd.concat(Component.options.middleware)
  211. }
  212. })
  213. }
  214. midd = midd.map((name) => {
  215. if (typeof name === 'function') {
  216. return name
  217. }
  218. if (typeof middleware[name] !== 'function') {
  219. unknownMiddleware = true
  220. this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
  221. }
  222. return middleware[name]
  223. })
  224. if (unknownMiddleware) {
  225. return
  226. }
  227. return middlewareSeries(midd, context)
  228. }
  229. async function render (to, from, next) {
  230. if (this._routeChanged === false && this._paramChanged === false && this._queryChanged === false) {
  231. return next()
  232. }
  233. // Handle first render on SPA mode
  234. let spaFallback = false
  235. if (to === from) {
  236. _lastPaths = []
  237. spaFallback = true
  238. } else {
  239. const fromMatches = []
  240. _lastPaths = getMatchedComponents(from, fromMatches).map((Component, i) => {
  241. return compile(from.matched[fromMatches[i]].path)(from.params)
  242. })
  243. }
  244. // nextCalled is true when redirected
  245. let nextCalled = false
  246. const _next = (path) => {
  247. if (from.path === path.path && this.$loading.finish) {
  248. this.$loading.finish()
  249. }
  250. if (from.path !== path.path && this.$loading.pause) {
  251. this.$loading.pause()
  252. }
  253. if (nextCalled) {
  254. return
  255. }
  256. nextCalled = true
  257. next(path)
  258. }
  259. // Update context
  260. await setContext(app, {
  261. route: to,
  262. from,
  263. next: _next.bind(this)
  264. })
  265. this._dateLastError = app.nuxt.dateErr
  266. this._hadError = Boolean(app.nuxt.err)
  267. // Get route's matched components
  268. const matches = []
  269. const Components = getMatchedComponents(to, matches)
  270. // If no Components matched, generate 404
  271. if (!Components.length) {
  272. // Default layout
  273. await callMiddleware.call(this, Components, app.context)
  274. if (nextCalled) {
  275. return
  276. }
  277. // Load layout for error page
  278. const errorLayout = (NuxtError.options || NuxtError).layout
  279. const layout = await this.loadLayout(
  280. typeof errorLayout === 'function'
  281. ? errorLayout.call(NuxtError, app.context)
  282. : errorLayout
  283. )
  284. await callMiddleware.call(this, Components, app.context, layout)
  285. if (nextCalled) {
  286. return
  287. }
  288. // Show error page
  289. app.context.error({ statusCode: 404, message: 'This page could not be found' })
  290. return next()
  291. }
  292. // Update ._data and other properties if hot reloaded
  293. Components.forEach((Component) => {
  294. if (Component._Ctor && Component._Ctor.options) {
  295. Component.options.asyncData = Component._Ctor.options.asyncData
  296. Component.options.fetch = Component._Ctor.options.fetch
  297. }
  298. })
  299. // Apply transitions
  300. this.setTransitions(mapTransitions(Components, to, from))
  301. try {
  302. // Call middleware
  303. await callMiddleware.call(this, Components, app.context)
  304. if (nextCalled) {
  305. return
  306. }
  307. if (app.context._errored) {
  308. return next()
  309. }
  310. // Set layout
  311. let layout = Components[0].options.layout
  312. if (typeof layout === 'function') {
  313. layout = layout(app.context)
  314. }
  315. layout = await this.loadLayout(layout)
  316. // Call middleware for layout
  317. await callMiddleware.call(this, Components, app.context, layout)
  318. if (nextCalled) {
  319. return
  320. }
  321. if (app.context._errored) {
  322. return next()
  323. }
  324. // Call .validate()
  325. let isValid = true
  326. try {
  327. for (const Component of Components) {
  328. if (typeof Component.options.validate !== 'function') {
  329. continue
  330. }
  331. isValid = await Component.options.validate(app.context)
  332. if (!isValid) {
  333. break
  334. }
  335. }
  336. } catch (validationError) {
  337. // ...If .validate() threw an error
  338. this.error({
  339. statusCode: validationError.statusCode || '500',
  340. message: validationError.message
  341. })
  342. return next()
  343. }
  344. // ...If .validate() returned false
  345. if (!isValid) {
  346. this.error({ statusCode: 404, message: 'This page could not be found' })
  347. return next()
  348. }
  349. let instances
  350. // Call asyncData & fetch hooks on components matched by the route.
  351. await Promise.all(Components.map(async (Component, i) => {
  352. // Check if only children route changed
  353. Component._path = compile(to.matched[matches[i]].path)(to.params)
  354. Component._dataRefresh = false
  355. const childPathChanged = Component._path !== _lastPaths[i]
  356. // Refresh component (call asyncData & fetch) when:
  357. // Route path changed part includes current component
  358. // Or route param changed part includes current component and watchParam is not `false`
  359. // Or route query is changed and watchQuery returns `true`
  360. if (this._routeChanged && childPathChanged) {
  361. Component._dataRefresh = true
  362. } else if (this._paramChanged && childPathChanged) {
  363. const watchParam = Component.options.watchParam
  364. Component._dataRefresh = watchParam !== false
  365. } else if (this._queryChanged) {
  366. const watchQuery = Component.options.watchQuery
  367. if (watchQuery === true) {
  368. Component._dataRefresh = true
  369. } else if (Array.isArray(watchQuery)) {
  370. Component._dataRefresh = watchQuery.some(key => this._diffQuery[key])
  371. } else if (typeof watchQuery === 'function') {
  372. if (!instances) {
  373. instances = getMatchedComponentsInstances(to)
  374. }
  375. Component._dataRefresh = watchQuery.apply(instances[i], [to.query, from.query])
  376. }
  377. }
  378. if (!this._hadError && this._isMounted && !Component._dataRefresh) {
  379. return
  380. }
  381. const promises = []
  382. const hasAsyncData = (
  383. Component.options.asyncData &&
  384. typeof Component.options.asyncData === 'function'
  385. )
  386. const hasFetch = Boolean(Component.options.fetch) && Component.options.fetch.length
  387. const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45
  388. // Call asyncData(context)
  389. if (hasAsyncData) {
  390. const promise = promisify(Component.options.asyncData, app.context)
  391. promise.then((asyncDataResult) => {
  392. applyAsyncData(Component, asyncDataResult)
  393. if (this.$loading.increase) {
  394. this.$loading.increase(loadingIncrease)
  395. }
  396. })
  397. promises.push(promise)
  398. }
  399. // Check disabled page loading
  400. this.$loading.manual = Component.options.loading === false
  401. // Call fetch(context)
  402. if (hasFetch) {
  403. let p = Component.options.fetch(app.context)
  404. if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) {
  405. p = Promise.resolve(p)
  406. }
  407. p.then((fetchResult) => {
  408. if (this.$loading.increase) {
  409. this.$loading.increase(loadingIncrease)
  410. }
  411. })
  412. promises.push(p)
  413. }
  414. return Promise.all(promises)
  415. }))
  416. // If not redirected
  417. if (!nextCalled) {
  418. if (this.$loading.finish && !this.$loading.manual) {
  419. this.$loading.finish()
  420. }
  421. next()
  422. }
  423. } catch (err) {
  424. const error = err || {}
  425. if (error.message === 'ERR_REDIRECT') {
  426. return this.$nuxt.$emit('routeChanged', to, from, error)
  427. }
  428. _lastPaths = []
  429. globalHandleError(error)
  430. // Load error layout
  431. let layout = (NuxtError.options || NuxtError).layout
  432. if (typeof layout === 'function') {
  433. layout = layout(app.context)
  434. }
  435. await this.loadLayout(layout)
  436. this.error(error)
  437. this.$nuxt.$emit('routeChanged', to, from, error)
  438. next()
  439. }
  440. }
  441. // Fix components format in matched, it's due to code-splitting of vue-router
  442. function normalizeComponents (to, ___) {
  443. flatMapComponents(to, (Component, _, match, key) => {
  444. if (typeof Component === 'object' && !Component.options) {
  445. // Updated via vue-router resolveAsyncComponents()
  446. Component = Vue.extend(Component)
  447. Component._Ctor = Component
  448. match.components[key] = Component
  449. }
  450. return Component
  451. })
  452. }
  453. function setLayoutForNextPage (to) {
  454. // Set layout
  455. let hasError = Boolean(this.$options.nuxt.err)
  456. if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) {
  457. hasError = false
  458. }
  459. let layout = hasError
  460. ? (NuxtError.options || NuxtError).layout
  461. : to.matched[0].components.default.options.layout
  462. if (typeof layout === 'function') {
  463. layout = layout(app.context)
  464. }
  465. this.setLayout(layout)
  466. }
  467. function checkForErrors (app) {
  468. // Hide error component if no error
  469. if (app._hadError && app._dateLastError === app.$options.nuxt.dateErr) {
  470. app.error()
  471. }
  472. }
  473. // When navigating on a different route but the same component is used, Vue.js
  474. // Will not update the instance data, so we have to update $data ourselves
  475. function fixPrepatch (to, ___) {
  476. if (this._routeChanged === false && this._paramChanged === false && this._queryChanged === false) {
  477. return
  478. }
  479. const instances = getMatchedComponentsInstances(to)
  480. const Components = getMatchedComponents(to)
  481. let triggerScroll = false
  482. Vue.nextTick(() => {
  483. instances.forEach((instance, i) => {
  484. if (!instance || instance._isDestroyed) {
  485. return
  486. }
  487. if (
  488. instance.constructor._dataRefresh &&
  489. Components[i] === instance.constructor &&
  490. instance.$vnode.data.keepAlive !== true &&
  491. typeof instance.constructor.options.data === 'function'
  492. ) {
  493. const newData = instance.constructor.options.data.call(instance)
  494. for (const key in newData) {
  495. Vue.set(instance.$data, key, newData[key])
  496. }
  497. triggerScroll = true
  498. }
  499. })
  500. if (triggerScroll) {
  501. // Ensure to trigger scroll event after calling scrollBehavior
  502. window.$nuxt.$nextTick(() => {
  503. window.$nuxt.$emit('triggerScroll')
  504. })
  505. }
  506. checkForErrors(this)
  507. // Hot reloading
  508. setTimeout(() => hotReloadAPI(this), 100)
  509. })
  510. }
  511. function nuxtReady (_app) {
  512. window.onNuxtReadyCbs.forEach((cb) => {
  513. if (typeof cb === 'function') {
  514. cb(_app)
  515. }
  516. })
  517. // Special JSDOM
  518. if (typeof window._onNuxtLoaded === 'function') {
  519. window._onNuxtLoaded(_app)
  520. }
  521. // Add router hooks
  522. router.afterEach((to, from) => {
  523. // Wait for fixPrepatch + $data updates
  524. Vue.nextTick(() => _app.$nuxt.$emit('routeChanged', to, from))
  525. })
  526. }
  527. const noopData = () => { return {} }
  528. const noopFetch = () => {}
  529. // Special hot reload with asyncData(context)
  530. function getNuxtChildComponents ($parent, $components = []) {
  531. $parent.$children.forEach(($child) => {
  532. if ($child.$vnode && $child.$vnode.data.nuxtChild && !$components.find(c =>(c.$options.__file === $child.$options.__file))) {
  533. $components.push($child)
  534. }
  535. if ($child.$children && $child.$children.length) {
  536. getNuxtChildComponents($child, $components)
  537. }
  538. })
  539. return $components
  540. }
  541. function hotReloadAPI(_app) {
  542. if (!module.hot) return
  543. let $components = getNuxtChildComponents(_app.$nuxt, [])
  544. $components.forEach(addHotReload.bind(_app))
  545. }
  546. function addHotReload ($component, depth) {
  547. if ($component.$vnode.data._hasHotReload) return
  548. $component.$vnode.data._hasHotReload = true
  549. var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
  550. $component.$vnode.context.$forceUpdate = async () => {
  551. let Components = getMatchedComponents(router.currentRoute)
  552. let Component = Components[depth]
  553. if (!Component) {
  554. return _forceUpdate()
  555. }
  556. if (typeof Component === 'object' && !Component.options) {
  557. // Updated via vue-router resolveAsyncComponents()
  558. Component = Vue.extend(Component)
  559. Component._Ctor = Component
  560. }
  561. this.error()
  562. let promises = []
  563. const next = function (path) {
  564. this.$loading.finish && this.$loading.finish()
  565. router.push(path)
  566. }
  567. await setContext(app, {
  568. route: router.currentRoute,
  569. isHMR: true,
  570. next: next.bind(this)
  571. })
  572. const context = app.context
  573. if (this.$loading.start && !this.$loading.manual) {
  574. this.$loading.start()
  575. }
  576. callMiddleware.call(this, Components, context)
  577. .then(() => {
  578. // If layout changed
  579. if (depth !== 0) {
  580. return
  581. }
  582. let layout = Component.options.layout || 'default'
  583. if (typeof layout === 'function') {
  584. layout = layout(context)
  585. }
  586. if (this.layoutName === layout) {
  587. return
  588. }
  589. let promise = this.loadLayout(layout)
  590. promise.then(() => {
  591. this.setLayout(layout)
  592. Vue.nextTick(() => hotReloadAPI(this))
  593. })
  594. return promise
  595. })
  596. .then(() => {
  597. return callMiddleware.call(this, Components, context, this.layout)
  598. })
  599. .then(() => {
  600. // Call asyncData(context)
  601. let pAsyncData = promisify(Component.options.asyncData || noopData, context)
  602. pAsyncData.then((asyncDataResult) => {
  603. applyAsyncData(Component, asyncDataResult)
  604. this.$loading.increase && this.$loading.increase(30)
  605. })
  606. promises.push(pAsyncData)
  607. // Call fetch()
  608. Component.options.fetch = Component.options.fetch || noopFetch
  609. let pFetch = Component.options.fetch.length && Component.options.fetch(context)
  610. if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) }
  611. pFetch.then(() => this.$loading.increase && this.$loading.increase(30))
  612. promises.push(pFetch)
  613. return Promise.all(promises)
  614. })
  615. .then(() => {
  616. this.$loading.finish && this.$loading.finish()
  617. _forceUpdate()
  618. setTimeout(() => hotReloadAPI(this), 100)
  619. })
  620. }
  621. }
  622. async function mountApp (__app) {
  623. // Set global variables
  624. app = __app.app
  625. router = __app.router
  626. // Create Vue instance
  627. const _app = new Vue(app)
  628. // Load layout
  629. const layout = NUXT.layout || 'default'
  630. await _app.loadLayout(layout)
  631. _app.setLayout(layout)
  632. // Mounts Vue app to DOM element
  633. const mount = () => {
  634. _app.$mount('#__nuxt')
  635. // Add afterEach router hooks
  636. router.afterEach(normalizeComponents)
  637. router.afterEach(setLayoutForNextPage.bind(_app))
  638. router.afterEach(fixPrepatch.bind(_app))
  639. // Listen for first Vue update
  640. Vue.nextTick(() => {
  641. // Call window.{{globals.readyCallback}} callbacks
  642. nuxtReady(_app)
  643. // Enable hot reloading
  644. hotReloadAPI(_app)
  645. })
  646. }
  647. // Resolve route components
  648. const Components = await Promise.all(resolveComponents(app.context.route))
  649. // Enable transitions
  650. _app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app)
  651. if (Components.length) {
  652. _app.setTransitions(mapTransitions(Components, router.currentRoute))
  653. _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
  654. }
  655. // Initialize error handler
  656. _app.$loading = {} // To avoid error while _app.$nuxt does not exist
  657. if (NUXT.error) {
  658. _app.error(NUXT.error)
  659. }
  660. // Add beforeEach router hooks
  661. router.beforeEach(loadAsyncComponents.bind(_app))
  662. router.beforeEach(render.bind(_app))
  663. // Fix in static: remove trailing slash to force hydration
  664. // Full static, if server-rendered: hydrate, to allow custom redirect to generated page
  665. // Fix in static: remove trailing slash to force hydration
  666. if (NUXT.serverRendered && isSamePath(NUXT.routePath, _app.context.route.path)) {
  667. return mount()
  668. }
  669. // First render on client-side
  670. const clientFirstMount = () => {
  671. normalizeComponents(router.currentRoute, router.currentRoute)
  672. setLayoutForNextPage.call(_app, router.currentRoute)
  673. checkForErrors(_app)
  674. // Don't call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render
  675. mount()
  676. }
  677. // fix: force next tick to avoid having same timestamp when an error happen on spa fallback
  678. await new Promise(resolve => setTimeout(resolve, 0))
  679. render.call(_app, router.currentRoute, router.currentRoute, (path) => {
  680. // If not redirected
  681. if (!path) {
  682. clientFirstMount()
  683. return
  684. }
  685. // Add a one-time afterEach hook to
  686. // mount the app wait for redirect and route gets resolved
  687. const unregisterHook = router.afterEach((to, from) => {
  688. unregisterHook()
  689. clientFirstMount()
  690. })
  691. // Push the path and let route to be resolved
  692. router.push(path, undefined, (err) => {
  693. if (err) {
  694. errorHandler(err)
  695. }
  696. })
  697. })
  698. }