UNPKG

7.25 kBJavaScriptView Raw
1const hasBasename = (path, prefix) => {
2 return (new RegExp('^' + prefix + '(\\/|\\?|#|$)', 'i')).test(path);
3};
4const stripBasename = (path, prefix) => {
5 return hasBasename(path, prefix) ? path.substr(prefix.length) : path;
6};
7const stripTrailingSlash = (path) => {
8 return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path;
9};
10const addLeadingSlash = (path) => {
11 return path.charAt(0) === '/' ? path : '/' + path;
12};
13const stripLeadingSlash = (path) => {
14 return path.charAt(0) === '/' ? path.substr(1) : path;
15};
16const parsePath = (path) => {
17 let pathname = path || '/';
18 let search = '';
19 let hash = '';
20 const hashIndex = pathname.indexOf('#');
21 if (hashIndex !== -1) {
22 hash = pathname.substr(hashIndex);
23 pathname = pathname.substr(0, hashIndex);
24 }
25 const searchIndex = pathname.indexOf('?');
26 if (searchIndex !== -1) {
27 search = pathname.substr(searchIndex);
28 pathname = pathname.substr(0, searchIndex);
29 }
30 return {
31 pathname,
32 search: search === '?' ? '' : search,
33 hash: hash === '#' ? '' : hash,
34 query: {},
35 key: ''
36 };
37};
38const createPath = (location) => {
39 const { pathname, search, hash } = location;
40 let path = pathname || '/';
41 if (search && search !== '?') {
42 path += (search.charAt(0) === '?' ? search : `?${search}`);
43 }
44 if (hash && hash !== '#') {
45 path += (hash.charAt(0) === '#' ? hash : `#${hash}`);
46 }
47 return path;
48};
49const parseQueryString = (query) => {
50 if (!query) {
51 return {};
52 }
53 return (/^[?#]/.test(query) ? query.slice(1) : query)
54 .split('&')
55 .reduce((params, param) => {
56 let [key, value] = param.split('=');
57 params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
58 return params;
59 }, {});
60};
61
62const isAbsolute = (pathname) => {
63 return pathname.charAt(0) === '/';
64};
65const createKey = (keyLength) => {
66 return Math.random().toString(36).substr(2, keyLength);
67};
68// About 1.5x faster than the two-arg version of Array#splice()
69const spliceOne = (list, index) => {
70 for (let i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) {
71 list[i] = list[k];
72 }
73 list.pop();
74};
75// This implementation is based heavily on node's url.parse
76const resolvePathname = (to, from = '') => {
77 let fromParts = from && from.split('/') || [];
78 let hasTrailingSlash;
79 let up = 0;
80 const toParts = to && to.split('/') || [];
81 const isToAbs = to && isAbsolute(to);
82 const isFromAbs = from && isAbsolute(from);
83 const mustEndAbs = isToAbs || isFromAbs;
84 if (to && isAbsolute(to)) {
85 // to is absolute
86 fromParts = toParts;
87 }
88 else if (toParts.length) {
89 // to is relative, drop the filename
90 fromParts.pop();
91 fromParts = fromParts.concat(toParts);
92 }
93 if (!fromParts.length) {
94 return '/';
95 }
96 if (fromParts.length) {
97 const last = fromParts[fromParts.length - 1];
98 hasTrailingSlash = (last === '.' || last === '..' || last === '');
99 }
100 else {
101 hasTrailingSlash = false;
102 }
103 for (let i = fromParts.length; i >= 0; i--) {
104 const part = fromParts[i];
105 if (part === '.') {
106 spliceOne(fromParts, i);
107 }
108 else if (part === '..') {
109 spliceOne(fromParts, i);
110 up++;
111 }
112 else if (up) {
113 spliceOne(fromParts, i);
114 up--;
115 }
116 }
117 if (!mustEndAbs) {
118 for (; up--; up) {
119 fromParts.unshift('..');
120 }
121 }
122 if (mustEndAbs && fromParts[0] !== '' && (!fromParts[0] || !isAbsolute(fromParts[0]))) {
123 fromParts.unshift('');
124 }
125 let result = fromParts.join('/');
126 if (hasTrailingSlash && result.substr(-1) !== '/') {
127 result += '/';
128 }
129 return result;
130};
131const valueEqual = (a, b) => {
132 if (a === b) {
133 return true;
134 }
135 if (a == null || b == null) {
136 return false;
137 }
138 if (Array.isArray(a)) {
139 return Array.isArray(b) && a.length === b.length && a.every((item, index) => {
140 return valueEqual(item, b[index]);
141 });
142 }
143 const aType = typeof a;
144 const bType = typeof b;
145 if (aType !== bType) {
146 return false;
147 }
148 if (aType === 'object') {
149 const aValue = a.valueOf();
150 const bValue = b.valueOf();
151 if (aValue !== a || bValue !== b) {
152 return valueEqual(aValue, bValue);
153 }
154 const aKeys = Object.keys(a);
155 const bKeys = Object.keys(b);
156 if (aKeys.length !== bKeys.length) {
157 return false;
158 }
159 return aKeys.every((key) => {
160 return valueEqual(a[key], b[key]);
161 });
162 }
163 return false;
164};
165const locationsAreEqual = (a, b) => {
166 return a.pathname === b.pathname &&
167 a.search === b.search &&
168 a.hash === b.hash &&
169 a.key === b.key &&
170 valueEqual(a.state, b.state);
171};
172const createLocation = (path, state, key, currentLocation) => {
173 let location;
174 if (typeof path === 'string') {
175 // Two-arg form: push(path, state)
176 location = parsePath(path);
177 if (state !== undefined) {
178 location.state = state;
179 }
180 }
181 else {
182 // One-arg form: push(location)
183 location = Object.assign({ pathname: '' }, path);
184 if (location.search && location.search.charAt(0) !== '?') {
185 location.search = '?' + location.search;
186 }
187 if (location.hash && location.hash.charAt(0) !== '#') {
188 location.hash = '#' + location.hash;
189 }
190 if (state !== undefined && location.state === undefined) {
191 location.state = state;
192 }
193 }
194 try {
195 location.pathname = decodeURI(location.pathname);
196 }
197 catch (e) {
198 if (e instanceof URIError) {
199 throw new URIError('Pathname "' + location.pathname + '" could not be decoded. ' +
200 'This is likely caused by an invalid percent-encoding.');
201 }
202 else {
203 throw e;
204 }
205 }
206 location.key = key;
207 if (currentLocation) {
208 // Resolve incomplete/relative pathname relative to current location.
209 if (!location.pathname) {
210 location.pathname = currentLocation.pathname;
211 }
212 else if (location.pathname.charAt(0) !== '/') {
213 location.pathname = resolvePathname(location.pathname, currentLocation.pathname);
214 }
215 }
216 else {
217 // When there is no prior location and pathname is empty, set it to /
218 if (!location.pathname) {
219 location.pathname = '/';
220 }
221 }
222 location.query = parseQueryString(location.search || '');
223 return location;
224};
225
226export { addLeadingSlash as a, stripBasename as b, createLocation as c, createKey as d, createPath as e, stripLeadingSlash as f, hasBasename as h, locationsAreEqual as l, stripTrailingSlash as s, valueEqual as v };