@ecopages/browser-router 0.2.0-alpha.5 → 0.2.0-alpha.6
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 +9 -7
- package/README.md +36 -36
- package/package.json +2 -2
- package/src/client/eco-router.d.ts +35 -1
- package/src/client/eco-router.js +335 -75
- package/src/client/eco-router.ts +430 -100
- package/src/client/services/dom-swapper.d.ts +23 -0
- package/src/client/services/dom-swapper.js +210 -38
- package/src/client/services/dom-swapper.ts +296 -45
- package/src/client/services/view-transition-manager.d.ts +7 -1
- package/src/client/services/view-transition-manager.js +10 -5
- package/src/client/services/view-transition-manager.ts +13 -7
- package/src/client/types.d.ts +3 -0
- package/src/client/types.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,19 +8,21 @@ All notable changes to `@ecopages/browser-router` are documented here.
|
|
|
8
8
|
|
|
9
9
|
### Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- **Global injector lifecycle management** — Enhanced global injector with structured lifecycle hooks and tests for hydration script handling (`2ba35aa4`).
|
|
11
|
+
- Added cross-router navigation handoff and document adoption hooks so browser-router can exchange control with other runtimes without forcing a reload.
|
|
12
|
+
- Improved head swaps to execute newly introduced scripts and preserve light-DOM custom elements during page transitions.
|
|
14
13
|
|
|
15
14
|
### Bug Fixes
|
|
16
15
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
16
|
+
- Fixed stale delegated navigations and rapid-click races so only the latest browser-router navigation can commit a swap.
|
|
17
|
+
- Re-executed `data-eco-rerun` scripts after body replacement and forced fresh module URLs for external rerun scripts so page bootstraps rebind correctly on every navigation.
|
|
18
|
+
- Synced rendered document ownership markers and live `<html>` attributes during swaps so mixed browser-router and React-router pages preserve the correct hydration owner.
|
|
19
|
+
- Prevented duplicate head-script execution, duplicate `/_hmr_runtime.js` injection, and listener accumulation across repeated navigations.
|
|
20
|
+
- Reset hydrated custom elements from incoming HTML and ignored superseded navigation fetch failures so cross-runtime handoffs no longer leave blank or mixed DOM state behind.
|
|
19
21
|
|
|
20
22
|
### Refactoring
|
|
21
23
|
|
|
22
|
-
-
|
|
24
|
+
- Routed handoff and current-page reload behavior through the shared navigation coordinator.
|
|
23
25
|
|
|
24
26
|
### Documentation
|
|
25
27
|
|
|
26
|
-
- README
|
|
28
|
+
- Updated the README examples for the current router API.
|
package/README.md
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
# @ecopages/browser-router
|
|
2
2
|
|
|
3
|
-
Client-side navigation and view transitions for Ecopages.
|
|
3
|
+
Client-side navigation and view transitions for Ecopages. It intercepts same-origin link clicks to provide smooth page transitions without full page reloads.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Client-side navigation
|
|
8
|
-
- **Efficient DOM diffing
|
|
9
|
-
- **State persistence
|
|
10
|
-
- **View Transitions
|
|
11
|
-
- **Lifecycle events
|
|
7
|
+
- **Client-side navigation**: Intercepts `<a>` clicks for robust, fast navigation.
|
|
8
|
+
- **Efficient DOM diffing**: Uses [morphdom](https://github.com/patrick-steele-idem/morphdom) to update only what changed, preserving scroll positions and internal state.
|
|
9
|
+
- **State persistence**: Elements with `data-eco-persist` are never recreated, preserving Web Component state, event listeners, and form values.
|
|
10
|
+
- **View Transitions**: Optional integration with the View Transition API.
|
|
11
|
+
- **Lifecycle events**: Hook into navigation with `eco:before-swap`, `eco:after-swap`, `eco:page-load`.
|
|
12
12
|
|
|
13
13
|
## Compatibility
|
|
14
14
|
|
|
15
|
-
This package
|
|
15
|
+
This package is designed for MPA-style rendering where the server returns full HTML pages (e.g., KitaJS, Lit, vanilla JS, or component-level React islands).
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> **Not compatible with full React applications.**
|
|
19
|
+
> If you are building a React application, use [@ecopages/react-router](../react-router/README.md) instead, as React manages its own virtual DOM and hydration lifecycle.
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
> [!NOTE]
|
|
22
|
+
> `@ecopages/browser-router` can still participate in mixed sites that contain both React-router pages and non-React pages. In that setup, browser-router remains responsible for DOM-swapping non-React documents, and React-router can hand off already-fetched non-React pages to it through the shared navigation coordinator.
|
|
20
23
|
|
|
21
24
|
## Installation
|
|
22
25
|
|
|
@@ -28,7 +31,8 @@ bunx jsr add @ecopages/browser-router
|
|
|
28
31
|
|
|
29
32
|
Create and start the router in a **global** client-side script (e.g., `src/layouts/base-layout.script.ts`).
|
|
30
33
|
|
|
31
|
-
>
|
|
34
|
+
> [!IMPORTANT]
|
|
35
|
+
> Ensure the router script is injected in a **consistent order** within the `<head>` across all pages. Inconsistent ordering causes `morphdom` to reload styles, leading to a "Flash of Unstyled Content" (FOUC).
|
|
32
36
|
|
|
33
37
|
```ts
|
|
34
38
|
import { createRouter } from '@ecopages/browser-router/client';
|
|
@@ -48,51 +52,45 @@ const router = createRouter({
|
|
|
48
52
|
});
|
|
49
53
|
```
|
|
50
54
|
|
|
55
|
+
Loading the router script is the opt-in point for browser-router-managed navigation on that page shell. Pages without the router script continue to use normal document navigation.
|
|
56
|
+
|
|
51
57
|
## Configuration
|
|
52
58
|
|
|
53
|
-
| Option |
|
|
54
|
-
| :----------------- |
|
|
55
|
-
| `linkSelector` |
|
|
56
|
-
| `persistAttribute` |
|
|
57
|
-
| `reloadAttribute` |
|
|
58
|
-
| `updateHistory` |
|
|
59
|
-
| `scrollBehavior` | `'top' \| 'preserve' \| 'auto'` |
|
|
60
|
-
| `viewTransitions` |
|
|
61
|
-
| `smoothScroll` |
|
|
59
|
+
| Option | Type | Default | Description |
|
|
60
|
+
| :----------------- | :------------------------------ | :------------------- | :--------------------------------------------- |
|
|
61
|
+
| `linkSelector` | `string` | `'a[href]'` | Selector for links to intercept |
|
|
62
|
+
| `persistAttribute` | `string` | `'data-eco-persist'` | Attribute to mark elements for DOM persistence |
|
|
63
|
+
| `reloadAttribute` | `string` | `'data-eco-reload'` | Attribute to force full page reload |
|
|
64
|
+
| `updateHistory` | `boolean` | `true` | Whether to update browser history |
|
|
65
|
+
| `scrollBehavior` | `'top' \| 'preserve' \| 'auto'` | `'top'` | Scroll behavior after navigation |
|
|
66
|
+
| `viewTransitions` | `boolean` | `false` | Use View Transition API for animations |
|
|
67
|
+
| `smoothScroll` | `boolean` | `false` | Use smooth scrolling during navigation |
|
|
62
68
|
|
|
63
69
|
## Persistence
|
|
64
70
|
|
|
65
|
-
Mark elements to preserve across navigations. These elements are never recreated during navigation
|
|
71
|
+
Mark elements to preserve across navigations. These elements are never recreated during navigation; morphdom skips them entirely.
|
|
66
72
|
|
|
67
73
|
```html
|
|
68
|
-
<!-- This counter keeps its state across all navigations -->
|
|
74
|
+
<!-- This counter keeps its internal state across all navigations -->
|
|
69
75
|
<radiant-counter data-eco-persist="counter"></radiant-counter>
|
|
70
76
|
```
|
|
71
77
|
|
|
72
78
|
## Script Re-execution
|
|
73
79
|
|
|
74
|
-
To force a script to re-execute on every navigation (e.g
|
|
80
|
+
To force a script to re-execute on every navigation (e.g., analytics), add `data-eco-rerun`:
|
|
75
81
|
|
|
76
82
|
```html
|
|
77
|
-
<script data-eco-rerun="true"
|
|
78
|
-
// This runs on every navigation
|
|
83
|
+
<script data-eco-rerun="true">
|
|
79
84
|
trackPageview();
|
|
80
85
|
</script>
|
|
81
86
|
```
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
When `@ecopages/browser-router` is used in an MPA-style app that also renders component-level React islands:
|
|
86
|
-
|
|
87
|
-
- island hydration scripts may need to run again after `eco:after-swap`
|
|
88
|
-
- hydration bootstraps should carry stable `data-eco-script-id` metadata
|
|
89
|
-
- `data-eco-rerun` allows those bootstraps to be re-executed safely during head reconciliation
|
|
90
|
-
|
|
91
|
-
This note is specific to DOM-swapping navigation with `@ecopages/browser-router`. It does **not** apply to full React applications using [@ecopages/react-router](../react-router/README.md), where page routing and hydration are handled by the React router runtime itself.
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> `data-eco-script-id` is optional. Use it when you want an explicit stable identity for a rerun script. Otherwise the router falls back to matching by original `src` and inline content.
|
|
92
90
|
|
|
93
91
|
## Force Full Reload
|
|
94
92
|
|
|
95
|
-
Use `data-eco-reload` to force a full page reload:
|
|
93
|
+
Use `data-eco-reload` on an anchor tag to bypass the router and force a full page reload:
|
|
96
94
|
|
|
97
95
|
```html
|
|
98
96
|
<a href="/logout" data-eco-reload>Logout</a>
|
|
@@ -100,12 +98,12 @@ Use `data-eco-reload` to force a full page reload:
|
|
|
100
98
|
|
|
101
99
|
## Events
|
|
102
100
|
|
|
103
|
-
Listen to navigation lifecycle events
|
|
101
|
+
Listen to navigation lifecycle events on the `document`:
|
|
104
102
|
|
|
105
103
|
```ts
|
|
106
104
|
document.addEventListener('eco:before-swap', (e) => {
|
|
107
105
|
console.log('Navigating to:', e.detail.url);
|
|
108
|
-
// Call e.detail.reload() to abort and do full reload
|
|
106
|
+
// Call e.detail.reload() to abort client-side navigation and do a full reload
|
|
109
107
|
});
|
|
110
108
|
|
|
111
109
|
document.addEventListener('eco:after-swap', (e) => {
|
|
@@ -119,6 +117,8 @@ document.addEventListener('eco:page-load', (e) => {
|
|
|
119
117
|
|
|
120
118
|
## Programmatic Navigation
|
|
121
119
|
|
|
120
|
+
Use the router instance to navigate programmatically:
|
|
121
|
+
|
|
122
122
|
```ts
|
|
123
123
|
import { createRouter } from '@ecopages/browser-router/client';
|
|
124
124
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/browser-router",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.6",
|
|
4
4
|
"description": "Client-side router for Ecopages with view transitions support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"directory": "packages/browser-router"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ecopages/core": "0.2.0-alpha.
|
|
35
|
+
"@ecopages/core": "0.2.0-alpha.6",
|
|
36
36
|
"morphdom": "^2.7.8"
|
|
37
37
|
},
|
|
38
38
|
"types": "./src/index.d.ts"
|
|
@@ -9,12 +9,36 @@ import type { EcoRouterOptions } from './types.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare class EcoRouter {
|
|
11
11
|
private options;
|
|
12
|
-
private
|
|
12
|
+
private unregisterNavigationRuntime;
|
|
13
|
+
private started;
|
|
14
|
+
private pendingNavigations;
|
|
15
|
+
private pendingPointerNavigation;
|
|
16
|
+
private pendingHoverNavigation;
|
|
17
|
+
private queuedNavigationHref;
|
|
13
18
|
private domSwapper;
|
|
14
19
|
private scrollManager;
|
|
15
20
|
private viewTransitionManager;
|
|
16
21
|
private prefetchManager;
|
|
17
22
|
constructor(options?: EcoRouterOptions);
|
|
23
|
+
private getLinkFromEvent;
|
|
24
|
+
private canInterceptLink;
|
|
25
|
+
private getRecoveredPointerHref;
|
|
26
|
+
private getRecoveredHoverHref;
|
|
27
|
+
private isAnotherNavigationRuntimeActive;
|
|
28
|
+
private getDocumentOwner;
|
|
29
|
+
private adoptDocumentOwner;
|
|
30
|
+
private syncDocumentElementAttributes;
|
|
31
|
+
private reloadDocument;
|
|
32
|
+
/**
|
|
33
|
+
* Commits a fully fetched document into the live page.
|
|
34
|
+
*
|
|
35
|
+
* When browser-router accepts a handoff from another runtime, it delays source
|
|
36
|
+
* runtime cleanup until the incoming document has been prepared and is ready to
|
|
37
|
+
* commit. That ordering avoids the blank-page window we previously hit when a
|
|
38
|
+
* delegated navigation went stale after the source runtime had already torn
|
|
39
|
+
* itself down.
|
|
40
|
+
*/
|
|
41
|
+
private commitDocumentNavigation;
|
|
18
42
|
/**
|
|
19
43
|
* Starts the router and begins intercepting navigation.
|
|
20
44
|
*
|
|
@@ -51,6 +75,8 @@ export declare class EcoRouter {
|
|
|
51
75
|
* Uses `event.composedPath()` to correctly detect clicks on anchors inside
|
|
52
76
|
* Shadow DOM boundaries (Web Components).
|
|
53
77
|
*/
|
|
78
|
+
private handlePointerDown;
|
|
79
|
+
private handleHoverIntent;
|
|
54
80
|
private handleClick;
|
|
55
81
|
/**
|
|
56
82
|
* Handles browser back/forward navigation.
|
|
@@ -62,6 +88,8 @@ export declare class EcoRouter {
|
|
|
62
88
|
* Cross-origin navigation always falls back to full page reload.
|
|
63
89
|
*/
|
|
64
90
|
private isSameOrigin;
|
|
91
|
+
private cancelNavigationTransaction;
|
|
92
|
+
private beginNavigationTransaction;
|
|
65
93
|
/**
|
|
66
94
|
* Executes the core navigation flow.
|
|
67
95
|
*
|
|
@@ -91,6 +119,12 @@ export declare class EcoRouter {
|
|
|
91
119
|
}
|
|
92
120
|
/**
|
|
93
121
|
* Creates and starts a router instance.
|
|
122
|
+
*
|
|
123
|
+
* Stops the previously active router (if any) before creating a new one so
|
|
124
|
+
* click listeners and coordinator registrations from earlier instances are
|
|
125
|
+
* cleaned up on re-execution (e.g. when the layout script is re-run via
|
|
126
|
+
* `data-eco-rerun` after a browser-router page commit).
|
|
127
|
+
*
|
|
94
128
|
* @param options - Configuration options for the router
|
|
95
129
|
* @returns A started EcoRouter instance
|
|
96
130
|
*/
|