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) if (process.client) { window.<%= globals.readyCallback %>Cbs = [] window.<%= globals.readyCallback %> = (cb) => { window.<%= globals.readyCallback %>Cbs.push(cb) } } 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) { if (Vue.config.errorHandler) { Vue.config.errorHandler(error) } } export function interopDefault (promise) { return promise.then(m => m.default || m) } <% if (features.fetch) { %> 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) { if (child.$fetch) { instances.push(child) } if (child.$children) { getChildrenComponentInstancesUsingFetch(child, instances) } } return instances } <% } %> <% if (features.asyncData) { %> export function applyAsyncData (Component, asyncData) { if ( // For SSR, we once all this function without second param to just apply asyncData // Prevent doing this for each SSR request !asyncData && Component.options.__hasNuxtData ) { return } const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} } Component.options._originDataFn = ComponentData Component.options.data = function () { const data = ComponentData.call(this, this) if (this.$ssrContext) { asyncData = this.$ssrContext.asyncData[Component.cid] } return { ...data, ...asyncData } } Component.options.__hasNuxtData = true if (Component._Ctor && Component._Ctor.options) { Component._Ctor.options.data = Component.options.data } } <% } %> export function sanitizeComponent (Component) { // If Component already sanitized if (Component.options && Component._Ctor === Component) { return Component } if (!Component.options) { Component = Vue.extend(Component) // fix issue #6 Component._Ctor = Component } else { Component._Ctor = Component Component.extendOptions = Component.options } // If no component name defined, set file path as name, (also fixes #5703) if (!Component.options.name && Component.options.__file) { Component.options.name = Component.options.__file } return Component } export function getMatchedComponents (route, matches = false, prop = 'components') { return Array.prototype.concat.apply([], route.matched.map((m, index) => { return Object.keys(m[prop]).map((key) => { matches && matches.push(index) return m[prop][key] }) })) } export function getMatchedComponentsInstances (route, matches = false) { return getMatchedComponents(route, matches, 'instances') } export function flatMapComponents (route, fn) { return Array.prototype.concat.apply([], route.matched.map((m, index) => { return Object.keys(m.components).reduce((promises, key) => { if (m.components[key]) { promises.push(fn(m.components[key], m.instances[key], m, key, index)) } else { delete m.components[key] } return promises }, []) })) } export function resolveRouteComponents (route, fn) { return Promise.all( flatMapComponents(route, async (Component, instance, match, key) => { // If component is a function, resolve it if (typeof Component === 'function' && !Component.options) { 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() try { 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 */) } } catch { // don't throw an error if we have issues reading sessionStorage } } throw error } } match.components[key] = Component = sanitizeComponent(Component) return typeof fn === 'function' ? fn(Component, instance, match, key) : Component }) ) } export async function getRouteData (route) { if (!route) { return } // Make sure the components are resolved (code-splitting) await resolveRouteComponents(route) // Send back a copy of route with meta based on Component definition return { ...route, meta: getMatchedComponents(route).map((Component, index) => { return { ...Component.options.meta, ...(route.matched[index] || {}).meta } }) } } export async function setContext (app, context) { // If context not defined, create it if (!app.context) { app.context = { isStatic: process.static, isDev: <%= isDev %>, isHMR: false, app, <%= (store ? 'store: app.store,' : '') %> payload: context.payload, error: context.error, base: app.router.options.base, env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %> } // Only set once <% if (!isFullStatic) { %> if (context.req) { app.context.req = context.req } if (context.res) { app.context.res = context.res } <% } %> if (context.ssrContext) { app.context.ssrContext = context.ssrContext } app.context.redirect = (status, path, query) => { if (!status) { return } app.context._redirected = true // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' }) let pathType = typeof path if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { query = path || {} path = status pathType = typeof path status = 302 } if (pathType === 'object') { path = app.router.resolve(path).route.fullPath } // "/absolute/route", "./relative/route" or "../relative/route" if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { app.context.next({ path, query, status }) } else { path = withQuery(path, query) if (process.server) { app.context.next({ path, status }) } if (process.client) { // https://developer.mozilla.org/en-US/docs/Web/API/Location/assign window.location.assign(path) // Throw a redirect error throw new Error('ERR_REDIRECT') } } } if (process.server) { app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn) app.context.beforeSerialize = fn => context.beforeSerializeFns.push(fn) } if (process.client) { app.context.nuxtState = window.<%= globals.context %> } } // Dynamic keys const [currentRouteData, fromRouteData] = await Promise.all([ getRouteData(context.route), getRouteData(context.from) ]) if (context.route) { app.context.route = currentRouteData } if (context.from) { app.context.from = fromRouteData } if (context.error) { app.context.error = context.error } app.context.next = context.next app.context._redirected = false app.context._errored = false app.context.isHMR = <% if(isDev) { %>Boolean(context.isHMR)<% } else { %>false<% } %> app.context.params = app.context.route.params || {} app.context.query = app.context.route.query || {} } <% if (features.middleware) { %> export function middlewareSeries (promises, appContext, renderState) { if (!promises.length || appContext._redirected || appContext._errored || (renderState && renderState.aborted)) { return Promise.resolve() } return promisify(promises[0], appContext) .then(() => { return middlewareSeries(promises.slice(1), appContext, renderState) }) } <% } %> export function promisify (fn, context) { <% if (features.deprecations) { %> let promise if (fn.length === 2) { <% if (isDev) { %> console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. ' + 'Please switch to promises or async/await syntax') <% } %> // fn(context, callback) promise = new Promise((resolve) => { fn(context, function (err, data) { if (err) { context.error(err) } data = data || {} resolve(data) }) }) } else { promise = fn(context) } <% } else { %> const promise = fn(context) <% } %> if (promise && promise instanceof Promise && typeof promise.then === 'function') { return promise } return Promise.resolve(promise) } // Imported from vue-router export function getLocation (base, mode) { if (mode === 'hash') { return window.location.hash.replace(/^#\//, '') } 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) } const fullPath = (path || '/') + window.location.search + window.location.hash return normalizeURL(fullPath) } // Imported from path-to-regexp /** * Compile a string to a template function for the path. * * @param {string} str * @param {Object=} options * @return {!function(Object=, Object=)} */ export function compile (str, options) { return tokensToFunction(parse(str, options), options) } export function getQueryDiff (toQuery, fromQuery) { const diff = {} const queries = { ...toQuery, ...fromQuery } for (const k in queries) { if (String(toQuery[k]) !== String(fromQuery[k])) { diff[k] = true } } return diff } export function normalizeError (err) { let message if (!(err.message || typeof err === 'string')) { try { message = JSON.stringify(err, null, 2) } catch (e) { message = `[${err.constructor.name}]` } } else { message = err.message || err } return { ...err, message, statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500) } } /** * The main path matching regexp utility. * * @type {RegExp} */ const PATH_REGEXP = new RegExp([ // Match escaped characters that would otherwise appear in future matches. // This allows the user to escape special characters that won't transform. '(\\\\.)', // Match Express-style parameters and un-named parameters with a prefix // and optional suffixes. Matches appear as: // // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' ].join('|'), 'g') /** * Parse a string for the raw tokens. * * @param {string} str * @param {Object=} options * @return {!Array} */ function parse (str, options) { const tokens = [] let key = 0 let index = 0 let path = '' const defaultDelimiter = (options && options.delimiter) || '/' let res while ((res = PATH_REGEXP.exec(str)) != null) { const m = res[0] const escaped = res[1] const offset = res.index path += str.slice(index, offset) index = offset + m.length // Ignore already escaped sequences. if (escaped) { path += escaped[1] continue } const next = str[index] const prefix = res[2] const name = res[3] const capture = res[4] const group = res[5] const modifier = res[6] const asterisk = res[7] // Push the current path onto the tokens. if (path) { tokens.push(path) path = '' } const partial = prefix != null && next != null && next !== prefix const repeat = modifier === '+' || modifier === '*' const optional = modifier === '?' || modifier === '*' const delimiter = res[2] || defaultDelimiter const pattern = capture || group tokens.push({ name: name || key++, prefix: prefix || '', delimiter, optional, repeat, partial, asterisk: Boolean(asterisk), pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') }) } // Match any characters still remaining. if (index < str.length) { path += str.substr(index) } // If the path exists, push it onto the end. if (path) { tokens.push(path) } return tokens } /** * Prettier encoding of URI path segments. * * @param {string} * @return {string} */ function encodeURIComponentPretty (str, slashAllowed) { const re = slashAllowed ? /[?#]/g : /[/?#]/g return encodeURI(str).replace(re, (c) => { return '%' + c.charCodeAt(0).toString(16).toUpperCase() }) } /** * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. * * @param {string} * @return {string} */ function encodeAsterisk (str) { return encodeURIComponentPretty(str, true) } /** * Escape a regular expression string. * * @param {string} str * @return {string} */ function escapeString (str) { return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') } /** * Escape the capturing group by escaping special characters and meaning. * * @param {string} group * @return {string} */ function escapeGroup (group) { return group.replace(/([=!:$/()])/g, '\\$1') } /** * Expose a method for transforming tokens into the path function. */ function tokensToFunction (tokens, options) { // Compile all the tokens into regexps. const matches = new Array(tokens.length) // Compile all the patterns before compilation. for (let i = 0; i < tokens.length; i++) { if (typeof tokens[i] === 'object') { matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options)) } } return function (obj, opts) { let path = '' const data = obj || {} const options = opts || {} const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent for (let i = 0; i < tokens.length; i++) { const token = tokens[i] if (typeof token === 'string') { path += token continue } const value = data[token.name || 'pathMatch'] let segment if (value == null) { if (token.optional) { // Prepend partial segment prefixes. if (token.partial) { path += token.prefix } continue } else { throw new TypeError('Expected "' + token.name + '" to be defined') } } if (Array.isArray(value)) { if (!token.repeat) { throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') } if (value.length === 0) { if (token.optional) { continue } else { throw new TypeError('Expected "' + token.name + '" to not be empty') } } for (let j = 0; j < value.length; j++) { segment = encode(value[j]) if (!matches[i].test(segment)) { throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') } path += (j === 0 ? token.prefix : token.delimiter) + segment } continue } segment = token.asterisk ? encodeAsterisk(value) : encode(value) if (!matches[i].test(segment)) { throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') } path += token.prefix + segment } return path } } /** * Get the flags for a regexp from the options. * * @param {Object} options * @return {string} */ function flags (options) { return options && options.sensitive ? '' : 'i' } export function addLifecycleHook(vm, hook, fn) { if (!vm.$options[hook]) { vm.$options[hook] = [] } if (!vm.$options[hook].includes(fn)) { vm.$options[hook].push(fn) } } export const urlJoin = joinURL export const stripTrailingSlash = withoutTrailingSlash export const isSamePath = _isSamePath export function setScrollRestoration (newVal) { try { window.history.scrollRestoration = newVal; } catch(e) {} }