From: Eduardo San Martin Morote Date: Mon, 14 Oct 2019 18:09:35 +0000 (+0200) Subject: feat: add base option to history X-Git-Tag: v4.0.0-alpha.0~193 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=35605233ffcbed5d5b6af1b3a980b86038f4a46a;p=thirdparty%2Fvuejs%2Frouter.git feat: add base option to history --- diff --git a/__tests__/router-link.spec.ts b/__tests__/router-link.spec.ts index 5bef966c..5416a5a3 100644 --- a/__tests__/router-link.spec.ts +++ b/__tests__/router-link.spec.ts @@ -58,6 +58,9 @@ describe('RouterLink', () => { ) { const router = { history: createMemoryHistory(), + createHref(to: RouteLocationNormalized): string { + return this.history.base + to.fullPath + }, resolve: jest.fn(), push: jest.fn(), } diff --git a/__tests__/router.spec.ts b/__tests__/router.spec.ts index 60d326a3..105e6dce 100644 --- a/__tests__/router.spec.ts +++ b/__tests__/router.spec.ts @@ -1,5 +1,5 @@ import fakePromise from 'faked-promise' -import { Router, createMemoryHistory } from '../src' +import { Router, createMemoryHistory, createHistory } from '../src' import { NavigationCancelled } from '../src/errors' import { createDom, components, tick } from './utils' import { RouteRecord, RouteLocation } from '../src/types' @@ -265,5 +265,51 @@ describe('Router', () => { }) }) + it('allows base option in abstract history', async () => { + const history = createMemoryHistory('/app/') + const router = new Router({ history, routes }) + expect(router.currentRoute).toEqual({ + name: undefined, + fullPath: '/', + hash: '', + params: {}, + path: '/', + query: {}, + meta: {}, + }) + await router.replace('/foo') + expect(router.currentRoute).toMatchObject({ + name: 'Foo', + fullPath: '/foo', + hash: '', + params: {}, + path: '/foo', + query: {}, + }) + }) + + it('allows base option with html5 history', async () => { + const history = createHistory('/app/') + const router = new Router({ history, routes }) + expect(router.currentRoute).toEqual({ + name: undefined, + fullPath: '/', + hash: '', + params: {}, + path: '/', + query: {}, + meta: {}, + }) + await router.replace('/foo') + expect(router.currentRoute).toMatchObject({ + name: 'Foo', + fullPath: '/foo', + hash: '', + params: {}, + path: '/foo', + query: {}, + }) + }) + // it('redirects with route record redirect') }) diff --git a/src/components/Link.ts b/src/components/Link.ts index 8824ef86..1efa1580 100644 --- a/src/components/Link.ts +++ b/src/components/Link.ts @@ -20,6 +20,7 @@ const Link: Component = { const to = this.to as RouteLocation const route = router.resolve(to) + const href = router.createHref(route) // TODO: active classes // TODO: handle replace prop @@ -35,7 +36,7 @@ const Link: Component = { const data: any = { on, - attrs: { href: route.fullPath }, + attrs: { href }, } // @ts-ignore diff --git a/src/history/common.ts b/src/history/common.ts index a78f3e65..50bef9fe 100644 --- a/src/history/common.ts +++ b/src/history/common.ts @@ -277,3 +277,15 @@ export function normalizeLocation( hash: location.hash || '', } } + +/** + * Strips off the base from the beginning of a location.pathname + * @param pathname location.pathname + * @param base base to strip off + */ +export function stripBase(pathname: string, base: string): string { + return ( + (base && pathname.indexOf(base) === 0 && pathname.replace(base, '')) || + pathname + ) +} diff --git a/src/history/hash.ts b/src/history/hash.ts index 706f5fbe..1c8ac40a 100644 --- a/src/history/hash.ts +++ b/src/history/hash.ts @@ -1,8 +1,7 @@ import { RouterHistory } from './common' import createHistory from './html5' -export default function createHashHistory(): RouterHistory { +export default function createHashHistory(base: string = ''): RouterHistory { // Make sure this implementation is fine in terms of encoding, specially for IE11 - // @ts-ignore: TODO: implement it in history first - return createHistory('/#/') + return createHistory('/#' + base) } diff --git a/src/history/html5.ts b/src/history/html5.ts index c964c5bf..62759e77 100644 --- a/src/history/html5.ts +++ b/src/history/html5.ts @@ -3,10 +3,12 @@ import { NavigationCallback, parseQuery, normalizeLocation, + stripBase, NavigationType, NavigationDirection, HistoryLocationNormalized, HistoryState, + parseURL, } from './common' import { computeScrollPosition, ScrollToPosition } from '../utils/scroll' // import consola from 'consola' @@ -24,7 +26,7 @@ interface StateEntry { scroll: ScrollToPosition | null } -export default function createHistory(): RouterHistory { +export default function createHistory(base: string = ''): RouterHistory { const { history } = window /** @@ -35,11 +37,18 @@ export default function createHistory(): RouterHistory { function createCurrentLocation( location: Location ): HistoryLocationNormalized { + const { pathname, search, hash } = location + // allows hash based url + if (base.indexOf('#') > -1) { + // prepend the starting slash to hash so the url starts with /# + return parseURL(stripBase('/' + hash, base)) + } + const path = stripBase(pathname, base) return { - fullPath: location.pathname + location.search + location.hash, - path: location.pathname, - query: parseQuery(location.search), - hash: location.hash, + fullPath: path + search + hash, + path, + query: parseQuery(search), + hash: hash, } } @@ -136,9 +145,10 @@ export default function createHistory(): RouterHistory { function changeLocation( state: StateEntry, title: string, - url: string, + fullPath: string, replace: boolean ): void { + const url = base + fullPath try { // BROWSER QUIRK // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds @@ -159,8 +169,7 @@ export default function createHistory(): RouterHistory { const routerHistory: RouterHistory = { // it's overriden right after location, - // TODO: implement it - base: '/', + base, replace(to) { const normalized = normalizeLocation(to) diff --git a/src/history/memory.ts b/src/history/memory.ts index 8ce4e3bb..634161eb 100644 --- a/src/history/memory.ts +++ b/src/history/memory.ts @@ -14,7 +14,13 @@ import { // const cs = console // const cs = consola.withTag('abstract') -export default function createMemoryHistory(): RouterHistory { +/** + * Creates a in-memory based history. The main purporse of this history is to handle SSR. It starts in a special location that is nowhere. + * It's up to the user to replace that location with the starter location. + * @param base Base applied to all urls, defaults to '/' + * @returns a history object that can be passed to the router constructor + */ +export default function createMemoryHistory(base: string = ''): RouterHistory { let listeners: NavigationCallback[] = [] // TODO: make sure this is right as the first location is nowhere so maybe this should be empty instead let queue: HistoryLocationNormalized[] = [START] @@ -53,8 +59,8 @@ export default function createMemoryHistory(): RouterHistory { const routerHistory: RouterHistory = { // rewritten by Object.defineProperty location: START, - // TODO: implement it - base: '/', + // TODO: acutally use it + base, replace(to) { const toNormalized = normalizeLocation(to) diff --git a/src/index.ts b/src/index.ts index a46e17b2..10468236 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { Router, RouterOptions } from './router' import { PluginFunction, VueConstructor } from 'vue' import createHistory from './history/html5' import createMemoryHistory from './history/memory' +import createHashHistory from './history/hash' import View from './components/View' import Link from './components/Link' @@ -63,7 +64,7 @@ const plugin: PluginFunction = Vue => { strats.created } -export { Router, createHistory, createMemoryHistory, plugin } +export { Router, createHistory, createMemoryHistory, createHashHistory, plugin } // TODO: refactor somewhere else // const inBrowser = typeof window !== 'undefined' diff --git a/src/router.ts b/src/router.ts index babf70fc..ab20592e 100644 --- a/src/router.ts +++ b/src/router.ts @@ -143,7 +143,7 @@ export class Router { resolve( to: RouteLocation, currentLocation?: RouteLocationNormalized /*, append?: boolean */ - ) { + ): RouteLocationNormalized { if (typeof to === 'string') return this.resolveLocation( // TODO: refactor and remove import @@ -158,6 +158,10 @@ export class Router { }) } + createHref(to: RouteLocationNormalized): string { + return this.history.base + to.fullPath + } + private resolveLocation( location: MatcherLocation & Required, currentLocation?: RouteLocationNormalized,