Skip to content

About

shop-three-ways is a small e-commerce demo where every feature is implemented three times — in React, Vue, and Svelte — and rendered side-by-side as Astro islands. The point is to make the three framework idioms readable at a glance, so a developer fluent in one can pick up the other two without translating between mental models.

Who it's for

Anyone who's confident in one of {React, Vue, Svelte} and wants a Rosetta-stone-style intro to the other two. The reading mode is: find the column you already know, then read the same feature in the other two columns next to it.

How it's structured

A working shop comes first, with a Rosetta reference layered on top:

  • /shop — three product lists fetching the same API, sharing one cart.
  • /cart — three views of the same cart, each with the framework's two-way binding idiom on the qty input.
  • / — the home page hosts the slide-banner (timers + cleanup) and product-carousel (DOM refs) demos.
  • /compare — for every feature, the three real source files side-by-side, syntax-highlighted. Always in sync with the live shop because they're loaded with ?raw.

The concepts each feature isolates

  • Slide banner — timers + cleanup (useEffect vs onMounted/onUnmounted vs $effect).
  • Product carousel — DOM refs (useRef vs template ref vs bind:this).
  • Product list — async fetch state machine + derived filter (useMemo vs computed vs $derived).
  • Add-to-cart + cart badge — global state via nanostores. The same atom is read and written from all three frameworks.
  • Quantity stepper — two-way binding (controlled onChange vs v-model.lazy vs bind:value + onchange).
  • Wishlist toggle + toast notifications — local UI state on a persistent store, plus the composition primitive each framework uses (children / <slot /> / Snippet).

Tech stack

  • Astro 6 as the multi-framework host.
  • React 19, Vue 3.5, Svelte 5 (runes) — all latest idiomatic syntax.
  • nanostores as the framework-agnostic store, with @nanostores/react, @nanostores/vue, and Svelte's native $store auto-subscription. Persisted to localStorage via @nanostores/persistent.
  • Tailwind CSS v4 (Vite plugin) so the visual layer is identical across the three columns.
  • TypeScript everywhere, kept minimal.
  • Astro's built-in <Code> component (Shiki) for the source-comparison pages.

A note on the size badges

Each /compare panel shows the source file's gzipped byte size and line count. That measures the component file you wrote — useful for "how much code did this take?" — not the runtime cost paid in the browser. Framework runtime is shared across every island on the page (rough orders: React ~45 KB, Vue ~35 KB, Svelte ~5 KB gzipped) and isn't reflected in the per-component badges.

Data source

Products come from DummyJSON (read-only). The wrapped { products, total, ... } response is normalized in src/data/products.ts by a single fetchProducts() helper that also generates a slug from each title (DummyJSON has no slug field). Each framework's product list and carousel call that helper on mount, which is the demo's async/effects exercise.

A note on framework versions

The Svelte column uses Svelte 5 runes ($state, $derived, $effect, $props), which is a meaningful syntactic shift from older tutorials. The React column uses React 19 conventions, and Vue uses <script setup> with the Composition API. Older syntax works but is intentionally excluded so each column shows the framework's current best practice.