client.js 29 KB

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