UNPKG

17.7 kBJavaScriptView Raw
1import Vue from 'vue'
2
3// window.{{globals.loadedCallback}} hook
4// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
5if (process.client) {
6 window.<%= globals.readyCallback %>Cbs = []
7 window.<%= globals.readyCallback %> = (cb) => {
8 window.<%= globals.readyCallback %>Cbs.push(cb)
9 }
10}
11
12export function empty () {}
13
14export function globalHandleError (error) {
15 if (Vue.config.errorHandler) {
16 Vue.config.errorHandler(error)
17 }
18}
19
20export function interopDefault (promise) {
21 return promise.then(m => m.default || m)
22}
23
24<% if (features.fetch) { %>
25export function hasFetch(vm) {
26 return vm.$options && typeof vm.$options.fetch === 'function' && !vm.$options.fetch.length
27}
28export function getChildrenComponentInstancesUsingFetch(vm, instances = []) {
29 const children = vm.$children || []
30 for (const child of children) {
31 if (child.$fetch) {
32 instances.push(child)
33 continue; // Don't get the children since it will reload the template
34 }
35 if (child.$children) {
36 getChildrenComponentInstancesUsingFetch(child, instances)
37 }
38 }
39 return instances
40}
41<% } %>
42<% if (features.asyncData) { %>
43export function applyAsyncData (Component, asyncData) {
44 if (
45 // For SSR, we once all this function without second param to just apply asyncData
46 // Prevent doing this for each SSR request
47 !asyncData && Component.options.__hasNuxtData
48 ) {
49 return
50 }
51
52 const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} }
53 Component.options._originDataFn = ComponentData
54
55 Component.options.data = function () {
56 const data = ComponentData.call(this, this)
57 if (this.$ssrContext) {
58 asyncData = this.$ssrContext.asyncData[Component.cid]
59 }
60 return { ...data, ...asyncData }
61 }
62
63 Component.options.__hasNuxtData = true
64
65 if (Component._Ctor && Component._Ctor.options) {
66 Component._Ctor.options.data = Component.options.data
67 }
68}
69<% } %>
70
71export function sanitizeComponent (Component) {
72 // If Component already sanitized
73 if (Component.options && Component._Ctor === Component) {
74 return Component
75 }
76 if (!Component.options) {
77 Component = Vue.extend(Component) // fix issue #6
78 Component._Ctor = Component
79 } else {
80 Component._Ctor = Component
81 Component.extendOptions = Component.options
82 }
83 // If no component name defined, set file path as name, (also fixes #5703)
84 if (!Component.options.name && Component.options.__file) {
85 Component.options.name = Component.options.__file
86 }
87 return Component
88}
89
90export function getMatchedComponents (route, matches = false, prop = 'components') {
91 return Array.prototype.concat.apply([], route.matched.map((m, index) => {
92 return Object.keys(m[prop]).map((key) => {
93 matches && matches.push(index)
94 return m[prop][key]
95 })
96 }))
97}
98
99export function getMatchedComponentsInstances (route, matches = false) {
100 return getMatchedComponents(route, matches, 'instances')
101}
102
103export function flatMapComponents (route, fn) {
104 return Array.prototype.concat.apply([], route.matched.map((m, index) => {
105 return Object.keys(m.components).reduce((promises, key) => {
106 if (m.components[key]) {
107 promises.push(fn(m.components[key], m.instances[key], m, key, index))
108 } else {
109 delete m.components[key]
110 }
111 return promises
112 }, [])
113 }))
114}
115
116export function resolveRouteComponents (route, fn) {
117 return Promise.all(
118 flatMapComponents(route, async (Component, instance, match, key) => {
119 // If component is a function, resolve it
120 if (typeof Component === 'function' && !Component.options) {
121 Component = await Component()
122 }
123 match.components[key] = Component = sanitizeComponent(Component)
124 return typeof fn === 'function' ? fn(Component, instance, match, key) : Component
125 })
126 )
127}
128
129export async function getRouteData (route) {
130 if (!route) {
131 return
132 }
133 // Make sure the components are resolved (code-splitting)
134 await resolveRouteComponents(route)
135 // Send back a copy of route with meta based on Component definition
136 return {
137 ...route,
138 meta: getMatchedComponents(route).map((Component, index) => {
139 return { ...Component.options.meta, ...(route.matched[index] || {}).meta }
140 })
141 }
142}
143
144export async function setContext (app, context) {
145 // If context not defined, create it
146 if (!app.context) {
147 app.context = {
148 isStatic: process.static,
149 isDev: <%= isDev %>,
150 isHMR: false,
151 app,
152 <%= (store ? 'store: app.store,' : '') %>
153 payload: context.payload,
154 error: context.error,
155 base: '<%= router.base %>',
156 env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
157 }
158 // Only set once
159 if (!process.static && context.req) {
160 app.context.req = context.req
161 }
162 if (!process.static && context.res) {
163 app.context.res = context.res
164 }
165 if (context.ssrContext) {
166 app.context.ssrContext = context.ssrContext
167 }
168 app.context.redirect = (status, path, query) => {
169 if (!status) {
170 return
171 }
172 app.context._redirected = true
173 // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' })
174 let pathType = typeof path
175 if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) {
176 query = path || {}
177 path = status
178 pathType = typeof path
179 status = 302
180 }
181 if (pathType === 'object') {
182 path = app.router.resolve(path).route.fullPath
183 }
184 // "/absolute/route", "./relative/route" or "../relative/route"
185 if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) {
186 app.context.next({
187 path,
188 query,
189 status
190 })
191 } else {
192 path = formatUrl(path, query)
193 if (process.server) {
194 app.context.next({
195 path,
196 status
197 })
198 }
199 if (process.client) {
200 // https://developer.mozilla.org/en-US/docs/Web/API/Location/replace
201 window.location.replace(path)
202
203 // Throw a redirect error
204 throw new Error('ERR_REDIRECT')
205 }
206 }
207 }
208 if (process.server) {
209 app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn)
210 }
211 if (process.client) {
212 app.context.nuxtState = window.<%= globals.context %>
213 }
214 }
215
216 // Dynamic keys
217 const [currentRouteData, fromRouteData] = await Promise.all([
218 getRouteData(context.route),
219 getRouteData(context.from)
220 ])
221
222 if (context.route) {
223 app.context.route = currentRouteData
224 }
225
226 if (context.from) {
227 app.context.from = fromRouteData
228 }
229
230 app.context.next = context.next
231 app.context._redirected = false
232 app.context._errored = false
233 app.context.isHMR = <% if(isDev) { %>Boolean(context.isHMR)<% } else { %>false<% } %>
234 app.context.params = app.context.route.params || {}
235 app.context.query = app.context.route.query || {}
236}
237<% if (features.middleware) { %>
238export function middlewareSeries (promises, appContext) {
239 if (!promises.length || appContext._redirected || appContext._errored) {
240 return Promise.resolve()
241 }
242 return promisify(promises[0], appContext)
243 .then(() => {
244 return middlewareSeries(promises.slice(1), appContext)
245 })
246}
247<% } %>
248export function promisify (fn, context) {
249 <% if (features.deprecations) { %>
250 let promise
251 if (fn.length === 2) {
252 <% if (isDev) { %>
253 console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. ' +
254 'Please switch to promises or async/await syntax')
255 <% } %>
256
257 // fn(context, callback)
258 promise = new Promise((resolve) => {
259 fn(context, function (err, data) {
260 if (err) {
261 context.error(err)
262 }
263 data = data || {}
264 resolve(data)
265 })
266 })
267 } else {
268 promise = fn(context)
269 }
270 <% } else { %>
271 const promise = fn(context)
272 <% } %>
273 if (promise && promise instanceof Promise && typeof promise.then === 'function') {
274 return promise
275 }
276 return Promise.resolve(promise)
277}
278
279// Imported from vue-router
280export function getLocation (base, mode) {
281 let path = decodeURI(window.location.pathname)
282 if (mode === 'hash') {
283 return window.location.hash.replace(/^#\//, '')
284 }
285 // To get matched with sanitized router.base add trailing slash
286 if (base && (path.endsWith('/') ? path : path + '/').startsWith(base)) {
287 path = path.slice(base.length)
288 }
289 return (path || '/') + window.location.search + window.location.hash
290}
291
292// Imported from path-to-regexp
293
294/**
295 * Compile a string to a template function for the path.
296 *
297 * @param {string} str
298 * @param {Object=} options
299 * @return {!function(Object=, Object=)}
300 */
301export function compile (str, options) {
302 return tokensToFunction(parse(str, options), options)
303}
304
305export function getQueryDiff (toQuery, fromQuery) {
306 const diff = {}
307 const queries = { ...toQuery, ...fromQuery }
308 for (const k in queries) {
309 if (String(toQuery[k]) !== String(fromQuery[k])) {
310 diff[k] = true
311 }
312 }
313 return diff
314}
315
316export function normalizeError (err) {
317 let message
318 if (!(err.message || typeof err === 'string')) {
319 try {
320 message = JSON.stringify(err, null, 2)
321 } catch (e) {
322 message = `[${err.constructor.name}]`
323 }
324 } else {
325 message = err.message || err
326 }
327 return {
328 ...err,
329 message,
330 statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500)
331 }
332}
333
334/**
335 * The main path matching regexp utility.
336 *
337 * @type {RegExp}
338 */
339const PATH_REGEXP = new RegExp([
340 // Match escaped characters that would otherwise appear in future matches.
341 // This allows the user to escape special characters that won't transform.
342 '(\\\\.)',
343 // Match Express-style parameters and un-named parameters with a prefix
344 // and optional suffixes. Matches appear as:
345 //
346 // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
347 // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
348 // "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
349 '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
350].join('|'), 'g')
351
352/**
353 * Parse a string for the raw tokens.
354 *
355 * @param {string} str
356 * @param {Object=} options
357 * @return {!Array}
358 */
359function parse (str, options) {
360 const tokens = []
361 let key = 0
362 let index = 0
363 let path = ''
364 const defaultDelimiter = (options && options.delimiter) || '/'
365 let res
366
367 while ((res = PATH_REGEXP.exec(str)) != null) {
368 const m = res[0]
369 const escaped = res[1]
370 const offset = res.index
371 path += str.slice(index, offset)
372 index = offset + m.length
373
374 // Ignore already escaped sequences.
375 if (escaped) {
376 path += escaped[1]
377 continue
378 }
379
380 const next = str[index]
381 const prefix = res[2]
382 const name = res[3]
383 const capture = res[4]
384 const group = res[5]
385 const modifier = res[6]
386 const asterisk = res[7]
387
388 // Push the current path onto the tokens.
389 if (path) {
390 tokens.push(path)
391 path = ''
392 }
393
394 const partial = prefix != null && next != null && next !== prefix
395 const repeat = modifier === '+' || modifier === '*'
396 const optional = modifier === '?' || modifier === '*'
397 const delimiter = res[2] || defaultDelimiter
398 const pattern = capture || group
399
400 tokens.push({
401 name: name || key++,
402 prefix: prefix || '',
403 delimiter,
404 optional,
405 repeat,
406 partial,
407 asterisk: Boolean(asterisk),
408 pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
409 })
410 }
411
412 // Match any characters still remaining.
413 if (index < str.length) {
414 path += str.substr(index)
415 }
416
417 // If the path exists, push it onto the end.
418 if (path) {
419 tokens.push(path)
420 }
421
422 return tokens
423}
424
425/**
426 * Prettier encoding of URI path segments.
427 *
428 * @param {string}
429 * @return {string}
430 */
431function encodeURIComponentPretty (str, slashAllowed) {
432 const re = slashAllowed ? /[?#]/g : /[/?#]/g
433 return encodeURI(str).replace(re, (c) => {
434 return '%' + c.charCodeAt(0).toString(16).toUpperCase()
435 })
436}
437
438/**
439 * Encode the asterisk parameter. Similar to `pretty`, but allows slashes.
440 *
441 * @param {string}
442 * @return {string}
443 */
444function encodeAsterisk (str) {
445 return encodeURIComponentPretty(str, true)
446}
447
448/**
449 * Escape a regular expression string.
450 *
451 * @param {string} str
452 * @return {string}
453 */
454function escapeString (str) {
455 return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1')
456}
457
458/**
459 * Escape the capturing group by escaping special characters and meaning.
460 *
461 * @param {string} group
462 * @return {string}
463 */
464function escapeGroup (group) {
465 return group.replace(/([=!:$/()])/g, '\\$1')
466}
467
468/**
469 * Expose a method for transforming tokens into the path function.
470 */
471function tokensToFunction (tokens, options) {
472 // Compile all the tokens into regexps.
473 const matches = new Array(tokens.length)
474
475 // Compile all the patterns before compilation.
476 for (let i = 0; i < tokens.length; i++) {
477 if (typeof tokens[i] === 'object') {
478 matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options))
479 }
480 }
481
482 return function (obj, opts) {
483 let path = ''
484 const data = obj || {}
485 const options = opts || {}
486 const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent
487
488 for (let i = 0; i < tokens.length; i++) {
489 const token = tokens[i]
490
491 if (typeof token === 'string') {
492 path += token
493
494 continue
495 }
496
497 const value = data[token.name || 'pathMatch']
498 let segment
499
500 if (value == null) {
501 if (token.optional) {
502 // Prepend partial segment prefixes.
503 if (token.partial) {
504 path += token.prefix
505 }
506
507 continue
508 } else {
509 throw new TypeError('Expected "' + token.name + '" to be defined')
510 }
511 }
512
513 if (Array.isArray(value)) {
514 if (!token.repeat) {
515 throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
516 }
517
518 if (value.length === 0) {
519 if (token.optional) {
520 continue
521 } else {
522 throw new TypeError('Expected "' + token.name + '" to not be empty')
523 }
524 }
525
526 for (let j = 0; j < value.length; j++) {
527 segment = encode(value[j])
528
529 if (!matches[i].test(segment)) {
530 throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
531 }
532
533 path += (j === 0 ? token.prefix : token.delimiter) + segment
534 }
535
536 continue
537 }
538
539 segment = token.asterisk ? encodeAsterisk(value) : encode(value)
540
541 if (!matches[i].test(segment)) {
542 throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
543 }
544
545 path += token.prefix + segment
546 }
547
548 return path
549 }
550}
551
552/**
553 * Get the flags for a regexp from the options.
554 *
555 * @param {Object} options
556 * @return {string}
557 */
558function flags (options) {
559 return options && options.sensitive ? '' : 'i'
560}
561
562/**
563 * Format given url, append query to url query string
564 *
565 * @param {string} url
566 * @param {string} query
567 * @return {string}
568 */
569function formatUrl (url, query) {
570 <% if (features.clientUseUrl) { %>
571 url = new URL(url, top.location.href)
572 for (const key in query) {
573 const value = query[key]
574 if (value == null) {
575 continue
576 }
577 if (Array.isArray(value)) {
578 for (const arrayValue of value) {
579 url.searchParams.append(key, arrayValue)
580 }
581 continue
582 }
583 url.searchParams.append(key, value)
584 }
585 url.searchParams.sort()
586 return url.toString()
587 <% } else { %>
588 let protocol
589 const index = url.indexOf('://')
590 if (index !== -1) {
591 protocol = url.substring(0, index)
592 url = url.substring(index + 3)
593 } else if (url.startsWith('//')) {
594 url = url.substring(2)
595 }
596
597 let parts = url.split('/')
598 let result = (protocol ? protocol + '://' : '//') + parts.shift()
599
600 let path = parts.join('/')
601 if (path === '' && parts.length === 1) {
602 result += '/'
603 }
604
605 let hash
606 parts = path.split('#')
607 if (parts.length === 2) {
608 [path, hash] = parts
609 }
610
611 result += path ? '/' + path : ''
612
613 if (query && JSON.stringify(query) !== '{}') {
614 result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query)
615 }
616 result += hash ? '#' + hash : ''
617
618 return result
619 <% } %>
620}
621<% if (!features.clientUseUrl) { %>
622/**
623 * Transform data object to query string
624 *
625 * @param {object} query
626 * @return {string}
627 */
628function formatQuery (query) {
629 return Object.keys(query).sort().map((key) => {
630 const val = query[key]
631 if (val == null) {
632 return ''
633 }
634 if (Array.isArray(val)) {
635 return val.slice().map(val2 => [key, '=', val2].join('')).join('&')
636 }
637 return key + '=' + val
638 }).filter(Boolean).join('&')
639}
640<% } %>
641
642export function addLifecycleHook(vm, hook, fn) {
643 if (!vm.$options[hook]) {
644 vm.$options[hook] = []
645 }
646 if (!vm.$options[hook].includes(fn)) {
647 vm.$options[hook].push(fn)
648 }
649}
650
651export function urlJoin () {
652 return [].slice
653 .call(arguments)
654 .join('/')
655 .replace(/\/+/g, '/')
656 .replace(':/', '://')
657}
658
659export function stripTrailingSlash (path) {
660 return path.replace(/\/+$/, '') || '/'
661}
662
663export function isSamePath (p1, p2) {
664 return stripTrailingSlash(p1) === stripTrailingSlash(p2)
665}
666
\No newline at end of file