react-view-router
Version:
react-view-router
1,267 lines (1,257 loc) • 59 kB
JavaScript
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