mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 03:01:16 +00:00
236 lines
6.5 KiB
TypeScript
236 lines
6.5 KiB
TypeScript
import { type PlainObject, type RawLocation } from '@alilc/lowcode-renderer-core';
|
|
import { pick } from 'lodash-es';
|
|
import { createRouteRecordMatcher, type RouteRecordMatcher } from './utils/record-matcher';
|
|
import { type PathParserOptions } from './utils/path-parser';
|
|
|
|
import type { RouteRecord, RouteParams, RouteLocationNormalized } from './types';
|
|
|
|
export interface RouteRecordNormalized {
|
|
/**
|
|
* {@link RouteRecord.name}
|
|
*/
|
|
name: RouteRecord['name'];
|
|
path: RouteRecord['path'];
|
|
page: string;
|
|
meta: PlainObject;
|
|
/**
|
|
* {@link RouteRecord.redirect}
|
|
*/
|
|
redirect: RouteRecord['redirect'];
|
|
children: RouteRecord[];
|
|
}
|
|
|
|
/**
|
|
* 作为 matcher 解析 location 的关键参数及输出内容
|
|
*/
|
|
export type MatcherLocation = Pick<
|
|
RouteLocationNormalized,
|
|
'name' | 'path' | 'params' | 'matched' | 'meta'
|
|
>;
|
|
|
|
/**
|
|
* 路由匹配器
|
|
*/
|
|
export interface RouterMatcher {
|
|
/**
|
|
* 新增路由记录
|
|
*/
|
|
addRoute: (record: RouteRecord, parent?: RouteRecordMatcher) => void;
|
|
/**
|
|
* 删除路由记录
|
|
*/
|
|
removeRoute: {
|
|
(matcher: RouteRecordMatcher): void;
|
|
(name: string): void;
|
|
};
|
|
/**
|
|
* 获取所有的路由匹配对象
|
|
*/
|
|
getRecordMatchers: () => RouteRecordMatcher[];
|
|
/**
|
|
* 获取路由匹配对象
|
|
*/
|
|
getRecordMatcher: (name: string) => RouteRecordMatcher | undefined;
|
|
/**
|
|
* Resolves a location.
|
|
* 允许访问与实际路径对应的路由记录并加入相应的 params
|
|
*
|
|
* @param location - MatcherLocationRaw to resolve to a url
|
|
* @param currentLocation - MatcherLocation of the current location
|
|
*/
|
|
resolve: (location: RawLocation, currentLocation: MatcherLocation) => MatcherLocation;
|
|
}
|
|
|
|
export function createRouterMatcher(
|
|
records: RouteRecord[],
|
|
globalOptions: PathParserOptions,
|
|
): RouterMatcher {
|
|
const matchers: RouteRecordMatcher[] = [];
|
|
const matcherMap = new Map<string, RouteRecordMatcher>();
|
|
|
|
function addRoute(record: RouteRecord, parent?: RouteRecordMatcher) {
|
|
const normalizedRecord = normalizeRouteRecord(record);
|
|
const options: PathParserOptions = Object.assign(
|
|
{},
|
|
globalOptions,
|
|
pick(record, ['end', 'sensitive', 'strict']),
|
|
);
|
|
|
|
// 如果子路由不是绝对路径,则构建嵌套路由的路径。
|
|
// 仅在子路径不为空且父路径没有尾部斜杠时添加 / 分隔符。
|
|
const { path } = normalizedRecord;
|
|
if (parent && path[0] !== '/') {
|
|
const parentPath = parent.record.path;
|
|
const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
|
|
|
|
normalizedRecord.path = parent.record.path + (path && connectingSlash + path);
|
|
}
|
|
|
|
const matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
|
|
|
|
if (normalizedRecord.children) {
|
|
const children = normalizedRecord.children;
|
|
for (let i = 0; i < children.length; i++) {
|
|
addRoute(children[i], matcher);
|
|
}
|
|
}
|
|
|
|
if (matcher.record.path) {
|
|
matchers.push(matcher);
|
|
|
|
if (matcher.record.name) {
|
|
matcherMap.set(matcher.record.name, matcher);
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeRoute(matcherRef: string | RouteRecordMatcher) {
|
|
if (typeof matcherRef === 'string') {
|
|
const matcher = matcherMap.get(matcherRef);
|
|
if (matcher) {
|
|
matcherMap.delete(matcherRef);
|
|
matchers.splice(matchers.indexOf(matcher), 1);
|
|
matcher.children.forEach(removeRoute);
|
|
}
|
|
} else {
|
|
const index = matchers.indexOf(matcherRef);
|
|
if (index > -1) {
|
|
matchers.splice(index, 1);
|
|
if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name);
|
|
matcherRef.children.forEach(removeRoute);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getRecordMatcher(name: string) {
|
|
return matcherMap.get(name);
|
|
}
|
|
|
|
function resolve(location: RawLocation, currentLocation: MatcherLocation): MatcherLocation {
|
|
let matcher: RouteRecordMatcher | undefined;
|
|
let params: RouteParams = {};
|
|
let path: MatcherLocation['path'];
|
|
let name: MatcherLocation['name'];
|
|
|
|
if ('name' in location && location.name) {
|
|
matcher = matcherMap.get(location.name);
|
|
|
|
if (!matcher) {
|
|
throw new Error(`Router error: no match for ${JSON.stringify(location)}`);
|
|
}
|
|
|
|
name = matcher.record.name;
|
|
// 从当前路径与传入的参数中获取 params
|
|
params = Object.assign(
|
|
paramsFromLocation(
|
|
currentLocation.params ?? {},
|
|
matcher.keys
|
|
.filter((k) => {
|
|
return !(k.modifier === '?' || k.modifier === '*');
|
|
})
|
|
.map((k) => k.name),
|
|
),
|
|
location.params
|
|
? paramsFromLocation(
|
|
location.params,
|
|
matcher.keys.map((k) => k.name),
|
|
)
|
|
: {},
|
|
);
|
|
// throws if cannot be stringified
|
|
path = matcher.stringify(params);
|
|
} else if ('path' in location) {
|
|
path = location.path;
|
|
matcher = matchers.find((m) => m.re.test(path));
|
|
|
|
if (matcher) {
|
|
name = matcher.record.name;
|
|
params = Object.assign(params, matcher.parse(path));
|
|
}
|
|
} else {
|
|
matcher = currentLocation.name
|
|
? matcherMap.get(currentLocation.name)
|
|
: matchers.find((m) => m.re.test(currentLocation.path));
|
|
|
|
if (!matcher) {
|
|
throw new Error(
|
|
`no match for ${JSON.stringify(location)}, ${JSON.stringify(currentLocation)}`,
|
|
);
|
|
}
|
|
|
|
name = matcher.record.name;
|
|
params = Object.assign({}, currentLocation.params, location.params);
|
|
path = matcher.stringify(params);
|
|
}
|
|
|
|
const matched: RouteRecord[] = [];
|
|
let parentMatcher: RouteRecordMatcher | undefined = matcher;
|
|
while (parentMatcher) {
|
|
matched.unshift(parentMatcher.record);
|
|
parentMatcher = parentMatcher?.parent;
|
|
}
|
|
|
|
return {
|
|
name,
|
|
path,
|
|
params,
|
|
meta: matcher?.record.meta ?? {},
|
|
matched,
|
|
};
|
|
}
|
|
|
|
records.forEach((r) => addRoute(r));
|
|
|
|
return {
|
|
resolve,
|
|
|
|
addRoute,
|
|
removeRoute,
|
|
|
|
getRecordMatchers() {
|
|
return matchers;
|
|
},
|
|
getRecordMatcher,
|
|
};
|
|
}
|
|
|
|
function paramsFromLocation(params: RouteParams, keys: (string | number)[]): RouteParams {
|
|
const newParams = {} as RouteParams;
|
|
for (const key of keys) {
|
|
if (key in params) newParams[key] = params[key];
|
|
}
|
|
|
|
return newParams;
|
|
}
|
|
|
|
export function normalizeRouteRecord(record: RouteRecord): RouteRecordNormalized {
|
|
return {
|
|
path: record.path,
|
|
redirect: record.redirect,
|
|
name: record.name,
|
|
page: record.page || '',
|
|
meta: record['meta'] || {},
|
|
children: record.children || [],
|
|
};
|
|
}
|