kdu-router 3.0.1 → 3.0.7
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/README.md +0 -2
- package/dist/kdu-router.common.js +117 -68
- package/dist/kdu-router.esm.browser.js +2632 -0
- package/dist/kdu-router.esm.browser.min.js +11 -0
- package/dist/kdu-router.esm.js +117 -68
- package/dist/kdu-router.js +117 -68
- package/dist/kdu-router.min.js +8 -3
- package/package.json +43 -26
- package/src/create-matcher.js +200 -0
- package/src/create-route-map.js +171 -0
- package/src/index.js +247 -0
- package/src/install.js +52 -0
- package/types/kdu.d.ts +3 -3
- package/types/router.d.ts +9 -8
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kdu-router",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.7",
|
|
4
4
|
"description": "Official router for Kdu.js 2",
|
|
5
5
|
"author": "NKDuy",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/kdu-router.common.js",
|
|
8
8
|
"module": "dist/kdu-router.esm.js",
|
|
9
9
|
"unpkg": "dist/kdu-router.js",
|
|
10
|
+
"jsdelivr": "dist/kdu-router.js",
|
|
11
|
+
"sideEffects": false,
|
|
10
12
|
"repository": {
|
|
11
13
|
"type": "git",
|
|
12
14
|
"url": "https://github.com/kdujs/kdu-router.git"
|
|
13
15
|
},
|
|
14
16
|
"typings": "types/index.d.ts",
|
|
15
17
|
"files": [
|
|
18
|
+
"src/*.js",
|
|
16
19
|
"dist/*.js",
|
|
17
20
|
"types/*.d.ts"
|
|
18
21
|
],
|
|
@@ -25,40 +28,54 @@
|
|
|
25
28
|
"dev:dist": "rollup -wm -c build/rollup.dev.config.js",
|
|
26
29
|
"build": "node build/build.js",
|
|
27
30
|
"lint": "eslint src",
|
|
28
|
-
"release": "bash
|
|
31
|
+
"release": "bash scripts/release.sh"
|
|
32
|
+
},
|
|
33
|
+
"gitHooks": {
|
|
34
|
+
"commit-msg": "node scripts/verifyCommitMsg.js"
|
|
29
35
|
},
|
|
30
36
|
"devDependencies": {
|
|
37
|
+
"axios": "^0.19.0",
|
|
31
38
|
"babel-core": "^6.24.1",
|
|
32
|
-
"babel-eslint": "^
|
|
33
|
-
"babel-loader": "^7.
|
|
39
|
+
"babel-eslint": "^10.0.2",
|
|
40
|
+
"babel-loader": "^7.1.3",
|
|
34
41
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
|
35
|
-
"babel-preset-
|
|
42
|
+
"babel-preset-env": "^1.6.1",
|
|
36
43
|
"babel-preset-flow-kdu": "^1.0.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"browserstack-local": "^1.4.0",
|
|
45
|
+
"buble": "^0.19.8",
|
|
46
|
+
"chromedriver": "^74.0.0",
|
|
47
|
+
"conventional-changelog-cli": "^2.0.11",
|
|
48
|
+
"cross-spawn": "^6.0.5",
|
|
49
|
+
"css-loader": "^2.1.1",
|
|
50
|
+
"dotenv": "^8.0.0",
|
|
51
|
+
"es6-promise": "^4.2.8",
|
|
52
|
+
"eslint": "^4.19.1",
|
|
53
|
+
"eslint-plugin-flowtype": "^2.46.1",
|
|
54
|
+
"eslint-plugin-kdu-libs": "^2.1.0",
|
|
55
|
+
"express": "^4.17.1",
|
|
46
56
|
"express-urlrewrite": "^1.2.0",
|
|
47
|
-
"flow-bin": "^0.
|
|
57
|
+
"flow-bin": "^0.66.0",
|
|
58
|
+
"geckodriver": "^1.16.2",
|
|
48
59
|
"jasmine": "2.8.0",
|
|
49
|
-
"kdu": "^2.5.
|
|
60
|
+
"kdu": "^2.5.24",
|
|
61
|
+
"lint-staged": "^8.2.0",
|
|
50
62
|
"path-to-regexp": "^1.7.0",
|
|
51
|
-
"rollup": "^0.
|
|
52
|
-
"rollup-plugin-buble": "^0.
|
|
53
|
-
"rollup-plugin-commonjs": "^
|
|
63
|
+
"rollup": "^0.56.4",
|
|
64
|
+
"rollup-plugin-buble": "^0.19.8",
|
|
65
|
+
"rollup-plugin-commonjs": "^9.0.0",
|
|
54
66
|
"rollup-plugin-flow-no-whitespace": "^1.0.0",
|
|
55
|
-
"rollup-plugin-node-resolve": "^3.0.
|
|
67
|
+
"rollup-plugin-node-resolve": "^3.0.3",
|
|
56
68
|
"rollup-plugin-replace": "^2.0.0",
|
|
57
69
|
"rollup-watch": "^4.0.0",
|
|
58
|
-
"selenium-server": "^
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"webpack": "^
|
|
62
|
-
"webpack-dev-middleware": "^
|
|
63
|
-
|
|
70
|
+
"selenium-server": "^3.141.59",
|
|
71
|
+
"terser": "^4.0.2",
|
|
72
|
+
"typescript": "^3.5.2",
|
|
73
|
+
"webpack": "^4.35.2",
|
|
74
|
+
"webpack-dev-middleware": "^3.7.0",
|
|
75
|
+
"yorkie": "^2.0.0"
|
|
76
|
+
},
|
|
77
|
+
"bugs": {
|
|
78
|
+
"url": "https://github.com/kdujs/kdu-router/issues"
|
|
79
|
+
},
|
|
80
|
+
"homepage": "https://github.com/kdujs/kdu-router#readme"
|
|
64
81
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import type KduRouter from './index'
|
|
4
|
+
import { resolvePath } from './util/path'
|
|
5
|
+
import { assert, warn } from './util/warn'
|
|
6
|
+
import { createRoute } from './util/route'
|
|
7
|
+
import { fillParams } from './util/params'
|
|
8
|
+
import { createRouteMap } from './create-route-map'
|
|
9
|
+
import { normalizeLocation } from './util/location'
|
|
10
|
+
|
|
11
|
+
export type Matcher = {
|
|
12
|
+
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
|
|
13
|
+
addRoutes: (routes: Array<RouteConfig>) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function createMatcher (
|
|
17
|
+
routes: Array<RouteConfig>,
|
|
18
|
+
router: KduRouter
|
|
19
|
+
): Matcher {
|
|
20
|
+
const { pathList, pathMap, nameMap } = createRouteMap(routes)
|
|
21
|
+
|
|
22
|
+
function addRoutes (routes) {
|
|
23
|
+
createRouteMap(routes, pathList, pathMap, nameMap)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function match (
|
|
27
|
+
raw: RawLocation,
|
|
28
|
+
currentRoute?: Route,
|
|
29
|
+
redirectedFrom?: Location
|
|
30
|
+
): Route {
|
|
31
|
+
const location = normalizeLocation(raw, currentRoute, false, router)
|
|
32
|
+
const { name } = location
|
|
33
|
+
|
|
34
|
+
if (name) {
|
|
35
|
+
const record = nameMap[name]
|
|
36
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
37
|
+
warn(record, `Route with name '${name}' does not exist`)
|
|
38
|
+
}
|
|
39
|
+
if (!record) return _createRoute(null, location)
|
|
40
|
+
const paramNames = record.regex.keys
|
|
41
|
+
.filter(key => !key.optional)
|
|
42
|
+
.map(key => key.name)
|
|
43
|
+
|
|
44
|
+
if (typeof location.params !== 'object') {
|
|
45
|
+
location.params = {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (currentRoute && typeof currentRoute.params === 'object') {
|
|
49
|
+
for (const key in currentRoute.params) {
|
|
50
|
+
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
|
|
51
|
+
location.params[key] = currentRoute.params[key]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
location.path = fillParams(record.path, location.params, `named route "${name}"`)
|
|
57
|
+
return _createRoute(record, location, redirectedFrom)
|
|
58
|
+
} else if (location.path) {
|
|
59
|
+
location.params = {}
|
|
60
|
+
for (let i = 0; i < pathList.length; i++) {
|
|
61
|
+
const path = pathList[i]
|
|
62
|
+
const record = pathMap[path]
|
|
63
|
+
if (matchRoute(record.regex, location.path, location.params)) {
|
|
64
|
+
return _createRoute(record, location, redirectedFrom)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// no match
|
|
69
|
+
return _createRoute(null, location)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function redirect (
|
|
73
|
+
record: RouteRecord,
|
|
74
|
+
location: Location
|
|
75
|
+
): Route {
|
|
76
|
+
const originalRedirect = record.redirect
|
|
77
|
+
let redirect = typeof originalRedirect === 'function'
|
|
78
|
+
? originalRedirect(createRoute(record, location, null, router))
|
|
79
|
+
: originalRedirect
|
|
80
|
+
|
|
81
|
+
if (typeof redirect === 'string') {
|
|
82
|
+
redirect = { path: redirect }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!redirect || typeof redirect !== 'object') {
|
|
86
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
87
|
+
warn(
|
|
88
|
+
false, `invalid redirect option: ${JSON.stringify(redirect)}`
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
return _createRoute(null, location)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const re: Object = redirect
|
|
95
|
+
const { name, path } = re
|
|
96
|
+
let { query, hash, params } = location
|
|
97
|
+
query = re.hasOwnProperty('query') ? re.query : query
|
|
98
|
+
hash = re.hasOwnProperty('hash') ? re.hash : hash
|
|
99
|
+
params = re.hasOwnProperty('params') ? re.params : params
|
|
100
|
+
|
|
101
|
+
if (name) {
|
|
102
|
+
// resolved named direct
|
|
103
|
+
const targetRecord = nameMap[name]
|
|
104
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
105
|
+
assert(targetRecord, `redirect failed: named route "${name}" not found.`)
|
|
106
|
+
}
|
|
107
|
+
return match({
|
|
108
|
+
_normalized: true,
|
|
109
|
+
name,
|
|
110
|
+
query,
|
|
111
|
+
hash,
|
|
112
|
+
params
|
|
113
|
+
}, undefined, location)
|
|
114
|
+
} else if (path) {
|
|
115
|
+
// 1. resolve relative redirect
|
|
116
|
+
const rawPath = resolveRecordPath(path, record)
|
|
117
|
+
// 2. resolve params
|
|
118
|
+
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
|
|
119
|
+
// 3. rematch with existing query and hash
|
|
120
|
+
return match({
|
|
121
|
+
_normalized: true,
|
|
122
|
+
path: resolvedPath,
|
|
123
|
+
query,
|
|
124
|
+
hash
|
|
125
|
+
}, undefined, location)
|
|
126
|
+
} else {
|
|
127
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
128
|
+
warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
|
|
129
|
+
}
|
|
130
|
+
return _createRoute(null, location)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function alias (
|
|
135
|
+
record: RouteRecord,
|
|
136
|
+
location: Location,
|
|
137
|
+
matchAs: string
|
|
138
|
+
): Route {
|
|
139
|
+
const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
|
|
140
|
+
const aliasedMatch = match({
|
|
141
|
+
_normalized: true,
|
|
142
|
+
path: aliasedPath
|
|
143
|
+
})
|
|
144
|
+
if (aliasedMatch) {
|
|
145
|
+
const matched = aliasedMatch.matched
|
|
146
|
+
const aliasedRecord = matched[matched.length - 1]
|
|
147
|
+
location.params = aliasedMatch.params
|
|
148
|
+
return _createRoute(aliasedRecord, location)
|
|
149
|
+
}
|
|
150
|
+
return _createRoute(null, location)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _createRoute (
|
|
154
|
+
record: ?RouteRecord,
|
|
155
|
+
location: Location,
|
|
156
|
+
redirectedFrom?: Location
|
|
157
|
+
): Route {
|
|
158
|
+
if (record && record.redirect) {
|
|
159
|
+
return redirect(record, redirectedFrom || location)
|
|
160
|
+
}
|
|
161
|
+
if (record && record.matchAs) {
|
|
162
|
+
return alias(record, location, record.matchAs)
|
|
163
|
+
}
|
|
164
|
+
return createRoute(record, location, redirectedFrom, router)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
match,
|
|
169
|
+
addRoutes
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function matchRoute (
|
|
174
|
+
regex: RouteRegExp,
|
|
175
|
+
path: string,
|
|
176
|
+
params: Object
|
|
177
|
+
): boolean {
|
|
178
|
+
const m = path.match(regex)
|
|
179
|
+
|
|
180
|
+
if (!m) {
|
|
181
|
+
return false
|
|
182
|
+
} else if (!params) {
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (let i = 1, len = m.length; i < len; ++i) {
|
|
187
|
+
const key = regex.keys[i - 1]
|
|
188
|
+
const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
|
|
189
|
+
if (key) {
|
|
190
|
+
// Fix #1994: using * with props: true generates a param named 0
|
|
191
|
+
params[key.name || 'pathMatch'] = val
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return true
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function resolveRecordPath (path: string, record: RouteRecord): string {
|
|
199
|
+
return resolvePath(path, record.parent ? record.parent.path : '/', true)
|
|
200
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import Regexp from 'path-to-regexp'
|
|
4
|
+
import { cleanPath } from './util/path'
|
|
5
|
+
import { assert, warn } from './util/warn'
|
|
6
|
+
|
|
7
|
+
export function createRouteMap (
|
|
8
|
+
routes: Array<RouteConfig>,
|
|
9
|
+
oldPathList?: Array<string>,
|
|
10
|
+
oldPathMap?: Dictionary<RouteRecord>,
|
|
11
|
+
oldNameMap?: Dictionary<RouteRecord>
|
|
12
|
+
): {
|
|
13
|
+
pathList: Array<string>;
|
|
14
|
+
pathMap: Dictionary<RouteRecord>;
|
|
15
|
+
nameMap: Dictionary<RouteRecord>;
|
|
16
|
+
} {
|
|
17
|
+
// the path list is used to control path matching priority
|
|
18
|
+
const pathList: Array<string> = oldPathList || []
|
|
19
|
+
// $flow-disable-line
|
|
20
|
+
const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
|
|
21
|
+
// $flow-disable-line
|
|
22
|
+
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
|
|
23
|
+
|
|
24
|
+
routes.forEach(route => {
|
|
25
|
+
addRouteRecord(pathList, pathMap, nameMap, route)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// ensure wildcard routes are always at the end
|
|
29
|
+
for (let i = 0, l = pathList.length; i < l; i++) {
|
|
30
|
+
if (pathList[i] === '*') {
|
|
31
|
+
pathList.push(pathList.splice(i, 1)[0])
|
|
32
|
+
l--
|
|
33
|
+
i--
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
pathList,
|
|
39
|
+
pathMap,
|
|
40
|
+
nameMap
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addRouteRecord (
|
|
45
|
+
pathList: Array<string>,
|
|
46
|
+
pathMap: Dictionary<RouteRecord>,
|
|
47
|
+
nameMap: Dictionary<RouteRecord>,
|
|
48
|
+
route: RouteConfig,
|
|
49
|
+
parent?: RouteRecord,
|
|
50
|
+
matchAs?: string
|
|
51
|
+
) {
|
|
52
|
+
const { path, name } = route
|
|
53
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
54
|
+
assert(path != null, `"path" is required in a route configuration.`)
|
|
55
|
+
assert(
|
|
56
|
+
typeof route.component !== 'string',
|
|
57
|
+
`route config "component" for path: ${String(path || name)} cannot be a ` +
|
|
58
|
+
`string id. Use an actual component instead.`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}
|
|
63
|
+
const normalizedPath = normalizePath(
|
|
64
|
+
path,
|
|
65
|
+
parent,
|
|
66
|
+
pathToRegexpOptions.strict
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if (typeof route.caseSensitive === 'boolean') {
|
|
70
|
+
pathToRegexpOptions.sensitive = route.caseSensitive
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const record: RouteRecord = {
|
|
74
|
+
path: normalizedPath,
|
|
75
|
+
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
|
|
76
|
+
components: route.components || { default: route.component },
|
|
77
|
+
instances: {},
|
|
78
|
+
name,
|
|
79
|
+
parent,
|
|
80
|
+
matchAs,
|
|
81
|
+
redirect: route.redirect,
|
|
82
|
+
beforeEnter: route.beforeEnter,
|
|
83
|
+
meta: route.meta || {},
|
|
84
|
+
props: route.props == null
|
|
85
|
+
? {}
|
|
86
|
+
: route.components
|
|
87
|
+
? route.props
|
|
88
|
+
: { default: route.props }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (route.children) {
|
|
92
|
+
// Warn if route is named, does not redirect and has a default child route.
|
|
93
|
+
// If users navigate to this route by name, the default child will
|
|
94
|
+
// not be rendered (GH Issue #629)
|
|
95
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
96
|
+
if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) {
|
|
97
|
+
warn(
|
|
98
|
+
false,
|
|
99
|
+
`Named Route '${route.name}' has a default child route. ` +
|
|
100
|
+
`When navigating to this named route (:to="{name: '${route.name}'"), ` +
|
|
101
|
+
`the default child route will not be rendered. Remove the name from ` +
|
|
102
|
+
`this route and use the name of the default child route for named ` +
|
|
103
|
+
`links instead.`
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
route.children.forEach(child => {
|
|
108
|
+
const childMatchAs = matchAs
|
|
109
|
+
? cleanPath(`${matchAs}/${child.path}`)
|
|
110
|
+
: undefined
|
|
111
|
+
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (route.alias !== undefined) {
|
|
116
|
+
const aliases = Array.isArray(route.alias)
|
|
117
|
+
? route.alias
|
|
118
|
+
: [route.alias]
|
|
119
|
+
|
|
120
|
+
aliases.forEach(alias => {
|
|
121
|
+
const aliasRoute = {
|
|
122
|
+
path: alias,
|
|
123
|
+
children: route.children
|
|
124
|
+
}
|
|
125
|
+
addRouteRecord(
|
|
126
|
+
pathList,
|
|
127
|
+
pathMap,
|
|
128
|
+
nameMap,
|
|
129
|
+
aliasRoute,
|
|
130
|
+
parent,
|
|
131
|
+
record.path || '/' // matchAs
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!pathMap[record.path]) {
|
|
137
|
+
pathList.push(record.path)
|
|
138
|
+
pathMap[record.path] = record
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (name) {
|
|
142
|
+
if (!nameMap[name]) {
|
|
143
|
+
nameMap[name] = record
|
|
144
|
+
} else if (process.env.NODE_ENV !== 'production' && !matchAs) {
|
|
145
|
+
warn(
|
|
146
|
+
false,
|
|
147
|
+
`Duplicate named routes definition: ` +
|
|
148
|
+
`{ name: "${name}", path: "${record.path}" }`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp {
|
|
155
|
+
const regex = Regexp(path, [], pathToRegexpOptions)
|
|
156
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
157
|
+
const keys: any = Object.create(null)
|
|
158
|
+
regex.keys.forEach(key => {
|
|
159
|
+
warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`)
|
|
160
|
+
keys[key.name] = true
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
return regex
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizePath (path: string, parent?: RouteRecord, strict?: boolean): string {
|
|
167
|
+
if (!strict) path = path.replace(/\/$/, '')
|
|
168
|
+
if (path[0] === '/') return path
|
|
169
|
+
if (parent == null) return path
|
|
170
|
+
return cleanPath(`${parent.path}/${path}`)
|
|
171
|
+
}
|