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 (
useEffectvsonMounted/onUnmountedvs$effect). - Product carousel — DOM refs (
useRefvs template ref vsbind:this). - Product list — async fetch state machine + derived filter (
useMemovscomputedvs$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
onChangevsv-model.lazyvsbind: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$storeauto-subscription. Persisted tolocalStoragevia@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.