From: Eduardo San Martin Morote Date: Sat, 26 Oct 2019 14:02:33 +0000 (+0100) Subject: refactor(history): split into multiple functions X-Git-Tag: v4.0.0-alpha.0~182 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=59bcfec94854bd28073348410d3865f16e63842f;p=thirdparty%2Fvuejs%2Frouter.git refactor(history): split into multiple functions --- diff --git a/src/history/common.ts b/src/history/common.ts index 50bef9fe..c8a33f93 100644 --- a/src/history/common.ts +++ b/src/history/common.ts @@ -75,9 +75,12 @@ export const START: HistoryLocationNormalized = { hash: '', } +export type ValueContainer = { value: T } + export interface RouterHistory { readonly base: string readonly location: HistoryLocationNormalized + // readonly location: ValueContainer push(to: RawHistoryLocation, data?: any): void replace(to: RawHistoryLocation): void @@ -235,7 +238,7 @@ export function stringifyQuery(query: RawHistoryQuery): string { } export function normalizeQuery(query: RawHistoryQuery): HistoryQuery { - // TODO: implem + // TODO: properly test const normalizedQuery: HistoryQuery = {} for (const key in query) { const value = query[key] @@ -245,6 +248,7 @@ export function normalizeQuery(query: RawHistoryQuery): HistoryQuery { return normalizedQuery } +// FIXME: not used /** * Prepare a URI string to be passed to pushState * @param uri @@ -261,8 +265,7 @@ const originalDecodeURI = decodeURI export { originalDecodeURI as decodeURI } /** - * Normalize a History location into an object that looks like - * the one at window.location + * Normalize a History location object or string into a HistoryLocationNoramlized * @param location */ export function normalizeLocation( diff --git a/src/history/html5.ts b/src/history/html5.ts index 62759e77..f2d4102a 100644 --- a/src/history/html5.ts +++ b/src/history/html5.ts @@ -9,6 +9,8 @@ import { HistoryLocationNormalized, HistoryState, parseURL, + RawHistoryLocation, + ValueContainer, } from './common' import { computeScrollPosition, ScrollToPosition } from '../utils/scroll' // import consola from 'consola' @@ -26,74 +28,35 @@ interface StateEntry { scroll: ScrollToPosition | null } -export default function createHistory(base: string = ''): RouterHistory { - const { history } = window - - /** - * Creates a noramlized history location from a window.location object - * TODO: encoding is not handled like this - * @param location - */ - 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: path + search + hash, - path, - query: parseQuery(search), - hash: hash, - } +/** + * Creates a noramlized history location from a window.location object + * TODO: encoding is not handled like this + * @param location + */ +function createCurrentLocation( + base: string, + 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)) } - - /** - * Creates a state objec - */ - function buildState( - back: HistoryLocationNormalized | null, - current: HistoryLocationNormalized, - forward: HistoryLocationNormalized | null, - replaced: boolean = false, - computeScroll: boolean = false - ): StateEntry { - return { - back, - current, - forward, - replaced, - position: window.history.length, - scroll: computeScroll ? computeScrollPosition() : null, - } + const path = stripBase(pathname, base) + return { + fullPath: path + search + hash, + path, + query: parseQuery(search), + hash: hash, } +} - // private variables - let location: HistoryLocationNormalized = createCurrentLocation( - window.location - ) - let historyState: StateEntry = history.state - // build current history entry as this is a fresh navigation - if (!historyState) { - changeLocation( - { - back: null, - current: location, - forward: null, - // the length is off by one, we need to decrease it - position: history.length - 1, - replaced: true, - scroll: computeScrollPosition(), - }, - '', - location.fullPath, - true - ) - } +function useHistoryListeners( + base: string, + historyState: ValueContainer, + location: ValueContainer +) { let listeners: NavigationCallback[] = [] let teardowns: Array<() => void> = [] // TODO: should it be a stack? a Dict. Check if the popstate listener @@ -108,11 +71,11 @@ export default function createHistory(base: string = ''): RouterHistory { cs.info('popstate fired', state) cs.info('currentState', historyState) - const from = location - const fromState = historyState - const to = createCurrentLocation(window.location) - location = to - historyState = state + const from: HistoryLocationNormalized = location.value + const fromState: StateEntry = historyState.value + const to = createCurrentLocation(base, window.location) + location.value = to + historyState.value = state if (pauseState && pauseState.fullPath === from.fullPath) { cs.info('❌ Ignored beacuse paused for', pauseState.fullPath) @@ -127,7 +90,7 @@ export default function createHistory(base: string = ''): RouterHistory { console.log({ deltaFromCurrent }) // call all listeners listeners.forEach(listener => - listener(location, from, { + listener(location.value, from, { distance: deltaFromCurrent || 0, type: NavigationType.pop, direction: deltaFromCurrent @@ -139,9 +102,86 @@ export default function createHistory(base: string = ''): RouterHistory { ) } + function pauseListeners() { + cs.info(`⏸ for ${location.value.fullPath}`) + pauseState = location.value + } + + function listen(callback: NavigationCallback) { + // settup the listener and prepare teardown callbacks + listeners.push(callback) + + const teardown = () => { + const index = listeners.indexOf(callback) + if (index > -1) listeners.splice(index, 1) + } + + teardowns.push(teardown) + return teardown + } + + function destroy() { + for (const teardown of teardowns) teardown() + teardowns = [] + window.removeEventListener('popstate', popStateHandler) + } + // settup the listener and prepare teardown callbacks window.addEventListener('popstate', popStateHandler) + return { + pauseListeners, + listen, + destroy, + } +} + +function useHistoryStateNavigation(base: string) { + const { history } = window + + /** + * Creates a state object + */ + function buildState( + back: HistoryLocationNormalized | null, + current: HistoryLocationNormalized, + forward: HistoryLocationNormalized | null, + replaced: boolean = false, + computeScroll: boolean = false + ): StateEntry { + return { + back, + current, + forward, + replaced, + position: window.history.length, + scroll: computeScroll ? computeScrollPosition() : null, + } + } + + // private variables + let location: ValueContainer = { + value: createCurrentLocation(base, window.location), + } + let historyState: ValueContainer = { value: history.state } + // build current history entry as this is a fresh navigation + if (!historyState.value) { + changeLocation( + { + back: null, + current: location.value, + forward: null, + // the length is off by one, we need to decrease it + position: history.length - 1, + replaced: true, + scroll: computeScrollPosition(), + }, + '', + location.value.fullPath, + true + ) + } + function changeLocation( state: StateEntry, title: string, @@ -152,8 +192,11 @@ export default function createHistory(base: string = ''): RouterHistory { try { // BROWSER QUIRK // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds - history[replace ? 'replaceState' : 'pushState'](state, title, url) - historyState = state + const newState: StateEntry = replace + ? { ...historyState.value, ...state } + : state + history[replace ? 'replaceState' : 'pushState'](newState, title, url) + historyState.value = state } catch (err) { cs.log('[vue-router]: Error with push/replace State', err) // Force the navigation, this also resets the call count @@ -161,105 +204,92 @@ export default function createHistory(base: string = ''): RouterHistory { } } - function pauseListeners() { - cs.info(`⏸ for ${location.fullPath}`) - pauseState = location + function replace(to: RawHistoryLocation) { + const normalized = normalizeLocation(to) + + // cs.info('replace', location, normalized) + + const state: StateEntry = buildState( + historyState.value.back, + normalized, + historyState.value.forward, + true + ) + if (historyState) state.position = historyState.value.position + changeLocation( + // TODO: refactor state building + state, + '', + normalized.fullPath, + true + ) + location.value = normalized + } + + function push(to: RawHistoryLocation, data?: HistoryState) { + const normalized = normalizeLocation(to) + + // Add to current entry the information of where we are going + // as well as saving the current position + // TODO: the scroll position computation should be customizable + const currentState: StateEntry = { + ...historyState.value, + forward: normalized, + scroll: computeScrollPosition(), + } + changeLocation(currentState, '', currentState.current.fullPath, true) + + const state: StateEntry = { + ...buildState(location.value, normalized, null), + position: currentState.position + 1, + ...data, + } + + changeLocation(state, '', normalized.fullPath, false) + location.value = normalized + } + + return { + location, + state: historyState, + + push, + replace, } +} +export default function createHistory(base: string = ''): RouterHistory { + const historyNavigation = useHistoryStateNavigation(base) + const historyListeners = useHistoryListeners( + base, + historyNavigation.state, + historyNavigation.location + ) + function back(triggerListeners = true) { + go(-1, triggerListeners) + } + function forward(triggerListeners = true) { + go(1, triggerListeners) + } + function go(distance: number, triggerListeners = true) { + if (!triggerListeners) historyListeners.pauseListeners() + history.go(distance) + } const routerHistory: RouterHistory = { // it's overriden right after - location, + // @ts-ignore + location: historyNavigation.location.value, base, + back, + forward, + go, - replace(to) { - const normalized = normalizeLocation(to) - - // cs.info('replace', location, normalized) - - const state: StateEntry = buildState( - historyState.back, - normalized, - historyState.forward, - true - ) - if (historyState) state.position = historyState.position - changeLocation( - // TODO: refactor state building - state, - '', - normalized.fullPath, - true - ) - location = normalized - }, - - push(to, data?: HistoryState) { - const normalized = normalizeLocation(to) - - // Add to current entry the information of where we are going - // as well as saving the current position - // TODO: the scroll position computation should be customizable - const currentState = { - ...historyState, - forward: normalized, - scroll: computeScrollPosition(), - } - changeLocation(currentState, '', currentState.current.fullPath, true) - - const state: StateEntry = { - ...buildState(location, normalized, null), - position: currentState.position + 1, - ...data, - } - - // cs.info( - // 'push', - // location.fullPath, - // '->', - // normalized.fullPath, - // 'with state', - // state - // ) - - changeLocation(state, '', normalized.fullPath, false) - location = normalized - }, - - back(triggerListeners = true) { - this.go(-1, triggerListeners) - }, - - forward(triggerListeners = true) { - this.go(1, triggerListeners) - }, - - go(distance, triggerListeners = true) { - if (!triggerListeners) pauseListeners() - history.go(distance) - }, - - listen(callback) { - // settup the listener and prepare teardown callbacks - listeners.push(callback) - - const teardown = () => { - const index = listeners.indexOf(callback) - if (index > -1) listeners.splice(index, 1) - } - - teardowns.push(teardown) - return teardown - }, - - destroy() { - for (const teardown of teardowns) teardown() - teardowns = [] - window.removeEventListener('popstate', popStateHandler) - }, + ...historyNavigation, + ...historyListeners, } Object.defineProperty(routerHistory, 'location', { - get: () => location, + get: () => historyNavigation.location.value, }) return routerHistory