Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/en/apis/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@

APIs for Lynx bundles running inside Sparkling containers.

| API | Description |
| --- | --- |
| [GlobalProps](/apis/global-props/Interface.GlobalProps) | Runtime globals injected by the native SDK (`lynx.__globalProps`) |
| [Scheme](/apis/scheme) | The unified `hybrid://...` URL format for opening pages/containers |
| [Navigation](/apis/sparkling-methods/sparkling-navigation) | Router helpers for opening/closing pages from Lynx/JS |
| [Storage](/apis/sparkling-methods/sparkling-storage) | Key-value storage helpers for Lynx/JS |
| [Media](/apis/sparkling-methods/sparkling-media) | Media helpers for choosing, uploading, downloading, and saving files |
| API | Description | Web |
| --- | --- | --- |
| [GlobalProps](/apis/global-props/Interface.GlobalProps) | Runtime globals injected by the native SDK (`lynx.__globalProps`) | - |
| [Scheme](/apis/scheme) | The unified `hybrid://...` URL format for opening pages/containers | - |
| [Navigation](/apis/sparkling-methods/sparkling-navigation) | Router helpers for opening/closing pages from Lynx/JS | Yes |
| [Storage](/apis/sparkling-methods/sparkling-storage) | Key-value storage helpers for Lynx/JS | Yes |
| [Media](/apis/sparkling-methods/sparkling-media) | Media helpers for choosing, uploading, downloading, and saving files | Yes |

## Sparkling SDK

Expand Down
30 changes: 28 additions & 2 deletions docs/en/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ npx sparkling build
| Option | Description |
| --- | --- |
| `--config <path>` | Path to `app.config.ts` (default: `app.config.ts`) |
| `--platform <platform>` | Target platform: `android`, `ios`, `web`, or `all` (default: `all`) |
| `--copy` | Copy built assets to Android and iOS native shells |
| `--skip-copy` | Skip copying assets (default) |

By default, asset copying is skipped for faster iteration during development. Use `--copy` when you need the bundles inside the native projects (e.g. for a release build).

When `--platform web` is specified, only web bundles (`*.web.bundle`) are produced. This is faster than building all environments.

### `sparkling dev`

Start the Rspeedy dev server for hot-reload development. Instead of rebuilding and copying bundles manually, the dev server serves bundles over HTTP so changes are reflected instantly.
Expand All @@ -41,6 +44,7 @@ npx sparkling dev
| Option | Description |
| --- | --- |
| `--config <path>` | Path to `app.config.ts` (default: `app.config.ts`) |
| `--platform <platform>` | Target platform: `web`, `native`, or `all` (default: `all`) |
| `--port <number>` | Dev server port (default: `app.config.ts -> dev.server.port`, fallback `5969`) |
| `--host <host>` | Dev server host (default: `app.config.ts -> dev.server.host`, then Rspeedy default) |

Expand Down Expand Up @@ -81,12 +85,13 @@ npx sparkling autolink

| Option | Description |
| --- | --- |
| `--platform <platform>` | Platform to autolink: `android`, `ios`, or `all` (default: `all`) |
| `--platform <platform>` | Platform to autolink: `android`, `ios`, `web`, or `all` (default: `all`) |

**What it does:**

- **Android** — Links Sparkling method Gradle projects and generates `SparklingAutolink.kt`. Debug-tool packages are linked as `debugImplementation`.
- **iOS** — Links Sparkling method pods and generates `SparklingAutolink.swift`. Debug-tool packages are linked in the debug target.
- **Web** — Reads `web` entries (or `platforms` containing `web`) from each `module.config.json` and generates a `web-autolink.ts` file that imports all `*/web` method handlers.

### `sparkling run:android`

Expand Down Expand Up @@ -140,6 +145,24 @@ This command will:

You can also set the `SPARKLING_IOS_SIMULATOR` environment variable to specify a default simulator.

### `sparkling run:web`

Build web bundles and launch a browser preview.

```bash
npx sparkling run:web
```

This command will:

1. Build web bundles (`*.web.bundle`)
2. Start the `sparkling-web-shell` dev server
3. Open the app in your default browser at `http://localhost:3000`

Use `?page=<name>` query parameters to navigate to different entry points (e.g. `http://localhost:3000?page=second`).

For more details, see the [Web Platform Guide](/guide/web-platform).

### `sparkling doctor`

Verify that your development environment is properly set up.
Expand Down Expand Up @@ -196,6 +219,9 @@ npx sparkling run:android
# 5. Run on iOS
npx sparkling run:ios

# 6. Build bundles for release
# 6. Preview in browser
npx sparkling run:web

# 7. Build bundles for release
npx sparkling build --copy
```
11 changes: 11 additions & 0 deletions docs/en/guide/get-started/create-custom-method.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,17 @@ npx sparkling autolink

</Steps>

## Add web support (optional)

You can add a web implementation so your method works in the browser. See the full guide at [Web Method Implementations](/guide/web-method-implementations).

In brief:

1. Create `src/web/index.ts` with `registerWebMethod()` calls
2. Add `"./web"` subpath export to `package.json`
3. Add `"web"` to `platforms` in `module.config.json`
4. Run `npx sparkling autolink`

## Best practices

- **Naming convention**: package name should follow `sparkling-<module>` format.
Expand Down
7 changes: 5 additions & 2 deletions docs/en/guide/get-started/create-new-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ npm create sparkling-app@latest my-app
cd my-app
```

### Run native targets
### Run on any platform

```bash
# Android
npm run run:android

# iOS
npm run run:ios

# Web (browser preview)
npm run run:web
```

### Add Sparkling methods as needed
Expand Down Expand Up @@ -56,7 +59,7 @@ Key folders/files created by the default template:
- `src/`: Lynx/React entry points and assets
- `android/`, `ios/`: native shells wired to Sparkling SDK
- `app.config.ts`: build + routing config consumed by `sparkling-app-cli`
- `package.json`: scripts (`dev`, `build`, `run:android`, `run:ios`)
- `package.json`: scripts (`dev`, `build`, `run:android`, `run:ios`, `run:web`)

### Prerequisites

Expand Down
115 changes: 115 additions & 0 deletions docs/en/guide/nuxt-sparkling-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Nuxt on Sparkling: routing feature compatibility

This is the acceptance traversal for driving Sparkling native navigation
from Nuxt's file-based routing. Every Nuxt routing feature is walked once
and classified:

- ✅ **Supported** — works, with a ported test proving it.
- ⚠️ **Degraded** — works with a caveat inherent to the MPA model; the
build emits a diagnostic.
- ❌ **Unsupported** — cannot work under MPA; the reason is structural.

The classification is enforced in code: `pagesToSparklingManifest()`
returns a `diagnostics[]` list, and the Nuxt module prints a per-app
support report at build time (`analyzePages()`).

## The one constraint everything follows from

In VueLynx's SPA model, one LynxView holds one JS heap and Vue Router
navigates in memory. Here we drive **Sparkling native navigation**: each
page is a separate LynxView with a **separate JS heap**. There is no shared
memory across pages, so:

> A URL cannot be resolved by an in-memory route table that another page
> holds. It must be resolved from **build-time metadata** (the route
> manifest) embedded in every bundle.

Every "degraded" or "unsupported" verdict below traces back to this: state,
component instances, and outlets **cannot cross a JS heap**. Navigation
(opening another page) always can.

## Verification method

1. **Ported Nuxt fixtures.** Nuxt's official `packages/nuxt/test/pages.test.ts`
(v4.4.8) `pageTests` are the ground truth for file → route generation.
The exact `NuxtPage[]` trees Nuxt produces are fed through our
`pagesToSparklingManifest()` and asserted
(`packages/nuxt-sparkling/__tests__/nuxt-pages.spec.ts`, 12 cases
spanning every routing pattern).
2. **Runtime matching.** The generated manifest is matched against real
URLs via `matchSparklingRoute()` to prove params extract correctly.
3. **End-to-end.** Real Vue Router runs over `web-navigation-shim` + the
Sparkling host over the real `NativeModules.spkPipe` bridge
(`integration.spec.ts`): deep-link boot, cross-bundle `router.open`
handoff, native `router.close`, external `webview` handoff.

## Feature-by-feature

### Route generation (file → route)

| Feature | Verdict | Evidence / notes |
|---|---|---|
| `index.vue` → `/`, nested `parent/index.vue` → `/parent` | ✅ | flattened to absolute paths |
| Static routes (`about.vue`, `snake_case`, `kebab-case`) | ✅ | one bundle per route |
| Dynamic `[id]` → `:id()` | ✅ | whole-segment param, matches at runtime |
| Regex param `[id(\d+)]` | ✅ | custom regex preserved in manifest & matcher |
| Catch-all `[...slug]` → `:slug(.*)*` | ✅ | matches `/a/b/c` → `slug: ['a','b','c']` |
| Catch-all in middle (`[...id]/suffix`) | ✅ | matches `/a/b/suffix` → `id: ['a','b']`; the matcher splits segments paren-aware so the slash inside the param regex is respected |
| Optional `[[id]]` → `:id?` | ✅ | matches present & absent |
| Nested dynamic (`[bar]/index`, `nonopt/[slug]`) | ✅ | absolute-path flattening |
| Route groups `(foo)/…` | ✅ | folder dropped from URL; `groups` carried in `meta` |
| Unicode / encoded paths (`测试.vue`) | ✅ | Nuxt-encoded paths passed through and matched |
| Colliding names, hyphen edge cases | ✅ | Nuxt's `name` reused as the unique manifest entry |
| Mixed literal+param in one segment (`prefix-[[opt]]`, `route-[slug]`, `[[opt]]-postfix`, `[b2]_[2b]`) | ⚠️ | Native open works; the runtime matcher only matches whole-segment params, so in-page matching of this route is best-effort. Diagnostic: `mixed-segment`. |
| Malformed (`[slug.vue`, `[].vue`) | ✅ (as error) | Nuxt throws during scan before we see it; parity preserved |

### Navigation at runtime

| Feature | Verdict | Notes |
|---|---|---|
| `navigateTo('/other')` across bundles | ✅ | becomes `router.open(hybrid://…?bundle=other.lynx.bundle&__path=/other)` |
| `<NuxtLink to="/other">` | ✅ | RouterLink → `router.push` → shim `location.assign` → native open |
| `router.push` / `replace` across bundles | ✅ | replace maps to `router.open` with `replace: true` |
| Deep-link / initial route | ✅ | page boots at its URL, reconstructed from the `__path` container query item |
| External URL (`navigateTo(url, { external })`, `<NuxtLink>` off-origin) | ✅ | routed through the manifest's `externalScheme` (webview container) |
| `window.open(url)` | ✅ | host `open(url, { newWindow })` → webview |
| Hardware / UI back at page root | ✅ | `history.back()` at the bottom of the local stack → `router.close` (native pop); previous page's heap resumes with state intact |
| In-page back/forward, hash nav (within one bundle) | ✅ | full History API when `mode: 'web'`; `popstate` semantics verified against real Vue Router |
| `definePageMeta({ middleware })` (route middleware) | ✅ | runs in the target page's heap after boot; global middleware must be duplicated per page (build concern) |
| Static/string `redirect` in `definePageMeta` | ⚠️ | encoded into manifest `meta.redirect`; a host can resolve it before open. Diagnostic: `redirect`. |
| Function `redirect` | ⚠️ | only evaluable once the target page's JS runs; redirect happens after boot, not before the native open |

### Layout / outlet features

| Feature | Verdict | Notes |
|---|---|---|
| Single leaf page per URL | ✅ | the common case |
| Nested route with parent `<NuxtPage>` outlet (`parent.vue` + `parent/child.vue`) | ⚠️ | Child navigates as its own native page. The parent wrapper **is not kept mounted** across children (separate heaps), so a persistent parent layout/state is lost. Fix: duplicate shared layout into each child bundle (build), or express the wrapper as a native container. Diagnostic: `nested-outlet`. |
| `<NuxtPage>` used purely as an index outlet (`page1.vue` + `page1/index.vue`) | ✅ | the index child owns `/page1`; the wrapper collapses cleanly |
| Named views (`<NuxtPage name="sidebar">`) | ❌ | MPA has one native page = one outlet per URL. Multiple simultaneous named views cannot be rendered across heaps. Restructure as separate routes/containers. |
| `<NuxtLayout>` (app-level layouts) | ⚠️ | works within a single page's render, but a layout is not a *shared live instance* across pages — each page re-instantiates it. Fine for presentation, not for cross-page live state. |
| Client-only in-place transitions between routes (`<NuxtPage>` transitions) | ❌ across pages | cross-page transitions are native (Sparkling animation), not Vue `<Transition>`; in-page transitions still work |

### State / data (navigation-adjacent)

| Feature | Verdict | Notes |
|---|---|---|
| `useState` shared across pages | ❌ | separate heaps; use `sparkling-storage` or pass via URL/scheme params. See the further-portability doc. |
| Passing data between pages | ✅ | via route params / query (carried in `__path`) or `sparkling-storage` |
| `useRoute()` / `useRouter()` within a page | ✅ | standard Vue Router composables over the shim |
| `useRequestURL()` | ✅ | reads `location.href` from the shim |

## Summary

Nuxt's **routing surface** — file-based routes (all naming conventions),
dynamic/catch-all/optional params, route groups, `navigateTo`/`<NuxtLink>`/
programmatic navigation, deep links, external links, and hardware back — is
**fully supported** on Sparkling native navigation through the shim + route
manifest.

The features that **degrade or are unsupported** all reduce to one cause:
constructs that require a **single shared JS heap** — persistent nested
outlets, named views, cross-page `useState`, in-place cross-page
transitions. These are exactly the SPA assumptions the MPA model trades
away for real multi-page native navigation, and the build reports each one
so authors can restructure or accept the caveat deliberately.
80 changes: 80 additions & 0 deletions docs/en/guide/nuxt-sparkling-further-portability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Beyond routing: other Nuxt subsystems on Lynx + Sparkling

The navigation work (shim + manifest + module) makes Nuxt's **routing**
run on Sparkling native navigation. This document surveys the *rest* of
Nuxt and classifies each subsystem for portability into Lynx + Sparkling,
so follow-up work can be prioritized.

Classification:

- **A — Portable as-is**: pure build-time or pure-JS; nothing DOM/server.
- **B — Portable behind a shim**: needs a Web/host API the shim or a
Sparkling method can provide.
- **C — Remap to native**: the concept exists but must be backed by a
native capability (container config, storage) instead of the DOM.
- **D — Not applicable**: SSR/server-render/HTML-head concepts with no
device analogue (often better kept as a real backend the app calls).

The recurring constraint is the same as for routing: **each Sparkling page
is an isolated JS heap**, so anything Nuxt shares "across the app" on the
client (state, a live layout instance, the payload) does not automatically
span pages.

## A — Portable as-is

| Subsystem | Notes |
|---|---|
| **Auto-imports** (`unimport`) | Build-time transform; produces plain imports. Works unchanged in the rspeedy/Lynx build. |
| **`useRuntimeConfig`** | Values injected at build; a plain object at runtime. Portable. |
| **`definePageMeta` / route meta** | Extracted at build; already carried into the Sparkling manifest `meta`. Portable. |
| **`useRoute` / `useRouter` / `navigateTo`** | Vue Router composables over the shim. Covered by the navigation work. |
| **Pure composables / utils** (`useId`, `useState` *within a page*, app config) | Plain reactive JS; no DOM. Portable. |
| **`<ClientOnly>`** | On Lynx everything is the "client" — becomes a transparent passthrough (the server branch never runs). |

## B — Portable behind a shim

| Subsystem | What it needs | Path |
|---|---|---|
| **`$fetch` / `useFetch` / `useAsyncData`** | a global `fetch` | Lynx 3.x exposes a `fetch`; where absent, shim `fetch` over a Sparkling network method. The composable *caching/dedupe* layer is pure JS and portable. SSR **payload extraction** does not apply (no server render) — data is always fetched on device. |
| **Route middleware** (`defineNuxtRouteMiddleware`, `definePageMeta({ middleware })`) | vue-router guards | Runs inside the target page's heap after boot. Portable per-page. **Global** middleware must be duplicated into each bundle by the build (no shared app instance to register it once). |
| **Nuxt plugins** (`defineNuxtPlugin`) | app-init hook | Portable if the plugin is DOM/server-free. Each page runs its own plugin set (no shared app), so "install once" plugins become "install per page". |
| **`useError` / `error.vue`** | an error route | Portable as a normal page; `showError`/`clearError` work in-heap. Cross-page error propagation is native (open an error page). |
| **`useNuxtApp`** | the app context object | Portable; scoped to the page's heap. |

## C — Remap to native

| Subsystem | DOM behavior | Native remap |
|---|---|---|
| **`useHead` / `useSeoMeta`** | mutates `document.head` (title, meta, links) | No HTML head on device. Map the meaningful subset to the **container scheme**: `title` → nav-bar title, theme color → `nav_bar_color`, etc. (`sparkling-navigation` already passes these as scheme params). The rest (SEO meta, `<link>`) is inert on device. |
| **`useState` across pages** | one heap in SSR/CSR | Within a page: a plain `ref`, portable. Across pages: no shared heap — bridge through **`sparkling-storage`** (persist on navigate, rehydrate on boot) or pass via route params in the manifest `__path`. Highest-value follow-up: a `useSparklingState` that transparently persists. |
| **`useCookie`** | `document.cookie` | Map to `sparkling-storage` (or a cookie-jar method) keyed like cookies. |
| **App layouts / `<NuxtLayout>`** | one live instance wrapping pages | Rendered per page (presentation portable via VueLynx), but **not a shared live instance**. Shared layout state must go through storage or a native container; a persistent chrome (tab bar, nav bar) is better expressed as a **native Sparkling container** hosting the LynxViews. |
| **Page transitions** | Vue `<Transition>` on the outlet | Cross-page transitions are **native** Sparkling animations (scheme/animated option). In-page transitions still use Vue. |

## D — Not applicable (keep as backend, or no device analogue)

| Subsystem | Why | Recommendation |
|---|---|---|
| **Nitro server routes** (`server/api`, `server/routes`) | a Node/edge server runtime | Don't port to device. Run Nitro as the app's **real backend**; the device calls it via `$fetch`. This is the natural split. |
| **SSR / payload / hydration** (`useHydration`, `<NuxtPayload>`, island/server components) | server-render then hydrate HTML | No HTML render on device. Not applicable; data is fetched on device (category B). |
| **`useRequestHeaders` / `useRequestEvent` / `useRequestURL` (server branch)** | the server request | Server-only. `useRequestURL` *client* branch works over the shim (category A/B). |
| **`preloadComponents` / chunk prefetch / `emitRouteChunkError`** | HTTP code-splitting & chunk reload | Bundles are native assets, not HTTP chunks. Disabled by the module; native bundle preloading is a Sparkling concern, not Nuxt's. |
| **`<NuxtImg>` / image CDN**, DevTools, Nuxt UI DOM widgets | browser DOM / build integrations | Rendering is VueLynx's domain, not this navigation layer. Out of scope here. |

## Suggested follow-up order

1. **`useSparklingState`** — the biggest ergonomic win: `useState`-shaped
API that persists to `sparkling-storage` on navigate and rehydrates on
boot, giving the *feel* of shared state across the MPA.
2. **`$fetch` shim** — confirm/adopt Lynx's `fetch`; wire `useFetch`/
`useAsyncData` (pure caching layer is already portable). Unlocks most
real apps.
3. **`useHead` → container scheme** — map title/theme to nav-bar/container
params so `useHead({ title })` "just works" as native chrome.
4. **Global middleware & plugin duplication** — a build step that injects
app-level middleware/plugins into each page bundle so "register once"
semantics hold across the MPA.

None of these are blocked by the navigation architecture; each is an
additive shim or build step on top of the same layering
(framework → shim → `NavigationHost` → Sparkling).
Loading