Skip to content

Commit fc897cf

Browse files
committed
Implement router
1 parent aa742af commit fc897cf

File tree

4 files changed

+135
-0
lines changed

4 files changed

+135
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"node-fetch": "^1.7.3",
2727
"node-sass": "^4.9.0",
2828
"parcel-bundler": "1.7.x",
29+
"path-to-regexp": "^2.2.1",
2930
"preact": "^8.2.7",
3031
"prettier": "^1.12.1",
3132
"puppeteer": "^0.13.0",

src/router.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*!
2+
Copyright 2018 Propel http://propel.site/. All rights reserved.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
import pathToRegexp from "path-to-regexp";
17+
import { cloneElement, Component, VNode } from "preact";
18+
19+
let id = 0;
20+
const listeners = new Map<number, () => void>();
21+
const regexCache = new Map<string, RegExp>();
22+
const regexKeysCache = new Map<string, string[]>();
23+
24+
export interface MatchedResult {
25+
[key: string]: string;
26+
}
27+
28+
export function match(pattern: string): false | MatchedResult {
29+
const path = window.location.pathname;
30+
let regex = regexCache.get(pattern);
31+
let keys = regexKeysCache.get(pattern);
32+
if (!regex) {
33+
const mKeys = [];
34+
regex = pathToRegexp(pattern, mKeys);
35+
keys = mKeys.map(x => x.name);
36+
regexCache.set(pattern, regex);
37+
regexKeysCache.set(pattern, keys);
38+
}
39+
const re = regex.exec(path);
40+
if (!re) return false;
41+
const data = {};
42+
for (const i in keys) {
43+
if (!keys[i]) continue;
44+
const key = keys[i];
45+
data[key] = re[1 + Number(i)];
46+
}
47+
return data;
48+
}
49+
50+
function onPopState() {
51+
const mapValues = listeners.values();
52+
for (const cb of mapValues) {
53+
cb();
54+
}
55+
}
56+
57+
window.onpopstate = onPopState;
58+
59+
export function pushState(url) {
60+
window.history.pushState({}, document.title, url);
61+
onPopState();
62+
}
63+
64+
export function back() {
65+
window.history.back();
66+
}
67+
68+
export interface RouterChildProps {
69+
path?: string;
70+
}
71+
72+
export type RouterChild = VNode<RouterChildProps>;
73+
74+
export interface RouterProps {
75+
children?: RouterChild[];
76+
}
77+
78+
export interface RouterState {
79+
active: number | string;
80+
props: MatchedResult;
81+
}
82+
83+
export class Router extends Component<RouterProps, RouterState> {
84+
state = {
85+
active: null,
86+
props: null
87+
};
88+
id: number;
89+
90+
onLocationChange() {
91+
const children: RouterChild[] = this.props.children;
92+
for (const i in children) {
93+
if (!children[i]) continue;
94+
const child = children[i];
95+
const attributes = child.attributes;
96+
if (!attributes || !attributes.path) {
97+
this.setState({ active: i, props: null });
98+
return;
99+
}
100+
const props = match(attributes.path);
101+
if (!props) continue;
102+
this.setState({ active: i, props });
103+
return;
104+
}
105+
this.setState({ active: null, props: null });
106+
}
107+
108+
componentWillMount() {
109+
this.id = ++id;
110+
listeners.set(id, this.onLocationChange.bind(this));
111+
this.onLocationChange();
112+
}
113+
114+
componentWillUnmount() {
115+
listeners.delete(this.id);
116+
}
117+
118+
componentWillReceiveProps() {
119+
this.onLocationChange();
120+
}
121+
122+
render() {
123+
const { active, props } = this.state;
124+
if (active === null) return null;
125+
const activeEl = this.props.children[active];
126+
const newProps = { ...activeEl.attributes, matches: props };
127+
return cloneElement(activeEl, newProps);
128+
}
129+
}

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"preserveConstEnums": true,
2020
"pretty": true,
2121
"sourceMap": true,
22+
"allowSyntheticDefaultImports": true,
2223
"target": "es2017"
2324
},
2425
"exclude": ["build", "deps", "node_modules", "src/testdata/"]

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3960,6 +3960,10 @@ path-parse@^1.0.5:
39603960
version "1.0.5"
39613961
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
39623962

3963+
path-to-regexp@^2.2.1:
3964+
version "2.2.1"
3965+
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
3966+
39633967
path-type@^1.0.0:
39643968
version "1.1.0"
39653969
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"

0 commit comments

Comments
 (0)