This commit is contained in:
2022-07-18 02:50:52 +00:00
parent befd344ab0
commit 06181b34d6
8569 changed files with 818704 additions and 352705 deletions
+53 -23
View File
@@ -1,4 +1,5 @@
import Vue from 'vue'
import { decode, parsePath, withoutBase, withoutTrailingSlash, normalizeURL } from 'ufo'
<% utilsImports = [
...(features.asyncData || features.fetch) ? [
'getMatchedComponentsInstances',
@@ -12,7 +13,7 @@ import Vue from 'vue'
]: []
] %>
<% if (utilsImports.length) { %>import { <%= utilsImports.join(', ') %> } from './utils'<% } %>
<% if (features.layouts && components.ErrorPage) { %>import NuxtError from '<%= components.ErrorPage %>'<% } %>
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
<% if (loading) { %>import NuxtLoading from '<%= (typeof loading === "string" ? loading : "./components/nuxt-loading.vue") %>'<% } %>
<% if (buildIndicator) { %>import NuxtBuildIndicator from './components/nuxt-build-indicator'<% } %>
<% css.forEach((c) => { %>
@@ -98,7 +99,8 @@ export default {
},
created () {
// Add this.$nuxt in child instances
Vue.prototype.<%= globals.nuxt %> = this
this.$root.$options.<%= globals.nuxt %> = this
if (process.client) {
// add to window so we can listen when ready
window.<%= globals.nuxt %> = <%= (globals.nuxt !== '$nuxt' ? 'window.$nuxt = ' : '') %>this
@@ -129,11 +131,9 @@ export default {
<% } %>
},
<% } %>
<% if (loading) { %>
watch: {
'nuxt.err': 'errorChanged'
},
<% } %>
<% if (features.clientOnline) { %>
computed: {
isOffline () {
@@ -215,18 +215,29 @@ export default {
<% if (loading) { %>this.$loading.finish()<% } %>
<% } %>
},
<% if (loading) { %>
errorChanged () {
if (this.nuxt.err && this.$loading) {
if (this.$loading.fail) {
this.$loading.fail(this.nuxt.err)
<% if (splitChunks.layouts) { %>async <% } %>errorChanged () {
if (this.nuxt.err) {
<% if (loading) { %>
if (this.$loading) {
if (this.$loading.fail) {
this.$loading.fail(this.nuxt.err)
}
if (this.$loading.finish) {
this.$loading.finish()
}
}
if (this.$loading.finish) {
this.$loading.finish()
<% } %>
let errorLayout = (NuxtError.options || NuxtError).layout;
if (typeof errorLayout === 'function') {
errorLayout = errorLayout(this.context)
}
<% if (splitChunks.layouts) { %>
await this.loadLayout(errorLayout)
<% } %>
this.setLayout(errorLayout)
}
},
<% } %>
<% if (features.layouts) { %>
<% if (splitChunks.layouts) { %>
setLayout (layout) {
@@ -285,24 +296,43 @@ export default {
<% } /* splitChunks.layouts */ %>
<% } /* features.layouts */ %>
<% if (isFullStatic) { %>
getRouterBase() {
return withoutTrailingSlash(this.$router.options.base)
},
getRoutePath(route = '/') {
const base = this.getRouterBase()
return withoutTrailingSlash(withoutBase(parsePath(route).pathname, base))
},
getStaticAssetsPath(route = '/') {
const { staticAssetsBase } = window.<%= globals.context %>
return urlJoin(staticAssetsBase, this.getRoutePath(route))
},
<% if (nuxtOptions.generate.manifest) { %>
async fetchStaticManifest() {
return window.__NUXT_IMPORT__('manifest.js', normalizeURL(urlJoin(this.getStaticAssetsPath(), 'manifest.js')))
},
<% } %>
setPagePayload(payload) {
this._pagePayload = payload
this._payloadFetchIndex = 0
this._fetchCounters = {}
},
async fetchPayload(route) {
const { staticAssetsBase } = window.<%= globals.context %>
const base = (this.$router.options.base || '').replace(/\/+$/, '')
if (base && route.startsWith(base)) {
route = route.substr(base.length)
async fetchPayload(route, prefetch) {
const path = decode(this.getRoutePath(route))
<% if (nuxtOptions.generate.manifest) { %>
const manifest = await this.fetchStaticManifest()
if (!manifest.routes.includes(path)) {
if (!prefetch) { this.setPagePayload(false) }
throw new Error(`Route ${path} is not pre-rendered`)
}
route = (route.replace(/\/+$/, '') || '/').split('?')[0].split('#')[0]
const src = urlJoin(base, staticAssetsBase, route, 'payload.js')
<% } %>
const src = urlJoin(this.getStaticAssetsPath(route), 'payload.js')
try {
const payload = await window.__NUXT_IMPORT__(decodeURI(route), encodeURI(src))
this.setPagePayload(payload)
const payload = await window.__NUXT_IMPORT__(path, normalizeURL(src))
if (!prefetch) { this.setPagePayload(payload) }
return payload
} catch (err) {
this.setPagePayload(false)
if (!prefetch) { this.setPagePayload(false) }
throw err
}
}
+46 -24
View File
@@ -14,12 +14,16 @@ import {
<% if (features.transitions || features.asyncData || features.fetch) { %>getLocation,<% } %>
compile,
getQueryDiff,
globalHandleError
globalHandleError,
isSamePath,
urlJoin
} from './utils.js'
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.client'<% } %>
import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch ? "client" : "server" %>.js' // should be included after ./index.js
<% if (isFullStatic) { %>import './jsonp'<% } %>
<% if (isFullStatic) { %>import { installJsonp } from './jsonp'<% } %>
<% if (isFullStatic) { %>installJsonp()<% } %>
<% if (features.fetch) { %>
// Fetch mixin
@@ -44,6 +48,11 @@ let router
// Try to rehydrate SSR data from window
const NUXT = window.<%= globals.context %> || {}
const $config = NUXT.config || {}
if ($config._app) {
__webpack_public_path__ = urlJoin($config._app.cdnURL, $config._app.assetsPath)
}
Object.assign(Vue.config, <%= serialize(vue.config) %>)<%= isTest ? '// eslint-disable-line' : '' %>
<% if (nuxtOptions.render.ssrLog) { %>
@@ -220,10 +229,8 @@ function applySSRData (Component, ssrData) {
}
// Get matched components
function resolveComponents (router) {
const path = getLocation(router.options.base, router.options.mode)
return flatMapComponents(router.match(path), async (Component, _, match, key, index) => {
function resolveComponents (route) {
return flatMapComponents(route, async (Component, _, match, key, index) => {
// If component is not resolved yet, resolve it
if (typeof Component === 'function' && !Component.options) {
Component = await Component()
@@ -525,10 +532,12 @@ async function render (to, from, next) {
<% } %>
<% if (isFullStatic && store) { %>
// Replay store mutations, catching to avoid error page on SPA fallback
promises.push(this.fetchPayload(to.path).then(payload => {
payload.mutations.forEach(m => { this.$store.commit(m[0], m[1]) })
}).catch(err => null))
if (!this.isPreview && !spaFallback) {
// Replay store mutations, catching to avoid error page on SPA fallback
promises.push(this.fetchPayload(to.path).then(payload => {
payload.mutations.forEach(m => { this.$store.commit(m[0], m[1]) })
}).catch(err => null))
}
<% } %>
// Check disabled page loading
@@ -610,7 +619,7 @@ function normalizeComponents (to, ___) {
}
<% if (features.layouts) { %>
function setLayoutForNextPage (to) {
<% if (splitChunks.layouts) { %>async <% } %>function setLayoutForNextPage (to) {
// Set layout
let hasError = Boolean(this.$options.nuxt.err)
if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) {
@@ -623,6 +632,9 @@ function setLayoutForNextPage (to) {
if (typeof layout === 'function') {
layout = layout(app.context)
}
<% if (splitChunks.layouts) { %>
await this.loadLayout(layout)
<% } %>
this.setLayout(layout)
}
<% } %>
@@ -644,6 +656,8 @@ function fixPrepatch (to, ___) {
const instances = getMatchedComponentsInstances(to)
const Components = getMatchedComponents(to)
let triggerScroll = <%= features.transitions ? 'false' : 'true' %>
Vue.nextTick(() => {
instances.forEach((instance, i) => {
if (!instance || instance._isDestroyed) {
@@ -661,12 +675,17 @@ function fixPrepatch (to, ___) {
Vue.set(instance.$data, key, newData[key])
}
// Ensure to trigger scroll event after calling scrollBehavior
window.<%= globals.nuxt %>.$nextTick(() => {
window.<%= globals.nuxt %>.$emit('triggerScroll')
})
triggerScroll = true
}
})
if (triggerScroll) {
// Ensure to trigger scroll event after calling scrollBehavior
window.<%= globals.nuxt %>.$nextTick(() => {
window.<%= globals.nuxt %>.$emit('triggerScroll')
})
}
checkForErrors(this)
<% if (isDev) { %>
// Hot reloading
@@ -827,7 +846,7 @@ async function mountApp (__app) {
// Load page chunk
if (!NUXT.data && NUXT.serverRendered) {
try {
const payload = await _app.fetchPayload(_app.context.route.path)
const payload = await _app.fetchPayload(NUXT.routePath || _app.context.route.path)
Object.assign(NUXT, payload)
} catch (err) {}
}
@@ -863,7 +882,7 @@ async function mountApp (__app) {
}
<% if (features.transitions) { %>
// Resolve route components
const Components = await Promise.all(resolveComponents(router))
const Components = await Promise.all(resolveComponents(app.context.route))
// Enable transitions
_app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app)
@@ -872,7 +891,7 @@ async function mountApp (__app) {
_lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
}
<% } else if (features.asyncData || features.fetch) { %>
await Promise.all(resolveComponents(router))
await Promise.all(resolveComponents(app.context.route))
<% } %>
// Initialize error handler
_app.$loading = {} // To avoid error while _app.$nuxt does not exist
@@ -885,14 +904,17 @@ async function mountApp (__app) {
router.beforeEach(render.bind(_app))
// Fix in static: remove trailing slash to force hydration
if (process.static && NUXT.serverRendered && NUXT.routePath !== '/' && NUXT.routePath.slice(-1) !== '/' && _app.context.route.path.slice(-1) === '/') {
_app.context.route.path = _app.context.route.path.replace(/\/+$/, '')
// Full static, if server-rendered: hydrate, to allow custom redirect to generated page
<% if (isFullStatic) { %>
if (NUXT.serverRendered) {
return mount()
}
// If page already is server rendered and it was done on the same route path as client side render
if (NUXT.serverRendered && NUXT.routePath === _app.context.route.path) {
mount()
return
<% } else { %>
// Fix in static: remove trailing slash to force hydration
if (NUXT.serverRendered && isSamePath(NUXT.routePath, _app.context.route.path)) {
return mount()
}
<% } %>
// First render on client-side
const clientFirstMount = () => {
+2 -1
View File
@@ -7,7 +7,8 @@
<div class="title">{{ message }}</div>
<p v-if="statusCode === 404" class="description">
<NuxtLink class="error-link" to="/"><%= messages.back_to_home %></NuxtLink>
<a v-if="typeof $route === 'undefined'" class="error-link" href="/"><% messages.back_to_home %></a>
<NuxtLink v-else class="error-link" to="/"><%= messages.back_to_home %></NuxtLink>
</p>
<% if(debug) { %>
<p class="description" v-else><%= messages.client_error_details %></p>
+3 -2
View File
@@ -17,7 +17,7 @@ const cancelIdleCallback = window.cancelIdleCallback || function (id) {
const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => {
entries.forEach(({ intersectionRatio, target: link }) => {
if (intersectionRatio <= 0) {
if (intersectionRatio <= 0 || !link.__prefetch) {
return
}
link.__prefetch()
@@ -111,7 +111,8 @@ export default {
// Preload the data only if not in preview mode
if (!this.$root.isPreview) {
const { href } = this.$router.resolve(this.to, this.$route, this.append)
this.$nuxt.fetchPayload(href).catch(() => {})
if (this.<%= globals.nuxt %>)
this.<%= globals.nuxt %>.fetchPayload(href, true).catch(() => {})
}
<% } %>
<% if (router.linkPrefetchedClass) { %>
+51 -16
View File
@@ -1,4 +1,5 @@
import Vue from 'vue'
<% if (store) { %>import Vuex from 'vuex'<% } %>
<% if (features.meta) { %>import Meta from 'vue-meta'<% } %>
<% if (features.componentClientOnly) { %>import ClientOnly from 'vue-client-only'<% } %>
<% if (features.deprecations) { %>import NoSsr from 'vue-no-ssr'<% } %>
@@ -43,6 +44,17 @@ Vue.component(NuxtChild.name, NuxtChild)
// Component: <Nuxt>
Vue.component(Nuxt.name, Nuxt)
Object.defineProperty(Vue.prototype, '<%= globals.nuxt %>', {
get() {
const globalNuxt = this.$root.$options.<%= globals.nuxt %>
if (process.client && !globalNuxt && typeof window !== 'undefined') {
return window.<%= globals.nuxt %>
}
return globalNuxt
},
configurable: true
})
<% if (features.meta) {
// vue-meta configuration
const vueMetaOptions = {
@@ -66,8 +78,21 @@ const defaultTransition = <%=
%><%= isTest ? '// eslint-disable-line' : '' %>
<% } %>
<% if (store) { %>
const originalRegisterModule = Vuex.Store.prototype.registerModule
function registerModule (path, rawModule, options = {}) {
const preserveState = process.client && (
Array.isArray(path)
? !!path.reduce((namespacedState, path) => namespacedState && namespacedState[path], this.state)
: path in this.state
)
return originalRegisterModule.call(this, path, rawModule, { preserveState, ...options })
}
<% } %>
async function createApp(ssrContext, config = {}) {
const router = await createRouter(ssrContext)
const router = await createRouter(ssrContext, config)
<% if (store) { %>
const store = createStore(ssrContext)
@@ -75,8 +100,7 @@ async function createApp(ssrContext, config = {}) {
store.$router = router
<% if (mode === 'universal') { %>
// Fix SSR caveat https://github.com/nuxt/nuxt.js/issues/3757#issuecomment-414689141
const registerModule = store.registerModule
store.registerModule = (path, rawModule, options) => registerModule.call(store, path, rawModule, Object.assign({ preserveState: process.client }, options))
store.registerModule = registerModule
<% } %>
<% } %>
@@ -243,22 +267,33 @@ async function createApp(ssrContext, config = {}) {
}
}
// If server-side, wait for async component to be resolved first
if (process.server && ssrContext && ssrContext.url) {
await new Promise((resolve, reject) => {
router.push(ssrContext.url, resolve, () => {
// navigated to a different route in router guard
const unregister = router.afterEach(async (to, from, next) => {
// Wait for async component to be resolved first
await new Promise((resolve, reject) => {
// Ignore 404s rather than blindly replacing URL in browser
if (process.client) {
const { route } = router.resolve(app.context.route.fullPath)
if (!route.matched.length) {
return resolve()
}
}
router.replace(app.context.route.fullPath, resolve, (err) => {
// https://github.com/vuejs/vue-router/blob/v3.4.3/src/util/errors.js
if (!err._isRouter) return reject(err)
if (err.type !== 2 /* NavigationFailureType.redirected */) return resolve()
// navigated to a different route in router guard
const unregister = router.afterEach(async (to, from) => {
if (process.server && ssrContext && ssrContext.url) {
ssrContext.url = to.fullPath
app.context.route = await getRouteData(to)
app.context.params = to.params || {}
app.context.query = to.query || {}
unregister()
resolve()
})
}
app.context.route = await getRouteData(to)
app.context.params = to.params || {}
app.context.query = to.query || {}
unregister()
resolve()
})
})
}
})
return {
<% if(store) { %>store,<% } %>
+6 -3
View File
@@ -75,6 +75,9 @@ function importChunk(chunkId, src) {
return promise
}
window.__NUXT_JSONP__ = function (chunkId, exports) { chunks[chunkId] = exports }
window.__NUXT_JSONP_CACHE__ = chunks
window.__NUXT_IMPORT__ = importChunk
export function installJsonp() {
window.__NUXT_JSONP__ = function (chunkId, exports) { chunks[chunkId] = exports }
window.__NUXT_JSONP_CACHE__ = chunks
window.__NUXT_IMPORT__ = importChunk
}
+21 -5
View File
@@ -1,5 +1,5 @@
import Vue from 'vue'
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
import { hasFetch, normalizeError, addLifecycleHook, createGetCounter } from '../utils'
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.fetchKey
const nuxtState = window.<%= globals.context %>
@@ -38,7 +38,7 @@ function created() {
// Hydrate component
this._hydrated = true
this._fetchKey = +this.$vnode.elm.dataset.fetchKey
this._fetchKey = this.$vnode.elm.dataset.fetchKey
const data = nuxtState.fetch[this._fetchKey]
// If fetch error
@@ -60,12 +60,22 @@ function createdFullStatic() {
if (typeof this.$options.fetchOnServer === 'function') {
fetchedOnServer = this.$options.fetchOnServer.call(this) !== false
}
if (!fetchedOnServer || this.$nuxt.isPreview || !this.$nuxt._pagePayload) {
if (!fetchedOnServer || this.<%= globals.nuxt %>.isPreview || !this.<%= globals.nuxt %>._pagePayload) {
return
}
this._hydrated = true
this._fetchKey = this.$nuxt._payloadFetchIndex++
const data = this.$nuxt._pagePayload.fetch[this._fetchKey]
const defaultKey = this.$options._scopeId || this.$options.name || ''
const getCounter = createGetCounter(this.<%= globals.nuxt %>._fetchCounters, defaultKey)
if (typeof this.$options.fetchKey === 'function') {
this._fetchKey = this.$options.fetchKey.call(this, getCounter)
} else {
const key = 'string' === typeof this.$options.fetchKey ? this.$options.fetchKey : defaultKey
this._fetchKey = key ? key + ':' + getCounter(key) : String(getCounter(key))
}
const data = this.<%= globals.nuxt %>._pagePayload.fetch[this._fetchKey]
// If fetch error
if (data && data._error) {
@@ -73,6 +83,12 @@ function createdFullStatic() {
return
}
// If there is a missing payload
if (!data) {
this.$fetch()
return
}
// Merge data
for (const key in data) {
Vue.set(this.$data, key, data[key])
+21 -3
View File
@@ -1,5 +1,5 @@
import Vue from 'vue'
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
import { hasFetch, normalizeError, addLifecycleHook, purifyData, createGetCounter } from '../utils'
async function serverPrefetch() {
if (!this._fetchOnServer) {
@@ -19,14 +19,20 @@ async function serverPrefetch() {
// Define an ssrKey for hydration
this._fetchKey = this.$ssrContext.nuxt.fetch.length
this._fetchKey = this._fetchKey || this.$ssrContext.fetchCounters['']++
// Add data-fetch-key on parent element of Component
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {}
attrs['data-fetch-key'] = this._fetchKey
// Add to ssrContext for window.__NUXT__.fetch
this.$ssrContext.nuxt.fetch.push(this.$fetchState.error ? { _error: this.$fetchState.error } : this._data)
<% if (debug) { %>
if (this.$ssrContext.nuxt.fetch[this._fetchKey] !== undefined) {
console.warn(`Duplicate fetch key detected (${this._fetchKey}). This may lead to unexpected results.`)
}
<% } %>
this.$ssrContext.nuxt.fetch[this._fetchKey] =
this.$fetchState.error ? { _error: this.$fetchState.error } : purifyData(this._data)
}
export default {
@@ -41,6 +47,18 @@ export default {
this._fetchOnServer = this.$options.fetchOnServer !== false
}
const defaultKey = this.$options._scopeId || this.$options.name || ''
const getCounter = createGetCounter(this.$ssrContext.fetchCounters, defaultKey)
if (typeof this.$options.fetchKey === 'function') {
this._fetchKey = this.$options.fetchKey.call(this, getCounter)
} else {
const key = 'string' === typeof this.$options.fetchKey ? this.$options.fetchKey : defaultKey
this._fetchKey = key ? key + ':' + getCounter(key) : String(getCounter(key))
}
// Added for remove vue undefined warning while ssr
this.$fetch = () => {} // issue #8043
Vue.util.defineReactive(this, '$fetchState', {
pending: true,
error: null,
+22 -9
View File
@@ -1,5 +1,6 @@
import Vue from 'vue'
import Router from 'vue-router'
import { normalizeURL, decode } from 'ufo'
import { interopDefault } from './utils'<%= isTest ? '// eslint-disable-line no-unused-vars' : '' %>
import scrollBehavior from './router.scrollBehavior.js'
@@ -50,7 +51,7 @@ import scrollBehavior from './router.scrollBehavior.js'
res += firstIndent + 'path: ' + JSON.stringify(route.path)
res += (route.components) ? nextIndent + 'components: {' + resMap + '\n' + baseIndent + tab + '}' : ''
res += (route.component) ? nextIndent + 'component: ' + route._name : ''
res += (route.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
res += (route.redirect) ? nextIndent + 'redirect: ' + (typeof route.redirect === 'function' ? serialize(route.redirect) : JSON.stringify(route.redirect)) : ''
res += (route.meta) ? nextIndent + 'meta: ' + JSON.stringify(route.meta) : ''
res += (typeof route.props !== 'undefined') ? nextIndent + 'props: ' + (typeof route.props === 'function' ? serialize(route.props) : JSON.stringify(route.props)) : ''
res += (typeof route.caseSensitive !== 'undefined') ? nextIndent + 'caseSensitive: ' + JSON.stringify(route.caseSensitive) : ''
@@ -82,18 +83,13 @@ const _routes = recursiveRoutes(router.routes, ' ', _components, 1)
}
}).join('\n')%>
// TODO: remove in Nuxt 3
const emptyFn = () => {}
const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onComplete = emptyFn, onAbort) {
return originalPush.call(this, location, onComplete, onAbort)
}
Vue.use(Router)
export const routerOptions = {
mode: '<%= router.mode %>',
base: decodeURI('<%= router.base %>'),
base: '<%= router.base %>',
linkActiveClass: '<%= router.linkActiveClass %>',
linkExactActiveClass: '<%= router.linkExactActiveClass %>',
scrollBehavior,
@@ -105,6 +101,23 @@ export const routerOptions = {
fallback: <%= router.fallback %>
}
export function createRouter () {
return new Router(routerOptions)
export function createRouter (ssrContext, config) {
const base = (config._app && config._app.basePath) || routerOptions.base
const router = new Router({ ...routerOptions, base })
// TODO: remove in Nuxt 3
const originalPush = router.push
router.push = function push (location, onComplete = emptyFn, onAbort) {
return originalPush.call(this, location, onComplete, onAbort)
}
const resolve = router.resolve.bind(router)
router.resolve = (to, current, append) => {
if (typeof to === 'string') {
to = normalizeURL(to)
}
return resolve(to, current, append)
}
return router
}
+24 -22
View File
@@ -2,53 +2,55 @@
<%= isTest ? '/* eslint-disable quotes, semi, indent, comma-spacing, key-spacing, object-curly-spacing, space-before-function-paren */' : '' %>
export default <%= serializeFunction(router.scrollBehavior) %>
<%= isTest ? '/* eslint-enable quotes, semi, indent, comma-spacing, key-spacing, object-curly-spacing, space-before-function-paren */' : '' %>
<% } else { %>import { getMatchedComponents } from './utils'
<% } else { %>import { getMatchedComponents, setScrollRestoration } from './utils'
if (process.client) {
if ('scrollRestoration' in window.history) {
window.history.scrollRestoration = 'manual'
setScrollRestoration('manual')
// reset scrollRestoration to auto when leaving page, allowing page reload
// and back-navigation from other pages to use the browser to restore the
// scrolling position.
window.addEventListener('beforeunload', () => {
window.history.scrollRestoration = 'auto'
setScrollRestoration('auto')
})
// Setting scrollRestoration to manual again when returning to this page.
window.addEventListener('load', () => {
window.history.scrollRestoration = 'manual'
setScrollRestoration('manual')
})
}
}
export default function (to, from, savedPosition) {
// if the returned position is falsy or an empty object,
// will retain current scroll position.
let position = false
function shouldScrollToTop(route) {
const Pages = getMatchedComponents(route)
if (Pages.length === 1) {
const { options = {} } = Pages[0]
return options.scrollToTop !== false
}
return Pages.some(({ options }) => options && options.scrollToTop)
}
// if no children detected and scrollToTop is not explicitly disabled
const Pages = getMatchedComponents(to)
if (
Pages.length < 2 &&
Pages.every(Page => Page.options.scrollToTop !== false)
) {
// scroll to the top of the page
position = { x: 0, y: 0 }
} else if (Pages.some(Page => Page.options.scrollToTop)) {
// if one of the children has scrollToTop option set to true
position = { x: 0, y: 0 }
}
export default function (to, from, savedPosition) {
// If the returned position is falsy or an empty object, will retain current scroll position
let position = false
const isRouteChanged = to !== from
// savedPosition is only available for popstate navigations (back button)
if (savedPosition) {
position = savedPosition
} else if (isRouteChanged && shouldScrollToTop(to)) {
position = { x: 0, y: 0 }
}
const nuxt = window.<%= globals.nuxt %>
// triggerScroll is only fired when a new component is loaded
if (to.path === from.path && to.hash !== from.hash) {
if (
// Initial load (vuejs/vue-router#3199)
!isRouteChanged ||
// Route hash changes
(to.path === from.path && to.hash !== from.hash)
) {
nuxt.$nextTick(() => nuxt.$emit('triggerScroll'))
}
+40 -17
View File
@@ -1,5 +1,5 @@
import { stringify } from 'querystring'
import Vue from 'vue'
import { joinURL, normalizeURL, withQuery } from 'ufo'
<% if (fetch.server) { %>import fetch from 'node-fetch'<% } %>
<% if (features.middleware) { %>import middleware from './middleware.js'<% } %>
import {
@@ -24,17 +24,31 @@ if (!Vue.__nuxt__fetch__mixin__) {
}
<% } %>
<% if (isDev) { %>
if (!Vue.__original_use__) {
Vue.__original_use__ = Vue.use
Vue.__install_times__ = 0
Vue.use = function (plugin, ...args) {
plugin.__nuxt_external_installed__ = Vue._installedPlugins.includes(plugin)
return Vue.__original_use__(plugin, ...args)
}
}
if (Vue.__install_times__ === 2) {
Vue.__install_times__ = 0
Vue._installedPlugins = Vue._installedPlugins.filter(plugin => {
return plugin.__nuxt_external_installed__ === true
})
}
Vue.__install_times__++
<% } %>
// Component: <NuxtLink>
Vue.component(NuxtLink.name, NuxtLink)
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
<% if (fetch.server) { %>if (!global.fetch) { global.fetch = fetch }<% } %>
const noopApp = () => new Vue({ render: h => h('div') })
function urlJoin () {
return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/')
}
const noopApp = () => new Vue({ render: h => h('div', { domProps: { id: '<%= globals.id %>' } }) })
const createNext = ssrContext => (opts) => {
// If static target, render on client-side
@@ -43,19 +57,19 @@ const createNext = ssrContext => (opts) => {
ssrContext.nuxt.serverRendered = false
return
}
opts.query = stringify(opts.query)
opts.path = opts.path + (opts.query ? '?' + opts.query : '')
const routerBase = '<%= router.base %>'
if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) {
opts.path = urlJoin(routerBase, opts.path)
let fullPath = withQuery(opts.path, opts.query)
const $config = ssrContext.runtimeConfig || {}
const routerBase = ($config._app && $config._app.basePath) || '<%= router.base %>'
if (!fullPath.startsWith('http') && (routerBase !== '/' && !fullPath.startsWith(routerBase))) {
fullPath = joinURL(routerBase, fullPath)
}
// Avoid loop redirect
if (opts.path === ssrContext.url) {
if (decodeURI(fullPath) === decodeURI(ssrContext.url)) {
ssrContext.redirected = false
return
}
ssrContext.res.writeHead(opts.status, {
Location: opts.path
Location: normalizeURL(fullPath)
})
ssrContext.res.end()
}
@@ -72,15 +86,24 @@ export default async (ssrContext) => {
// Used for beforeNuxtRender({ Components, nuxtState })
ssrContext.beforeRenderFns = []
// Nuxt object (window.{{globals.context}}, defaults to window.__NUXT__)
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: [], <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: {}, <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
<% if (features.fetch) { %>
ssrContext.fetchCounters = {}
<% } %>
// Remove query from url is static target
if (process.static && ssrContext.url) {
<% if (isFullStatic) { %>
if (ssrContext.url) {
ssrContext.url = ssrContext.url.split('?')[0]
}
<% } %>
// Public runtime config
ssrContext.nuxt.config = ssrContext.runtimeConfig.public
if (ssrContext.nuxt.config._app) {
__webpack_public_path__ = joinURL(ssrContext.nuxt.config._app.cdnURL, ssrContext.nuxt.config._app.assetsPath)
}
// Create the app definition and the instance (created for each request)
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext, { ...ssrContext.runtimeConfig.public, ...ssrContext.runtimeConfig.private })
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext, ssrContext.runtimeConfig.private)
const _app = new Vue(app)
// Add ssr route path to nuxt context so we can account for page navigation between ssr and csr
ssrContext.nuxt.routePath = app.context.route.path
@@ -133,7 +156,7 @@ export default async (ssrContext) => {
<% if (debug) { %>const s = Date.now()<% } %>
// Components are already resolved by setContext -> getRouteData (app/utils.js)
const Components = getMatchedComponents(router.match(ssrContext.url))
const Components = getMatchedComponents(app.context.route)
<% if (store) { %>
/*
+2 -2
View File
@@ -94,10 +94,10 @@ function resolveStoreModules (moduleData, filename) {
// If src is a known Vuex property
if (VUEX_PROPERTIES.includes(moduleName)) {
const property = moduleName
const storeModule = getStoreModule(store, namespaces, { isProperty: true })
const propertyStoreModule = getStoreModule(store, namespaces, { isProperty: true })
// Replace state since it's a function
mergeProperty(storeModule, moduleData, property)
mergeProperty(propertyStoreModule, moduleData, property)
return
}
+77 -95
View File
@@ -1,4 +1,5 @@
import Vue from 'vue'
import { isSamePath as _isSamePath, joinURL, normalizeURL, withQuery, withoutTrailingSlash } from 'ufo'
// window.{{globals.loadedCallback}} hook
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
@@ -9,6 +10,15 @@ if (process.client) {
}
}
export function createGetCounter (counterObject, defaultKey = '') {
return function getCounter (id = defaultKey) {
if (counterObject[id] === undefined) {
counterObject[id] = 0
}
return counterObject[id]++
}
}
export function empty () {}
export function globalHandleError (error) {
@@ -25,6 +35,24 @@ export function interopDefault (promise) {
export function hasFetch(vm) {
return vm.$options && typeof vm.$options.fetch === 'function' && !vm.$options.fetch.length
}
export function purifyData(data) {
if (process.env.NODE_ENV === 'production') {
return data
}
return Object.entries(data).filter(
([key, value]) => {
const valid = !(value instanceof Function) && !(value instanceof Promise)
if (!valid) {
console.warn(`${key} is not able to be stringified. This will break in a production environment.`)
}
return valid
}
).reduce((obj, [key, value]) => {
obj[key] = value
return obj
}, {})
}
export function getChildrenComponentInstancesUsingFetch(vm, instances = []) {
const children = vm.$children || []
for (const child of children) {
@@ -118,7 +146,29 @@ export function resolveRouteComponents (route, fn) {
flatMapComponents(route, async (Component, instance, match, key) => {
// If component is a function, resolve it
if (typeof Component === 'function' && !Component.options) {
Component = await Component()
try {
Component = await Component()
} catch (error) {
// Handle webpack chunk loading errors
// This may be due to a new deployment or a network problem
if (
error &&
error.name === 'ChunkLoadError' &&
typeof window !== 'undefined' &&
window.sessionStorage
) {
const timeNow = Date.now()
const previousReloadTime = parseInt(window.sessionStorage.getItem('nuxt-reload'))
// check for previous reload time not to reload infinitely
if (!previousReloadTime || previousReloadTime + 60000 < timeNow) {
window.sessionStorage.setItem('nuxt-reload', timeNow)
window.location.reload(true /* skip cache */)
}
}
throw error
}
}
match.components[key] = Component = sanitizeComponent(Component)
return typeof fn === 'function' ? fn(Component, instance, match, key) : Component
@@ -152,16 +202,18 @@ export async function setContext (app, context) {
<%= (store ? 'store: app.store,' : '') %>
payload: context.payload,
error: context.error,
base: '<%= router.base %>',
base: app.router.options.base,
env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
}
// Only set once
if (!process.static && context.req) {
<% if (!isFullStatic) { %>
if (context.req) {
app.context.req = context.req
}
if (!process.static && context.res) {
if (context.res) {
app.context.res = context.res
}
<% } %>
if (context.ssrContext) {
app.context.ssrContext = context.ssrContext
}
@@ -189,7 +241,7 @@ export async function setContext (app, context) {
status
})
} else {
path = formatUrl(path, query)
path = withQuery(path, query)
if (process.server) {
app.context.next({
path,
@@ -278,15 +330,20 @@ export function promisify (fn, context) {
// Imported from vue-router
export function getLocation (base, mode) {
let path = decodeURI(window.location.pathname)
if (mode === 'hash') {
return window.location.hash.replace(/^#\//, '')
}
// To get matched with sanitized router.base add trailing slash
if (base && (path.endsWith('/') ? path : path + '/').startsWith(base)) {
base = decodeURI(base).slice(0, -1) // consideration is base is normalized with trailing slash
let path = decodeURI(window.location.pathname)
if (base && path.startsWith(base)) {
path = path.slice(base.length)
}
return (path || '/') + window.location.search + window.location.hash
const fullPath = (path || '/') + window.location.search + window.location.hash
return normalizeURL(fullPath)
}
// Imported from path-to-regexp
@@ -559,86 +616,6 @@ function flags (options) {
return options && options.sensitive ? '' : 'i'
}
/**
* Format given url, append query to url query string
*
* @param {string} url
* @param {string} query
* @return {string}
*/
function formatUrl (url, query) {
<% if (features.clientUseUrl) { %>
url = new URL(url, top.location.href)
for (const key in query) {
const value = query[key]
if (value == null) {
continue
}
if (Array.isArray(value)) {
for (const arrayValue of value) {
url.searchParams.append(key, arrayValue)
}
continue
}
url.searchParams.append(key, value)
}
url.searchParams.sort()
return url.toString()
<% } else { %>
let protocol
const index = url.indexOf('://')
if (index !== -1) {
protocol = url.substring(0, index)
url = url.substring(index + 3)
} else if (url.startsWith('//')) {
url = url.substring(2)
}
let parts = url.split('/')
let result = (protocol ? protocol + '://' : '//') + parts.shift()
let path = parts.join('/')
if (path === '' && parts.length === 1) {
result += '/'
}
let hash
parts = path.split('#')
if (parts.length === 2) {
[path, hash] = parts
}
result += path ? '/' + path : ''
if (query && JSON.stringify(query) !== '{}') {
result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query)
}
result += hash ? '#' + hash : ''
return result
<% } %>
}
<% if (!features.clientUseUrl) { %>
/**
* Transform data object to query string
*
* @param {object} query
* @return {string}
*/
function formatQuery (query) {
return Object.keys(query).sort().map((key) => {
const val = query[key]
if (val == null) {
return ''
}
if (Array.isArray(val)) {
return val.slice().map(val2 => [key, '=', val2].join('')).join('&')
}
return key + '=' + val
}).filter(Boolean).join('&')
}
<% } %>
export function addLifecycleHook(vm, hook, fn) {
if (!vm.$options[hook]) {
vm.$options[hook] = []
@@ -648,10 +625,15 @@ export function addLifecycleHook(vm, hook, fn) {
}
}
export const urlJoin = function urlJoin () {
return [].slice
.call(arguments)
.join('/')
.replace(/\/+/g, '/')
.replace(':/', '://')
export const urlJoin = joinURL
export const stripTrailingSlash = withoutTrailingSlash
export const isSamePath = _isSamePath
export function setScrollRestoration (newVal) {
try {
window.history.scrollRestoration = newVal;
} catch(e) {}
}