]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
refactor(history): split into multiple functions
authorEduardo San Martin Morote <posva13@gmail.com>
Sat, 26 Oct 2019 14:02:33 +0000 (15:02 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Sat, 26 Oct 2019 14:02:33 +0000 (15:02 +0100)
src/history/common.ts
src/history/html5.ts

index 50bef9fe177ba9aa04a8db71463af6ae0b12e2ce..c8a33f931f78f6a0f2e4ba7bbe2806a1575c63b7 100644 (file)
@@ -75,9 +75,12 @@ export const START: HistoryLocationNormalized = {
   hash: '',
 }
 
+export type ValueContainer<T> = { value: T }
+
 export interface RouterHistory {
   readonly base: string
   readonly location: HistoryLocationNormalized
+  // readonly location: ValueContainer<HistoryLocationNormalized>
 
   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(
index 62759e77d77283bcb472048687e6cdf775bc5cb4..f2d4102a12843dbb32b201b317e82595dd3786b1 100644 (file)
@@ -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<StateEntry>,
+  location: ValueContainer<HistoryLocationNormalized>
+) {
   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<HistoryLocationNormalized> = {
+    value: createCurrentLocation(base, window.location),
+  }
+  let historyState: ValueContainer<StateEntry> = { 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