UNPKG

react-view-router

Version:
963 lines (853 loc) 31.1 kB
import React, { useState, useEffect, ReactNode } from 'react'; import config from './config'; import { RouteLazy, isPromise, isRouteLazy } from './route-lazy'; import { REACT_FORWARD_REF_TYPE, getGuardsComponent } from './route-guard'; import matchPath, { computeRootMatch } from './match-path'; import { NormalizedConfigRouteArray, RouteIndexFn, ConfigRoute, MatchedRoute, RouteHistoryLocation, Route, RouteAfterGuardFn, RouteGuardInterceptor, LazyResolveFn, RouteRedirectFn, RouteAbortFn, RouteLocation, NormalizeRouteOptions, RouteGuardsInfoHooks, UserConfigRoute, RouteBranchArray, ReactAllComponentType, RouteChildrenFn, NormalizedRouteChildrenFn, ParseQueryProps, RouteMetaFunction, UserConfigRouteProps, UserConfigRoutePropsNormal, UserConfigRoutePropsNormalMap, UserConfigRoutePropsNormalItem } from './types'; import { RouterViewComponent as RouterView, RouterViewWrapper } from './router-view'; import ReactViewRouter from './router'; import { HistoryFix } from './history-fix'; import { HistoryType, Action, readonly } from './history'; import { hasOwnProp, copyOwnProperties, copyOwnProperty } from './history/utils'; const DEFAULT_STATE_NAME = '[root]'; function nextTick(cb: () => void, ctx?: object) { // @ts-ignore // eslint-disable-next-line no-promise-executor-return return cb && new Promise<any>(r => r()).then(() => (ctx ? cb.call(ctx) : cb())); } function ignoreCatch< T extends( (...args: any) => any ) >( fn: T, onCatch?: (ex: any) => void ) { return function (...args: Parameters<T>): void|ReturnType<T> { try { // eslint-disable-next-line prefer-spread return (fn as any).apply(this, args); } catch (ex) { onCatch ? onCatch(ex) : warn(ex); } }; } function innumerable<T extends object>( obj: T, key: string, value: any, options: PropertyDescriptor = { configurable: true } ) { Object.defineProperty(obj, key, { value, ...options }); return obj; } function concatConfigRoutePath(routePath?: string|null, parentPath?: string|null) { if (routePath == null || routePath === '/') routePath = ''; else if (routePath[0] !== '/') routePath = '/' + routePath; if (parentPath == null || parentPath === '/') parentPath = ''; return (parentPath + routePath) || '/'; } function getRoutePath(route?: ConfigRoute|UserConfigRoute|null) { if (!route || !route.path) return '/'; return route.path; } function normalizeRoute( route: UserConfigRoute|ConfigRoute, parent?: ConfigRoute | null, options: NormalizeRouteOptions = {} ): ConfigRoute { if (route._normalized) route = route._normalized; const subpath = getRoutePath(route); const path = concatConfigRoutePath(subpath, parent && parent.path); const r: ConfigRoute = ({ path, subpath, depth: parent ? (parent.depth + 1) : 0, components: {}, }) as any; if (parent) innumerable(r, 'parent', parent); if (isFunction(route.children)) { r.children = isRouteChildrenNormalized(route.children) ? route.children : normalizeRouteChildrenFn(route.children); } else { innumerable(r, 'children', normalizeRoutes(route.children || [], r, options)); } r.exact = route.exact !== undefined ? (route.exact || false) : Boolean(route.redirect || route.index || subpath === '/'); r.components = { ...route.components, default: route.component }; Object.keys(r.components).forEach(key => { const comp = r.components[key]; if (comp instanceof RouteLazy) { (comp as RouteLazy).updaters.push(c => { if (c && c.__children) { let children = c.__children || []; if (isFunction(children)) children = (children as ((r: any) => any[]))(r) || []; innumerable(r, 'children', normalizeRoutes(children, r, options)); // r.exact = !(r as ConfigRoute).children.length; } return r.components[key] = c; }); } }); const meta = route.meta || {}; readonly(r, 'meta', () => meta); if (route.props) innumerable(r, 'props', normalizeProps(route.props)); if (route.paramsProps) innumerable(r, 'paramsProps', normalizeProps(route.paramsProps)); if (route.queryProps) innumerable(r, 'queryProps', normalizeProps(route.queryProps)); innumerable(r, '_pending', { completeCallbacks: {} }); innumerable(r, '_normalized', route); copyOwnProperties(r, route); return r; } function normalizeRoutes( routes: UserConfigRoute[]|NormalizedConfigRouteArray|ConfigRoute[]|null|undefined, parent?: ConfigRoute | null, options: NormalizeRouteOptions = {} ) { const _normalized = getRoutePath(parent); if (!options.force && (isNormalizedConfigRouteArray(routes) && routes._normalized === _normalized)) { return routes; } if (routes) { routes = routes.map((route: any) => route && normalizeRoute(route, parent, options)).filter(Boolean) as any; } else { routes = [] as any; } innumerable(routes as any, '_normalized', _normalized); return routes as NormalizedConfigRouteArray; } function walkRoutes( routes: ConfigRoute[]|RouteChildrenFn, walkFn: (route: ConfigRoute, routeIndex: number, routes: ConfigRoute[]) => boolean|void, parent?: ConfigRoute ): boolean { if (isFunction(routes)) routes = normalizeRoutes(routes(parent), parent); return routes.some((route, routeIndex) => { if (walkFn(route, routeIndex, routes as ConfigRoute[])) return true; if (!route.children || !Array.isArray(route.children)) return; return walkRoutes(route.children, walkFn, route); }); } function normalizePath(path: string) { const paths = path.split('/'); if (paths.length > 2 && !paths[paths.length - 1]) paths.splice(paths.length - 1, 1); for (let i = paths.length - 1; i > -1; i--) { if (paths[i + 1] === '..') paths.splice(i, 2); else if (paths[i] === '.') paths.splice(i, 1); } return paths.join('/').replace(/\/{2,}/g, '/'); } function normalizeRoutePath( path: string, route?: Route|MatchedRoute|ConfigRoute|RouteHistoryLocation|RouteLocation|null, append?: boolean, basename = '' ) { if (isAbsoluteUrl(path)) return path; if (isRoute(route)) route = route.matched[route.matched.length - 1]; if (!path || ['/', '#'].includes(path[0]) || !route) return normalizePath(basename + (path || '')); if (isMatchedRoute(route)) route = route.config; let parent: any = (append || /^\.\//.test(path)) ? route : ((route as ConfigRoute).parent || { path: '' }); while (parent && path[0] !== '/') { path = `${parent.path}/${path}`; parent = (route as ConfigRoute).parent; } if (basename && path[0] === '/') path = basename + path; return normalizePath(path); } function matchRoutes( routes: ConfigRoute[] | RouteChildrenFn, to: RouteHistoryLocation | Route | string, parent?: ConfigRoute, options: { branch?: RouteBranchArray, level?: number, queryProps?: ParseQueryProps, } = {} ) { const { queryProps } = options; to = normalizeLocation(to, { queryProps }) as RouteHistoryLocation; if (!to || to.path === '') return []; const { branch = [], level = 0 } = options; routes = getRouteChildren(routes, parent); routes.some(route => { let match = route.path ? matchPath((to as RouteHistoryLocation).path, route) : branch.length ? branch[branch.length - 1].match // use parent match : computeRootMatch((to as RouteHistoryLocation).path); // use default "root" match if (match && route.index) { route = resolveIndex(route.index, routes as ConfigRoute[]) as ConfigRoute; if (!route) return; (to as RouteHistoryLocation).pathname = (to as RouteHistoryLocation).path = route.path; match = matchPath(route.path, route); } if (!match) return; branch.push({ route, match }); if (route.children && (route.children.length || isFunction(route.children)) ) matchRoutes(route.children as any, to, route, { branch, level: level + 1, queryProps }); return true; }); if (!level && branch.length) { const lastRouteBranch = branch[branch.length - 1]; branch.unmatchedPath = to.pathname.substr(lastRouteBranch.match.path.length, to.pathname.length); } return branch; } function normalizeLocation( to: any, options: { route?: Route|MatchedRoute|ConfigRoute|RouteHistoryLocation|RouteLocation|null, append?: boolean, basename?: string, mode?: string, resolvePathCb?: (path: string, to: RouteHistoryLocation) => string, queryProps?: ParseQueryProps } = {} ): RouteHistoryLocation | null { const { route, append = false, basename = '', mode = '', resolvePathCb, queryProps } = options; if (!to || (isPlainObject(to) && !to.path && !to.pathname)) return null; if (to._routeNormalized) return to; if (isString(to)) { const searchs = to.match(/\?[^#]+/g) || ([] as string[]); const pathname = searchs.reduce((p, v) => p.replace(v, ''), to); const search = searchs.sort().reduce((p, v, i) => { if (!i) return v; const s = v.substr(1); return p + (s ? `&${s}` : ''); }, ''); to = { pathname, path: pathname, search, fullPath: to }; } if (to.query) Object.keys(to.query).forEach(key => (to.query[key] === undefined) && (delete to.query[key])); else if (to.search) to.query = config.parseQuery(to.search, queryProps); let isAbsolute = isAbsoluteUrl(to.pathname); if (isAbsolute && mode !== HistoryType.browser) { const hash = getCurrentPageHash(to.pathname); if (hash) { to.pathname = hash; isAbsolute = false; } } if (!isAbsolute) { let path = to.pathname || to.path; if (resolvePathCb) path = resolvePathCb(path, to); path = normalizeRoutePath( path, route, to.append || append, (to.absolute || to.basename) ? '' : basename ) || '/'; to.pathname = to.path = path; } if (to.basename == null && basename != null) to.basename = to.absolute ? '' : basename; readonly(to, 'search', function () { return config.stringifyQuery(this.query); }); readonly(to, 'fullPath', function () { return `${this.path}${this.search || ''}` || '/'; }); if (!to.query) to.query = {}; innumerable(to, '_routeNormalized', true); return to; } const _toString = Object.prototype.toString; function isPlainObject(obj: any): obj is Record<string, any> { return _toString.call(obj) === '[object Object]'; } function isFunction(value: any): value is Function { return typeof value === 'function'; } function isNull(value: any): value is (null | undefined) { return value === null || value === undefined; } function isMatchedRoute(value: any): value is MatchedRoute { return Boolean(value && value.config); } function isLocation(v: any): v is RouteLocation { return isPlainObject(v) && (v.path || v.pathname); } function isHistoryLocation(v: any): v is RouteHistoryLocation { return isLocation(v) && Boolean(v._routeNormalized); } function isBoolean(v: any): v is boolean { return typeof v === 'boolean'; } function normalizeProps(props: UserConfigRouteProps) { const res: UserConfigRoutePropsNormal|UserConfigRoutePropsNormalMap = {}; if (isBoolean(props)) return props; if (Array.isArray(props)) { props.forEach(key => res[key] = { type: null }); } else if (isPlainObject(props)) { Object.keys(props).forEach(key => { const val = props[key]; res[key] = isPlainObject(val) ? (val as any).type !== undefined ? val as UserConfigRoutePropsNormalItem : normalizeProps(val as any) as Record<string, UserConfigRoutePropsNormalItem> : { type: val }; }); } else return false; return res; } type MatchRegxList =RegExp|string|(RegExp|string)[]; function isMatchRegxList(key: string, regx: MatchRegxList): boolean { if (Array.isArray(regx)) return regx.some(v => isMatchRegxList(key, v)); return regx instanceof RegExp ? regx.test(key) : regx === key; } function omitProps<T extends Record<string, any>>(props: T, excludes: RegExp|string|(string|RegExp)[]) { if (!excludes) return props; const ret: Record<string, any> = {}; Object.getOwnPropertyNames(props).forEach(key => { if (isMatchRegxList(key, excludes)) return; ret[key] = props[key]; }); return ret; } function once<T extends Function>(fn: T, ctx?: any) { if (!fn) return fn; let ret: any; let called = false; const fnWrapper: T = (function _once() { if (called) return ret; called = true; ret = fn.apply(ctx || this, arguments); return ret; }) as any; copyOwnProperties(fnWrapper, fn); return fnWrapper; } function isAcceptRef(v: any) { if (!v) return false; if (v.$$typeof === REACT_FORWARD_REF_TYPE && v.__componentClass) return true; if (v.__component) v = getGuardsComponent(v); let ret = false; if (v.prototype) { if (v.prototype instanceof React.Component || v.prototype.componentDidMount !== undefined) ret = true; } else if (v.$$typeof === REACT_FORWARD_REF_TYPE && !v.__guards) ret = true; return ret; } function mergeFns(...fns: any[]) { return function (...args: any) { let ret; fns.forEach(fn => { ret = fn && fn.call(this, ...args); }); return ret; }; } function resolveIndex(originIndex: string | RouteIndexFn, routes: ConfigRoute[]): ConfigRoute | null { const index = isFunction(originIndex) ? (originIndex as RouteIndexFn)(routes) : originIndex; if (!index) return null; const r = routes.find((r: ConfigRoute) => { if (index === ':first' && !r.index) { const visible = readRouteMeta(r, 'visible'); if (visible !== false) return true; } const path1 = r.subpath[0] === '/' ? r.subpath : `/${r.subpath}`; const path2 = (index as string)[0] === '/' ? index : `/${index}`; return path1 === path2; }) || null; if (r && r.index) { if (r.index === originIndex) return null; return resolveIndex(r.index, routes); } return r; } function resolveRedirect(to: string | RouteLocation | RouteRedirectFn | undefined, route: MatchedRoute, options: { isInit?: boolean, from?: Route, queryProps?: ParseQueryProps, } = {}) { if (isFunction(to)) to = (to as RouteRedirectFn).call(route.config, options.from, options.isInit); if (!to) return ''; const ret = normalizeLocation(to, { route, queryProps: options.queryProps }); if (!ret) return ''; options.from && Object.assign(ret.query, options.from.query); ret.isRedirect = true; return ret; } function resolveAbort( abort: boolean|string|RouteAbortFn|undefined|Error, route: MatchedRoute, options: { isInit?: boolean, from?: Route, } = {} ) { if (isFunction(abort)) { try { abort = (abort as RouteAbortFn).call(route.config, options.from, options.isInit); } catch (ex: any) { abort = ex; } } return abort; } function warn(...args: any[]) { console.error(...args); } async function afterInterceptors(interceptors: RouteGuardInterceptor[], to: Route, from: Route | null) { for (let i = 0; i < interceptors.length; i++) { let interceptor = interceptors[i]; while (interceptor && (interceptor as LazyResolveFn).lazy) { interceptor = await (interceptor as LazyResolveFn)(interceptors, i); } if (!interceptor) return; interceptor && await (interceptor as RouteAfterGuardFn).call(this, to, from, interceptor.route); } } type LazyMethod<T extends ReactAllComponentType = ReactAllComponentType> = () => Promise<T|EsModule<T>|null>; function createLazyComponent<T extends ReactAllComponentType = ReactAllComponentType>( lazyMethodOrPromise: LazyMethod<T>|ReturnType<LazyMethod<T>> ) { return React.forwardRef<T>( (props, ref) => { const [$refs] = useState({ mounted: false }); const [comp, setComp] = useState<{ App: null|ReactAllComponentType }>({ App: null }); const { App } = comp; useEffect(() => { $refs.mounted = true; (isPromise(lazyMethodOrPromise) ? lazyMethodOrPromise : lazyMethodOrPromise()).then(App => { if (!$refs.mounted) return; if ((App as EsModule).__esModule) App = (App as EsModule).default; setComp({ App: App as any }); }); return () => { $refs.mounted = false; }; }, [$refs]); if (!App) return null; return React.createElement( App, { ...props, ...(ref ? { ref } : {}) }, props.children ); } ); } type RenderRouteOption = { router?: ReactViewRouter, name?: string; ref?: any, params?: Partial<any>, query?: Partial<any>, // getComponent?: (name: string, route: ConfigRoute) => React.ComponentType|null } function getConfigRouteProps(configs: UserConfigRouteProps, name?: string) { if (configs === false) return; if (name && (configs as UserConfigRoutePropsNormalMap)[name] !== undefined) configs = (configs as UserConfigRoutePropsNormalMap)[name]; if (configs === true) return true; else if (isPlainObject(configs)) return Object.keys(configs); else if (Array.isArray(configs)) return configs; return []; } function configRouteProps(_props: Record<string, any>, configs: UserConfigRouteProps, obj: any, name?: string) { if (!obj || configs === false) return; if (name && (configs as UserConfigRoutePropsNormalMap)[name] !== undefined) configs = (configs as UserConfigRoutePropsNormalMap)[name]; if (configs === true) Object.assign(_props, obj); else if (isPlainObject(configs)) { Object.keys(configs).forEach(key => { const prop = (configs as UserConfigRoutePropsNormalMap)[key]; const type = prop.type; const val = obj[key]; if (val === undefined) { if (prop.default) { if (isFunction(prop.default) && (type === Object || type === Array)) { _props[key] = (prop as any).default(); } else _props[key] = prop.default; } else return; } if (type != null && !isBoolean(type)) _props[key] = (type as Function)(val); else _props[key] = val; }); } } function renderRoute( route: ConfigRoute | MatchedRoute | null | undefined, routes: ConfigRoute[], props: any, children: React.ReactNode | null, options: RenderRouteOption = {} ): ReactNode|null { if (props === undefined) props = {}; if (!route) return null; if (React.isValidElement(route)) return route; if (isMatchedRoute(route)) route = route.config; function createComp(route: ConfigRoute, props: any, children: React.ReactNode, options: RenderRouteOption) { let component = route.components && route.components[options.name || 'default']; if (!component) { if (route.children && route.children.length && options.router) { component = RouterViewWrapper; } else return null; } const _props = { key: route.path }; if (route.defaultProps) { Object.assign(_props, isFunction(route.defaultProps) ? route.defaultProps(props) : route.defaultProps); } if (route.props) configRouteProps(_props, route.props, options.params, options.name); if (route.paramsProps) configRouteProps(_props, route.paramsProps, options.params, options.name); if (route.queryProps) configRouteProps(_props, route.queryProps, options.query, options.name); let ref: any = null; if (component) { if (isAcceptRef(component)) ref = options.ref; else if (route.enableRef) { if (!isFunction(route.enableRef) || route.enableRef(component)) ref = options.ref; } } const _pending = route._pending; const completeCallback = _pending && _pending.completeCallbacks[options.name || 'default']; const refHandler = once((el?: any, componentClass?: any) => { if (el || !ref) { // if (isFunction(componentClass)) componentClass = componentClass(el, route); if (componentClass && el && (el._reactInternalFiber || el._reactInternals)) { let refComp = null; let comp = el._reactInternalFiber || el._reactInternals; while (comp && !refComp) { if (comp.type === componentClass) { refComp = comp; break; } comp = comp.child; } if (refComp && refComp.stateNode instanceof componentClass) el = refComp.stateNode; else warn('componentClass', componentClass, 'not found in route component: ', el); } completeCallback && completeCallback(el); } }); _pending && (_pending.completeCallbacks[options.name || 'default'] = null); if (ref) ref = mergeFns(ref, (el: any) => el && refHandler && refHandler(el, component.__componentClass)); if (component.__component) component = getGuardsComponent(component); if (isRouteLazy(component)) { const routeLazy = component; component = createLazyComponent(() => routeLazy.toResolve( options.router as any, isMatchedRoute(route) ? route.config : route, options.name as string )); warn(`route [${route.path}] component should not be RouteLazy instance!`); } const ret = React.createElement( component, Object.assign( _props, config.inheritProps ? { route } : null, props, { ref } ), ...(Array.isArray(children) ? children : [children]) ); if (!ref) nextTick(refHandler); return ret; } let renderRoute: ConfigRoute|null = route; if (route && route.redirect) return null; if (route && route.index) renderRoute = resolveIndex(route.index, routes); if (!renderRoute) return null; const result = createComp(renderRoute, props, children, options) as any; return result; } function flatten<T>(array: T[]) { const flattend: T[] = []; (function flat(array) { array.forEach(function (el) { if (Array.isArray(el)) flat(el); else flattend.push(el); }); })(array); return flattend; } function camelize(str: string): string { let ret = str.replace(/[-](\w)/g, function (_, c) { return c ? c.toUpperCase() : ''; }); if (/^[A-Z]/.test(ret)) ret = ret.charAt(0).toLowerCase() + ret.substr(1); return ret; } function isPropChanged( prev: Record<string, any>|null, next: Record<string, any>|null, onChanged?: ((key: string, newVal: any, oldVal: any) => boolean)|null, keys?: string[] ) { if (!prev || !next) return prev !== next; return (keys || Object.keys(next)) .some(key => { const newVal = next[key]; const oldVal = prev[key]; let changed = newVal !== oldVal; if (changed && onChanged) changed = onChanged(key, newVal, oldVal); return changed; }); } function isRouteChanged(prev: ConfigRoute | MatchedRoute | null, next: ConfigRoute | MatchedRoute | null) { if (!prev || !next) return prev !== next; return prev.path !== next.path || prev.subpath !== next.subpath; } function isMatchedRoutePropsChanged(matchedRoute: MatchedRoute|null, router: ReactViewRouter, name?: string) { if (!matchedRoute) return false; return router && ['query', 'params'].some(key => { let configs = key === 'params' ? matchedRoute.config.paramsProps || matchedRoute.config.props : matchedRoute.config.queryProps; let keys = configs && getConfigRouteProps(configs, name); if (!keys) return false; if (keys === true) { if (!router.currentRoute) return false; keys = Object.keys((router.currentRoute as any)[key]); } return isPropChanged( router.currentRoute && ((router as any).currentRoute as any)[key], router.prevRoute && ((router as any).prevRoute as any)[key], null, keys as string[] ); }) } function isRoutesChanged(prevs: ConfigRoute[], nexts: ConfigRoute[]) { if (!prevs || !nexts) return true; if (prevs.length !== nexts.length) return true; let changed = false; prevs.some((prev, i) => { changed = isRouteChanged(prev, nexts[i]); return changed; }); return changed; } function getHostRouterView(ctx: any, continueCb?: any) { let parent = (ctx._reactInternalFiber || ctx._reactInternals).return; while (parent) { if (continueCb && continueCb(parent) === false) return null; const memoizedState = parent.memoizedState; // const memoizedProps = parent.memoizedProps; if (memoizedState && hasOwnProp(memoizedState, '_routerRoot')) { return parent.stateNode as RouterView; } parent = parent.return; } return null; } function getParentRoute(ctx: any): MatchedRoute | null { const view = getHostRouterView(ctx); return (view && view.state.currentRoute) || null; } function isConfigRoute(value: any): value is ConfigRoute { return value && value._normalized && value._pending; } function isNormalizedConfigRouteArray(value: any): value is NormalizedConfigRouteArray { return Array.isArray(value) && (value as any)._normalized; } function isString(value: any): value is string { return typeof value === 'string'; } function isNumber(value: any): value is number { return typeof value === 'number'; } function isAbsoluteUrl(to: any) { return isString(to) && /^(https?:)?\/\/.+/.test(to); } function getCurrentPageHash(to: string) { if (!to || !globalThis.location) return ''; const [, host = '', hash = ''] = to.match(/(.+)#(.+)$/) || []; return globalThis.location.href.startsWith(host) ? hash : ''; } function getSessionStorage(key: string, json: boolean = false) { if (!globalThis.sessionStorage) return null; const v = globalThis.sessionStorage[key]; if (v === undefined) return json ? null : ''; return json ? JSON.parse(v) : v; } function setSessionStorage(key: string, value?: any, replacer?: (number | string)[]|((this: any, key: string, value: any) => any)) { if (!globalThis.sessionStorage) return; const isNull = value === undefined || value === null; const v = isString(value) ? value : JSON.stringify(value, replacer as any); if (!v || isNull) globalThis.sessionStorage.removeItem(key); else globalThis.sessionStorage[key] = v; } function getRouterViewPath(routerView: RouterView) { if (!routerView || !routerView.state.currentRoute) return ''; return routerView.state.currentRoute.path; } function isRoute(route: any): route is Route { return Boolean(route && route.isViewRoute); } function isReactViewRouter(v: any): v is ReactViewRouter { return v && v.isReactViewRouterInstance; } function isHistory(v: any): v is HistoryFix { return v && v.isHistoryInstance; } function isRouteGuardInfoHooks(v: any): v is RouteGuardsInfoHooks { return v && v.__routeGuardInfoHooks; } function isReadonly(obj: any, key: string) { const d = obj && Object.getOwnPropertyDescriptor(obj, key); return Boolean(!obj || (d && !d.writable)); } function isRouteChildrenNormalized(fn: any): fn is NormalizedRouteChildrenFn { return fn && fn._normalized; } function normalizeRouteChildrenFn( childrenFn: RouteChildrenFn | NormalizedRouteChildrenFn, checkDirty?: (oldRoutes?: NormalizedConfigRouteArray) => boolean ): NormalizedRouteChildrenFn { if (!childrenFn || isRouteChildrenNormalized(childrenFn)) return childrenFn; const cache: NormalizedRouteChildrenFn['cache'] = {}; const ret: RouteChildrenFn = function (parent) { const isDirty = checkDirty && checkDirty(cache.routes); if (!isDirty && cache.routes) return cache.routes || normalizeRoutes([], parent); return cache.routes = normalizeRoutes(childrenFn(parent), parent); }; innumerable(cache, 'cache', cache); innumerable(ret, '_normalized', true); Object.getOwnPropertyNames(childrenFn).forEach(key => { if (['cache', '_normalized'].includes(key)) return; const p = Object.getOwnPropertyDescriptor(childrenFn, key); p && Object.defineProperty(ret, key, p); }); return ret as NormalizedRouteChildrenFn; } function getRouteChildren(children: ConfigRoute[]|RouteChildrenFn, parent?: ConfigRoute|null) { if (isFunction(children)) children = children(parent); return children || []; } function readRouteMeta(configOrMatchedRoute: ConfigRoute|MatchedRoute, key: string = '', props: { router?: ReactViewRouter|null, [key: string]: any } = {}) { if (!key) return; // if (isMatchedRoute(configOrMatchedRoute)) return configOrMatchedRoute.meta[key]; const route: ConfigRoute = isMatchedRoute(configOrMatchedRoute) ? configOrMatchedRoute.config : configOrMatchedRoute; let value = route.meta[key]; if (isFunction(value)) { const routes = route.parent ? route.parent.children : (props.router && props.router.routes); value = (value as RouteMetaFunction)( route, (isFunction(routes) ? normalizeRoutes(routes(route.parent), route.parent) : routes) || normalizeRoutes([], route.parent), props ); } return value; } function getCompleteRoute(route: Route|null) { if (!route || route.isComplete) return route; while (route.redirectedFrom) { route = route.redirectedFrom; if (route.isComplete) return route; } return null; } function getLoactionAction(to?: Route): undefined|Action { if (!to) return; return (to.isRedirect && !to.isComplete) ? getLoactionAction(to.redirectedFrom) : to.action; } function reverseArray<T>(originArray: T[]) { const ret: T[] = []; for (let i = originArray.length - 1; i >= 0; i--) { ret.push(originArray[i]); } return ret; } function createUserConfigRoute(route: UserConfigRoute): UserConfigRoute { return route; } function createUserConfigRoutes<T extends RouteChildrenFn | NormalizedRouteChildrenFn>(routes: T): T function createUserConfigRoutes(routes: Array<UserConfigRoute|ConfigRoute>) { return routes; } const EMPTY_ROTUE_STATE_NAME = 'empty-state'; function createEmptyRouteState() { return innumerable({}, EMPTY_ROTUE_STATE_NAME, true); } function isEmptyRouteState(state: any) { return !state || state[EMPTY_ROTUE_STATE_NAME]; } export { DEFAULT_STATE_NAME, MatchRegxList, camelize, flatten, warn, once, ignoreCatch, mergeFns, reverseArray, copyOwnProperty, copyOwnProperties, isAcceptRef, nextTick, hasOwnProp, isNull, isBoolean, isString, isNumber, isPlainObject, isFunction, isMatchedRoute, isLocation, isConfigRoute, isNormalizedConfigRouteArray, isHistoryLocation, isPropChanged, isRouteChanged, isRoutesChanged, isMatchedRoutePropsChanged, isAbsoluteUrl, isRoute, isReactViewRouter, isRouteGuardInfoHooks, isHistory, isReadonly, isPromise, isRouteLazy, isRouteChildrenNormalized, isMatchRegxList, resolveRedirect, resolveAbort, resolveIndex, normalizePath, normalizeRoute, normalizeRoutes, normalizeRouteChildrenFn, normalizeRoutePath, normalizeLocation, normalizeProps, omitProps, walkRoutes, matchPath, matchRoutes, configRouteProps, renderRoute, innumerable, readonly, afterInterceptors, getParentRoute, getRouteChildren, getHostRouterView, getCurrentPageHash, getRouterViewPath, getCompleteRoute, getLoactionAction, getSessionStorage, setSessionStorage, readRouteMeta, createLazyComponent, createUserConfigRoute, createUserConfigRoutes, createEmptyRouteState, isEmptyRouteState };