@aegisjsproject/atlas 0.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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/atlas.cjs +669 -0
- package/atlas.js +3 -0
- package/atlas.min.js +2 -0
- package/atlas.min.js.map +1 -0
- package/package.json +83 -0
- package/preload.js +241 -0
- package/router.js +338 -0
- package/routes.js +74 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!-- markdownlint-disable -->
|
|
2
|
+
# Changelog
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [v1.0.0] - 2023-05-31
|
|
11
|
+
|
|
12
|
+
Initial Release
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Chris Zuber
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# `@aegisjsproject/atlas`
|
|
2
|
+
|
|
3
|
+
A client-side router library using `Navigation` & `URLPattern`
|
|
4
|
+
|
|
5
|
+
[](https://github.com/AegisJSProject/atlas/actions/workflows/codeql-analysis.yml)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
[](https://github.com/AegisJSProject/atlas/blob/master/LICENSE)
|
|
10
|
+
[](https://github.com/AegisJSProject/atlas/commits/master)
|
|
11
|
+
[](https://github.com/AegisJSProject/atlas/releases)
|
|
12
|
+
[](https://github.com/sponsors/shgysk8zer0)
|
|
13
|
+
|
|
14
|
+
[](https://www.npmjs.com/package/@aegisjsproject/atlas)
|
|
15
|
+

|
|
16
|
+

|
|
17
|
+
[](https://www.npmjs.com/package/@aegisjsproject/atlas)
|
|
18
|
+
|
|
19
|
+
[](https://github.com/shgysk8zer0)
|
|
20
|
+

|
|
21
|
+

|
|
22
|
+
[](https://twitter.com/shgysk8zer0)
|
|
23
|
+
|
|
24
|
+
[](https://liberapay.com/shgysk8zer0/donate "Donate using Liberapay")
|
|
25
|
+
- - -
|
|
26
|
+
|
|
27
|
+
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
|
28
|
+
- [Contributing](./.github/CONTRIBUTING.md)
|
|
29
|
+
<!-- - [Security Policy](./.github/SECURITY.md) -->
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
This router intercepts same-origin navigations and resolves them to registered route modules. Each module can return content in multiple native formats (e.g. `Response`, `Document`, `Element`), allowing flexibility without imposing rendering constraints.
|
|
34
|
+
|
|
35
|
+
Key characteristics:
|
|
36
|
+
|
|
37
|
+
- Native Navigation API (`navigation`)
|
|
38
|
+
- Route-to-module mapping via dynamic `import()`
|
|
39
|
+
- Direct DOM updates (no diffing layer)
|
|
40
|
+
- Supports HTML streaming via `Response`
|
|
41
|
+
- Built-in metadata handling (title, description, styles)
|
|
42
|
+
- Optional preload observation
|
|
43
|
+
- Abort-safe lifecycle with `AbortController` and `DisposableStack`
|
|
44
|
+
|
|
45
|
+
> [!IMPORTANT]
|
|
46
|
+
> This requires the [Navigation API](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API), which is Baseline 2026.
|
|
47
|
+
> It also creates a [Trusted Types Policy](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API), where supported,
|
|
48
|
+
> labeled `"aegis-atlas#html"` for handling HTML responses without sanitizer restrictions.
|
|
49
|
+
|
|
50
|
+
> [!TIP]
|
|
51
|
+
> Route module specifiers can use bare specifiers like `@acme/blog`. These can be resolved via an import map, for example:
|
|
52
|
+
>
|
|
53
|
+
> ```html
|
|
54
|
+
> <script type="importmap">
|
|
55
|
+
> {
|
|
56
|
+
> "imports": {
|
|
57
|
+
> "@acme/blog": "https://cdn.example.com/acme-blog/index.js"
|
|
58
|
+
> }
|
|
59
|
+
> }
|
|
60
|
+
> </script>
|
|
61
|
+
> ```
|
|
62
|
+
>
|
|
63
|
+
> This allows modules to be loaded from a CDN without changing route definitions.
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
This module is intended to be used directly in modern browser environments.
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { init } from '@aegisjsproject/atlas';
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
No dependencies required.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Core Concepts
|
|
79
|
+
|
|
80
|
+
### Route Modules
|
|
81
|
+
|
|
82
|
+
Each route resolves to a module with the following shape:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
|
|
86
|
+
export default async function handler(request, context) {
|
|
87
|
+
return new Response('<h1>Hello</h1>', {
|
|
88
|
+
headers: { 'Content-Type': 'text/html' }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const title = 'Page Title';
|
|
93
|
+
|
|
94
|
+
export const description = 'Page description';
|
|
95
|
+
|
|
96
|
+
export const styles = new CSSStyleSheet();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Supported Exports
|
|
100
|
+
|
|
101
|
+
- `default` (required)
|
|
102
|
+
- Function: `(Request, RouteContextObject) => HandlerResult`
|
|
103
|
+
- Or static value: `HandlerResult`
|
|
104
|
+
- `title` (optional)
|
|
105
|
+
- `description` (optional)
|
|
106
|
+
- `styles` (optional: `CSSStyleSheet` or array)
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Handler Return Types
|
|
111
|
+
|
|
112
|
+
Handlers may return:
|
|
113
|
+
- `Response` (must be `text/html`)
|
|
114
|
+
- `HTMLDocument`
|
|
115
|
+
- `Element`
|
|
116
|
+
- `DocumentFragment`
|
|
117
|
+
- `URL` (triggers navigation)
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### Route Context
|
|
121
|
+
|
|
122
|
+
Each handler receives a `context` object:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
{
|
|
126
|
+
|
|
127
|
+
result, // URLPatternResult
|
|
128
|
+
params, // extracted route params
|
|
129
|
+
stack, // DisposableStack
|
|
130
|
+
controller, // AbortController
|
|
131
|
+
signal, // AbortSignal
|
|
132
|
+
type, // navigation type
|
|
133
|
+
url, // URL instance
|
|
134
|
+
state, // navigation state
|
|
135
|
+
info, // navigation info
|
|
136
|
+
timestamp // performance timestamp
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Usage
|
|
144
|
+
|
|
145
|
+
### Initialize Router
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
|
|
149
|
+
init({
|
|
150
|
+
'/': '/routes/home.js',
|
|
151
|
+
'/users/:id': '/routes/user.js'
|
|
152
|
+
'/posts/:year(\\d{4})/:month(\\d{2})/:day(\\d{2})/:slug': '@acme/blog',
|
|
153
|
+
'/product/:sku': '@acme/store/product',
|
|
154
|
+
}, {
|
|
155
|
+
root: 'app',
|
|
156
|
+
preload: true
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Options
|
|
162
|
+
|
|
163
|
+
- `root`: Element or element ID where content is rendered
|
|
164
|
+
- `preload`: Enable preload observation
|
|
165
|
+
- `signal`: Optional `AbortSignal` for teardown
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Navigation Helpers
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
|
|
173
|
+
import { navigate, back, forward, reload } from './router.js';
|
|
174
|
+
|
|
175
|
+
navigate('/about');
|
|
176
|
+
back();
|
|
177
|
+
forward();
|
|
178
|
+
reload();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### Navigation Lifecycle
|
|
184
|
+
|
|
185
|
+
Wait for navigation completion:
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
import { whenLoaded } from './router.js';
|
|
189
|
+
|
|
190
|
+
await whenLoaded();
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Behavior Details
|
|
196
|
+
|
|
197
|
+
### Interception Rules
|
|
198
|
+
|
|
199
|
+
Navigation is intercepted only if:
|
|
200
|
+
|
|
201
|
+
- `event.canIntercept` is true
|
|
202
|
+
- URL is same-origin
|
|
203
|
+
- Triggering element does **not** have `.no-router`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Content Handling
|
|
208
|
+
|
|
209
|
+
#### `Response`
|
|
210
|
+
|
|
211
|
+
- Must be `text/html`
|
|
212
|
+
- Parsed via `Document.parseHTMLUnsafe`
|
|
213
|
+
- Re-processed as `HTMLDocument`
|
|
214
|
+
|
|
215
|
+
#### `HTMLDocument`
|
|
216
|
+
|
|
217
|
+
- Updates:
|
|
218
|
+
- `document.title`
|
|
219
|
+
- meta description
|
|
220
|
+
- root content
|
|
221
|
+
|
|
222
|
+
#### `Element` / `DocumentFragment`
|
|
223
|
+
|
|
224
|
+
- Directly replaces root children
|
|
225
|
+
|
|
226
|
+
#### `URL`
|
|
227
|
+
|
|
228
|
+
- Triggers navigation
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### Root Management
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
|
|
236
|
+
setRoot('app');
|
|
237
|
+
|
|
238
|
+
// or
|
|
239
|
+
|
|
240
|
+
setRoot(document.getElementById('app'));
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
If root is `<body>`, full body is replaced.
|
|
245
|
+
|
|
246
|
+
If root is an element with `id`, only matching subtree is replaced.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Metadata Updates
|
|
251
|
+
|
|
252
|
+
Route modules can define:
|
|
253
|
+
|
|
254
|
+
- `title` → updates `document.title`
|
|
255
|
+
- `description` → updates all matching meta tags:
|
|
256
|
+
- `name="description"`
|
|
257
|
+
- `og:description`
|
|
258
|
+
- `twitter:description`
|
|
259
|
+
- `styles` → appended to `document.adoptedStyleSheets`
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### Form Handling
|
|
264
|
+
|
|
265
|
+
- Automatically determines method (`GET` or `POST`)
|
|
266
|
+
- Submits `FormData` when applicable
|
|
267
|
+
- Uses `Request` API for consistency
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### Abort + Cleanup
|
|
272
|
+
|
|
273
|
+
Each navigation:
|
|
274
|
+
|
|
275
|
+
- Uses `AbortController`
|
|
276
|
+
- Combines signals via `AbortSignal.any`
|
|
277
|
+
- Cleans up via `DisposableStack`
|
|
278
|
+
|
|
279
|
+
Handlers should respect `context.signal` where applicable.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
### Trusted Types
|
|
284
|
+
|
|
285
|
+
If supported, a Trusted Types policy is used to safely pass HTML into:
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
|
|
289
|
+
Document.parseHTMLUnsafe(...)
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
This ensures CSP compatibility without stripping critical markup like:
|
|
294
|
+
|
|
295
|
+
- `<iframe>`
|
|
296
|
+
- inline event handlers
|
|
297
|
+
- form attributes
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Example Route
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
|
|
305
|
+
export default async function(request, { params }) {
|
|
306
|
+
return new Response(`
|
|
307
|
+
<h1>User ${params.id}</h1>
|
|
308
|
+
`, {
|
|
309
|
+
headers: { 'Content-Type': 'text/html' }
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const title = 'User Profile';
|
|
314
|
+
export const description = 'User details page';
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Notes
|
|
321
|
+
|
|
322
|
+
- Only `text/html` responses are supported for `Response`
|
|
323
|
+
- Non-matching routes fall back to `fetch()`
|
|
324
|
+
- Errors during routing are surfaced via `reportError`
|
|
325
|
+
- Designed for modern browsers with Navigation API support
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Summary
|
|
330
|
+
|
|
331
|
+
This router provides a low-level, high-control alternative to framework routers by:
|
|
332
|
+
|
|
333
|
+
- Eliminating abstraction layers
|
|
334
|
+
- Leveraging native platform APIs
|
|
335
|
+
- Supporting flexible content types
|
|
336
|
+
- Maintaining strict control over navigation lifecycle
|
|
337
|
+
|
|
338
|
+
Intended for environments where performance, control, and minimal overhead are priorities.
|