UNPKG

react-view-router

Version:
1,267 lines (1,257 loc) 59 kB
const _excluded = ["name", "mode", "basename"], _excluded2 = ["routes", "inheritProps", "rememberInitialRoute", "install", "queryProps"]; 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; } import "core-js/modules/web.dom.iterable.js"; function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } 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 { createHashHistory, createBrowserHistory, createMemoryHistory, getPossibleHistory } from './history-fix'; import config from './config'; import { flatten, isAbsoluteUrl, innumerable, isPlainObject, getRouterViewPath, getCompleteRoute, normalizeRoutes, normalizeLocation, resolveRedirect, resolveAbort, copyOwnProperties, matchRoutes, isFunction, isLocation, nextTick, once, isRouteGuardInfoHooks, isReadonly, afterInterceptors, getRouteChildren, readRouteMeta, readonly, getLoactionAction, getHostRouterView, camelize, reverseArray, isMatchedRoutePropsChanged, isRoute, walkRoutes, warn, createEmptyRouteState, DEFAULT_STATE_NAME, hasOwnProp, isString, isNumber, isEmptyRouteState } from './util'; import { RouteLazy, hasRouteLazy, hasMatchedRouteLazy } from './route-lazy'; import { getGuardsComponent } from './route-guard'; import { createHashHref, HistoryType } from './history'; const HISTORY_METHS = ['push', 'replace', 'redirect', 'go', 'back', 'forward', 'block']; let idSeed = 1; // eslint-disable-next-line no-undef const version = typeof "2.2.3" === 'undefined' ? undefined : "2.2.3"; class ReactViewRouter { // scrollBehavior: ReactViewRouterScrollBehavior|null = null; constructor(options = {}) { _defineProperty(this, "version", version); _defineProperty(this, "isReactViewRouterInstance", true); _defineProperty(this, "parent", null); _defineProperty(this, "children", []); _defineProperty(this, "options", {}); _defineProperty(this, "mode", HistoryType.hash); _defineProperty(this, "basename", ''); _defineProperty(this, "basenameNoSlash", ''); _defineProperty(this, "name", ''); _defineProperty(this, "routeNameMap", {}); _defineProperty(this, "routes", normalizeRoutes([])); _defineProperty(this, "stacks", []); _defineProperty(this, "plugins", []); _defineProperty(this, "beforeEachGuards", []); _defineProperty(this, "afterUpdateGuards", []); _defineProperty(this, "beforeResolveGuards", []); _defineProperty(this, "afterEachGuards", []); _defineProperty(this, "resolveNameFns", []); _defineProperty(this, "prevRoute", null); _defineProperty(this, "currentRoute", null); _defineProperty(this, "pendingRoute", null); _defineProperty(this, "initialRoute", {}); _defineProperty(this, "queryProps", {}); _defineProperty(this, "viewRoot", null); _defineProperty(this, "errorCallbacks", []); _defineProperty(this, "apps", []); _defineProperty(this, "Apps", []); _defineProperty(this, "isRunning", false); _defineProperty(this, "isHistoryCreater", false); _defineProperty(this, "rememberInitialRoute", false); _defineProperty(this, "getHostRouterView", void 0); _defineProperty(this, "nextTick", void 0); _defineProperty(this, "_history", null); _defineProperty(this, "_unlisten", void 0); _defineProperty(this, "_uninterceptor", void 0); _defineProperty(this, "id", void 0); _defineProperty(this, "_nexting", null); _defineProperty(this, "vuelike", void 0); _defineProperty(this, "_interceptorCounter", 0); this.id = idSeed++; this._initRouter(options); this.getHostRouterView = getHostRouterView; this.nextTick = nextTick.bind(this); this._handleRouteInterceptor = this._handleRouteInterceptor.bind(this); this.use(this.options); if (!options.manual) this.start(undefined, true); } _initRouter(options) { let { name, mode, basename } = options, moreOptions = _objectWithoutProperties(options, _excluded); if (name != null) this.name = name; if (mode != null) { if (typeof mode !== 'string') { moreOptions.history = mode; mode = mode.type; } if (this.mode !== mode) this.mode = mode; } if (basename !== undefined) { if (basename && !/\/$/.test(basename)) basename += '/'; this.basename = this.mode === HistoryType.memory && (!moreOptions.history || moreOptions.history.type !== HistoryType.memory) ? '' : basename.replace(/\/{2,}/g, '/'); this.basenameNoSlash = this.basename ? this.basename.endsWith('/') ? this.basename.substr(0, basename.length - 1) : this.basename : this.basename; } Object.assign(this.options, moreOptions); if (this.options.keepAlive && !this.options.renderUtils) { throw new Error('endable keepAlive need "renderUtils", but it cannot be found from router\'s options!'); } } _updateParent(parent) { if (parent === this) parent = null; if (this.parent === parent) return; if (parent) { if (parent.children && !parent.children.includes(this)) parent.children.push(this); } else if (this.parent && this.parent.children) { const idx = this.parent.children.indexOf(this); if (~idx) this.parent.children.splice(idx, 1); } this.parent = parent; } get history() { if (this._history) return this._history; switch (this.mode) { case HistoryType.browser: this._history = createBrowserHistory(this.options, this); break; case HistoryType.memory: this._history = createMemoryHistory(this.options, this); break; default: this._history = createHashHistory(this.options, this); } HISTORY_METHS.forEach(key => { if (!this[key] || !this[key].bindThis) return; this[key] = this[key].bind(this); innumerable(this[key], 'bindThis', true); }); this._history.refresh && this._history.refresh(); this._history && (this._history.destroy = () => this._history = null); return this._history; } get pluginName() { return this.name; } get top() { return this.parent ? this.parent.top : this; } get isBrowserMode() { return this.mode === HistoryType.browser; } get isHashMode() { return this.mode === HistoryType.hash; } get isMemoryMode() { return this.mode === HistoryType.memory; } get isPrepared() { return this.isRunning && Boolean(this.currentRoute && this.viewRoot && this.viewRoot.state.inited && this.viewRoot._isMounted); } _startHistory() { if (this._history && this._history.destroy) this._history.destroy(); this._unlisten = this.history.listen(({ location }, interceptors) => { let to = location; if (interceptors && Array.isArray(interceptors)) { const interceptorItem = interceptors.find(v => v.router === this); if (interceptorItem && interceptorItem.payload) to = interceptorItem.payload; } this.updateRoute(to); }); this._uninterceptor = this.history.interceptorTransitionTo(this._handleRouteInterceptor, this); this._refreshInitialRoute(); } start(routerOptions = {}, isInit = false) { this.stop({ isInit }); this._callEvent('onStart', this, routerOptions, isInit); this._initRouter(routerOptions); this._startHistory(); this.isRunning = true; } stop(options = {}) { this._callEvent('onStop', this, options); if (this._unlisten) { this._unlisten(); this._unlisten = undefined; } if (this._uninterceptor) { this._uninterceptor(); this._uninterceptor = undefined; } if (this._history && this._history.destroy) { this._history.destroy(); } this._history = null; this._interceptorCounter = 0; this.isRunning = false; this.isHistoryCreater = false; if (!options.ignoreClearRoute) { this.initialRoute = { location: {} }; this.prevRoute = null; this.currentRoute = null; this.pendingRoute = null; } } use(_ref) { let { routes, inheritProps, rememberInitialRoute, install, queryProps } = _ref, restOptions = _objectWithoutProperties(_ref, _excluded2); if (rememberInitialRoute !== undefined) this.rememberInitialRoute = rememberInitialRoute; // if (scrollBehavior !== undefined) this.scrollBehavior = scrollBehavior; if (queryProps) this.queryProps = queryProps; if (routes) { const originRoutes = this.routes; this.routes = routes ? normalizeRoutes(routes) : normalizeRoutes([]); this._callEvent('onRoutesChange', this.routes, originRoutes); this._walkRoutes(this.routes); if (this._history && this.initialRoute) this._refreshInitialRoute(); } if (inheritProps !== undefined) config.inheritProps = inheritProps; Object.assign(config, restOptions); if (install) this.install = install.bind(this); } plugin(plugin) { if (isFunction(plugin)) { plugin = this.plugins.find(p => p.onRouteChange === plugin) || { onRouteChange: plugin }; } else if (~this.plugins.indexOf(plugin)) return; const idx = this.plugins.findIndex(p => { if (plugin.name) return p.name === plugin.name; return p === plugin; }); if (~idx) { const [old] = this.plugins.splice(idx, 1, plugin); if (old && old.uninstall) old.uninstall(this); } else this.plugins.push(plugin); if (plugin.install) plugin.install(this); return () => { const idx = this.plugins.indexOf(plugin); if (~idx) { this.plugins.splice(idx, 1); if (plugin.uninstall) plugin.uninstall(this); } }; } _walkRoutes(routes, parent) { walkRoutes(routes, (route, routeIndex, rs) => { this._callEvent('onWalkRoute', route, routeIndex, rs); if (route.name) { const name = camelize(route.name); if (this.routeNameMap[name]) { warn(`[react-view-router] route name '${route.name}'(path is [${route.path}]) is duplicate with [${this.routeNameMap[name]}]`); } this.routeNameMap[name] = route.path; } }, parent); } _refreshInitialRoute() { let historyLocation = _objectSpread({}, this.history.realtimeLocation); this.updateRoute(historyLocation); if (this.rememberInitialRoute) { let stack; if (this.basename) { const stacks = reverseArray(this.history.stacks.concat(historyLocation)); const basename = this.basenameNoSlash; for (let i = 0; i < stacks.length; i++) { const currentStack = stacks[i]; if (!currentStack.pathname.startsWith(basename)) break; if (i === stacks.length - 1 || !stacks[i + 1].pathname.startsWith(basename)) { stack = currentStack; break; } } } else if (this.history.stacks.length) { stack = this.history.stacks[0]; } if (stack) { historyLocation = this._normalizeLocation({ pathname: stack.pathname, search: stack.search }); if (!this.isMemoryMode && globalThis?.location?.search) { const query = this.parseQuery(globalThis.location.search, this.queryProps); if (this.isHashMode) { Object.assign(historyLocation.query, query); } else if (this.isBrowserMode) { Object.keys(historyLocation.query).forEach(key => { if (query[key] !== undefined) historyLocation.query[key] = query[key]; }); } } } } this.initialRoute = this.createRoute(this._transformLocation(historyLocation)); // innumerable(this.initialRoute, 'location', location); } _callEvent(event, ...args) { let plugin = null; try { let ret; this.plugins.forEach(p => { plugin = p; const newRet = p[event] && p[event].call(p, ...args, ret); if (newRet !== undefined) ret = newRet; }); return ret; } catch (ex) { if (plugin && plugin.name && ex && ex.message) { ex.message = `[${plugin.name}:${event}]${ex.message}`; } throw ex; } } _isVuelikeComponent(comp) { return comp && this.vuelike && ( // eslint-disable-next-line no-proto comp.__proto__ && comp.isVuelikeComponentInstance || comp.__vuelike || comp.__vuelikeComponentClass); } _getComponentGurads(mr, guardName, onBindInstance, onGetLazyResovle) { const ret = []; const componentInstances = mr.componentInstances; const getGuard = (obj, guardName) => { const guard = obj && obj[guardName]; if (guard) innumerable(guard, 'instance', obj); return guard; }; // route config const routeGuardName = guardName.replace('Route', ''); const r = mr.config; const guards = r[routeGuardName]; if (guards) ret.push(guards); const toResovle = (c, componentKey) => { let ret = []; if (c) { const cc = c.__component ? getGuardsComponent(c, true) : c; const cg = getGuard(c.__guards, guardName); if (cg) ret.push(cg); let ccg = cc && getGuard(cc.prototype, guardName); if (ccg) { if (this.vuelike && !ccg.isMobxFlow && cc.__flows && ~cc.__flows.indexOf(guardName)) ccg = this.vuelike.flow(ccg); ret.push(ccg); } if (this._isVuelikeComponent(cc) && Array.isArray(cc.mixins)) { cc.mixins.forEach(m => { let ccg = m && (getGuard(m, guardName) || getGuard(m.prototype, guardName)); if (!ccg) return; if (this.vuelike && !ccg.isMobxFlow && m.__flows && ~m.__flows.indexOf(guardName)) ccg = this.vuelike.flow(ccg); ret.push(ccg); }); } } const ci = componentInstances[componentKey]; if (isRouteGuardInfoHooks(ci)) { const cig = getGuard(ci, guardName); if (cig) ret.push(cig); } if (onBindInstance) ret = ret.map(v => onBindInstance(v, componentKey, ci, mr)).filter(Boolean);else if (ci) { ret = ret.map(v => { const ret = v.bind(ci); ret.instance = v.instance; return ret; }); } ret = flatten(ret); ret.forEach(v => v.route = mr); return ret; }; const replaceInterceptors = (newInterceptors, interceptors, index) => { interceptors.splice(index, 1, ...newInterceptors); return interceptors[index]; }; // route component r.components && Object.keys(r.components).forEach(key => { const c = r.components[key]; if (!c) return; const isResolved = this._callEvent('onGetRouteComponentGurads', ret, r, c, key, guardName, { router: this, onBindInstance, onGetLazyResovle, toResovle, getGuard, replaceInterceptors: replaceInterceptors }); if (isResolved === true) return ret; if (c instanceof RouteLazy) { let lazyResovleCb; const lazyResovle = async (interceptors, index) => { lazyResovleCb && lazyResovleCb(); let nc = await c.toResolve(this, r, key); nc = this._callEvent('onLazyResolveComponent', nc, r) || nc; const ret = toResovle(nc, key); return replaceInterceptors(ret, interceptors, index); }; lazyResovle.lazy = true; lazyResovle.route = mr; onGetLazyResovle && onGetLazyResovle(lazyResovle, cb => lazyResovleCb = cb); ret.push(lazyResovle); return; } ret.push(...toResovle(c, key)); }); return ret; } _getSameMatched(route, compare) { const ret = []; if (!compare) return []; route && route.matched.some((tr, i) => { const fr = compare.matched[i]; if (tr.path !== fr.path) return true; ret.push(tr); }); return ret.filter(r => !r.redirect); } _getChangeMatched(route, route2, options = {}) { const ret = []; if (!route2) return [...route.matched]; let start = false; route && route.matched.some((tr, i) => { const fr = route2.matched[i]; if (!start) { start = options.containLazy && hasRouteLazy(tr) || !fr || fr.path !== tr.path || Boolean(options.compare && options.compare(tr, fr)); if (!start) return; } ret.push(tr); let count = options.count; if (isFunction(count)) count = count(ret, tr, fr); return isNumber(count) && ret.length === count; }); return ret.filter(r => !r.redirect); } _getBeforeEachGuards(to, from, current = null) { const ret = [...this.beforeEachGuards]; if (this.viewRoot && this.viewRoot.props.beforeEach) { ret.push(this.viewRoot.props.beforeEach); } // to.matched.forEach(mr => { // Object.keys(mr.viewInstances).forEach(key => { // let view = mr.viewInstances[key]; // if (!view || view === this.viewRoot) return; // const beforeEach = view.props.beforeEach; // if (beforeEach) ret.push(beforeEach); // }); // }); if (from) { const fm = this._getChangeMatched(from, to).filter(r => Object.keys(r.viewInstances).some(key => r.viewInstances[key])); reverseArray(fm).forEach(mr => { reverseArray(mr.guards.beforeLeave).forEach(g => { if (g.called) return; if (g.instance) { const beforeEnterGuard = mr.guards.beforeEnter.find(g2 => g2.instance === g.instance); if (beforeEnterGuard && !beforeEnterGuard.called) return; } ret.push(g.guard); }); }); } if (to) { const tm = this._getChangeMatched(to, from, { containLazy: true, compare: (tr, fr) => fr.guards.beforeEnter.some(g => !g.called) }); tm.forEach(mr => mr.guards.beforeEnter.forEach(g => { if (!g.lazy && g.called) return; ret.push(g.guard); })); } return flatten(ret); } _getBeforeResolveGuards(to, from) { const ret = [...this.beforeResolveGuards]; if (to) { const tm = this._getChangeMatched(to, from, { compare: (tr, fr) => fr.guards.beforeResolve.some(g => !g.called) }); tm.forEach(mr => { mr.guards.beforeResolve.forEach(g => { if (g.called) return; ret.push(g.guard); }); }); } return flatten(ret); } _getRouteUpdateGuards(to, from) { const ret = [...this.afterUpdateGuards, ...this.afterEachGuards.filter(g => g.update)]; const fm = []; to && to.matched.some((tr, i) => { const fr = from && from.matched[i]; if (!fr || fr.path !== tr.path) return true; fm.push(fr); }); reverseArray(fm.filter(r => !r.redirect)).forEach(mr => reverseArray(mr.guards.update).forEach(g => { if (g.called) return; ret.push(g.guard); })); return ret; } _getAfterEachGuards(to, from) { const ret = []; if (from) { const fm = this._getChangeMatched(from, to).filter(r => Object.keys(r.viewInstances).some(key => r.viewInstances[key])); reverseArray(fm.filter(r => !r.redirect)).forEach(mr => reverseArray(mr.guards.afterLeave).forEach(g => { if (g.called) return; ret.push(g.guard); })); } if (this.viewRoot && this.viewRoot.props.afterEach) { ret.push(this.viewRoot.props.afterEach); } ret.push(...this.afterEachGuards); return flatten(ret); } _isMatchBasename(location) { if (!this.basename) return true; if (location.basename && !location.absolute) return location.basename === this.basename; const pathname = location.path || location.pathname; return pathname.startsWith(this.basenameNoSlash); } _transformLocation(location) { if (!location || isRoute(location)) return location; if (this.basename) { let pathname = location.pathname; if (!location.absolute && location.basename != null) pathname = location.basename + pathname;else if (pathname.length < this.basename.length) { let parent = this.parent; while (parent) { if (pathname.length >= parent.basename.length) { const parentMatched = parent.getMatched(pathname); if (parentMatched.length) { pathname = parentMatched[parentMatched.length - 1].path + parentMatched.unmatchedPath; break; } } parent = parent.parent; } } if (!/\/$/.test(pathname)) pathname += '/'; location = _objectSpread(_objectSpread({}, location), {}, { absolute: false }); const isCurrentBasename = pathname.startsWith(this.basenameNoSlash); if (isCurrentBasename) { location.pathname = location.pathname.substr(this.basename.length - 1) || '/'; location.fullPath = location.pathname + location.search; location.basename = this.basename; } else { location.pathname = ''; location.fullPath = ''; } if (location.path != null) location.path = location.pathname; } return location; } async _getInterceptor(interceptors, index) { let interceptor = interceptors[index]; while (interceptor && interceptor.lazy) { interceptor = await interceptor(interceptors, index); } const newInterceptor = this._callEvent('onGetRouteInterceptor', interceptor, interceptors, index); if (newInterceptor && (isFunction(newInterceptor) || newInterceptor.then)) interceptor = newInterceptor; return interceptor; } async _routetInterceptors(interceptors, to, from, next) { let throwError = false; const isBlock = (v, interceptor, update) => { if (throwError) return true; let _isLocation = isString(v) || isLocation(v); if (_isLocation && interceptor) { const _to = isRoute(v) ? v : this._normalizeLocation(v, { route: interceptor.route }); v = _to && this.createRoute(_to, { action: getLoactionAction(to), from: to, matchedProvider: getCompleteRoute(to) || getCompleteRoute(from), isRedirect: true }); if (v && v.fullPath === to.fullPath) { v = undefined; _isLocation = false; } else if (v) update(v); } return !this._history || v === false || _isLocation || v instanceof Error; }; async function beforeInterceptor(interceptor, index, to, from, next) { if (!interceptor) return next(); const nextWrapper = this._nexting = once(async f1 => { if (isBlock.call(this, f1, interceptor, v => { f1 = v; })) return next(f1); if (f1 === true) f1 = undefined; try { const nextInterceptor = await this._getInterceptor(interceptors, ++index); if (!nextInterceptor) return next(res => isFunction(f1) && f1(res)); return await beforeInterceptor.call(this, nextInterceptor, index, to, from, res => { const ret = next(res); if (interceptor.global) isFunction(f1) && f1(res); return ret; }); } catch (ex) { throwError = true; console.error(ex); next(isString(ex) ? new Error(ex) : ex); } }); return await interceptor(to, from, nextWrapper, { route: interceptor.route, router: this }); } if (next) await beforeInterceptor.call(this, await this._getInterceptor(interceptors, 0), 0, to, from, next);else afterInterceptors.call(this, interceptors, to, from); } async _handleRouteInterceptor(location, callback, isInit = false) { if (!this.isRunning) return callback(true); if (location) { const pathname = location.path || location.pathname; if (isInit && this.basename && !location.basename && this.history.location.pathname === pathname) { if (this.parent && this.parent.currentRoute) { let url = this.parent.currentRoute.url; if (url && this.parent.basename) url = this.parent.basename.substr(0, this.parent.basename.length - 1) + url; if (!pathname.startsWith(url)) { if (location.pathname != null) location.pathname = url; if (location.path != null) location.path = url; if (location.query) location.query = this.parent.currentRoute.query; if (!isReadonly(location, 'search')) location.search = this.parent.currentRoute.search; } } } if (this.pendingRoute && this.pendingRoute.fullPath === location.fullPath) return callback(!this._nexting); if (this.basename && location.absolute && !pathname.startsWith(this.basename)) return callback(true); location = this._transformLocation(location); } if (!this._isMatchBasename(location)) return callback(true); if (!location || !location.pathname && this.currentRoute && !this.currentRoute.path) { return callback(true); } if (!isInit && !location.onInit && (!this.viewRoot || !this.viewRoot.state.inited)) return callback(true); const nexts = []; let error; let isContinue = true; this._callEvent('onRouteing', ok => { if (ok === false) isContinue = false;else if (isLocation(ok)) location = ok;else if (isFunction(ok)) nexts.push(ok); }); try { return isContinue && (await this._internalHandleRouteInterceptor(location, callback, isInit)); } catch (ex) { error = ex; } finally { nexts.forEach(next => next(Boolean(!isContinue || error), error, { location, isInit })); } } _normalizeLocation(to, options = {}) { return normalizeLocation(to, _objectSpread({ basename: this.basename, mode: this.mode, queryProps: this.queryProps }, options)); } _internalHandleRouteInterceptor(location, callback, isInit = false) { let isContinue = false; const interceptorCounter = ++this._interceptorCounter; try { const to = this.createRoute(location, { matchedProvider: hasOwnProp(location, 'basename') && location.basename !== this.basename ? this.currentRoute : null }); const from = isInit ? null : to.redirectedFrom && to.redirectedFrom.basename === this.basename ? to.redirectedFrom : this.currentRoute; const current = this.currentRoute; const checkIsContinue = () => !to.path || this.isRunning && interceptorCounter === this._interceptorCounter && Boolean(this.viewRoot && this.viewRoot._isMounted); const afterCallback = (isContinue, to, isRouteAbort = true, ok = undefined) => { if (isContinue) to.onInit && to.onInit(isContinue, to);else if (isRouteAbort) { this._callEvent('onRouteAbort', to, ok); to.onAbort && to.onAbort(isContinue, to, ok); } this.nextTick(() => { if (!checkIsContinue()) return; if (!isInit && (!current || current.fullPath !== to.fullPath)) { this._routetInterceptors(this._getRouteUpdateGuards(to, current), to, current); } }); }; if (!to) return; const realtimeLocation = this.history.realtimeLocation; if (from && from.isComplete && (!realtimeLocation || realtimeLocation.pathname === this.history.location.pathname) && to.matchedPath === from.matchedPath) { isContinue = checkIsContinue(); if (isContinue) { callback(newIsContinue => { afterCallback(newIsContinue, to); }, to); } else callback(isContinue, to); return; } let fallbackViews = []; if (hasMatchedRouteLazy(to.matched)) { this.viewRoot && fallbackViews.push(this.viewRoot); reverseArray(this._getSameMatched(isInit ? null : this.currentRoute, to)).some(m => { const keys = Object.keys(m.viewInstances).filter(key => m.viewInstances[key] && m.viewInstances[key].props.fallback); if (!keys.length) return; return fallbackViews = keys.map(key => m.viewInstances[key]); }); } fallbackViews.forEach(fallbackView => fallbackView._updateResolving(true, to)); this._routetInterceptors(this._getBeforeEachGuards(to, from, current), to, from, ok => { this._nexting = null; fallbackViews.length && setTimeout(() => fallbackViews.forEach(fallbackView => fallbackView._updateResolving(false)), 0); const resolveOptions = { from: to, isInit: Boolean(isInit || to.onInit) }; if (resolveOptions.isInit && this.pendingRoute && to.fullPath !== this.pendingRoute.fullPath) { ok = this.pendingRoute; this.pendingRoute = null; } // if (isString(ok)) ok = { path: ok }; isContinue = checkIsContinue() && Boolean(ok === undefined || ok && !(ok instanceof Error) && !isLocation(ok)); if (isContinue) { const toLast = to.matched[to.matched.length - 1]; if (toLast && toLast.config.exact && toLast.redirect) { ok = resolveRedirect(toLast.redirect, toLast, resolveOptions); if (ok) isContinue = false; } } if (isContinue && !isLocation(ok)) { to.matched.filter(mr => mr.abort).some(r => { const abort = resolveAbort(r.abort, r, resolveOptions); if (abort) { ok = isString(abort) ? new Error(abort) : abort === true ? undefined : abort; isContinue = false; } return abort; }); } const onNext = newOk => { isContinue = newOk; if (isContinue) this._routetInterceptors(this._getBeforeResolveGuards(to, current), to, current); // callback(isContinue, to); const okIsLocation = isLocation(ok); const isRouteAbort = !isContinue && !okIsLocation; afterCallback(isContinue, to, isRouteAbort, ok); if (!isContinue) { if (okIsLocation) { return this.redirect(ok, isRouteAbort ? undefined : to.onComplete, isRouteAbort ? undefined : to.onAbort, to.onInit || (isInit ? callback : null), to); } if (ok instanceof Error) this.errorCallbacks.forEach(cb => cb(ok)); return; } this.nextTick(() => { if (isFunction(ok)) ok = ok(to); if (to && isFunction(to.onComplete)) to.onComplete(Boolean(ok), to); this._routetInterceptors(this._getAfterEachGuards(to, current), to, current); }); }; if (!isContinue) { try { onNext(isContinue); } finally { callback(isContinue, to); } return; } return callback(onNext, to); }); } catch (ex) { console.error(ex); if (!isContinue) callback(isContinue, null); } } _go(to, onComplete, onAbort, onInit, replace) { return new Promise((resolve, reject) => { function doComplete(res, _to) { onComplete && onComplete(res, _to); resolve(res); } function doAbort(res, _to) { onAbort && onAbort(res, _to); reject(res === false && _to === null ? new Error('to path cannot be empty!') : res); } if (!this.isRunning) { doAbort(new Error('router is not running!'), null); return; } let _to; try { _to = this._normalizeLocation(to, { route: to && to.route || this.currentRoute, resolvePathCb: (path, to) => { const newPath = path.replace(/\[([A-z0-9.\-_#@$%^&*():|?<>=+]+)\]/g, (m, name) => { const ret = this.nameToPath(name, to, { onComplete: onInit || onComplete, onAbort }); if (ret == null) throw new Error(`route name [${name}]not be found!`); if (ret === true) throw 'cancel'; return ret; }); return newPath; } }); } catch (ex) { if (ex === 'cancel') return; throw ex; } if (!_to) { doAbort(false, null); return; } if (isFunction(onComplete)) _to.onComplete = once(doComplete); if (isFunction(onAbort)) _to.onAbort = once(doAbort); if (onInit) _to.onInit = onInit; let holdInitialQueryProps = this.options.holdInitialQueryProps; if (holdInitialQueryProps) { let query = this.initialRoute.query; if (Array.isArray(holdInitialQueryProps)) { query = holdInitialQueryProps.reduce((p, key) => { const value = query[key]; if (value === undefined) return p; p[key] = value; return p; }, {}); } else if (isFunction(holdInitialQueryProps)) { query = holdInitialQueryProps(this.initialRoute.query); } copyOwnProperties(_to.query, query); } _to.isReplace = Boolean(replace); if (this._nexting && (!to.pendingIfNotPrepared || this.isPrepared)) { this._nexting(_to); return; } if (_to.fullPath && isAbsoluteUrl(_to.fullPath) && globalThis?.location) { if (replace) globalThis.location.replace(_to.fullPath);else globalThis.location.href = _to.fullPath; return; } if (!this.isPrepared && !onInit) { this.pendingRoute = _to; const location = this.history.realtimeLocation; if (_to.fullPath === `${location.pathname}${location.search}`) return; } else { this.pendingRoute = null; } const isContinue = onInit || this._callEvent('onRouteGo', to, doComplete, doAbort, Boolean(replace)); if (isContinue === false) return; let history = this.history; if (_to.absolute && globalThis.location) { let url = ''; if (this.basename) { if (this.top && !this.top.basename) { history = this.top.history; } else if (!this.isMemoryMode) { url = history.createHref(_to); } } else if (this.isMemoryMode) { let mode = isString(_to.absolute) ? _to.absolute : ''; if (!mode || mode === HistoryType.memory) { const guessHistory = getPossibleHistory(this.options); if (guessHistory) { if (guessHistory.type === HistoryType.memory) history = guessHistory;else { mode = guessHistory.type; url = guessHistory.createHref(_to); } } else { mode = HistoryType.hash; url = createHashHref(_to, this.options.hashType); } warn(`[react-view-router] warning: parent router mode is ${mode || 'unknown'}, it could be '${mode}' mode that to go!`); } else url = history.createHref(_to); // if (mode === HistoryType.hash) url = getBaseHref() + (url.startsWith('#') ? '' : '#') + url; } if (url) { if (replace) globalThis.location.replace(url);else globalThis.location.href = url; return; } } if (_to.backIfVisited && _to.basename === this.basename) { const historyIndex = this.history.index; const isMatch = _to.backIfVisited === 'full-matcth' ? (to, s) => to.search === s.search : (to, s) => Object.keys(to.query).every(key => to.query[key] == s.query[key]); const stack = reverseArray(this.stacks).find(s => { const stackPath = this.getMatchedPath(s.pathname); let toPath = _to.path || ''; if (this.basename) toPath = toPath.substr(this.basenameNoSlash.length, toPath.length); return stackPath === this.getMatchedPath(toPath) && isMatch(to, s) && s.index <= historyIndex; }); if (stack) { this.go(stack); return; } } if (replace) history.replace(_to);else history.push(_to); }); } _replace(to, onComplete, onAbort, onInit) { return this._go(to, onComplete, onAbort, onInit, true); } _push(to, onComplete, onAbort, onInit) { return this._go(to, onComplete, onAbort, onInit); } resolveRouteName(fn) { const _off = () => { const idx = this.resolveNameFns.indexOf(fn); if (~idx) this.resolveNameFns.splice(idx, 1); }; _off(); this.resolveNameFns.push(fn); return _off; } nameToPath(name, options = {}, events = {}) { name = camelize(name); let path = this.routeNameMap[name]; if (path == null) { this.resolveNameFns.some(fn => { const newPath = fn(name, options, this, events); if (typeof newPath !== 'string' && newPath !== true) return; path = newPath; return true; }); } else if (options.absolute) { if (this.basename) path = `${this.basename}${path}`; } if (path == null && options.absolute && this.parent) { path = this.parent.nameToPath(name, options, events); if (path != null && this.parent.basename) { path = `${this.parent.basename}${path}`; } } return path; } updateRouteMeta(route, newValue, options = {}) { if (!route || !route.meta) return; let changed = false; const oldValue = {}; Object.keys(newValue).forEach(key => { if (route.meta[key] === newValue[key]) return; oldValue[key] = route.meta[key]; changed = true; }); if (!changed) return; Object.assign(route.meta, newValue); route.metaComputed = null; // if (route.config && !options.ignoreConfigRoute) Object.assign(route.config.meta, newValue); this._callEvent('onRouteMetaChange', newValue, oldValue, route, this); return changed; } createMatchedRoute(route, match) { const { url, path = route.path, regx, params } = match || {}; const { subpath, meta = {}, redirect, depth } = route; function guardToGuardInfo(originGuard, instance) { const guardInfo = { guard: function matchedRouteGuardWrapper() { guardInfo.called = true; return originGuard.apply(this, arguments); }, instance: instance || originGuard.instance, called: false }; copyOwnProperties(guardInfo.guard, originGuard); return guardInfo; } function guardsToMatchedRouteGuards(guards) { return guards.filter(v => !v.lazy).map(v => guardToGuardInfo(v)); } let beforeEnter; let beforeResolve; let update; let beforeLeave; let afterLeave; const that = this; const ret = { url, path, subpath, depth, regx, redirect, params, componentInstances: {}, viewInstances: {}, guards: { get beforeEnter() { if (beforeEnter) return beforeEnter; beforeEnter = []; that._getComponentGurads(ret, 'beforeRouteEnter', (fn, name, ci, r) => { const ret = function beforeRouteEnterWraper(to, from, next) { return fn(to, from, (cb, ...args) => { if (isFunction(cb)) { const _cb = cb; r.config._pending && (r.config._pending.completeCallbacks[name] = ci => { const res = _cb(ci); that._callEvent('onRouteEnterNext', r, ci, res); return res; }); cb = undefined; } return next(cb, ...args); }, { route: r, router: that }); }; const guardInfo = guardToGuardInfo(ret, fn.instance); beforeEnter.push(guardInfo); return guardInfo.guard; }, (lazyResovleFn, hook) => { const guardInfo = { lazy: true, guard: lazyResovleFn }; hook(() => { const idx = beforeEnter.indexOf(guardInfo); if (~idx) beforeEnter.splice(idx, 1); }); return beforeEnter.push(guardInfo); }); return beforeEnter; }, get beforeResolve() { if (beforeResolve) return beforeResolve; beforeResolve = guardsToMatchedRouteGuards(that._getComponentGurads(ret, 'beforeRouteResolve')); return beforeResolve; }, get update() { if (update) return update; update = guardsToMatchedRouteGuards(that._getComponentGurads(ret, 'beforeRouteUpdate')); return update; }, get beforeLeave() { if (beforeLeave) return beforeLeave; beforeLeave = []; that._getComponentGurads(ret, 'beforeRouteLeave', (fn, name, ci, r) => { const ret = function beforeRouteLeaveWraper(to, from, next) { return fn.call(ci, to, from, (cb, ...args) => { if (isFunction(cb)) { const _cb = cb; cb = (...as) => { const res = _cb(...as); that._callEvent('onRouteLeaveNext', r, ci, res); return res; }; } return next(cb, ...args); }, { route: r, router: that }); }; const guardInfo = guardToGuardInfo(ret, fn.instance); beforeLeave.push(guardInfo); return guardInfo.guard; }); return beforeLeave; }, get afterLeave() { if (afterLeave) return afterLeave; afterLeave = guardsToMatchedRouteGuards(that._getComponentGurads(ret, 'afterRouteLeave')); return afterLeave; } }, config: route }; readonly(ret, 'meta', () => meta); let metaComputed; Object.defineProperty(ret, 'metaComputed', { get() { if (metaComputed) return metaComputed; metaComputed = Object.keys(meta).reduce((p, key) => { readonly(p, key, () => readRouteMeta(route, key, { router: this })); return p; }, {}); return metaComputed; }, set(v) { metaComputed = v; }, enumerable: true, configurable: true }); if (match.isNull) ret.isNull = true; return ret; } getMatched(to, from, parent) { if (!from) from = this.currentRoute; // function copyInstance(to: MatchedRoute, from: MatchedRoute | null) { // if (!from) return; // if (from.componentInstances) to.componentInstances = from.componentInstances; // if (from.viewInstances) to.viewInstances = from.viewInstances; // } function isSameMatch(tr, fr) { return fr && tr && fr.path === tr.match.path; } const matched = matchRoutes(this.routes, to, parent, { queryProps: this.queryProps }); const isHistoryLocation = !isRoute(to) && typeof to !== 'string'; const state = isHistoryLocation && to.state && to.state[this.basename || DEFAULT_STATE_NAME] || {}; let isSameMatchedRoute = true; const ret = matched.map(({ route, match }, i) => { let ret; let viewInstances; if (isSameMatchedRoute && from && i < from.matched.length) { const fr = from.matched[i]; const tr = matched[i]; isSameMatchedRoute = isSameMatch(tr, fr); if (isSameMatchedRoute) ret = fr;else if (i && isSameMatch(matched[i - 1], from.matched[i - 1])) viewInstances = fr.viewInstances; } if (!ret) { ret = this.createMatchedRoute(route, match); if (viewInstances) ret.viewInstances = viewInstances; ret.state = state[ret.url]; if (!ret.state || !Object.keys(ret.state).length) ret.state = createEmptyRouteState(); } return ret; }); innumerable(ret, 'unmatchedPath', matched.unmatchedPath || ''); innumerable(ret, 'first', ret[0]); innumerable(ret, 'last', ret[ret.length - 1]); return ret; } getMatchedComponents(to, from, parent) { const ret = []; this.getMatched(to, from, parent).forEach(r => { Array.prototype.push.apply(ret, Object.values(r.componentInstances)); }); return ret.filter(Boolean); } getMatchedViews(to, from, parent) { const ret = []; this.getMatched(to, from, parent).map(r => { Array.prototype.push.apply(Object.values(r.viewInstances)); }); return ret.filter(Boolean); } getMatchedPath(path = '') { const matched = this.getMatched(path); return matched.length ? `${this.basenameNoSlash}${matched[matched.length - 1].path}${matched.unmatchedPath}` : path; } createRoute(to, options = {}) { if (isRoute(to)) return to; if (isString(to)) to = this._normalizeLocation(to); let { from, matchedProvider, action } = options; if (!from && to) from = to.redirectedFrom || this.currentRoute; const matched = to ? this.getMatched(to, matchedProvider || from) : []; const last = matched.length ? matched[matched.length - 1] : { url: '', path: '', params: {}, meta: {}, metaComputed: {}, state: {} }; const { search = '', query, path = '', delta = 0, onAbort, onComplete, isRedirect, isReplace, onInit, fromEvent = false } = to || {}; const params = to?.params || {}; Object.assign(params, last.params); const ret = { action: action || getLoactionAction(to) || this.history.action, url: last.url, basename: this.basename, path, search, fullPath: `${path}${search}`, isRedirect: Boolean(isRedirect || options.isRedirect), isReplace: Boolean(isReplace), fromEvent, query: query || (search ? config.parseQuery(search, this.queryProps) : {}), params, matched, matchedPath: last.path, delta, onAbort, onComplete, isComplete: false }; readonly(ret, 'meta', () => last.meta); readonly(ret, 'metaComputed', () => last.metaComputed); readonly(ret, 'state', () => last.state); // Object.defineProperty(ret, 'fullPath', { // enumerable: true, // configurable: true, // get() { // return `${to.path}${to.search}`; // } // }); innumerable(ret, 'isViewRoute', true); Object.defineProperty(ret, 'origin', { configurable: true, value: to }); if (isRedirect && from && from.basename === this.basename) { ret.redirectedFrom = from; if (!ret.onAbort && from.onAbort) ret.onAbort = from.onAbort; if (!ret.onComplete && from.onComplete) ret.onComplete = from.onComplete; if (!ret.onInit && onInit) ret.onInit = onInit; } return ret; } updateRoute(location) { location = location && this._transformLocation(location); if (!location || !this._isMatchBasename(location)) return; if (!this.isRunning && (location.path || this.currentRoute && !this.currentRoute.path)) return; let prevRoute = this.currentRoute; const realtimeLocation = this.history.realtimeLocation; if (prevRoute && realtimeLocation && realtimeLocation !== this.history.location) { prevRoute = this.createRoute(realtimeLocation, { from: prevRoute }); } const currentRoute = isRoute(location) ? location : this.createRoute(location, { from: prevRoute }); if (prevRoute && prevRoute.fullPath === currentRoute.fullPath && prevRoute.matched.length === currentRoute.matched.length && prevRoute.matched.every((v, i) => v.path === currentRoute.matched[i].path)) return; this.prevRoute = prevRoute; this.currentRoute = currentRoute; if (this.basename) { const basename = this.basenameNoSlash; const stacks = []; let prevStack = null; for (let i = this.history.stacks.length - 1; i >= 0; i--) { const info = this.history.stacks[i]; if (!info.pathname.startsWith(basename) || prevStack && prevStack.index <= info.index) break; let pathname = info.pathname.substr(basename.length, info.pathname.length); if (!pathname) pathname = '/'; prevStack = _objectSpread(_objectSpread({}, info), {}, { pathname }); stacks.unshift(prevStack); } if (!this.stacks.length && stacks.length) { this.stacks.splice(0, this.stacks.length, ...stacks); } else { let idx = this.stacks.findIndex((currentStack, i) => { const newStack = stacks[i]; if (newSta