UNPKG

react-view-router

Version:
683 lines (681 loc) 26.2 kB
import "core-js/modules/web.dom.iterable.js"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import React, { useState, useEffect } 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 { RouterViewWrapper } from './router-view'; import { HistoryType, readonly } from './history'; import { hasOwnProp, copyOwnProperties, copyOwnProperty } from './history/utils'; const DEFAULT_STATE_NAME = '[root]'; function nextTick(cb, ctx) { // @ts-ignore // eslint-disable-next-line no-promise-executor-return return cb && new Promise(r => r()).then(() => ctx ? cb.call(ctx) : cb()); } function ignoreCatch(fn, onCatch) { return function (...args) { try { // eslint-disable-next-line prefer-spread return fn.apply(this, args); } catch (ex) { onCatch ? onCatch(ex) : warn(ex); } }; } function innumerable(obj, key, value, options = { configurable: true }) { Object.defineProperty(obj, key, _objectSpread({ value }, options)); return obj; } function concatConfigRoutePath(routePath, parentPath) { if (routePath == null || routePath === '/') routePath = '';else if (routePath[0] !== '/') routePath = '/' + routePath; if (parentPath == null || parentPath === '/') parentPath = ''; return parentPath + routePath || '/'; } function getRoutePath(route) { if (!route || !route.path) return '/'; return route.path; } function normalizeRoute(route, parent, options = {}) { if (route._normalized) route = route._normalized; const subpath = getRoutePath(route); const path = concatConfigRoutePath(subpath, parent && parent.path); const r = { path, subpath, depth: parent ? parent.depth + 1 : 0, components: {} }; 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 = _objectSpread(_objectSpread({}, route.components), {}, { default: route.component }); Object.keys(r.components).forEach(key => { const comp = r.components[key]; if (comp instanceof RouteLazy) { comp.updaters.push(c => { if (c && c.__children) { let children = c.__children || []; if (isFunction(children)) children = children(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, parent, options = {}) { const _normalized = getRoutePath(parent); if (!options.force && isNormalizedConfigRouteArray(routes) && routes._normalized === _normalized) { return routes; } if (routes) { routes = routes.map(route => route && normalizeRoute(route, parent, options)).filter(Boolean); } else { routes = []; } innumerable(routes, '_normalized', _normalized); return routes; } function walkRoutes(routes, walkFn, parent) { if (isFunction(routes)) routes = normalizeRoutes(routes(parent), parent); return routes.some((route, routeIndex) => { if (walkFn(route, routeIndex, routes)) return true; if (!route.children || !Array.isArray(route.children)) return; return walkRoutes(route.children, walkFn, route); }); } function normalizePath(path) { 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, route, append, 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 = append || /^\.\//.test(path) ? route : route.parent || { path: '' }; while (parent && path[0] !== '/') { path = `${parent.path}/${path}`; parent = route.parent; } if (basename && path[0] === '/') path = basename + path; return normalizePath(path); } function matchRoutes(routes, to, parent, options = {}) { const { queryProps } = options; to = normalizeLocation(to, { queryProps }); if (!to || to.path === '') return []; const { branch = [], level = 0 } = options; routes = getRouteChildren(routes, parent); routes.some(route => { let match = route.path ? matchPath(to.path, route) : branch.length ? branch[branch.length - 1].match // use parent match : computeRootMatch(to.path); // use default "root" match if (match && route.index) { route = resolveIndex(route.index, routes); if (!route) return; to.pathname = to.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, 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, options = {}) { 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) || []; 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) { return _toString.call(obj) === '[object Object]'; } function isFunction(value) { return typeof value === 'function'; } function isNull(value) { return value === null || value === undefined; } function isMatchedRoute(value) { return Boolean(value && value.config); } function isLocation(v) { return isPlainObject(v) && (v.path || v.pathname); } function isHistoryLocation(v) { return isLocation(v) && Boolean(v._routeNormalized); } function isBoolean(v) { return typeof v === 'boolean'; } function normalizeProps(props) { const res = {}; 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.type !== undefined ? val : normalizeProps(val) : { type: val }; }); } else return false; return res; } function isMatchRegxList(key, regx) { if (Array.isArray(regx)) return regx.some(v => isMatchRegxList(key, v)); return regx instanceof RegExp ? regx.test(key) : regx === key; } function omitProps(props, excludes) { if (!excludes) return props; const ret = {}; Object.getOwnPropertyNames(props).forEach(key => { if (isMatchRegxList(key, excludes)) return; ret[key] = props[key]; }); return ret; } function once(fn, ctx) { if (!fn) return fn; let ret; let called = false; const fnWrapper = function _once() { if (called) return ret; called = true; ret = fn.apply(ctx || this, arguments); return ret; }; copyOwnProperties(fnWrapper, fn); return fnWrapper; } function isAcceptRef(v) { 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) { return function (...args) { let ret; fns.forEach(fn => { ret = fn && fn.call(this, ...args); }); return ret; }; } function resolveIndex(originIndex, routes) { const index = isFunction(originIndex) ? originIndex(routes) : originIndex; if (!index) return null; const r = routes.find(r => { 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[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, route, options = {}) { if (isFunction(to)) to = to.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, route, options = {}) { if (isFunction(abort)) { try { abort = abort.call(route.config, options.from, options.isInit); } catch (ex) { abort = ex; } } return abort; } function warn(...args) { console.error(...args); } async function afterInterceptors(interceptors, to, from) { for (let i = 0; i < interceptors.length; i++) { let interceptor = interceptors[i]; while (interceptor && interceptor.lazy) { interceptor = await interceptor(interceptors, i); } if (!interceptor) return; interceptor && (await interceptor.call(this, to, from, interceptor.route)); } } function createLazyComponent(lazyMethodOrPromise) { return /*#__PURE__*/React.forwardRef((props, ref) => { const [$refs] = useState({ mounted: false }); const [comp, setComp] = useState({ App: null }); const { App } = comp; useEffect(() => { $refs.mounted = true; (isPromise(lazyMethodOrPromise) ? lazyMethodOrPromise : lazyMethodOrPromise()).then(App => { if (!$refs.mounted) return; if (App.__esModule) App = App.default; setComp({ App: App }); }); return () => { $refs.mounted = false; }; }, [$refs]); if (!App) return null; return /*#__PURE__*/React.createElement(App, _objectSpread(_objectSpread({}, props), ref ? { ref } : {}), props.children); }); } function getConfigRouteProps(configs, name) { if (configs === false) return; if (name && configs[name] !== undefined) configs = configs[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, configs, obj, name) { if (!obj || configs === false) return; if (name && configs[name] !== undefined) configs = configs[name]; if (configs === true) Object.assign(_props, obj);else if (isPlainObject(configs)) { Object.keys(configs).forEach(key => { const prop = configs[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.default(); } else _props[key] = prop.default; } else return; } if (type != null && !isBoolean(type)) _props[key] = type(val);else _props[key] = val; }); } } function renderRoute(route, routes, props, children, options = {}) { if (props === undefined) props = {}; if (!route) return null; if (/*#__PURE__*/React.isValidElement(route)) return route; if (isMatchedRoute(route)) route = route.config; function createComp(route, props, children, options) { 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 = 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, componentClass) => { 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 => el && refHandler && refHandler(el, component.__componentClass)); if (component.__component) component = getGuardsComponent(component); if (isRouteLazy(component)) { const routeLazy = component; component = createLazyComponent(() => routeLazy.toResolve(options.router, isMatchedRoute(route) ? route.config : route, options.name)); warn(`route [${route.path}] component should not be RouteLazy instance!`); } const ret = /*#__PURE__*/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 = 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); return result; } function flatten(array) { const flattend = []; (function flat(array) { array.forEach(function (el) { if (Array.isArray(el)) flat(el);else flattend.push(el); }); })(array); return flattend; } function camelize(str) { 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, next, onChanged, keys) { 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, next) { if (!prev || !next) return prev !== next; return prev.path !== next.path || prev.subpath !== next.subpath; } function isMatchedRoutePropsChanged(matchedRoute, router, name) { 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[key]); } return isPropChanged(router.currentRoute && router.currentRoute[key], router.prevRoute && router.prevRoute[key], null, keys); }); } function isRoutesChanged(prevs, nexts) { 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, continueCb) { 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; } parent = parent.return; } return null; } function getParentRoute(ctx) { const view = getHostRouterView(ctx); return view && view.state.currentRoute || null; } function isConfigRoute(value) { return value && value._normalized && value._pending; } function isNormalizedConfigRouteArray(value) { return Array.isArray(value) && value._normalized; } function isString(value) { return typeof value === 'string'; } function isNumber(value) { return typeof value === 'number'; } function isAbsoluteUrl(to) { return isString(to) && /^(https?:)?\/\/.+/.test(to); } function getCurrentPageHash(to) { if (!to || !globalThis.location) return ''; const [, host = '', hash = ''] = to.match(/(.+)#(.+)$/) || []; return globalThis.location.href.startsWith(host) ? hash : ''; } function getSessionStorage(key, json = 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, value, replacer) { if (!globalThis.sessionStorage) return; const isNull = value === undefined || value === null; const v = isString(value) ? value : JSON.stringify(value, replacer); if (!v || isNull) globalThis.sessionStorage.removeItem(key);else globalThis.sessionStorage[key] = v; } function getRouterViewPath(routerView) { if (!routerView || !routerView.state.currentRoute) return ''; return routerView.state.currentRoute.path; } function isRoute(route) { return Boolean(route && route.isViewRoute); } function isReactViewRouter(v) { return v && v.isReactViewRouterInstance; } function isHistory(v) { return v && v.isHistoryInstance; } function isRouteGuardInfoHooks(v) { return v && v.__routeGuardInfoHooks; } function isReadonly(obj, key) { const d = obj && Object.getOwnPropertyDescriptor(obj, key); return Boolean(!obj || d && !d.writable); } function isRouteChildrenNormalized(fn) { return fn && fn._normalized; } function normalizeRouteChildrenFn(childrenFn, checkDirty) { if (!childrenFn || isRouteChildrenNormalized(childrenFn)) return childrenFn; const cache = {}; const ret = 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; } function getRouteChildren(children, parent) { if (isFunction(children)) children = children(parent); return children || []; } function readRouteMeta(configOrMatchedRoute, key = '', props = {}) { if (!key) return; // if (isMatchedRoute(configOrMatchedRoute)) return configOrMatchedRoute.meta[key]; const route = 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(route, (isFunction(routes) ? normalizeRoutes(routes(route.parent), route.parent) : routes) || normalizeRoutes([], route.parent), props); } return value; } function getCompleteRoute(route) { if (!route || route.isComplete) return route; while (route.redirectedFrom) { route = route.redirectedFrom; if (route.isComplete) return route; } return null; } function getLoactionAction(to) { if (!to) return; return to.isRedirect && !to.isComplete ? getLoactionAction(to.redirectedFrom) : to.action; } function reverseArray(originArray) { const ret = []; for (let i = originArray.length - 1; i >= 0; i--) { ret.push(originArray[i]); } return ret; } function createUserConfigRoute(route) { return route; } function createUserConfigRoutes(routes) { return routes; } const EMPTY_ROTUE_STATE_NAME = 'empty-state'; function createEmptyRouteState() { return innumerable({}, EMPTY_ROTUE_STATE_NAME, true); } function isEmptyRouteState(state) { return !state || state[EMPTY_ROTUE_STATE_NAME]; } export { DEFAULT_STATE_NAME, 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 };