From 78f4bce57cab6d6858491a68834d3671e8ac399d Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 10:52:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(router):=20inula-router=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E8=A7=84=E5=88=99=E5=85=BC=E5=AE=B9react-rou?= =?UTF-8?q?ter=EF=BC=9BHashHistory=20hash=E6=A0=BC=E5=BC=8F=E4=B8=8D?= =?UTF-8?q?=E5=90=88=E6=B3=95=E6=97=B6=E9=87=8D=E5=AE=9A=E5=90=91=E8=87=B3?= =?UTF-8?q?=E5=90=88=E6=B3=95URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inula-router/src/history/hashHistory.ts | 7 ++ .../router/matcher/__tests__/parser.test.ts | 10 ++- .../inula-router/src/router/matcher/parser.ts | 70 +++++++++---------- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/packages/inula-router/src/history/hashHistory.ts b/packages/inula-router/src/history/hashHistory.ts index 6deb725e..8ce51b0a 100644 --- a/packages/inula-router/src/history/hashHistory.ts +++ b/packages/inula-router/src/history/hashHistory.ts @@ -57,6 +57,13 @@ export function createHashHistory(option: HashHistoryOptio const pathDecoder = addHeadSlash; const pathEncoder = hashType === 'slash' ? addHeadSlash : stripHeadSlash; + const startLocation = getHashContent(window.location.href); + const encodeLocation = pathEncoder(startLocation); + // 初始化hash格式不合法时会重定向 + if (startLocation !== encodeLocation) { + window.location.replace(stripHash(window.location.href) + '#' + encodeLocation); + } + function getLocation() { let hashPath = pathDecoder(getHashContent(window.location.hash)); if (basename) { diff --git a/packages/inula-router/src/router/matcher/__tests__/parser.test.ts b/packages/inula-router/src/router/matcher/__tests__/parser.test.ts index 91493cf6..e2a779d7 100644 --- a/packages/inula-router/src/router/matcher/__tests__/parser.test.ts +++ b/packages/inula-router/src/router/matcher/__tests__/parser.test.ts @@ -121,7 +121,13 @@ describe('parser test', () => { it('url without end slash match wildcard', function () { const parser = createPathParser('/about/', { strictMode: false }); const matched = parser.parse('/about'); - expect(matched).toBeNull(); + expect(matched).toStrictEqual({ + path: '/about/', + url: '/about', + score: [10], + isExact: true, + param: {}, + }); }); it('url without end slash match wildcard (strictMode)', function () { @@ -259,7 +265,7 @@ describe('parser test', () => { }); it('dynamic param with complex regexp pattern', () => { - const parser = createPathParser('/detail/:action([\\da-z]+)', { exact: true }); + const parser = createPathParser('/detail/:action([\\da-z]+)', { exact: true, caseSensitive: true }); const res = parser.parse('/detail/a123'); expect(res).toEqual({ isExact: true, diff --git a/packages/inula-router/src/router/matcher/parser.ts b/packages/inula-router/src/router/matcher/parser.ts index 9ea88fa7..1ddbbb5c 100644 --- a/packages/inula-router/src/router/matcher/parser.ts +++ b/packages/inula-router/src/router/matcher/parser.ts @@ -97,12 +97,14 @@ export function createPathParser

(pathname: string, option: ParserOp const token = tokens[tokenIdx]; const nextToken = tokens[tokenIdx + 1]; switch (token.type) { - case TokenType.Delimiter: - { - const hasOptional = lookToNextDelimiter(tokenIdx + 1); - pattern += `/${hasOptional ? '?' : ''}`; - } + case TokenType.Delimiter: { + // 该分隔符后有可选参数则该分割符在匹配时是可选的 + const hasOptional = lookToNextDelimiter(tokenIdx + 1); + // 该分割符为最后一个且strictMode===false时,该分隔符在匹配时是可选的 + const isSlashOptional = nextToken === undefined && !strictMode; + pattern += `/${hasOptional || isSlashOptional ? '?' : ''}`; break; + } case TokenType.Static: pattern += token.value.replace(REGEX_CHARS_RE, '\\$&'); if (nextToken && nextToken.type === TokenType.Pattern) { @@ -112,32 +114,31 @@ export function createPathParser

(pathname: string, option: ParserOp } scores.push(MatchScore.static); break; - case TokenType.Param: - { - // 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式 - let paramRegexp = ''; - if (nextToken) { - switch (nextToken.type) { - case TokenType.LBracket: - // 跳过当前Token和左括号 - tokenIdx += 2; - while (tokens[tokenIdx].type !== TokenType.RBracket) { - paramRegexp += tokens[tokenIdx].value; - tokenIdx++; - } - paramRegexp = `(${paramRegexp})`; - break; - case TokenType.Pattern: + case TokenType.Param: { + // 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式 + let paramRegexp = ''; + if (nextToken) { + switch (nextToken.type) { + case TokenType.LBracket: + // 跳过当前Token和左括号 + tokenIdx += 2; + while (tokens[tokenIdx].type !== TokenType.RBracket) { + paramRegexp += tokens[tokenIdx].value; tokenIdx++; - paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`; - break; - } + } + paramRegexp = `(${paramRegexp})`; + break; + case TokenType.Pattern: + tokenIdx++; + paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`; + break; } - pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`; - keys.push(token.value); - scores.push(MatchScore.param); } + pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`; + keys.push(token.value); + scores.push(MatchScore.param); break; + } case TokenType.WildCard: keys.push(token.value); pattern += `((?:${BASE_PARAM_PATTERN})${onlyHasWildCard ? '?' : ''}(?:/(?:${BASE_PARAM_PATTERN}))*)`; @@ -215,16 +216,15 @@ export function createPathParser

(pathname: string, option: ParserOp } path += params[token.value]; break; - case TokenType.WildCard: - { - const wildCard = params['*']; - if (wildCard instanceof Array) { - path += wildCard.join('/'); - } else { - path += wildCard; - } + case TokenType.WildCard: { + const wildCard = params['*']; + if (wildCard instanceof Array) { + path += wildCard.join('/'); + } else { + path += wildCard; } break; + } case TokenType.Delimiter: path += token.value; break;