refactor(utils, vue-app): use ufo to parse queries and join urls (#8765)

This commit is contained in:
Daniel Roe 2021-02-09 16:01:37 +00:00 committed by GitHub
parent c8a4b91ad4
commit 119091c8d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 17 additions and 114 deletions

View File

@ -69,7 +69,6 @@ export default () => ({
fetch: true, fetch: true,
clientOnline: true, clientOnline: true,
clientPrefetch: true, clientPrefetch: true,
clientUseUrl: false,
componentAliases: true, componentAliases: true,
componentClientOnly: true componentClientOnly: true
} }

View File

@ -189,7 +189,6 @@ Object {
"asyncData": true, "asyncData": true,
"clientOnline": true, "clientOnline": true,
"clientPrefetch": true, "clientPrefetch": true,
"clientUseUrl": false,
"componentAliases": true, "componentAliases": true,
"componentClientOnly": true, "componentClientOnly": true,
"deprecations": true, "deprecations": true,

View File

@ -164,7 +164,6 @@ Object {
"asyncData": true, "asyncData": true,
"clientOnline": true, "clientOnline": true,
"clientPrefetch": true, "clientPrefetch": true,
"clientUseUrl": false,
"componentAliases": true, "componentAliases": true,
"componentClientOnly": true, "componentClientOnly": true,
"deprecations": true, "deprecations": true,
@ -550,7 +549,6 @@ Object {
"asyncData": true, "asyncData": true,
"clientOnline": true, "clientOnline": true,
"clientPrefetch": true, "clientPrefetch": true,
"clientUseUrl": false,
"componentAliases": true, "componentAliases": true,
"componentClientOnly": true, "componentClientOnly": true,
"deprecations": true, "deprecations": true,

View File

@ -2,6 +2,9 @@ export interface NuxtOptionsFeatures {
asyncData?: boolean asyncData?: boolean
clientOnline?: boolean clientOnline?: boolean
clientPrefetch?: boolean clientPrefetch?: boolean
/**
* @deprecated
*/
clientUseUrl?: boolean clientUseUrl?: boolean
componentAliases?: boolean componentAliases?: boolean
componentClientOnly?: boolean componentClientOnly?: boolean

View File

@ -1,3 +1,5 @@
import { joinURL } from 'ufo'
export const encodeHtml = function encodeHtml (str) { export const encodeHtml = function encodeHtml (str) {
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;') return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
} }
@ -12,13 +14,7 @@ export const isUrl = function isUrl (url) {
return ['http', '//'].some(str => url.startsWith(str)) return ['http', '//'].some(str => url.startsWith(str))
} }
export const urlJoin = function urlJoin () { export const urlJoin = joinURL
return [].slice
.call(arguments)
.join('/')
.replace(/\/+/g, '/')
.replace(':/', '://')
}
/** /**
* Wraps value in array if it is not already an array * Wraps value in array if it is not already an array

View File

@ -1,6 +1,5 @@
import { stringify } from 'querystring'
import Vue from 'vue' import Vue from 'vue'
import { normalizeURL, joinURL } from 'ufo' import { joinURL, normalizeURL, withQuery } from 'ufo'
<% if (fetch.server) { %>import fetch from 'node-fetch'<% } %> <% if (fetch.server) { %>import fetch from 'node-fetch'<% } %>
<% if (features.middleware) { %>import middleware from './middleware.js'<% } %> <% if (features.middleware) { %>import middleware from './middleware.js'<% } %>
import { import {
@ -58,20 +57,19 @@ const createNext = ssrContext => (opts) => {
ssrContext.nuxt.serverRendered = false ssrContext.nuxt.serverRendered = false
return return
} }
opts.query = stringify(opts.query) let fullPath = withQuery(opts.path, opts.query)
opts.path = opts.path + (opts.query ? '?' + opts.query : '')
const $config = ssrContext.runtimeConfig || {} const $config = ssrContext.runtimeConfig || {}
const routerBase = ($config.app && $config.app.basePath) || '<%= router.base %>' const routerBase = ($config.app && $config.app.basePath) || '<%= router.base %>'
if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) { if (!fullPath.startsWith('http') && (routerBase !== '/' && !fullPath.startsWith(routerBase))) {
opts.path = joinURL(routerBase, opts.path) fullPath = joinURL(routerBase, fullPath)
} }
// Avoid loop redirect // Avoid loop redirect
if (decodeURI(opts.path) === decodeURI(ssrContext.url)) { if (decodeURI(fullPath) === decodeURI(ssrContext.url)) {
ssrContext.redirected = false ssrContext.redirected = false
return return
} }
ssrContext.res.writeHead(opts.status, { ssrContext.res.writeHead(opts.status, {
Location: normalizeURL(opts.path) Location: normalizeURL(fullPath)
}) })
ssrContext.res.end() ssrContext.res.end()
} }

View File

@ -1,5 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import { normalizeURL } from 'ufo' import { isSamePath as _isSamePath, joinURL, normalizeURL, withQuery, withoutTrailingSlash } from 'ufo'
// window.{{globals.loadedCallback}} hook // window.{{globals.loadedCallback}} hook
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
@ -219,7 +219,7 @@ export async function setContext (app, context) {
status status
}) })
} else { } else {
path = formatUrl(path, query) path = withQuery(path, query)
if (process.server) { if (process.server) {
app.context.next({ app.context.next({
path, path,
@ -594,86 +594,6 @@ function flags (options) {
return options && options.sensitive ? '' : 'i' 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) { export function addLifecycleHook(vm, hook, fn) {
if (!vm.$options[hook]) { if (!vm.$options[hook]) {
vm.$options[hook] = [] vm.$options[hook] = []
@ -683,21 +603,11 @@ export function addLifecycleHook(vm, hook, fn) {
} }
} }
export function urlJoin () { export const urlJoin = joinURL
return [].slice
.call(arguments)
.join('/')
.replace(/\/+/g, '/')
.replace(':/', '://')
}
export function stripTrailingSlash (path) { export const stripTrailingSlash = withoutTrailingSlash
return path.replace(/\/+$/, '') || '/'
}
export function isSamePath (p1, p2) { export const isSamePath = _isSamePath
return stripTrailingSlash(p1) === stripTrailingSlash(p2)
}
export function setScrollRestoration (newVal) { export function setScrollRestoration (newVal) {
try { try {