├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.html
├── log.md
├── package.json
├── pnpm-lock.yaml
├── src
├── main.ts
└── router.ts
├── tsconfig.json
└── vite.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | js/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | index.html
2 | tsconfig.json
3 | vite.config.ts
4 | demo/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 欧阳鹏
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vanjs-router
2 |
3 | Simple Frontend Routing Component Based on Van.js.
4 |
5 | This is version 2. For version 1 documentation, please [click here](https://github.com/iuroc/vanjs-router/tree/79b190f56846bef9906de886ddf29f6c62b892db).
6 |
7 | ## Features
8 |
9 | - Supports both string and regex matching.
10 | - Supports setting page display delay.
11 | - Supports configuring events for the first route match (`onFirst`) and subsequent route matches (`onLoad`).
12 | - Implemented using TypeScript.
13 | - Simple API.
14 |
15 | ## Quick Start
16 |
17 | ```shell
18 | npm install vanjs-router
19 | ```
20 |
21 | ```html
22 |
23 |
24 |
62 | ```
63 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | VanJS Router V2
8 |
9 |
10 |
11 |
12 |
13 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/log.md:
--------------------------------------------------------------------------------
1 | ## Vanjs Router 的异常情况(2024 年 11 月 4 日)
2 |
3 | Vanjs Router 的基本原理是创建一个 `now` 状态,通过监听 Window 的 `hashchange` 事件来更新 `now` 的值。每个路由内部通过 `van.derive(() => now.val)` 来监听路由的改变,而不是直接使用 `hashchange`。此外,`onLoad` 和 `onFirst` 也是在 `van.derive` 内部被执行的。接下来我们看下面的代码:
4 |
5 | ```ts
6 | const count = van.state(0)
7 |
8 | // 可以监听到 count 值的变化
9 | van.derive(() => {
10 | return count.val * 2
11 | })
12 |
13 | // 可以监听到 count 值的变化
14 | van.derive(() => {
15 | count.val
16 |
17 | setTimeout(() => {
18 | count.val * 2
19 | }, 1000)
20 | })
21 |
22 | // 可以监听到 count 值的变化
23 | van.derive(async () => {
24 | count.val // 此处在同步上下文中保留了一个引用
25 |
26 | await new Promise((resolve) => setTimeout(resolve, 1000))
27 | return count.val * 2 // 该处不属于同步上下文,所以是无效引用,但由于有上面的引用,这个 derive 可以监听
28 | })
29 |
30 | // 无法监听到 count 值的变化
31 | van.derive(async () => {
32 | await new Promise((resolve) => setTimeout(resolve, 1000))
33 | return count.val * 2 // 该处不属于同步上下文,且同步上下文中不存在状态值的引用,因此无法监听。
34 | })
35 |
36 | const func = async () => {
37 | return count.val * 2
38 | }
39 |
40 | van.derive(async () => {
41 | return await func() // 可以监听到,因为 func 的同步上下文也是 derive 回调函数的同步上下文,因此可以被监听。
42 | })
43 | ```
44 |
45 | 可以看到,为了能监听到 `derive` 回调函数中的状态值变化,就必须让状态值位于回调函数的同步上下文中。
46 |
47 | 如果 `onLoad` 和 `onFirst` 的同步上下文中存在某些状态值的引用,当这些值被异步更新时,就会导致与 `onLoad` 和 `onFirst` 处于同一同步上下文的 `now` 值监听函数被触发,进而导致 `onLoad` 和 `onFirst` 被重复触发的 Bug。
48 |
49 | 因为函数的同步上下文会继承外部的同步上下文,而在继承上下文中的多个作用域中的状态值,都会被 `derive` 监听到。我们要解决的是确保 `onLoad` 和 `onFirst` 的同步上下文中存在的状态值引用不会触发外部的 `derive`,从而导致 `onLoad` 和 `onFirst` 被重复触发。
50 |
51 | 我们监听 `now` 的目的是实现对 `hashchange` 监听器的动态更新。只需创建一个全局的 `hashchange` 监听器,各个路由通过 `derive` 来共享 `hashchange` 的动态更新通知。期望 `derive` 的监听效果比 `hashchange` 更好,操作也更加便捷,且具备更好的可拓展性,比如可以将 `derive` 的返回值作为新的状态用于其他开发场景。
52 |
53 | 然而,我们要注意的是,写这样的 `derive` 来监听 `now` 的主要目的实际上就是监听 `hashchange`。创建多个 `hashchange` 监听器并不会对性能产生明显影响。如果确实需要用到 `Van State` 来监听路由变化,其实我们已经有 `now`,并且它被全局的 `hashchange` 自动更新。这个 `now` 本身就可以作为拓展性的接口状态值按需使用,而各个路由需要监听路由变化时,最好使用正常的独立 `hashchange` 监听器。这样可以实现最纯粹的路由变化监听,而无需担心 `derive` 在监听 `now` 的同时带来的数据和事件异常情况。
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "typescript": "^5.5.4",
4 | "vite": "^5.3.5"
5 | },
6 | "dependencies": {
7 | "vanjs-core": "^1.5.1"
8 | },
9 | "scripts": {
10 | "build": "vite build && tsc"
11 | },
12 | "type": "module",
13 | "name": "vanjs-router",
14 | "version": "2.1.4",
15 | "main": "js/router.js",
16 | "types": "src/router.ts"
17 | }
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | vanjs-core:
12 | specifier: ^1.5.1
13 | version: 1.5.1
14 | devDependencies:
15 | typescript:
16 | specifier: ^5.5.4
17 | version: 5.5.4
18 | vite:
19 | specifier: ^5.3.5
20 | version: 5.3.5
21 |
22 | packages:
23 |
24 | '@esbuild/aix-ppc64@0.21.5':
25 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
26 | engines: {node: '>=12'}
27 | cpu: [ppc64]
28 | os: [aix]
29 |
30 | '@esbuild/android-arm64@0.21.5':
31 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
32 | engines: {node: '>=12'}
33 | cpu: [arm64]
34 | os: [android]
35 |
36 | '@esbuild/android-arm@0.21.5':
37 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
38 | engines: {node: '>=12'}
39 | cpu: [arm]
40 | os: [android]
41 |
42 | '@esbuild/android-x64@0.21.5':
43 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
44 | engines: {node: '>=12'}
45 | cpu: [x64]
46 | os: [android]
47 |
48 | '@esbuild/darwin-arm64@0.21.5':
49 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
50 | engines: {node: '>=12'}
51 | cpu: [arm64]
52 | os: [darwin]
53 |
54 | '@esbuild/darwin-x64@0.21.5':
55 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
56 | engines: {node: '>=12'}
57 | cpu: [x64]
58 | os: [darwin]
59 |
60 | '@esbuild/freebsd-arm64@0.21.5':
61 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
62 | engines: {node: '>=12'}
63 | cpu: [arm64]
64 | os: [freebsd]
65 |
66 | '@esbuild/freebsd-x64@0.21.5':
67 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
68 | engines: {node: '>=12'}
69 | cpu: [x64]
70 | os: [freebsd]
71 |
72 | '@esbuild/linux-arm64@0.21.5':
73 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
74 | engines: {node: '>=12'}
75 | cpu: [arm64]
76 | os: [linux]
77 |
78 | '@esbuild/linux-arm@0.21.5':
79 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
80 | engines: {node: '>=12'}
81 | cpu: [arm]
82 | os: [linux]
83 |
84 | '@esbuild/linux-ia32@0.21.5':
85 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
86 | engines: {node: '>=12'}
87 | cpu: [ia32]
88 | os: [linux]
89 |
90 | '@esbuild/linux-loong64@0.21.5':
91 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
92 | engines: {node: '>=12'}
93 | cpu: [loong64]
94 | os: [linux]
95 |
96 | '@esbuild/linux-mips64el@0.21.5':
97 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
98 | engines: {node: '>=12'}
99 | cpu: [mips64el]
100 | os: [linux]
101 |
102 | '@esbuild/linux-ppc64@0.21.5':
103 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
104 | engines: {node: '>=12'}
105 | cpu: [ppc64]
106 | os: [linux]
107 |
108 | '@esbuild/linux-riscv64@0.21.5':
109 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
110 | engines: {node: '>=12'}
111 | cpu: [riscv64]
112 | os: [linux]
113 |
114 | '@esbuild/linux-s390x@0.21.5':
115 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
116 | engines: {node: '>=12'}
117 | cpu: [s390x]
118 | os: [linux]
119 |
120 | '@esbuild/linux-x64@0.21.5':
121 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
122 | engines: {node: '>=12'}
123 | cpu: [x64]
124 | os: [linux]
125 |
126 | '@esbuild/netbsd-x64@0.21.5':
127 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
128 | engines: {node: '>=12'}
129 | cpu: [x64]
130 | os: [netbsd]
131 |
132 | '@esbuild/openbsd-x64@0.21.5':
133 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
134 | engines: {node: '>=12'}
135 | cpu: [x64]
136 | os: [openbsd]
137 |
138 | '@esbuild/sunos-x64@0.21.5':
139 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
140 | engines: {node: '>=12'}
141 | cpu: [x64]
142 | os: [sunos]
143 |
144 | '@esbuild/win32-arm64@0.21.5':
145 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
146 | engines: {node: '>=12'}
147 | cpu: [arm64]
148 | os: [win32]
149 |
150 | '@esbuild/win32-ia32@0.21.5':
151 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
152 | engines: {node: '>=12'}
153 | cpu: [ia32]
154 | os: [win32]
155 |
156 | '@esbuild/win32-x64@0.21.5':
157 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
158 | engines: {node: '>=12'}
159 | cpu: [x64]
160 | os: [win32]
161 |
162 | '@rollup/rollup-android-arm-eabi@4.19.0':
163 | resolution: {integrity: sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==}
164 | cpu: [arm]
165 | os: [android]
166 |
167 | '@rollup/rollup-android-arm64@4.19.0':
168 | resolution: {integrity: sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==}
169 | cpu: [arm64]
170 | os: [android]
171 |
172 | '@rollup/rollup-darwin-arm64@4.19.0':
173 | resolution: {integrity: sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==}
174 | cpu: [arm64]
175 | os: [darwin]
176 |
177 | '@rollup/rollup-darwin-x64@4.19.0':
178 | resolution: {integrity: sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==}
179 | cpu: [x64]
180 | os: [darwin]
181 |
182 | '@rollup/rollup-linux-arm-gnueabihf@4.19.0':
183 | resolution: {integrity: sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==}
184 | cpu: [arm]
185 | os: [linux]
186 |
187 | '@rollup/rollup-linux-arm-musleabihf@4.19.0':
188 | resolution: {integrity: sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==}
189 | cpu: [arm]
190 | os: [linux]
191 |
192 | '@rollup/rollup-linux-arm64-gnu@4.19.0':
193 | resolution: {integrity: sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==}
194 | cpu: [arm64]
195 | os: [linux]
196 |
197 | '@rollup/rollup-linux-arm64-musl@4.19.0':
198 | resolution: {integrity: sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==}
199 | cpu: [arm64]
200 | os: [linux]
201 |
202 | '@rollup/rollup-linux-powerpc64le-gnu@4.19.0':
203 | resolution: {integrity: sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==}
204 | cpu: [ppc64]
205 | os: [linux]
206 |
207 | '@rollup/rollup-linux-riscv64-gnu@4.19.0':
208 | resolution: {integrity: sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==}
209 | cpu: [riscv64]
210 | os: [linux]
211 |
212 | '@rollup/rollup-linux-s390x-gnu@4.19.0':
213 | resolution: {integrity: sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==}
214 | cpu: [s390x]
215 | os: [linux]
216 |
217 | '@rollup/rollup-linux-x64-gnu@4.19.0':
218 | resolution: {integrity: sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==}
219 | cpu: [x64]
220 | os: [linux]
221 |
222 | '@rollup/rollup-linux-x64-musl@4.19.0':
223 | resolution: {integrity: sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==}
224 | cpu: [x64]
225 | os: [linux]
226 |
227 | '@rollup/rollup-win32-arm64-msvc@4.19.0':
228 | resolution: {integrity: sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==}
229 | cpu: [arm64]
230 | os: [win32]
231 |
232 | '@rollup/rollup-win32-ia32-msvc@4.19.0':
233 | resolution: {integrity: sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==}
234 | cpu: [ia32]
235 | os: [win32]
236 |
237 | '@rollup/rollup-win32-x64-msvc@4.19.0':
238 | resolution: {integrity: sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==}
239 | cpu: [x64]
240 | os: [win32]
241 |
242 | '@types/estree@1.0.5':
243 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
244 |
245 | esbuild@0.21.5:
246 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
247 | engines: {node: '>=12'}
248 | hasBin: true
249 |
250 | fsevents@2.3.3:
251 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
252 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
253 | os: [darwin]
254 |
255 | nanoid@3.3.7:
256 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
257 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
258 | hasBin: true
259 |
260 | picocolors@1.0.1:
261 | resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
262 |
263 | postcss@8.4.40:
264 | resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==}
265 | engines: {node: ^10 || ^12 || >=14}
266 |
267 | rollup@4.19.0:
268 | resolution: {integrity: sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==}
269 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
270 | hasBin: true
271 |
272 | source-map-js@1.2.0:
273 | resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
274 | engines: {node: '>=0.10.0'}
275 |
276 | typescript@5.5.4:
277 | resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
278 | engines: {node: '>=14.17'}
279 | hasBin: true
280 |
281 | vanjs-core@1.5.1:
282 | resolution: {integrity: sha512-KT2XPqlwRdkyr7+pHc9BWOwmA6l7UqWrtt80xM1QkaUujc7Fq4Tgr6RhFo5wFO1u2sV1q7sR2PBKpVEGx4fEdA==}
283 |
284 | vite@5.3.5:
285 | resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==}
286 | engines: {node: ^18.0.0 || >=20.0.0}
287 | hasBin: true
288 | peerDependencies:
289 | '@types/node': ^18.0.0 || >=20.0.0
290 | less: '*'
291 | lightningcss: ^1.21.0
292 | sass: '*'
293 | stylus: '*'
294 | sugarss: '*'
295 | terser: ^5.4.0
296 | peerDependenciesMeta:
297 | '@types/node':
298 | optional: true
299 | less:
300 | optional: true
301 | lightningcss:
302 | optional: true
303 | sass:
304 | optional: true
305 | stylus:
306 | optional: true
307 | sugarss:
308 | optional: true
309 | terser:
310 | optional: true
311 |
312 | snapshots:
313 |
314 | '@esbuild/aix-ppc64@0.21.5':
315 | optional: true
316 |
317 | '@esbuild/android-arm64@0.21.5':
318 | optional: true
319 |
320 | '@esbuild/android-arm@0.21.5':
321 | optional: true
322 |
323 | '@esbuild/android-x64@0.21.5':
324 | optional: true
325 |
326 | '@esbuild/darwin-arm64@0.21.5':
327 | optional: true
328 |
329 | '@esbuild/darwin-x64@0.21.5':
330 | optional: true
331 |
332 | '@esbuild/freebsd-arm64@0.21.5':
333 | optional: true
334 |
335 | '@esbuild/freebsd-x64@0.21.5':
336 | optional: true
337 |
338 | '@esbuild/linux-arm64@0.21.5':
339 | optional: true
340 |
341 | '@esbuild/linux-arm@0.21.5':
342 | optional: true
343 |
344 | '@esbuild/linux-ia32@0.21.5':
345 | optional: true
346 |
347 | '@esbuild/linux-loong64@0.21.5':
348 | optional: true
349 |
350 | '@esbuild/linux-mips64el@0.21.5':
351 | optional: true
352 |
353 | '@esbuild/linux-ppc64@0.21.5':
354 | optional: true
355 |
356 | '@esbuild/linux-riscv64@0.21.5':
357 | optional: true
358 |
359 | '@esbuild/linux-s390x@0.21.5':
360 | optional: true
361 |
362 | '@esbuild/linux-x64@0.21.5':
363 | optional: true
364 |
365 | '@esbuild/netbsd-x64@0.21.5':
366 | optional: true
367 |
368 | '@esbuild/openbsd-x64@0.21.5':
369 | optional: true
370 |
371 | '@esbuild/sunos-x64@0.21.5':
372 | optional: true
373 |
374 | '@esbuild/win32-arm64@0.21.5':
375 | optional: true
376 |
377 | '@esbuild/win32-ia32@0.21.5':
378 | optional: true
379 |
380 | '@esbuild/win32-x64@0.21.5':
381 | optional: true
382 |
383 | '@rollup/rollup-android-arm-eabi@4.19.0':
384 | optional: true
385 |
386 | '@rollup/rollup-android-arm64@4.19.0':
387 | optional: true
388 |
389 | '@rollup/rollup-darwin-arm64@4.19.0':
390 | optional: true
391 |
392 | '@rollup/rollup-darwin-x64@4.19.0':
393 | optional: true
394 |
395 | '@rollup/rollup-linux-arm-gnueabihf@4.19.0':
396 | optional: true
397 |
398 | '@rollup/rollup-linux-arm-musleabihf@4.19.0':
399 | optional: true
400 |
401 | '@rollup/rollup-linux-arm64-gnu@4.19.0':
402 | optional: true
403 |
404 | '@rollup/rollup-linux-arm64-musl@4.19.0':
405 | optional: true
406 |
407 | '@rollup/rollup-linux-powerpc64le-gnu@4.19.0':
408 | optional: true
409 |
410 | '@rollup/rollup-linux-riscv64-gnu@4.19.0':
411 | optional: true
412 |
413 | '@rollup/rollup-linux-s390x-gnu@4.19.0':
414 | optional: true
415 |
416 | '@rollup/rollup-linux-x64-gnu@4.19.0':
417 | optional: true
418 |
419 | '@rollup/rollup-linux-x64-musl@4.19.0':
420 | optional: true
421 |
422 | '@rollup/rollup-win32-arm64-msvc@4.19.0':
423 | optional: true
424 |
425 | '@rollup/rollup-win32-ia32-msvc@4.19.0':
426 | optional: true
427 |
428 | '@rollup/rollup-win32-x64-msvc@4.19.0':
429 | optional: true
430 |
431 | '@types/estree@1.0.5': {}
432 |
433 | esbuild@0.21.5:
434 | optionalDependencies:
435 | '@esbuild/aix-ppc64': 0.21.5
436 | '@esbuild/android-arm': 0.21.5
437 | '@esbuild/android-arm64': 0.21.5
438 | '@esbuild/android-x64': 0.21.5
439 | '@esbuild/darwin-arm64': 0.21.5
440 | '@esbuild/darwin-x64': 0.21.5
441 | '@esbuild/freebsd-arm64': 0.21.5
442 | '@esbuild/freebsd-x64': 0.21.5
443 | '@esbuild/linux-arm': 0.21.5
444 | '@esbuild/linux-arm64': 0.21.5
445 | '@esbuild/linux-ia32': 0.21.5
446 | '@esbuild/linux-loong64': 0.21.5
447 | '@esbuild/linux-mips64el': 0.21.5
448 | '@esbuild/linux-ppc64': 0.21.5
449 | '@esbuild/linux-riscv64': 0.21.5
450 | '@esbuild/linux-s390x': 0.21.5
451 | '@esbuild/linux-x64': 0.21.5
452 | '@esbuild/netbsd-x64': 0.21.5
453 | '@esbuild/openbsd-x64': 0.21.5
454 | '@esbuild/sunos-x64': 0.21.5
455 | '@esbuild/win32-arm64': 0.21.5
456 | '@esbuild/win32-ia32': 0.21.5
457 | '@esbuild/win32-x64': 0.21.5
458 |
459 | fsevents@2.3.3:
460 | optional: true
461 |
462 | nanoid@3.3.7: {}
463 |
464 | picocolors@1.0.1: {}
465 |
466 | postcss@8.4.40:
467 | dependencies:
468 | nanoid: 3.3.7
469 | picocolors: 1.0.1
470 | source-map-js: 1.2.0
471 |
472 | rollup@4.19.0:
473 | dependencies:
474 | '@types/estree': 1.0.5
475 | optionalDependencies:
476 | '@rollup/rollup-android-arm-eabi': 4.19.0
477 | '@rollup/rollup-android-arm64': 4.19.0
478 | '@rollup/rollup-darwin-arm64': 4.19.0
479 | '@rollup/rollup-darwin-x64': 4.19.0
480 | '@rollup/rollup-linux-arm-gnueabihf': 4.19.0
481 | '@rollup/rollup-linux-arm-musleabihf': 4.19.0
482 | '@rollup/rollup-linux-arm64-gnu': 4.19.0
483 | '@rollup/rollup-linux-arm64-musl': 4.19.0
484 | '@rollup/rollup-linux-powerpc64le-gnu': 4.19.0
485 | '@rollup/rollup-linux-riscv64-gnu': 4.19.0
486 | '@rollup/rollup-linux-s390x-gnu': 4.19.0
487 | '@rollup/rollup-linux-x64-gnu': 4.19.0
488 | '@rollup/rollup-linux-x64-musl': 4.19.0
489 | '@rollup/rollup-win32-arm64-msvc': 4.19.0
490 | '@rollup/rollup-win32-ia32-msvc': 4.19.0
491 | '@rollup/rollup-win32-x64-msvc': 4.19.0
492 | fsevents: 2.3.3
493 |
494 | source-map-js@1.2.0: {}
495 |
496 | typescript@5.5.4: {}
497 |
498 | vanjs-core@1.5.1: {}
499 |
500 | vite@5.3.5:
501 | dependencies:
502 | esbuild: 0.21.5
503 | postcss: 8.4.40
504 | rollup: 4.19.0
505 | optionalDependencies:
506 | fsevents: 2.3.3
507 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import * as router from './router'
2 |
3 | Object.defineProperty(window, 'router', { value: router })
--------------------------------------------------------------------------------
/src/router.ts:
--------------------------------------------------------------------------------
1 | import van, { State } from 'vanjs-core'
2 |
3 | /** 返回当前的 `location.hash` 值的 `#/` 后面的部分,如果为空则返回 `home` */
4 | export const nowHash = (): string => location.hash ? location.hash.slice(2) : 'home'
5 |
6 | /** 当前的 Hash 值,不含开头的 #,值通过 `nowHash()` 返回。 */
7 | export const now: State = van.state(nowHash())
8 |
9 | window.addEventListener('hashchange', () => {
10 | now.val = nowHash()
11 | })
12 |
13 | /** 路由处理程序初始化配置项 */
14 | export type HandlerConfig = {
15 | /** 当前路由的命中规则
16 | * - 如果传入字符串,则只匹配 Hash 以 `/` 分割后的首项,其余项作为 `args`。
17 | * - 如果传入正则,则匹配正则,捕获分组作为 `args`。
18 | *
19 | * 例如:
20 | * ```ts
21 | * { rule: 'about' }
22 | * { rule: /list_(\d+)/ }
23 | * ```
24 | */
25 | rule: Handler['rule']
26 | /** 当前路由页面的构建方法 */
27 | Loader: Handler['Loader']
28 | /** 延迟加载页面,需要显示调用 `show()` 方法显示页面。 */
29 | delayed?: Handler['delayed']
30 | /** 首次路由命中事件,在 `onLoad` 之前执行。 */
31 | onFirst?: Handler['onFirst']
32 | /** 路由命中事件,在 `await onFirst()` 之后执行。 */
33 | onLoad?: Handler['onLoad']
34 | }
35 |
36 | /** 路由处理程序实例 */
37 | export class Handler {
38 | private rule: string | RegExp
39 | /** 当前路由被命中后接收到的路由参数 */
40 | public args: string[] = []
41 | private Loader: (this: Handler) => E
42 | private delayed = false
43 | private onFirst: (this: Handler) => any
44 | private onLoad: (this: Handler) => any
45 | /** 路由根元素,对外导出,可直接添加到 DOM 树 */
46 | public element: E
47 | /** 记录当前是否初次命中路由,决定是否执行 `onFirst` 事件。 */
48 | private isFirstLoad = true
49 |
50 | constructor(config: HandlerConfig) {
51 | if (!config) throw new Error('config 不能为空')
52 | if (!config.rule) throw new Error('rule 不能为空')
53 | if (!config.Loader) throw new Error('Loader 不能为空')
54 | // 载入路由的基础配置
55 | this.rule = config.rule
56 | this.Loader = config.Loader
57 | this.delayed = config.delayed || false
58 | this.onFirst = config.onFirst || (async () => { })
59 | this.onLoad = config.onLoad || (async () => { })
60 | // 创建页面元素
61 | this.element = this.Loader()
62 | this.element.hidden = true
63 | // 根据 Hash 的变化,自动更新路由状态
64 | const func = async () => {
65 | // 获取当前路由的命中状态
66 | const match = this.matchHash()
67 | if (!match) {
68 | // 未被命中,刷新路由,页面隐藏。
69 | this.hide()
70 | } else {
71 | // 路由命中
72 | // 将接收到的路由参数保存起来
73 | this.args.splice(0) // 清空存储的旧参数
74 | this.args.push(...match.args)
75 | if (this.isFirstLoad) {
76 | this.isFirstLoad = false
77 | await this.onFirst()
78 | }
79 | await this.onLoad()
80 | if (!this.delayed) this.show()
81 | }
82 | }
83 | window.addEventListener('hashchange', func)
84 | func()
85 | }
86 |
87 | /** 判断当前 Hash 是否与本路由的规则匹配 */
88 | private matchHash(): false | { hash: string, args: string[] } {
89 | if (this.rule instanceof RegExp) {
90 | const match = now.val.match(this.rule)
91 | if (!match) return false
92 | return { hash: now.val, args: [...match].slice(1) }
93 | }
94 | const parts = now.val.split('/').filter(i => i.length > 0)
95 | if (parts.length < 1) parts.push('home')
96 | return parts[0] == this.rule ? { hash: now.val, args: parts.slice(1) } : false
97 | }
98 |
99 | /** 显示当前路由元素 */
100 | public show() {
101 | this.element.hidden = false
102 | }
103 |
104 | /** 隐藏当前路由元素 */
105 | public hide() {
106 | this.element.hidden = true
107 | }
108 | }
109 |
110 | /** 创建一个自动管理路由状态的 DOM 元素 */
111 | export const Route = (config: HandlerConfig): E => new Handler(config).element
112 |
113 | /**
114 | * 跳转到指定 Hash
115 | * @param name 需要前往的路由名称,对应字符串类型的 rule
116 | * @param args 路由参数
117 | */
118 | export const goto = (name: string, ...args: any[]): void => {
119 | location.hash = name == 'home' && args.length == 0 ? '' : `/${[name, ...args].join('/')}`
120 | }
121 |
122 | /**
123 | * 路由重定向
124 | * @param from 来源路由规则,用于定义 `rule` 属性
125 | * @param to 目标路由规则,用于传入 `goto` 方法进行跳转
126 | */
127 | export const redirect = (from: string | RegExp, to: string) => {
128 | Route({
129 | rule: from,
130 | Loader: van.tags.div,
131 | onLoad() {
132 | goto(to)
133 | },
134 | })
135 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "outDir": "js",
5 | "lib": [
6 | "DOM",
7 | "ESNext"
8 | ],
9 | "module": "ESNext",
10 | "target": "ESNext",
11 | "moduleResolution": "Bundler"
12 | },
13 | "include": [
14 | "src"
15 | ]
16 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 |
3 | export default defineConfig({
4 | build: {
5 | lib: {
6 | entry: 'src/router.ts',
7 | name: 'router',
8 | fileName: 'vanjs-router',
9 | formats: ['iife']
10 | },
11 | rollupOptions: {
12 | }
13 | },
14 | })
--------------------------------------------------------------------------------