191 lines
9.3 KiB
Markdown
191 lines
9.3 KiB
Markdown
# Native Vue Router
|
|
|
|
Native Vue Router is a gesture-first navigation runtime for Vue 3 and Vue Router 5. It keeps Vue Router in charge of matching, URLs, guards, and history while rendering live route stacks that can be manipulated interactively.
|
|
|
|
The repository includes a reusable headless core, a platform-adaptive visual preset, Capacitor and Electron adapters, and one messaging demo delivered as a PWA and through native hosts.
|
|
|
|
## Install the routeless core-v2 experiment
|
|
|
|
The repository root is an installable facade for
|
|
`@native-vue-router/core-v2`. npm builds the package from source when it installs
|
|
the Git dependency:
|
|
|
|
```bash
|
|
npm install git+https://git.harvmaster.com/Harvmaster/Native-Router-Vue.git
|
|
```
|
|
|
|
Import it by its package name and include the compositor stylesheet:
|
|
|
|
```ts
|
|
import {
|
|
OriginScene,
|
|
createOriginScene,
|
|
gesture,
|
|
originView,
|
|
} from "@native-vue-router/core-v2";
|
|
import "@native-vue-router/core-v2/style.css";
|
|
```
|
|
|
|
Pin production applications to a commit or tag:
|
|
|
|
```bash
|
|
npm install "git+https://git.harvmaster.com/Harvmaster/Native-Router-Vue.git#<commit-or-tag>"
|
|
```
|
|
|
|
Core-v2 is routeless and does not depend on Vue Router. Its usage guide and API
|
|
are in [packages/core-v2/README.md](packages/core-v2/README.md) and
|
|
[packages/core-v2/API.md](packages/core-v2/API.md).
|
|
|
|
### Codex skill
|
|
|
|
The Git package also includes
|
|
[`build-with-native-vue-router-v2`](skills/build-with-native-vue-router-v2/SKILL.md),
|
|
a Codex skill for creating and integrating routeless core-v2 applications.
|
|
After installing the dependency, copy the skill into Codex's skill directory:
|
|
|
|
```bash
|
|
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills"
|
|
cp -R \
|
|
node_modules/@native-vue-router/core-v2/skills/build-with-native-vue-router-v2 \
|
|
"${CODEX_HOME:-$HOME/.codex}/skills/"
|
|
```
|
|
|
|
Restart or reload Codex if necessary, then invoke it explicitly with
|
|
`$build-with-native-vue-router-v2`.
|
|
|
|
## What works
|
|
|
|
- Interactive edge pop that can be held indefinitely at any progress.
|
|
- Ordered horizontal route paging with replace-by-default history.
|
|
- Component-originated route dragging with a live target route.
|
|
- Interactive push, adjacent-page sibling slide, modal, safe-area-contained content/snap-point sheets with scroll-boundary handoff, fade, and application-defined presentations.
|
|
- Concurrent `from` and `to` routes using only public Vue Router 5 APIs.
|
|
- Guarded commits: previews do not alter the URL, and rejected navigation springs back.
|
|
- Cold-start predictive back through declared parent routes.
|
|
- Bounded live view caching, nested router views, focus isolation, RTL, and reduced motion.
|
|
- PWA, Electron, and Capacitor iOS/Android hosts.
|
|
|
|
## Run it
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
The default app is the installable messaging PWA. Other useful commands:
|
|
|
|
```bash
|
|
npm run build # packages, declarations, demo, and service worker
|
|
npm test # core transaction tests
|
|
npm run test:e2e # desktop and mobile Playwright projects
|
|
npm run electron # build and launch the Electron host
|
|
npm run cap:sync # build and synchronize iOS and Android projects
|
|
```
|
|
|
|
Native projects live under `apps/capacitor/ios` and `apps/capacitor/android`. Open or run them from `apps/capacitor` with `npx cap open ios`, `npx cap open android`, or `npx cap run <platform>`.
|
|
|
|
### Test the installed iOS PWA
|
|
|
|
Run the normal `npm run dev` command, expose its printed network address through an HTTPS URL, and open that URL on the iPhone. The development server includes the PWA service worker and already listens on the local network. Safari still requires a secure context for the service worker; a plain LAN `http://` address is not sufficient. Choose **Share → Add to Home Screen**, then launch **NVR Messenger** from its Home Screen icon.
|
|
|
|
The Navigation Lab reports `Standalone`, `ready`, and `App reserved` when the correct environment is active, and shows the exact build ID plus update-check count. Production builds check for updates whenever the app starts, returns to the foreground, regains connectivity, or has been open for a minute; activation reload waits for any live gesture to finish. Open a conversation and drag from the extreme left edge. The “Leading-edge touches claimed” counter should increment while the router renders its live predictive-back view.
|
|
|
|
For more aggressive lifecycle testing, open **You → Runtime stress lab**. It is a deeper route that keeps the primary tab bar, opts into push-style sibling history, exposes its mount lifetime, and renders a one-second async child through `<Suspense>`. Backing out evicts this pushed screen after its exit; browser Forward reconstructs it and shows the fallback again. To exercise a guard against an already-mounted destination, visit **Stories**, switch to **You**, enable **Block cached Stories re-entry**, and try returning to Stories. The guard rejects and evicts the cached view.
|
|
|
|
An installed web app cannot access `WKWebView.allowsBackForwardNavigationGestures`. The demo therefore reserves leading-edge touch sequences at the web-content boundary as an iOS standalone-only safeguard. Capacitor remains the deterministic option when native-level gesture suppression is required.
|
|
|
|
## Minimal integration
|
|
|
|
```ts
|
|
import { createApp } from "vue";
|
|
import { createRouter, createWebHistory } from "vue-router";
|
|
import { createNativeRouter } from "@native-vue-router/core";
|
|
|
|
const router = createRouter({
|
|
history: createWebHistory(),
|
|
routes: [
|
|
{ path: "/", component: Home },
|
|
{
|
|
path: "/chat/:id",
|
|
component: Chat,
|
|
meta: {
|
|
native: { presentation: "push", parent: "/", gesture: "edge" },
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const nativeRouter = createNativeRouter({ router });
|
|
createApp(App).use(router).use(nativeRouter).mount("#app");
|
|
```
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import {
|
|
NativeGestureLink,
|
|
NativeNavigator,
|
|
NativeRouterView,
|
|
} from "@native-vue-router/core";
|
|
</script>
|
|
|
|
<template>
|
|
<NativeNavigator :siblings="['/', '/stories', '/profile']">
|
|
<NativeRouterView />
|
|
</NativeNavigator>
|
|
<NativeGestureLink to="/chat/maya" presentation="reveal">
|
|
Drag this row into the chat route
|
|
</NativeGestureLink>
|
|
</template>
|
|
```
|
|
|
|
Import `@native-vue-router/core/style.css` for the built-in presentation layers. The demo imports `@native-vue-router/preset-native/style.css` as well.
|
|
|
|
Use `nativeRouter.sibling(to)` for tab or peer-route navigation. Direction is derived from `siblingOrder`, repeated navigation to the active route is a no-op, and `siblingHistory: 'replace'` keeps cached tab views out of the back stack.
|
|
|
|
Sibling views are lazy rather than pre-mounted: only the initial route exists on startup, and a sibling joins the bounded cache on its first visit or interactive preview. Route metadata accepts `cache: false` to opt out or `cache: 'pin'` for views that must survive ordinary trimming. `useNativeViewLifecycle()`, the `onNativeView*` hooks, and `useNativeViewActiveEffect()` let cached screens pause polling, media, or subscriptions while retaining their local UI state.
|
|
|
|
Call `nativeRouter.unload('/some-route')` to manually unmount inactive instances of one location while retaining their lightweight history descriptors. The active route and views participating in a transition are protected.
|
|
|
|
### Capture frame pacing on a real device
|
|
|
|
The Navigation Lab contains an opt-in profiler. Tap **Start profiling**, leave the lab, reproduce the choppy navigation once or twice, return to the lab, tap **Stop**, then **Share JSON**. Installed iOS PWAs use the system share sheet; other browsers download the file. Attach that JSON to a bug report.
|
|
|
|
The core API is also available directly:
|
|
|
|
```ts
|
|
import { createNativeNavigationProfiler } from "@native-vue-router/core";
|
|
|
|
const profiler = createNativeNavigationProfiler(nativeRouter, {
|
|
metadata: { build: import.meta.env.VITE_BUILD_ID },
|
|
});
|
|
|
|
profiler.start();
|
|
// Reproduce the navigation issue.
|
|
const report = profiler.stop();
|
|
const json = profiler.toJSON(report);
|
|
```
|
|
|
|
No rAF loop or browser performance observer runs before `start()`, and `stop()` removes them. Reports contain frame intervals, refresh-rate estimates, per-navigation timing, cold-mount preparation, route loading, cache eviction, visibility changes, and browser-supported Long Task/layout-shift/resource timing. Route params, query values, and application state are omitted.
|
|
|
|
## Packages
|
|
|
|
- `@native-vue-router/core` — transactions, route ledger, concurrent views, gestures, caching, and public components/composables.
|
|
- `@native-vue-router/preset-native` — adaptive tab/back controls, safe-area CSS, and platform motion defaults.
|
|
- `@native-vue-router/capacitor` — hardware back, deep links, pause cancellation, root exit, and haptics.
|
|
- `@native-vue-router/electron` — Chromium history-gesture suppression and renderer back/forward bridging.
|
|
|
|
Design and engineering documentation:
|
|
|
|
- [Complete installation and usage guide](usage.md)
|
|
- [How it works and why the pattern is uncommon](docs/how-it-works.md)
|
|
- [Engineering challenges, Vue Router limitations, and trade-offs](docs/challenges-and-tradeoffs.md)
|
|
- [Core principles, scalability, and flexibility](docs/principles-and-scalability.md)
|
|
- [Architecture reference](docs/architecture.md)
|
|
- [Platform integration reference](docs/platforms.md)
|
|
|
|
## Support contract
|
|
|
|
The target is Vue 3.5+ and Vue Router 5. Installed PWAs, current Electron, and Capacitor 8 are first-class. Normal browser tabs remain functional but browsers can reserve edge gestures that page content cannot consistently override.
|
|
|
|
MIT
|