UNPKG

5.2 kBJavaScriptView Raw
1import { parsePath, parseQueryString } from './path-utils';
2const isAbsolute = (pathname) => {
3 return pathname.charAt(0) === '/';
4};
5export const createKey = (keyLength) => {
6 return Math.random().toString(36).substr(2, keyLength);
7};
8// About 1.5x faster than the two-arg version of Array#splice()
9const spliceOne = (list, index) => {
10 for (let i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) {
11 list[i] = list[k];
12 }
13 list.pop();
14};
15// This implementation is based heavily on node's url.parse
16export const resolvePathname = (to, from = '') => {
17 let fromParts = from && from.split('/') || [];
18 let hasTrailingSlash;
19 let up = 0;
20 const toParts = to && to.split('/') || [];
21 const isToAbs = to && isAbsolute(to);
22 const isFromAbs = from && isAbsolute(from);
23 const mustEndAbs = isToAbs || isFromAbs;
24 if (to && isAbsolute(to)) {
25 // to is absolute
26 fromParts = toParts;
27 }
28 else if (toParts.length) {
29 // to is relative, drop the filename
30 fromParts.pop();
31 fromParts = fromParts.concat(toParts);
32 }
33 if (!fromParts.length) {
34 return '/';
35 }
36 if (fromParts.length) {
37 const last = fromParts[fromParts.length - 1];
38 hasTrailingSlash = (last === '.' || last === '..' || last === '');
39 }
40 else {
41 hasTrailingSlash = false;
42 }
43 for (let i = fromParts.length; i >= 0; i--) {
44 const part = fromParts[i];
45 if (part === '.') {
46 spliceOne(fromParts, i);
47 }
48 else if (part === '..') {
49 spliceOne(fromParts, i);
50 up++;
51 }
52 else if (up) {
53 spliceOne(fromParts, i);
54 up--;
55 }
56 }
57 if (!mustEndAbs) {
58 for (; up--; up) {
59 fromParts.unshift('..');
60 }
61 }
62 if (mustEndAbs && fromParts[0] !== '' && (!fromParts[0] || !isAbsolute(fromParts[0]))) {
63 fromParts.unshift('');
64 }
65 let result = fromParts.join('/');
66 if (hasTrailingSlash && result.substr(-1) !== '/') {
67 result += '/';
68 }
69 return result;
70};
71export const valueEqual = (a, b) => {
72 if (a === b) {
73 return true;
74 }
75 if (a == null || b == null) {
76 return false;
77 }
78 if (Array.isArray(a)) {
79 return Array.isArray(b) && a.length === b.length && a.every((item, index) => {
80 return valueEqual(item, b[index]);
81 });
82 }
83 const aType = typeof a;
84 const bType = typeof b;
85 if (aType !== bType) {
86 return false;
87 }
88 if (aType === 'object') {
89 const aValue = a.valueOf();
90 const bValue = b.valueOf();
91 if (aValue !== a || bValue !== b) {
92 return valueEqual(aValue, bValue);
93 }
94 const aKeys = Object.keys(a);
95 const bKeys = Object.keys(b);
96 if (aKeys.length !== bKeys.length) {
97 return false;
98 }
99 return aKeys.every((key) => {
100 return valueEqual(a[key], b[key]);
101 });
102 }
103 return false;
104};
105export const locationsAreEqual = (a, b) => {
106 return a.pathname === b.pathname &&
107 a.search === b.search &&
108 a.hash === b.hash &&
109 a.key === b.key &&
110 valueEqual(a.state, b.state);
111};
112export const createLocation = (path, state, key, currentLocation) => {
113 let location;
114 if (typeof path === 'string') {
115 // Two-arg form: push(path, state)
116 location = parsePath(path);
117 if (state !== undefined) {
118 location.state = state;
119 }
120 }
121 else {
122 // One-arg form: push(location)
123 location = Object.assign({ pathname: '' }, path);
124 if (location.search && location.search.charAt(0) !== '?') {
125 location.search = '?' + location.search;
126 }
127 if (location.hash && location.hash.charAt(0) !== '#') {
128 location.hash = '#' + location.hash;
129 }
130 if (state !== undefined && location.state === undefined) {
131 location.state = state;
132 }
133 }
134 try {
135 location.pathname = decodeURI(location.pathname);
136 }
137 catch (e) {
138 if (e instanceof URIError) {
139 throw new URIError('Pathname "' + location.pathname + '" could not be decoded. ' +
140 'This is likely caused by an invalid percent-encoding.');
141 }
142 else {
143 throw e;
144 }
145 }
146 location.key = key;
147 if (currentLocation) {
148 // Resolve incomplete/relative pathname relative to current location.
149 if (!location.pathname) {
150 location.pathname = currentLocation.pathname;
151 }
152 else if (location.pathname.charAt(0) !== '/') {
153 location.pathname = resolvePathname(location.pathname, currentLocation.pathname);
154 }
155 }
156 else {
157 // When there is no prior location and pathname is empty, set it to /
158 if (!location.pathname) {
159 location.pathname = '/';
160 }
161 }
162 location.query = parseQueryString(location.search || '');
163 return location;
164};