mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-21 07:59:33 +00:00
fix: handle route encoding (#8325)
Co-authored-by: farnabaz <farnabaz@users.noreply.github.com>
This commit is contained in:
parent
7cac3c7fc9
commit
cc1f6d94b5
@ -63,7 +63,7 @@
|
|||||||
"vue-client-only": "^2.0.0",
|
"vue-client-only": "^2.0.0",
|
||||||
"vue-meta": "^2.4.0",
|
"vue-meta": "^2.4.0",
|
||||||
"vue-no-ssr": "^1.1.1",
|
"vue-no-ssr": "^1.1.1",
|
||||||
"vue-router": "3.4.8",
|
"vue-router": "^3.4.9",
|
||||||
"vuex": "^3.5.1"
|
"vuex": "^3.5.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -126,6 +126,7 @@ export function getNuxtConfig (_options) {
|
|||||||
if (!/\/$/.test(options.router.base)) {
|
if (!/\/$/.test(options.router.base)) {
|
||||||
options.router.base += '/'
|
options.router.base += '/'
|
||||||
}
|
}
|
||||||
|
options.router.base = encodeURI(decodeURI(options.router.base))
|
||||||
|
|
||||||
// Legacy support for export
|
// Legacy support for export
|
||||||
if (options.export) {
|
if (options.export) {
|
||||||
|
@ -50,6 +50,7 @@ export default class Listener {
|
|||||||
}
|
}
|
||||||
this.port = address.port
|
this.port = address.port
|
||||||
this.url = `http${this.https ? 's' : ''}://${this.host}:${this.port}${this.baseURL}`
|
this.url = `http${this.https ? 's' : ''}://${this.host}:${this.port}${this.baseURL}`
|
||||||
|
this.url = decodeURI(this.url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.url = `unix+http://${address}`
|
this.url = `unix+http://${address}`
|
||||||
|
@ -167,7 +167,7 @@ export default class Server {
|
|||||||
prefix: false,
|
prefix: false,
|
||||||
handler: (req, res) => {
|
handler: (req, res) => {
|
||||||
const to = urlJoin(this.nuxt.options.router.base, req.url)
|
const to = urlJoin(this.nuxt.options.router.base, req.url)
|
||||||
consola.info(`[Development] Redirecting from \`${req.url}\` to \`${to}\` (router.base specified).`)
|
consola.info(`[Development] Redirecting from \`${decodeURI(req.url)}\` to \`${decodeURI(to)}\` (router.base specified).`)
|
||||||
res.writeHead(302, {
|
res.writeHead(302, {
|
||||||
Location: to
|
Location: to
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"vue-client-only": "^2.0.0",
|
"vue-client-only": "^2.0.0",
|
||||||
"vue-meta": "^2.4.0",
|
"vue-meta": "^2.4.0",
|
||||||
"vue-no-ssr": "^1.1.1",
|
"vue-no-ssr": "^1.1.1",
|
||||||
"vue-router": "3.4.8",
|
"vue-router": "^3.4.9",
|
||||||
"vue-template-compiler": "^2.6.12",
|
"vue-template-compiler": "^2.6.12",
|
||||||
"vuex": "^3.5.1"
|
"vuex": "^3.5.1"
|
||||||
},
|
},
|
||||||
|
@ -47,7 +47,7 @@ import scrollBehavior from './router.scrollBehavior.js'
|
|||||||
}
|
}
|
||||||
// @see: https://router.vuejs.org/api/#router-construction-options
|
// @see: https://router.vuejs.org/api/#router-construction-options
|
||||||
res += '{'
|
res += '{'
|
||||||
res += firstIndent + 'path: ' + JSON.stringify(route.path)
|
res += firstIndent + 'path: ' + JSON.stringify(encodeURI(decodeURI(route.path)))
|
||||||
res += (route.components) ? nextIndent + 'components: {' + resMap + '\n' + baseIndent + tab + '}' : ''
|
res += (route.components) ? nextIndent + 'components: {' + resMap + '\n' + baseIndent + tab + '}' : ''
|
||||||
res += (route.component) ? nextIndent + 'component: ' + route._name : ''
|
res += (route.component) ? nextIndent + 'component: ' + route._name : ''
|
||||||
res += (route.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
|
res += (route.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
|
||||||
@ -93,7 +93,7 @@ Vue.use(Router)
|
|||||||
|
|
||||||
export const routerOptions = {
|
export const routerOptions = {
|
||||||
mode: '<%= router.mode %>',
|
mode: '<%= router.mode %>',
|
||||||
base: decodeURI('<%= router.base %>'),
|
base: '<%= router.base %>',
|
||||||
linkActiveClass: '<%= router.linkActiveClass %>',
|
linkActiveClass: '<%= router.linkActiveClass %>',
|
||||||
linkExactActiveClass: '<%= router.linkExactActiveClass %>',
|
linkExactActiveClass: '<%= router.linkExactActiveClass %>',
|
||||||
scrollBehavior,
|
scrollBehavior,
|
||||||
@ -106,5 +106,16 @@ export const routerOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createRouter () {
|
export function createRouter () {
|
||||||
return new Router(routerOptions)
|
const router = new Router(routerOptions)
|
||||||
|
const resolve = router.resolve.bind(router)
|
||||||
|
|
||||||
|
// encodeURI(decodeURI()) ~> support both encoded and non-encoded urls
|
||||||
|
router.resolve = (to, current, append) => {
|
||||||
|
if (typeof to === 'string') {
|
||||||
|
to = encodeURI(decodeURI(to))
|
||||||
|
}
|
||||||
|
return resolve(to, current, append)
|
||||||
|
}
|
||||||
|
|
||||||
|
return router
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ import {
|
|||||||
<% if (features.middleware) { %>middlewareSeries,<% } %>
|
<% if (features.middleware) { %>middlewareSeries,<% } %>
|
||||||
<% if (features.middleware && features.layouts) { %>sanitizeComponent,<% } %>
|
<% if (features.middleware && features.layouts) { %>sanitizeComponent,<% } %>
|
||||||
getMatchedComponents,
|
getMatchedComponents,
|
||||||
promisify
|
promisify,
|
||||||
|
ensureURIEncoded
|
||||||
} from './utils.js'
|
} from './utils.js'
|
||||||
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.server'<% } %>
|
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.server'<% } %>
|
||||||
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
|
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
|
||||||
@ -50,7 +51,7 @@ const createNext = ssrContext => (opts) => {
|
|||||||
opts.path = urlJoin(routerBase, opts.path)
|
opts.path = urlJoin(routerBase, opts.path)
|
||||||
}
|
}
|
||||||
// Avoid loop redirect
|
// Avoid loop redirect
|
||||||
if (decodeURIComponent(opts.path) === ssrContext.url) {
|
if (encodeURI(decodeURI(opts.path)) === ssrContext.url) {
|
||||||
ssrContext.redirected = false
|
ssrContext.redirected = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -296,15 +296,20 @@ export function promisify (fn, context) {
|
|||||||
|
|
||||||
// Imported from vue-router
|
// Imported from vue-router
|
||||||
export function getLocation (base, mode) {
|
export function getLocation (base, mode) {
|
||||||
let path = decodeURI(window.location.pathname)
|
|
||||||
if (mode === 'hash') {
|
if (mode === 'hash') {
|
||||||
return window.location.hash.replace(/^#\//, '')
|
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)
|
path = path.slice(base.length)
|
||||||
}
|
}
|
||||||
return (path || '/') + window.location.search + window.location.hash
|
|
||||||
|
const fullPath = (path || '/') + window.location.search + window.location.hash
|
||||||
|
|
||||||
|
return encodeURI(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imported from path-to-regexp
|
// Imported from path-to-regexp
|
||||||
|
@ -274,7 +274,8 @@ export default class VueRenderer {
|
|||||||
consola.debug(`Rendering url ${url}`)
|
consola.debug(`Rendering url ${url}`)
|
||||||
|
|
||||||
// Add url to the renderContext
|
// Add url to the renderContext
|
||||||
renderContext.url = url
|
renderContext.url = encodeURI(decodeURI(url))
|
||||||
|
|
||||||
// Add target to the renderContext
|
// Add target to the renderContext
|
||||||
renderContext.target = this.options.target
|
renderContext.target = this.options.target
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ const url = route => 'http://localhost:' + port + encodeURI(route)
|
|||||||
|
|
||||||
let nuxt = null
|
let nuxt = null
|
||||||
|
|
||||||
describe('unicode-base', () => {
|
describe('encoding', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const config = await loadFixture('unicode-base')
|
const config = await loadFixture('encoding')
|
||||||
nuxt = new Nuxt(config)
|
nuxt = new Nuxt(config)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ describe('unicode-base', () => {
|
|||||||
test('/ö/ (router base)', async () => {
|
test('/ö/ (router base)', async () => {
|
||||||
const { body: response } = await rp(url('/ö/'))
|
const { body: response } = await rp(url('/ö/'))
|
||||||
|
|
||||||
expect(response).toContain('<h1>Unicode base works!</h1>')
|
expect(response).toContain('Unicode base works!')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Close server and ask nuxt to stop listening to file changes
|
// Close server and ask nuxt to stop listening to file changes
|
@ -1,26 +0,0 @@
|
|||||||
import { resolve } from 'path'
|
|
||||||
import { getResourcesSize } from '../utils'
|
|
||||||
|
|
||||||
const distDir = resolve(__dirname, '../fixtures/unicode-base/.nuxt/dist')
|
|
||||||
|
|
||||||
describe('nuxt minimal vue-app bundle size limit', () => {
|
|
||||||
expect.extend({
|
|
||||||
toBeWithinSize (received, size) {
|
|
||||||
const maxSize = size * 1.02
|
|
||||||
const minSize = size * 0.98
|
|
||||||
const pass = received >= minSize && received <= maxSize
|
|
||||||
return {
|
|
||||||
pass,
|
|
||||||
message: () =>
|
|
||||||
`expected ${received} to be within range ${minSize} - ${maxSize}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should stay within the size limit range', async () => {
|
|
||||||
const filter = filename => filename === 'vue-app.nuxt.js'
|
|
||||||
const legacyResourcesSize = await getResourcesSize(distDir, 'client', { filter })
|
|
||||||
const LEGACY_JS_RESOURCES_KB_SIZE = 17.1
|
|
||||||
expect(legacyResourcesSize.uncompressed).toBeWithinSize(LEGACY_JS_RESOURCES_KB_SIZE)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,3 +1,3 @@
|
|||||||
import { buildFixture } from '../../utils/build'
|
import { buildFixture } from '../../utils/build'
|
||||||
|
|
||||||
buildFixture('unicode-base')
|
buildFixture('encoding')
|
32
test/fixtures/encoding/layouts/default.vue
vendored
Normal file
32
test/fixtures/encoding/layouts/default.vue
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<NLink to="/тест">
|
||||||
|
/тест
|
||||||
|
</NLink>
|
||||||
|
<NLink :to="encodeURI('/тест')">
|
||||||
|
/тест (encoded)
|
||||||
|
</NLink>
|
||||||
|
<br>
|
||||||
|
<NLink to="/тест?spa">
|
||||||
|
/тест (SPA)
|
||||||
|
</NLink>
|
||||||
|
<NLink :to="encodeURI('/тест?spa')">
|
||||||
|
/тест (SPA encoded)
|
||||||
|
</NLink>
|
||||||
|
</div>
|
||||||
|
<Nuxt />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
a {
|
||||||
|
color: grey;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nuxt-link-exact-active {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
12
test/fixtures/encoding/nuxt.config.js
vendored
Normal file
12
test/fixtures/encoding/nuxt.config.js
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default {
|
||||||
|
router: {
|
||||||
|
base: '/ö/'
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
'vue-renderer:context' (ssrContext) {
|
||||||
|
if (ssrContext.url.includes('?spa')) {
|
||||||
|
ssrContext.spa = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
test/fixtures/encoding/pages/index.vue
vendored
Normal file
5
test/fixtures/encoding/pages/index.vue
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Unicode base works!
|
||||||
|
</div>
|
||||||
|
</template>
|
43
test/fixtures/unicode-base/nuxt.config.js
vendored
43
test/fixtures/unicode-base/nuxt.config.js
vendored
@ -1,43 +0,0 @@
|
|||||||
export default {
|
|
||||||
modern: 'server',
|
|
||||||
router: {
|
|
||||||
base: '/%C3%B6/'
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
loadingIndicator: false,
|
|
||||||
fetch: {
|
|
||||||
client: false,
|
|
||||||
server: false
|
|
||||||
},
|
|
||||||
features: {
|
|
||||||
store: false,
|
|
||||||
layouts: false,
|
|
||||||
meta: false,
|
|
||||||
middleware: false,
|
|
||||||
transitions: false,
|
|
||||||
deprecations: false,
|
|
||||||
validate: false,
|
|
||||||
asyncData: false,
|
|
||||||
fetch: false,
|
|
||||||
clientOnline: false,
|
|
||||||
clientPrefetch: false,
|
|
||||||
clientUseUrl: true,
|
|
||||||
componentAliases: false,
|
|
||||||
componentClientOnly: false
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
indicator: false,
|
|
||||||
terser: true,
|
|
||||||
optimization: {
|
|
||||||
splitChunks: {
|
|
||||||
cacheGroups: {
|
|
||||||
nuxtApp: {
|
|
||||||
test: /[\\/]\.nuxt[\\/]/,
|
|
||||||
filename: 'vue-app.nuxt.js',
|
|
||||||
enforce: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13403,6 +13403,11 @@ vue-router@3.4.8:
|
|||||||
resolved "https://registry.npmjs.org/vue-router/-/vue-router-3.4.8.tgz#2c06261d35d8075893470352d42d70b6287b8194"
|
resolved "https://registry.npmjs.org/vue-router/-/vue-router-3.4.8.tgz#2c06261d35d8075893470352d42d70b6287b8194"
|
||||||
integrity sha512-3BsR84AqarcmweXjItxw3jwQsiYNssYg090yi4rlzTnCJxmHtkyCvhNz9Z7qRSOkmiV485KkUCReTp5AjNY4wg==
|
integrity sha512-3BsR84AqarcmweXjItxw3jwQsiYNssYg090yi4rlzTnCJxmHtkyCvhNz9Z7qRSOkmiV485KkUCReTp5AjNY4wg==
|
||||||
|
|
||||||
|
vue-router@^3.4.9:
|
||||||
|
version "3.4.9"
|
||||||
|
resolved "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz#c016f42030ae2932f14e4748b39a1d9a0e250e66"
|
||||||
|
integrity sha512-CGAKWN44RqXW06oC+u4mPgHLQQi2t6vLD/JbGRDAXm0YpMv0bgpKuU5bBd7AvMgfTz9kXVRIWKHqRwGEb8xFkA==
|
||||||
|
|
||||||
vue-server-renderer@^2.6.12:
|
vue-server-renderer@^2.6.12:
|
||||||
version "2.6.12"
|
version "2.6.12"
|
||||||
resolved "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz#a8cb9c49439ef205293cb41c35d0d2b0541653a5"
|
resolved "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz#a8cb9c49439ef205293cb41c35d0d2b0541653a5"
|
||||||
|
Loading…
Reference in New Issue
Block a user