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 ADDED
@@ -0,0 +1,296 @@
1
+ # singlepage-router
2
+
3
+ ![npm version](https://img.shields.io/npm/v/singlepage-router?color=brightgreen)
4
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/singlepage-router?color=blue)
5
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178c6?logo=typescript&logoColor=white)
6
+ ![license](https://img.shields.io/badge/license-MIT-green)
7
+ ![build](https://img.shields.io/github/actions/workflow/status/filiperak/singlepage/build.yml?branch=main)
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"}