mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 04:43:14 +00:00
.substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated Signed-off-by: Tobias Speicher <rootcommander@gmail.com>
289 lines
7.0 KiB
JavaScript
289 lines
7.0 KiB
JavaScript
// Inspired by react-router and universal-router
|
|
import { useState, useEffect, useLayoutEffect, createElement } from 'rax';
|
|
import pathToRegexp from 'path-to-regexp';
|
|
|
|
const cache = {};
|
|
function decodeParam(val) {
|
|
try {
|
|
return decodeURIComponent(val);
|
|
} catch (err) {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
function matchPath(route, pathname, parentParams) {
|
|
let { path, routes, exact: end = true, strict = false, sensitive = false } = route;
|
|
// If not has path or has routes that should do not exact match
|
|
if (path == null || routes) {
|
|
end = false;
|
|
}
|
|
|
|
// Default path is empty
|
|
path = path || '';
|
|
|
|
const regexpCacheKey = `${path}|${end}|${strict}|${sensitive}`;
|
|
const keysCacheKey = `${regexpCacheKey }|`;
|
|
|
|
let regexp = cache[regexpCacheKey];
|
|
const keys = cache[keysCacheKey] || [];
|
|
|
|
if (!regexp) {
|
|
regexp = pathToRegexp(path, keys, {
|
|
end,
|
|
strict,
|
|
sensitive,
|
|
});
|
|
cache[regexpCacheKey] = regexp;
|
|
cache[keysCacheKey] = keys;
|
|
}
|
|
|
|
const result = regexp.exec(pathname);
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
|
|
const url = result[0];
|
|
const params = { ...parentParams, history: router.history, location: router.history.location };
|
|
|
|
for (let i = 1; i < result.length; i++) {
|
|
const key = keys[i - 1];
|
|
const prop = key.name;
|
|
const value = result[i];
|
|
if (value !== undefined || !Object.prototype.hasOwnProperty.call(params, prop)) {
|
|
if (key.repeat) {
|
|
params[prop] = value ? value.split(key.delimiter).map(decodeParam) : [];
|
|
} else {
|
|
params[prop] = value ? decodeParam(value) : value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
path: !end && url.charAt(url.length - 1) === '/' ? url.slice(1) : url,
|
|
params,
|
|
};
|
|
}
|
|
|
|
function matchRoute(route, baseUrl, pathname, parentParams) {
|
|
let matched;
|
|
let childMatches;
|
|
let childIndex = 0;
|
|
|
|
return {
|
|
next() {
|
|
if (!matched) {
|
|
matched = matchPath(route, pathname, parentParams);
|
|
|
|
if (matched) {
|
|
return {
|
|
done: false,
|
|
$: {
|
|
route,
|
|
baseUrl,
|
|
path: matched.path,
|
|
params: matched.params,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
if (matched && route.routes) {
|
|
while (childIndex < route.routes.length) {
|
|
if (!childMatches) {
|
|
const childRoute = route.routes[childIndex];
|
|
childRoute.parent = route;
|
|
|
|
childMatches = matchRoute(
|
|
childRoute,
|
|
baseUrl + matched.path,
|
|
pathname.slice(matched.path.length),
|
|
matched.params,
|
|
);
|
|
}
|
|
|
|
const childMatch = childMatches.next();
|
|
if (!childMatch.done) {
|
|
return {
|
|
done: false,
|
|
$: childMatch.$,
|
|
};
|
|
}
|
|
|
|
childMatches = null;
|
|
childIndex++;
|
|
}
|
|
}
|
|
|
|
return { done: true };
|
|
},
|
|
};
|
|
}
|
|
|
|
let _initialized = false;
|
|
let _routerConfig = null;
|
|
const router = {
|
|
history: null,
|
|
handles: [],
|
|
errorHandler() { },
|
|
addHandle(handle) {
|
|
return router.handles.push(handle);
|
|
},
|
|
removeHandle(handleId) {
|
|
router.handles[handleId - 1] = null;
|
|
},
|
|
triggerHandles(component) {
|
|
router.handles.forEach((handle) => {
|
|
handle && handle(component);
|
|
});
|
|
},
|
|
match(fullpath) {
|
|
if (fullpath == null) return;
|
|
|
|
router.fullpath = fullpath;
|
|
|
|
const parent = router.root;
|
|
const matched = matchRoute(
|
|
parent,
|
|
parent.path,
|
|
fullpath,
|
|
);
|
|
|
|
function next(parent) {
|
|
const current = matched.next();
|
|
|
|
if (current.done) {
|
|
const error = new Error(`No match for ${fullpath}`);
|
|
return router.errorHandler(error, router.history.location);
|
|
}
|
|
|
|
let { component } = current.$.route;
|
|
if (typeof component === 'function') {
|
|
component = component(current.$.params, router.history.location);
|
|
}
|
|
if (component instanceof Promise) {
|
|
// Lazy loading component by import('./Foo')
|
|
return component.then((component) => {
|
|
// Check current fullpath avoid router has changed before lazy loading complete
|
|
if (fullpath === router.fullpath) {
|
|
router.triggerHandles(component);
|
|
}
|
|
});
|
|
} else if (component != null) {
|
|
router.triggerHandles(component);
|
|
return component;
|
|
} else {
|
|
return next(parent);
|
|
}
|
|
}
|
|
|
|
return next(parent);
|
|
},
|
|
};
|
|
|
|
function matchLocation({ pathname }) {
|
|
router.match(pathname);
|
|
}
|
|
|
|
|
|
function getInitialComponent(routerConfig) {
|
|
let InitialComponent = [];
|
|
|
|
if (_routerConfig === null) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (!routerConfig) {
|
|
throw new Error('Error: useRouter should have routerConfig, see: https://www.npmjs.com/package/rax-use-router.');
|
|
}
|
|
if (!routerConfig.history || !routerConfig.routes) {
|
|
throw new Error('Error: routerConfig should contain history and routes, see: https://www.npmjs.com/package/rax-use-router.');
|
|
}
|
|
}
|
|
_routerConfig = routerConfig;
|
|
}
|
|
if (_routerConfig.InitialComponent) {
|
|
InitialComponent = _routerConfig.InitialComponent;
|
|
}
|
|
router.history = _routerConfig.history;
|
|
|
|
return InitialComponent;
|
|
}
|
|
|
|
let unlisten = null;
|
|
let handleId = null;
|
|
let pathes = '';
|
|
export function useRouter(routerConfig) {
|
|
const [component, setComponent] = useState(getInitialComponent(routerConfig));
|
|
|
|
let newPathes = '';
|
|
if (routerConfig) {
|
|
_routerConfig = routerConfig;
|
|
const { routes } = _routerConfig;
|
|
router.root = Array.isArray(routes) ? { routes } : routes;
|
|
if (Array.isArray(routes)) {
|
|
newPathes = routes.map(it => it.path).join(',');
|
|
} else {
|
|
newPathes = routes.path;
|
|
}
|
|
}
|
|
if (_initialized && _routerConfig.history) {
|
|
if (newPathes !== pathes) {
|
|
matchLocation(_routerConfig.history.location);
|
|
pathes = newPathes;
|
|
}
|
|
}
|
|
|
|
useLayoutEffect(() => {
|
|
if (unlisten) {
|
|
unlisten();
|
|
unlisten = null;
|
|
}
|
|
|
|
if (handleId) {
|
|
router.removeHandle(handleId);
|
|
handleId = null;
|
|
}
|
|
|
|
const { history } = _routerConfig;
|
|
const { routes } = _routerConfig;
|
|
|
|
router.root = Array.isArray(routes) ? { routes } : routes;
|
|
|
|
handleId = router.addHandle((component) => {
|
|
setComponent(component);
|
|
});
|
|
|
|
// Init path match
|
|
if (_initialized || !_routerConfig.InitialComponent) {
|
|
matchLocation(history.location);
|
|
pathes = newPathes;
|
|
}
|
|
|
|
unlisten = history.listen(({ location }) => {
|
|
matchLocation(location);
|
|
pathes = newPathes;
|
|
});
|
|
|
|
_initialized = true;
|
|
|
|
return () => {
|
|
pathes = '';
|
|
router.removeHandle(handleId);
|
|
handleId = null;
|
|
unlisten();
|
|
unlisten = null;
|
|
};
|
|
}, []);
|
|
|
|
return { component };
|
|
}
|
|
|
|
export function withRouter(Component) {
|
|
function Wrapper(props) {
|
|
const { history } = router;
|
|
return createElement(Component, { ...props, history, location: history.location });
|
|
}
|
|
|
|
Wrapper.displayName = `withRouter(${ Component.displayName || Component.name })`;
|
|
Wrapper.WrappedComponent = Component;
|
|
return Wrapper;
|
|
}
|