Add web platform support with navigation shim and Nuxt integration - #2
Draft
Huxpro wants to merge 9 commits into
Draft
Add web platform support with navigation shim and Nuxt integration#2Huxpro wants to merge 9 commits into
Huxpro wants to merge 9 commits into
Conversation
Ports the experimental web platform support from tiktok#22 (sparkling-web-shell, web method bridge/registry, method web implementations, CLI web autolink/build/dev/run:web/doctor support, create-app --web option, template + playground web environments, docs) onto the current main, adapting for changes that landed since: - autolink: coexists with Lynx library autolink (devtool modules, maven deps); web autolink keeps reading platforms/web keys from module.config.json - dev: --platform web|native|all maps to rspeedy --environment on top of the new config-watching restart loop - create-app: askWebPlatform rewritten with @clack/prompts (inquirer was removed on main) - playground: web/lynx environments layered over lynx.shared.config.ts of the 13-page demo app - website: Web Platform sidebar entries in current rspress structure - sparkling-media module.config.json: platforms/web keys on top of the simplified config format Verified end-to-end: playground web bundles build, web shell renders main page in Chromium, router.open navigates across pages (?page=nav-chain) and browser back returns.
Bugfix for the original web support (upstream PR #22), kept as a separate commit so it can be stacked as its own PR: - sparkling-web-shell was marked private with a stale version while the app template depends on it from the registry, so 'run:web' could never resolve the shell in a created app. Make it publishable and align the version with the workspace. - repository.url pointed at a personal fork; point it at tiktok/sparkling. - move @rsbuild/core from devDependencies to dependencies: run:web executes 'rsbuild dev' inside the installed package, where devDependencies are never installed for consumers. - declare published files (src, rsbuild.config.ts, tsconfig.json) — the shell runs from source via rsbuild.
Bugfix for the original web support (upstream PR #22), kept as a separate commit so it can be stacked as its own PR: 'sparkling run:web --port N' and '--no-open' pass PORT/BROWSER env variables to the shell, but rsbuild reads neither, so the flags were silently ignored and the server always started on 4200. Read them in rsbuild.config.ts (verified: PORT=4300 binds to 4300).
…vigationHost contract Framework-agnostic package implementing the Web history/location/popstate surface on top of a minimal NavigationHost contract (initialUrl, open, close, optional go/reload/isSameDocument). This is the layer that lets web routing frameworks (vue-router/Nuxt today, others tomorrow) run inside non-browser JS contexts (Lynx pages) while cross-document navigations are delegated to the platform's real page stack (Sparkling native navigation) — the MPA model, not an in-memory SPA router. Semantics implemented per the Nuxt 4 / vue-router 5 browser-API audit: synchronous event-free pushState/replaceState with forward truncation, async go() with exactly one popstate per call (location/state updated before listeners), out-of-range no-op at the top, native traversal when crossing the bottom of the local stack, hash-only same-document policy (host-overridable via route-manifest policy), pagehide/visibilitychange scroll-persistence path, and boot stubs (document gate, navigator, rAF, scroll) behind a non-clobbering install(). Includes MemoryDocumentStack: an in-heap reference host modeling a native stack of isolated documents for tests/SSR. Verified by 37 tests in plain Node (no jsdom), including integration tests running real vue-router 5 createWebHistory entirely on the shim: boot, two-write push protocol, back/forward, guard rollback via go(-delta), and the manifest-guard MPA handoff pattern.
Companion to the web platform port: the /web method handlers added by upstream PR #22 shipped with no unit tests, which drops the sparkling-navigation/storage/media coverage gates below threshold once those files are collected. Add node jest tests that mock the browser globals (window.history, localStorage, file input, fetch/FormData) and exercise every handler branch, restoring the packages above their coverage thresholds. Kept separate so it can stack onto the web-support PR.
… manifest Adds the Sparkling implementation of web-navigation-shim's NavigationHost contract, plus the route-manifest format that connects file-based routes to Lynx bundles across isolated JS heaps (the MPA model). - manifest.ts: SparklingRouteManifest format + a vue-router-style path matcher (static/param/optional/splat segments, specificity ordering), bundle resolution, and reverse bundle→route lookup. - shim-host/index.ts: createSparklingNavigationHost() maps in-app URLs to router.open scheme calls (carrying the full app URL in a __path query item so a freshly-opened page can seed its own shim location), routes off-origin URLs through the webview container scheme, backs close()/ reload() on router.close, and implements isSameDocument (hash-only by default; optionally same-Lynx-bundle for SPA-inside-MPA hybrids). - deriveInitialUrl() reconstructs a page's initial app URL from container query items (__path, then bundle/url lookup, then base). Exposed via the sparkling-navigation/shim-host subpath export. This is the only Sparkling-aware layer: the shim above it and the methods below it stay platform-agnostic. Verified by 26 new jest tests (manifest matching + host behavior); sparkling-navigation coverage stays above threshold.
The Nuxt integration layer that ties the shim + host to Nuxt's file-based routing, per the MPA (multi-page, isolated-heap) model. - manifest.ts: pagesToSparklingManifest() flattens Nuxt's nested NuxtPage[] tree (as delivered to pages:extend) into a flat Sparkling route manifest — each URL maps to exactly one bundle, nested <NuxtPage> outlets collapse (index child wins its parent URL), and every construct that cannot cross a JS heap is reported as a diagnostic (degraded vs blocking). - module.ts: createNuxtSparklingModule() hooks pages:extend to emit the manifest as a virtual module, prints a per-feature support report, and disables browser-only Nuxt subsystems (emitRouteChunkError, appManifest, navigationRepaint, restoreState). analyzePages() exposes the same analysis for tooling/tests. - runtime/history.ts: sparklingHistory() installs the shim over the Sparkling host and returns a vue-router history (web or memory); sparklingScrollBehavior() returns false so vue-router never touches a DOM viewport. Also fixes web-navigation-shim install() to tolerate read-only host globals (e.g. Node's navigator getter) instead of throwing, and points its package exports at the composite-build dist/src output. Verified: ports Nuxt's official pages.test.ts fixtures (v4.4.8) through the generator (12 cases across every routing pattern) and an end-to-end integration test running real vue-router over the real NativeModules.spkPipe bridge — deep-link boot, cross-bundle router.open handoff, native router.close, external webview handoff. 15 tests.
Task deliverable: the acceptance traversal answering, per Nuxt routing
feature, whether it is supported / degraded / unsupported under the MPA
model, with rationale traced to the isolated-JS-heap constraint.
- docs/en/guide/nuxt-sparkling-compat.md: feature-by-feature verdicts
(route generation, runtime navigation, layout/outlet, state), the
verification method (ported Nuxt fixtures + runtime matching + e2e),
and the summary that Nuxt's whole routing surface is supported while
the degraded/unsupported cases all reduce to shared-heap requirements.
- docs/en/guide/nuxt-web-api-dependencies.md: the Web history/location/
popstate/URL dependency inventory behind web-navigation-shim, with the
minimum NavigationHost contract for web vs memory history.
- website sidebar: new 'Nuxt (MPA)' section linking both.
Also fixes a real matcher bug surfaced while writing the report: the
sparkling-navigation route matcher split paths on every '/', mangling a
mid-path catch-all whose custom regex body contains a slash
(Nuxt's [...id]/suffix → /:id([^/]*)*/suffix). Segment splitting is now
parenthesis-depth aware; regression test added ('/a/b/suffix' →
id: ['a','b'], plain catch-all still wins '/a/b').
Classifies the rest of Nuxt (beyond routing) for portability into Lynx+Sparkling: portable as-is (auto-imports, runtimeConfig, definePageMeta, pure composables), portable behind a shim ($fetch/ useFetch/useAsyncData, route middleware, plugins, error pages), remap to native (useHead→container scheme, cross-page useState→sparkling-storage, useCookie→storage, layouts/transitions→native container/animation), and not applicable (Nitro server routes→keep as backend, SSR/payload/ hydration, chunk prefetch). Ends with a prioritized follow-up order (useSparklingState, $fetch, useHead→scheme, global middleware/plugin duplication). Added to the website sidebar.
This was referenced Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds comprehensive web platform support to Sparkling, enabling Lynx bundles to run in browsers alongside native Android/iOS targets. The implementation includes:
web-navigation-shim: A framework-agnostic History/Location API implementation that bridges web routing frameworks (vue-router, Nuxt) to a pluggable
NavigationHostcontract, enabling cross-page navigation in multi-page app (MPA) models.nuxt-sparkling: A Nuxt module that transforms file-based
pages/routing into a Sparkling route manifest, enabling Nuxt apps to drive native navigation while supporting deep linking and route matching.sparkling-navigation web handlers: Browser implementations of
router.open,router.close, androuter.navigateusing the History API.sparkling-web-shell: A minimal web host for rendering Lynx web bundles in browsers, with method handler registration and bundle loading.
Web method implementations: Added web support for storage (localStorage) and media (file picker) methods.
CLI and tooling updates: Extended
sparkling-app-cliwithdev:web,build:web, andrun:webcommands; updatedcreate-sparkling-appto optionally scaffold web support.Related issues
Enables web platform development and preview for Sparkling apps without native simulators.
Changes
ShimHistory,ShimLocation,ShimDocument, andShimWindowinterfaces with full WHATWG semantics (pushState/replaceState synchronous, go() async with popstate, proper state round-tripping).pagesToSparklingManifest()transform, route matching, and diagnostics for unsupported features (nested outlets, named views, redirects).<input type="file">.run-web.ts, extended build/dev commands with--platform web, added web checks to doctor command.askWebPlatform()prompt and web scaffold removal logic.app.config.tsandpackage.jsonwith web build/dev scripts.How to test
Unit tests: Run
pnpm test— new test suites cover:web-navigation-shimhistory/location semantics with vue-router integrationnuxt-sparklingmanifest generation and route matchingIntegration test:
packages/nuxt-sparkling/__tests__/integration.spec.tsproves end-to-end MPA wiring (Nuxt pages → manifest → NavigationHost → shim → vue-router).Manual verification:
Existing tests: All existing Android/iOS tests remain unaffected; web is opt-in.
Checklist
CONTRIBUTING.md.https://claude.ai/code/session_01RaEVSkjEsyQM37Qr2JFXmm