import { ComponentObject , HandlerFunc , RouterInstance } from './types' ; import { cleanPath , includes , isObject } from './utils' ; const REGEX_PARAMS = /([:*])(\w+)/g ; const REGEX_REPLACE_VAR = '([^/]+)' ; function onRouterChange ( fn ) { window . addEventListener ( 'hashchange' , ( ) => { fn ( ) ; } ) ; } interface Route { path : string ; component : ComponentObject ; } function go ( path ) { window . location . hash = '#' + path ; } function useRouter ( routesArray ) { let params = Object . create ( null ) ; const routes = Object . create ( null ) ; let currentComponent = null ; for ( const r of routesArray ) { r . path = cleanPath ( r . path ) ; routes [ r . path ] = r . component ; } function getPath ( ) { return cleanPath ( location . hash ) ; } function regexToParams ( match , names ) { if ( names . length === 0 ) return null ; if ( ! match ) return null ; const matchInfo = match . slice ( 1 , match . length ) ; return matchInfo . reduce ( ( paramObj , value , index ) => { const name = names [ index ] ; paramObj [ name ] = value ; return paramObj ; } , { } ) ; } function createParamsPattern ( routerPath ) { const paramNames = [ ] ; const pattern = routerPath . replace ( REGEX_PARAMS , ( _1 , _2 , name ) => { paramNames . push ( name ) ; return REGEX_REPLACE_VAR ; } ) ; return { paramNames , pattern } ; } function match ( browserPath ) { if ( isObject ( routes [ browserPath ] ) ) { currentComponent = routes [ browserPath ] ; return routes [ browserPath ] ; } params = Object . create ( null ) ; for ( const routerPath of Object . keys ( routes ) ) { if ( includes ( routerPath , ':' ) ) { const { pattern , paramNames } = createParamsPattern ( routerPath ) ; const regexp = new RegExp ( pattern ) ; const match = browserPath . match ( regexp ) ; if ( match ) { params = regexToParams ( match , paramNames ) ; currentComponent = routes [ routerPath ] ; return routes [ routerPath ] ; } } } if ( isObject ( routes [ '*' ] ) ) { currentComponent = routes [ '*' ] ; return routes [ browserPath ] ; } const default404Route = { render : ( ) => `Not found` } ; currentComponent = default404Route ; return default404Route ; } function routerContextFn ( ) { return { params : ( ) => params , go } ; } function renderer ( render , forceUnmount ) { onRouterChange ( ( ) => { if ( currentComponent ) { forceUnmount ( currentComponent ) ; } render ( match ( getPath ( ) ) ) ; } ) ; render ( match ( getPath ( ) ) ) ; } return { renderer , routerContextFn } ; } export const createRouter = useRouter ; export default { createRouter } ;