singlepage-router 1.0.0
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 +296 -0
- package/dist/cjs/index.cjs +15 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/singlepage.cjs +568 -0
- package/dist/cjs/singlepage.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/singlepage.js +561 -0
- package/dist/esm/singlepage.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/singlepage.d.ts +113 -0
- package/dist/types/singlepage.d.ts.map +1 -0
- package/license.md +21 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# singlepage-router
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
A micro client-side router with a clean TypeScript rewrite. Full `History` and `hashbang` support, zero runtime dependencies beyond `path-to-regexp`, and a modern dual ESM/CJS build system.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install singlepage-router
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import singlepage from 'singlepage-router';
|
|
24
|
+
|
|
25
|
+
singlepage('/', () => render('home'));
|
|
26
|
+
singlepage('/users', () => render('users'));
|
|
27
|
+
singlepage('/users/:id', (ctx) => render('user', ctx.params.id));
|
|
28
|
+
singlepage('*', () => render('404'));
|
|
29
|
+
|
|
30
|
+
singlepage(); // start the router
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Basic routes
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import singlepage from 'singlepage-router';
|
|
41
|
+
|
|
42
|
+
singlepage('/', (ctx, next) => {
|
|
43
|
+
console.log('home', ctx.pathname);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
singlepage('/about', (ctx, next) => {
|
|
47
|
+
console.log('about page');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
singlepage(); // start
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Route parameters
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
singlepage('/users/:id', (ctx) => {
|
|
57
|
+
const { id } = ctx.params;
|
|
58
|
+
console.log('user id:', id);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
singlepage('/posts/:year/:month/:slug', (ctx) => {
|
|
62
|
+
const { year, month, slug } = ctx.params;
|
|
63
|
+
console.log(`Post: ${slug} from ${month}/${year}`);
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Middleware / chaining handlers
|
|
68
|
+
|
|
69
|
+
Handlers receive a `next` function. Call it to pass control to the next matching handler, just like Express middleware.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import singlepage, { Context } from 'singlepage-router';
|
|
73
|
+
|
|
74
|
+
function authenticate(ctx: Context, next: () => void) {
|
|
75
|
+
if (!isLoggedIn()) return singlepage.show('/login');
|
|
76
|
+
next();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function loadUser(ctx: Context, next: () => void) {
|
|
80
|
+
ctx.state.user = fetchUser(ctx.params.id);
|
|
81
|
+
next();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderProfile(ctx: Context) {
|
|
85
|
+
render('profile', ctx.state.user);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
singlepage('/profile/:id', authenticate, loadUser, renderProfile);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Wildcard / catch-all
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
singlepage('*', (ctx) => {
|
|
95
|
+
console.log('no route matched:', ctx.path);
|
|
96
|
+
render('404');
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Redirects
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// Declarative redirect — from one path to another
|
|
104
|
+
singlepage.redirect('/old-path', '/new-path');
|
|
105
|
+
|
|
106
|
+
// Imperative redirect from inside a handler
|
|
107
|
+
singlepage('/legacy', () => {
|
|
108
|
+
singlepage.redirect('/modern');
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Navigate programmatically
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// Push a new history entry
|
|
116
|
+
singlepage.show('/users/42');
|
|
117
|
+
|
|
118
|
+
// Replace the current history entry (no new entry added)
|
|
119
|
+
singlepage.replace('/users/42');
|
|
120
|
+
|
|
121
|
+
// Go back — falls back to a path if there is no history
|
|
122
|
+
singlepage.back('/home');
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Exit handlers
|
|
126
|
+
|
|
127
|
+
Exit handlers run when navigating *away* from a route. Useful for teardown, unsaved-change guards, or cancelling in-flight requests.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
singlepage('/editor', (ctx) => {
|
|
131
|
+
startEditor();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
singlepage.exit('/editor', (ctx, next) => {
|
|
135
|
+
if (hasUnsavedChanges()) {
|
|
136
|
+
if (!confirm('Leave without saving?')) return;
|
|
137
|
+
}
|
|
138
|
+
stopEditor();
|
|
139
|
+
next();
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Hashbang mode
|
|
144
|
+
|
|
145
|
+
For environments that can't use the HTML5 History API (e.g. `file://` protocol):
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
singlepage.start({ hashbang: true });
|
|
149
|
+
|
|
150
|
+
// URLs will look like: /#!/users/42
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Base path
|
|
154
|
+
|
|
155
|
+
If your app is not served from the root, set a base path:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
singlepage.base('/my-app');
|
|
159
|
+
|
|
160
|
+
singlepage('/dashboard', () => render('dashboard'));
|
|
161
|
+
// Matches: /my-app/dashboard
|
|
162
|
+
|
|
163
|
+
singlepage();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Multiple isolated instances
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { createPage } from 'singlepage-router';
|
|
170
|
+
|
|
171
|
+
const adminRouter = createPage();
|
|
172
|
+
const publicRouter = createPage();
|
|
173
|
+
|
|
174
|
+
adminRouter('/dashboard', () => { /* ... */ });
|
|
175
|
+
publicRouter('/home', () => { /* ... */ });
|
|
176
|
+
|
|
177
|
+
adminRouter();
|
|
178
|
+
publicRouter();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Accessing the Context object
|
|
182
|
+
|
|
183
|
+
Every handler receives a `Context` instance with the following properties:
|
|
184
|
+
|
|
185
|
+
| Property | Type | Description |
|
|
186
|
+
|-----------------|-----------------------------------|------------------------------------------|
|
|
187
|
+
| `path` | `string` | The path without base |
|
|
188
|
+
| `canonicalPath` | `string` | The full path including base |
|
|
189
|
+
| `pathname` | `string` | Path without querystring or hash |
|
|
190
|
+
| `querystring` | `string` | Querystring without the leading `?` |
|
|
191
|
+
| `hash` | `string` | Hash fragment without the `#` |
|
|
192
|
+
| `params` | `Record<string, string>` | Matched route parameters |
|
|
193
|
+
| `state` | `Record<string, unknown>` | Arbitrary state stored in history entry |
|
|
194
|
+
| `handled` | `boolean` | Whether the context was handled |
|
|
195
|
+
| `routePath` | `string` | The route pattern that matched |
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
singlepage('/search', (ctx) => {
|
|
199
|
+
console.log(ctx.querystring); // "q=typescript&page=2"
|
|
200
|
+
console.log(ctx.hash); // "results"
|
|
201
|
+
console.log(ctx.state); // any state passed via singlepage.show()
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## API Reference
|
|
208
|
+
|
|
209
|
+
### `singlepage(path, ...handlers)`
|
|
210
|
+
Register a route.
|
|
211
|
+
|
|
212
|
+
### `singlepage()` / `singlepage.start(options?)`
|
|
213
|
+
Start the router. Dispatches the current URL immediately.
|
|
214
|
+
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
|----------------------|-----------|---------|--------------------------------------------------|
|
|
217
|
+
| `dispatch` | `boolean` | `true` | Dispatch the current route on start |
|
|
218
|
+
| `click` | `boolean` | `true` | Intercept link clicks automatically |
|
|
219
|
+
| `popstate` | `boolean` | `true` | Listen to `popstate` events |
|
|
220
|
+
| `hashbang` | `boolean` | `false` | Use `#!` URLs instead of History API |
|
|
221
|
+
| `decodeURLComponents`| `boolean` | `true` | Decode URL params and querystrings |
|
|
222
|
+
| `window` | `Window` | `window`| Use a custom window object |
|
|
223
|
+
|
|
224
|
+
### `singlepage.stop()`
|
|
225
|
+
Unbind all event listeners and stop the router.
|
|
226
|
+
|
|
227
|
+
### `singlepage.show(path, state?, dispatch?, push?)`
|
|
228
|
+
Navigate to `path`, pushing a new history entry.
|
|
229
|
+
|
|
230
|
+
### `singlepage.replace(path, state?, init?, dispatch?)`
|
|
231
|
+
Navigate to `path`, replacing the current history entry.
|
|
232
|
+
|
|
233
|
+
### `singlepage.back(fallback?, state?)`
|
|
234
|
+
Go back in history. If no history exists, navigates to `fallback`.
|
|
235
|
+
|
|
236
|
+
### `singlepage.redirect(from, to?)`
|
|
237
|
+
Register a redirect from one path to another, or immediately redirect to a path.
|
|
238
|
+
|
|
239
|
+
### `singlepage.base(path?)`
|
|
240
|
+
Get or set the base path.
|
|
241
|
+
|
|
242
|
+
### `singlepage.strict(enable?)`
|
|
243
|
+
Get or set strict trailing-slash matching.
|
|
244
|
+
|
|
245
|
+
### `singlepage.exit(path, ...handlers)`
|
|
246
|
+
Register exit handlers for a route.
|
|
247
|
+
|
|
248
|
+
### `singlepage.configure(options)`
|
|
249
|
+
Reconfigure the router after start.
|
|
250
|
+
|
|
251
|
+
### `createPage()`
|
|
252
|
+
Create a new isolated router instance.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Project Structure
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
your-project/
|
|
260
|
+
├── src/
|
|
261
|
+
│ └── page.ts
|
|
262
|
+
├── scripts/
|
|
263
|
+
│ └── fix-cjs-ext.mjs
|
|
264
|
+
├── dist/ ← generated, do not edit
|
|
265
|
+
│ ├── esm/page.js
|
|
266
|
+
│ ├── cjs/page.cjs
|
|
267
|
+
│ └── types/page.d.ts
|
|
268
|
+
├── package.json
|
|
269
|
+
├── tsconfig.json
|
|
270
|
+
├── tsconfig.esm.json
|
|
271
|
+
├── tsconfig.cjs.json
|
|
272
|
+
└── tsconfig.types.json
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Building
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
npm install
|
|
281
|
+
npm run build
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Individual build steps:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npm run build:esm # ESM output → dist/esm/
|
|
288
|
+
npm run build:cjs # CJS output → dist/cjs/
|
|
289
|
+
npm run build:types # Type declarations → dist/types/
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## License
|
|
295
|
+
|
|
296
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/index.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PageInstance = exports.Route = exports.Context = exports.createPage = exports.default = void 0;
|
|
8
|
+
var singlepage_js_1 = require("./singlepage.js");
|
|
9
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(singlepage_js_1).default; } });
|
|
10
|
+
Object.defineProperty(exports, "createPage", { enumerable: true, get: function () { return singlepage_js_1.createPage; } });
|
|
11
|
+
var singlepage_js_2 = require("./singlepage.js");
|
|
12
|
+
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return singlepage_js_2.Context; } });
|
|
13
|
+
Object.defineProperty(exports, "Route", { enumerable: true, get: function () { return singlepage_js_2.Route; } });
|
|
14
|
+
Object.defineProperty(exports, "PageInstance", { enumerable: true, get: function () { return singlepage_js_2.PageInstance; } });
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAI,eAAe;;;;;;AAEf,iDAAsD;AAA7C,yHAAA,OAAO,OAAA;AAAE,2GAAA,UAAU,OAAA;AAE5B,iDAA+D;AAAtD,wGAAA,OAAO,OAAA;AAAE,sGAAA,KAAK,OAAA;AAAE,6GAAA,YAAY,OAAA"}
|