@shuvi/router 2.0.0-dev.30 → 2.0.0-dev.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createRoutesFromArray } from './createRoutesFromArray';
2
2
  export { matchPathname, matchStringify } from './matchPathname';
3
- export { matchRoutes, IRouteBaseObject, rankRouteBranches } from './matchRoutes';
3
+ export { matchRoutes, IRouteBaseObject, rankRouteBranches, IMatchRoutesOptions, getTrailingSlashRedirectPath } from './matchRoutes';
4
4
  export { pathToString, parseQuery, resolvePath, joinPaths, createLocation } from './utils';
5
5
  export * from './types';
6
6
  export * from './history';
package/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createRoutesFromArray } from './createRoutesFromArray';
2
2
  export { matchPathname, matchStringify } from './matchPathname';
3
- export { matchRoutes, rankRouteBranches } from './matchRoutes';
3
+ export { matchRoutes, rankRouteBranches, getTrailingSlashRedirectPath } from './matchRoutes';
4
4
  export { pathToString, parseQuery, resolvePath, joinPaths, createLocation } from './utils';
5
5
  export * from './types';
6
6
  export * from './history';
@@ -27,9 +27,9 @@ export function matchPathname(pattern, pathname) {
27
27
  if (typeof pattern === 'string') {
28
28
  pattern = { path: pattern };
29
29
  }
30
- const { path, caseSensitive = false, end = true } = pattern;
30
+ const { path, caseSensitive = false, end = true, strict = false } = pattern;
31
31
  const pathParser = tokensToParser(tokenizePath(path), {
32
- strict: false,
32
+ strict,
33
33
  end,
34
34
  sensitive: caseSensitive
35
35
  });
@@ -3,4 +3,16 @@ export interface IRouteBaseObject<Element = any> extends Omit<IRouteRecord<Eleme
3
3
  children?: IRouteBaseObject<Element>[];
4
4
  }
5
5
  export declare function rankRouteBranches<T extends [string, ...any[]]>(branches: T[]): T[];
6
- export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string): IRouteMatch<T>[] | null;
6
+ export interface IMatchRoutesOptions {
7
+ strict?: boolean;
8
+ }
9
+ export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string, options?: IMatchRoutesOptions): IRouteMatch<T>[] | null;
10
+ /**
11
+ * When strict trailing-slash routing is enabled, a pathname that does not
12
+ * match any route may be a non-canonical variant (e.g. `/foo/` for a route
13
+ * declared as `/foo`). This computes the canonical alternative by stripping
14
+ * a single trailing slash (or adding one when the route is slash-suffixed) and
15
+ * returns it so the caller can issue a redirect. Returns `null` when the
16
+ * pathname is already canonical (`/` or no trailing slash to strip).
17
+ */
18
+ export declare function getTrailingSlashRedirectPath(pathname: string): string | null;
@@ -2,7 +2,7 @@ import { matchPathname } from './matchPathname';
2
2
  import { joinPaths, normalizeBase, resolvePath, stripBase } from './utils';
3
3
  import { tokensToParser, comparePathParserScore } from './pathParserRanker';
4
4
  import { tokenizePath } from './pathTokenizer';
5
- function matchRouteBranch(branch, pathname) {
5
+ function matchRouteBranch(branch, pathname, strict = false) {
6
6
  let routes = branch[1];
7
7
  let matchedPathname = '/';
8
8
  let matchedParams = {};
@@ -15,7 +15,8 @@ function matchRouteBranch(branch, pathname) {
15
15
  let routeMatch = matchPathname({
16
16
  path: route.path,
17
17
  caseSensitive: route.caseSensitive,
18
- end: i === routes.length - 1
18
+ end: i === routes.length - 1,
19
+ strict
19
20
  }, remainingPathname);
20
21
  if (!routeMatch)
21
22
  return null;
@@ -70,7 +71,8 @@ function flattenRoutes(routes, branches = [], parentPath = '', parentRoutes = []
70
71
  });
71
72
  return branches;
72
73
  }
73
- export function matchRoutes(routes, location, basename = '') {
74
+ export function matchRoutes(routes, location, basename = '', options = {}) {
75
+ const { strict = false } = options;
74
76
  if (typeof location === 'string') {
75
77
  location = resolvePath(location);
76
78
  }
@@ -90,7 +92,26 @@ export function matchRoutes(routes, location, basename = '') {
90
92
  let matches = null;
91
93
  for (let i = 0; matches == null && i < branches.length; ++i) {
92
94
  // TODO: Match on search, state too?
93
- matches = matchRouteBranch(branches[i], pathname);
95
+ matches = matchRouteBranch(branches[i], pathname, strict);
94
96
  }
95
97
  return matches;
96
98
  }
99
+ /**
100
+ * When strict trailing-slash routing is enabled, a pathname that does not
101
+ * match any route may be a non-canonical variant (e.g. `/foo/` for a route
102
+ * declared as `/foo`). This computes the canonical alternative by stripping
103
+ * a single trailing slash (or adding one when the route is slash-suffixed) and
104
+ * returns it so the caller can issue a redirect. Returns `null` when the
105
+ * pathname is already canonical (`/` or no trailing slash to strip).
106
+ */
107
+ export function getTrailingSlashRedirectPath(pathname) {
108
+ if (pathname === '/' || pathname === '') {
109
+ return null;
110
+ }
111
+ // strip a single trailing slash
112
+ if (pathname.length > 1 && pathname.endsWith('/')) {
113
+ return pathname.slice(0, -1);
114
+ }
115
+ // otherwise the non-canonical variant is the slash-suffixed form
116
+ return pathname + '/';
117
+ }
package/esm/router.d.ts CHANGED
@@ -4,6 +4,13 @@ interface IRouterOptions<RouteRecord extends IPartialRouteRecord> {
4
4
  history: History;
5
5
  routes: RouteRecord[];
6
6
  caseSensitive?: boolean;
7
+ /**
8
+ * When enabled, path matching treats trailing slashes as significant.
9
+ * A request whose pathname does not match any route but differs only by a
10
+ * trailing slash from a route that does match will be redirected (308) to
11
+ * the canonical URL. Defaults to false (legacy loose matching).
12
+ */
13
+ strictTrailingSlash?: boolean;
7
14
  }
8
15
  export declare const createRouter: <RouteRecord extends IRouteRecord>(options: IRouterOptions<RouteRecord>) => IRouter<RouteRecord>;
9
16
  export {};
package/esm/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createDefer } from '@shuvi/utils/defer';
2
- import { matchRoutes } from './matchRoutes';
2
+ import { matchRoutes, getTrailingSlashRedirectPath } from './matchRoutes';
3
3
  import { createRoutesFromArray } from './createRoutesFromArray';
4
4
  import { createEvents, resolvePath } from './utils';
5
5
  import { isError, isFunction } from './utils/error';
@@ -17,7 +17,7 @@ const START = {
17
17
  redirected: false
18
18
  };
19
19
  class Router {
20
- constructor({ history, routes }) {
20
+ constructor({ history, routes, strictTrailingSlash = false }) {
21
21
  this._pending = null;
22
22
  this._cancleHandler = null;
23
23
  this._ready = false;
@@ -73,8 +73,8 @@ class Router {
73
73
  return this._history.resolve(to, from);
74
74
  };
75
75
  this.match = (to) => {
76
- const { _routes: routes } = this;
77
- const matches = matchRoutes(routes, to);
76
+ const { _routes: routes, _strictTrailingSlash: strict } = this;
77
+ const matches = matchRoutes(routes, to, undefined, { strict });
78
78
  return matches || [];
79
79
  };
80
80
  this.replaceRoutes = (routes) => {
@@ -101,6 +101,7 @@ class Router {
101
101
  };
102
102
  this._history = history;
103
103
  this._routes = createRoutesFromArray(routes);
104
+ this._strictTrailingSlash = strictTrailingSlash;
104
105
  this._current = START;
105
106
  this._history.doTransition = this._doTransition.bind(this);
106
107
  }
@@ -119,6 +120,9 @@ class Router {
119
120
  get basename() {
120
121
  return this._history.basename;
121
122
  }
123
+ get strictTrailingSlash() {
124
+ return this._strictTrailingSlash;
125
+ }
122
126
  /*
123
127
  The Full Navigation Resolution Flow for shuvi/router
124
128
  1. Navigation triggered.
@@ -140,6 +144,16 @@ class Router {
140
144
  redirectedFrom: redirectedFrom || nextRoute
141
145
  });
142
146
  }
147
+ // Strict trailing-slash routing: redirect to the canonical URL when the
148
+ // current pathname only differs by a trailing slash from a real route.
149
+ const trailingSlashRedirect = nextRoute.redirect;
150
+ if (trailingSlashRedirect) {
151
+ const transitionMethod = isReplace || isInitialNavigation ? 'replace' : 'push';
152
+ return this._history[transitionMethod](trailingSlashRedirect.path, {
153
+ redirectedFrom: redirectedFrom || nextRoute,
154
+ state: { status: trailingSlashRedirect.status }
155
+ });
156
+ }
143
157
  const routeContext = new Map();
144
158
  const queue = skipGuards
145
159
  ? []
@@ -270,8 +284,21 @@ class Router {
270
284
  const matches = this.match(to);
271
285
  const params = matches.length ? matches[matches.length - 1].params : {};
272
286
  const parsedPath = resolvePath(to);
273
- return Object.assign(Object.assign({ matches,
287
+ const route = Object.assign(Object.assign({ matches,
274
288
  params }, parsedPath), { key: '', state: null });
289
+ // Strict trailing-slash routing: when the pathname does not match but a
290
+ // canonical variant (differing only by a trailing slash) does, surface a
291
+ // redirect so the transition layer can redirect to the canonical URL.
292
+ if (this._strictTrailingSlash && !matches.length) {
293
+ const canonical = getTrailingSlashRedirectPath(parsedPath.pathname || '/');
294
+ if (canonical) {
295
+ const rematched = matchRoutes(this._routes, Object.assign(Object.assign({}, parsedPath), { pathname: canonical }), undefined, { strict: true });
296
+ if (rematched && rematched.length) {
297
+ route.redirect = { path: canonical, status: 308 };
298
+ }
299
+ }
300
+ }
301
+ return route;
275
302
  }
276
303
  }
277
304
  export const createRouter = (options) => {
@@ -44,6 +44,7 @@ export type IPathPattern = string | {
44
44
  path: string;
45
45
  caseSensitive?: boolean;
46
46
  end?: boolean;
47
+ strict?: boolean;
47
48
  };
48
49
  export interface IPathMatch {
49
50
  path: string;
@@ -57,6 +58,16 @@ export interface IRoute<RouteRecord extends IRouteRecord = IRouteRecord> extends
57
58
  matches: IRouteMatch<RouteRecord>[];
58
59
  redirected?: boolean;
59
60
  key: string;
61
+ /**
62
+ * Set when strict trailing-slash routing resolves a canonical URL for the
63
+ * current (non-matching) pathname. Carries the redirect target + status so
64
+ * the transition layer can issue a redirect instead of falling through to
65
+ * the not-found page.
66
+ */
67
+ redirect?: {
68
+ path: string;
69
+ status: number;
70
+ };
60
71
  }
61
72
  export interface IRouter<RouteRecord extends {
62
73
  path: string;
@@ -64,6 +75,7 @@ export interface IRouter<RouteRecord extends {
64
75
  current: IRoute<RouteRecord>;
65
76
  action: History['action'];
66
77
  basename: string;
78
+ strictTrailingSlash: boolean;
67
79
  push(to: PathRecord, state?: any): void;
68
80
  replace(to: PathRecord, state?: any): void;
69
81
  go: History['go'];
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createRoutesFromArray } from './createRoutesFromArray';
2
2
  export { matchPathname, matchStringify } from './matchPathname';
3
- export { matchRoutes, IRouteBaseObject, rankRouteBranches } from './matchRoutes';
3
+ export { matchRoutes, IRouteBaseObject, rankRouteBranches, IMatchRoutesOptions, getTrailingSlashRedirectPath } from './matchRoutes';
4
4
  export { pathToString, parseQuery, resolvePath, joinPaths, createLocation } from './utils';
5
5
  export * from './types';
6
6
  export * from './history';
package/lib/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.createLocation = exports.joinPaths = exports.resolvePath = exports.parseQuery = exports.pathToString = exports.rankRouteBranches = exports.matchRoutes = exports.matchStringify = exports.matchPathname = exports.createRoutesFromArray = void 0;
17
+ exports.createLocation = exports.joinPaths = exports.resolvePath = exports.parseQuery = exports.pathToString = exports.getTrailingSlashRedirectPath = exports.rankRouteBranches = exports.matchRoutes = exports.matchStringify = exports.matchPathname = exports.createRoutesFromArray = void 0;
18
18
  var createRoutesFromArray_1 = require("./createRoutesFromArray");
19
19
  Object.defineProperty(exports, "createRoutesFromArray", { enumerable: true, get: function () { return createRoutesFromArray_1.createRoutesFromArray; } });
20
20
  var matchPathname_1 = require("./matchPathname");
@@ -23,6 +23,7 @@ Object.defineProperty(exports, "matchStringify", { enumerable: true, get: functi
23
23
  var matchRoutes_1 = require("./matchRoutes");
24
24
  Object.defineProperty(exports, "matchRoutes", { enumerable: true, get: function () { return matchRoutes_1.matchRoutes; } });
25
25
  Object.defineProperty(exports, "rankRouteBranches", { enumerable: true, get: function () { return matchRoutes_1.rankRouteBranches; } });
26
+ Object.defineProperty(exports, "getTrailingSlashRedirectPath", { enumerable: true, get: function () { return matchRoutes_1.getTrailingSlashRedirectPath; } });
26
27
  var utils_1 = require("./utils");
27
28
  Object.defineProperty(exports, "pathToString", { enumerable: true, get: function () { return utils_1.pathToString; } });
28
29
  Object.defineProperty(exports, "parseQuery", { enumerable: true, get: function () { return utils_1.parseQuery; } });
@@ -31,9 +31,9 @@ function matchPathname(pattern, pathname) {
31
31
  if (typeof pattern === 'string') {
32
32
  pattern = { path: pattern };
33
33
  }
34
- const { path, caseSensitive = false, end = true } = pattern;
34
+ const { path, caseSensitive = false, end = true, strict = false } = pattern;
35
35
  const pathParser = (0, pathParserRanker_1.tokensToParser)((0, pathTokenizer_1.tokenizePath)(path), {
36
- strict: false,
36
+ strict,
37
37
  end,
38
38
  sensitive: caseSensitive
39
39
  });
@@ -3,4 +3,16 @@ export interface IRouteBaseObject<Element = any> extends Omit<IRouteRecord<Eleme
3
3
  children?: IRouteBaseObject<Element>[];
4
4
  }
5
5
  export declare function rankRouteBranches<T extends [string, ...any[]]>(branches: T[]): T[];
6
- export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string): IRouteMatch<T>[] | null;
6
+ export interface IMatchRoutesOptions {
7
+ strict?: boolean;
8
+ }
9
+ export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string, options?: IMatchRoutesOptions): IRouteMatch<T>[] | null;
10
+ /**
11
+ * When strict trailing-slash routing is enabled, a pathname that does not
12
+ * match any route may be a non-canonical variant (e.g. `/foo/` for a route
13
+ * declared as `/foo`). This computes the canonical alternative by stripping
14
+ * a single trailing slash (or adding one when the route is slash-suffixed) and
15
+ * returns it so the caller can issue a redirect. Returns `null` when the
16
+ * pathname is already canonical (`/` or no trailing slash to strip).
17
+ */
18
+ export declare function getTrailingSlashRedirectPath(pathname: string): string | null;
@@ -2,11 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rankRouteBranches = rankRouteBranches;
4
4
  exports.matchRoutes = matchRoutes;
5
+ exports.getTrailingSlashRedirectPath = getTrailingSlashRedirectPath;
5
6
  const matchPathname_1 = require("./matchPathname");
6
7
  const utils_1 = require("./utils");
7
8
  const pathParserRanker_1 = require("./pathParserRanker");
8
9
  const pathTokenizer_1 = require("./pathTokenizer");
9
- function matchRouteBranch(branch, pathname) {
10
+ function matchRouteBranch(branch, pathname, strict = false) {
10
11
  let routes = branch[1];
11
12
  let matchedPathname = '/';
12
13
  let matchedParams = {};
@@ -19,7 +20,8 @@ function matchRouteBranch(branch, pathname) {
19
20
  let routeMatch = (0, matchPathname_1.matchPathname)({
20
21
  path: route.path,
21
22
  caseSensitive: route.caseSensitive,
22
- end: i === routes.length - 1
23
+ end: i === routes.length - 1,
24
+ strict
23
25
  }, remainingPathname);
24
26
  if (!routeMatch)
25
27
  return null;
@@ -74,7 +76,8 @@ function flattenRoutes(routes, branches = [], parentPath = '', parentRoutes = []
74
76
  });
75
77
  return branches;
76
78
  }
77
- function matchRoutes(routes, location, basename = '') {
79
+ function matchRoutes(routes, location, basename = '', options = {}) {
80
+ const { strict = false } = options;
78
81
  if (typeof location === 'string') {
79
82
  location = (0, utils_1.resolvePath)(location);
80
83
  }
@@ -94,7 +97,26 @@ function matchRoutes(routes, location, basename = '') {
94
97
  let matches = null;
95
98
  for (let i = 0; matches == null && i < branches.length; ++i) {
96
99
  // TODO: Match on search, state too?
97
- matches = matchRouteBranch(branches[i], pathname);
100
+ matches = matchRouteBranch(branches[i], pathname, strict);
98
101
  }
99
102
  return matches;
100
103
  }
104
+ /**
105
+ * When strict trailing-slash routing is enabled, a pathname that does not
106
+ * match any route may be a non-canonical variant (e.g. `/foo/` for a route
107
+ * declared as `/foo`). This computes the canonical alternative by stripping
108
+ * a single trailing slash (or adding one when the route is slash-suffixed) and
109
+ * returns it so the caller can issue a redirect. Returns `null` when the
110
+ * pathname is already canonical (`/` or no trailing slash to strip).
111
+ */
112
+ function getTrailingSlashRedirectPath(pathname) {
113
+ if (pathname === '/' || pathname === '') {
114
+ return null;
115
+ }
116
+ // strip a single trailing slash
117
+ if (pathname.length > 1 && pathname.endsWith('/')) {
118
+ return pathname.slice(0, -1);
119
+ }
120
+ // otherwise the non-canonical variant is the slash-suffixed form
121
+ return pathname + '/';
122
+ }
package/lib/router.d.ts CHANGED
@@ -4,6 +4,13 @@ interface IRouterOptions<RouteRecord extends IPartialRouteRecord> {
4
4
  history: History;
5
5
  routes: RouteRecord[];
6
6
  caseSensitive?: boolean;
7
+ /**
8
+ * When enabled, path matching treats trailing slashes as significant.
9
+ * A request whose pathname does not match any route but differs only by a
10
+ * trailing slash from a route that does match will be redirected (308) to
11
+ * the canonical URL. Defaults to false (legacy loose matching).
12
+ */
13
+ strictTrailingSlash?: boolean;
7
14
  }
8
15
  export declare const createRouter: <RouteRecord extends IRouteRecord>(options: IRouterOptions<RouteRecord>) => IRouter<RouteRecord>;
9
16
  export {};
package/lib/router.js CHANGED
@@ -20,7 +20,7 @@ const START = {
20
20
  redirected: false
21
21
  };
22
22
  class Router {
23
- constructor({ history, routes }) {
23
+ constructor({ history, routes, strictTrailingSlash = false }) {
24
24
  this._pending = null;
25
25
  this._cancleHandler = null;
26
26
  this._ready = false;
@@ -76,8 +76,8 @@ class Router {
76
76
  return this._history.resolve(to, from);
77
77
  };
78
78
  this.match = (to) => {
79
- const { _routes: routes } = this;
80
- const matches = (0, matchRoutes_1.matchRoutes)(routes, to);
79
+ const { _routes: routes, _strictTrailingSlash: strict } = this;
80
+ const matches = (0, matchRoutes_1.matchRoutes)(routes, to, undefined, { strict });
81
81
  return matches || [];
82
82
  };
83
83
  this.replaceRoutes = (routes) => {
@@ -104,6 +104,7 @@ class Router {
104
104
  };
105
105
  this._history = history;
106
106
  this._routes = (0, createRoutesFromArray_1.createRoutesFromArray)(routes);
107
+ this._strictTrailingSlash = strictTrailingSlash;
107
108
  this._current = START;
108
109
  this._history.doTransition = this._doTransition.bind(this);
109
110
  }
@@ -122,6 +123,9 @@ class Router {
122
123
  get basename() {
123
124
  return this._history.basename;
124
125
  }
126
+ get strictTrailingSlash() {
127
+ return this._strictTrailingSlash;
128
+ }
125
129
  /*
126
130
  The Full Navigation Resolution Flow for shuvi/router
127
131
  1. Navigation triggered.
@@ -143,6 +147,16 @@ class Router {
143
147
  redirectedFrom: redirectedFrom || nextRoute
144
148
  });
145
149
  }
150
+ // Strict trailing-slash routing: redirect to the canonical URL when the
151
+ // current pathname only differs by a trailing slash from a real route.
152
+ const trailingSlashRedirect = nextRoute.redirect;
153
+ if (trailingSlashRedirect) {
154
+ const transitionMethod = isReplace || isInitialNavigation ? 'replace' : 'push';
155
+ return this._history[transitionMethod](trailingSlashRedirect.path, {
156
+ redirectedFrom: redirectedFrom || nextRoute,
157
+ state: { status: trailingSlashRedirect.status }
158
+ });
159
+ }
146
160
  const routeContext = new Map();
147
161
  const queue = skipGuards
148
162
  ? []
@@ -273,8 +287,21 @@ class Router {
273
287
  const matches = this.match(to);
274
288
  const params = matches.length ? matches[matches.length - 1].params : {};
275
289
  const parsedPath = (0, utils_1.resolvePath)(to);
276
- return Object.assign(Object.assign({ matches,
290
+ const route = Object.assign(Object.assign({ matches,
277
291
  params }, parsedPath), { key: '', state: null });
292
+ // Strict trailing-slash routing: when the pathname does not match but a
293
+ // canonical variant (differing only by a trailing slash) does, surface a
294
+ // redirect so the transition layer can redirect to the canonical URL.
295
+ if (this._strictTrailingSlash && !matches.length) {
296
+ const canonical = (0, matchRoutes_1.getTrailingSlashRedirectPath)(parsedPath.pathname || '/');
297
+ if (canonical) {
298
+ const rematched = (0, matchRoutes_1.matchRoutes)(this._routes, Object.assign(Object.assign({}, parsedPath), { pathname: canonical }), undefined, { strict: true });
299
+ if (rematched && rematched.length) {
300
+ route.redirect = { path: canonical, status: 308 };
301
+ }
302
+ }
303
+ }
304
+ return route;
278
305
  }
279
306
  }
280
307
  const createRouter = (options) => {
@@ -44,6 +44,7 @@ export type IPathPattern = string | {
44
44
  path: string;
45
45
  caseSensitive?: boolean;
46
46
  end?: boolean;
47
+ strict?: boolean;
47
48
  };
48
49
  export interface IPathMatch {
49
50
  path: string;
@@ -57,6 +58,16 @@ export interface IRoute<RouteRecord extends IRouteRecord = IRouteRecord> extends
57
58
  matches: IRouteMatch<RouteRecord>[];
58
59
  redirected?: boolean;
59
60
  key: string;
61
+ /**
62
+ * Set when strict trailing-slash routing resolves a canonical URL for the
63
+ * current (non-matching) pathname. Carries the redirect target + status so
64
+ * the transition layer can issue a redirect instead of falling through to
65
+ * the not-found page.
66
+ */
67
+ redirect?: {
68
+ path: string;
69
+ status: number;
70
+ };
60
71
  }
61
72
  export interface IRouter<RouteRecord extends {
62
73
  path: string;
@@ -64,6 +75,7 @@ export interface IRouter<RouteRecord extends {
64
75
  current: IRoute<RouteRecord>;
65
76
  action: History['action'];
66
77
  basename: string;
78
+ strictTrailingSlash: boolean;
67
79
  push(to: PathRecord, state?: any): void;
68
80
  replace(to: PathRecord, state?: any): void;
69
81
  go: History['go'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shuvi/router",
3
- "version": "2.0.0-dev.30",
3
+ "version": "2.0.0-dev.32",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/shuvijs/shuvi.git",
@@ -28,7 +28,7 @@
28
28
  "node": ">= 16.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@shuvi/utils": "2.0.0-dev.30",
31
+ "@shuvi/utils": "2.0.0-dev.32",
32
32
  "query-string": "6.13.8"
33
33
  }
34
34
  }