PAD + islands

Five frameworks, one page

The same counter written five times - React, Vue, Svelte, Preact and Solid - each an island with its start value from PAD, and beside it what its framework cost in bytes, read from the build's manifest.

{^field}{vite}padViteChunk react()vue()svelte()preact()solid()
Live
React140.0 kB
10
Vue62.6 kB
20
Svelte31.2 kB
30
Preact14.4 kB
40
Solid8.1 kB
50

The weight is what the browser fetches for the island the first time - the component, its framework's adapter and every chunk they import, uncompressed. A second island of the same framework costs only its component.

How it is built

PAD, on the server the islands, built by Vite

<div class="frameworks">
  {counters}
  <div class="card framework" style="--framework: {$colour}">
    <div class="framework-head"><strong>{$framework}</strong><span class="badge">{$kilobytes} kB</span></div>
    <div data-island="{$island}" data-props="{^props}"><div class="counter"><div class="counter-value">{$props.start}</div></div></div>
  </div>
  {/counters}
</div>
<p class="muted small">The weight is what the browser fetches for the island the first time - the component, its framework's adapter
   and every chunk they import, uncompressed. A second island of the same framework costs only its component.</p>
<?php

  // One counter, five frameworks: each island gets its start value and its framework's name
  // from PAD, and the card beside it what the island costs - the bytes of its component, its
  // framework's adapter and every chunk they import, read from the build's manifest.

  $counters = [];

  foreach ( [ [ 'React',  'ReactCounter',  'src/islands/Counter.react.jsx',  'src/adapters/react.js',  '#61dafb' ],
              [ 'Vue',    'VueCounter',    'src/islands/Counter.vue',        'src/adapters/vue.js',    '#42b883' ],
              [ 'Svelte', 'SvelteCounter', 'src/islands/Counter.svelte',     'src/adapters/svelte.js', '#ff3e00' ],
              [ 'Preact', 'PreactCounter', 'src/islands/Counter.preact.js',  'src/adapters/preact.js', '#673ab8' ],
              [ 'Solid',  'SolidCounter',  'src/islands/Counter.solid.jsx',  'src/adapters/solid.js',  '#2c4f7c' ] ]
            as $at => [ $framework, $island, $component, $adapter, $colour ] )

    $counters [] = [ 'framework' => $framework,
                     'island'    => $island,
                     'colour'    => $colour,
                     'kilobytes' => number_format ( islandsWeight ( [ $component, $adapter ] ) / 1024, 1 ),
                     'props'     => [ 'start' => 10 * ( $at + 1 ), 'framework' => $framework ] ];

?>
// The islands of every page, by the name a template gives them in data-island. Each entry
// pairs a component with its framework; nothing of either is fetched before an island of
// that name is on the page and due to load (pad-islands.js).

import './theme.js';
import { islands, react, vue, svelte, preact, solid } from './pad-islands.js';

islands({
  ReactCounter:  react(()  => import('./islands/Counter.react.jsx')),
  VueCounter:    vue(()    => import('./islands/Counter.vue')),
  SvelteCounter: svelte(() => import('./islands/Counter.svelte')),
  PreactCounter: preact(() => import('./islands/Counter.preact.js')),
  SolidCounter:  solid(()  => import('./islands/Counter.solid.jsx')),

  Shelf:         react(()  => import('./islands/Shelf.react.jsx')),
  CartSummary:   vue(()    => import('./islands/CartSummary.vue')),
  CartBadge:     svelte(() => import('./islands/CartBadge.svelte')),

  OrderCard:     react(()  => import('./islands/OrderCard.react.tsx')),

  LoadLog:       preact(() => import('./islands/LoadLog.preact.js')),
  Clock:         solid(()  => import('./islands/Clock.solid.jsx'))
});
import { useState } from 'react';

export default function Counter({ start, framework }) {
  const [count, setCount] = useState(start);
  return (
    <div className="counter">
      <div className="counter-value" key={count}>{count}</div>
      <div className="counter-buttons">
        <button onClick={() => setCount(count - 1)} aria-label="One less">โˆ’</button>
        <button onClick={() => setCount(count + 1)} aria-label="One more">+</button>
      </div>
      <p className="counter-note">{framework}: useState</p>
    </div>
  );
}
<script setup>
import { ref } from 'vue';

const props = defineProps({ start: Number, framework: String });
const count = ref(props.start);
</script>

<template>
  <div class="counter">
    <div class="counter-value">{{ count }}</div>
    <div class="counter-buttons">
      <button @click="count--" aria-label="One less">โˆ’</button>
      <button @click="count++" aria-label="One more">+</button>
    </div>
    <p class="counter-note">{{ framework }}: ref</p>
  </div>
</template>
<script>
  let { start, framework } = $props();
  let count = $state(start);
</script>

<div class="counter">
  <div class="counter-value">{count}</div>
  <div class="counter-buttons">
    <button onclick={() => count--} aria-label="One less">โˆ’</button>
    <button onclick={() => count++} aria-label="One more">+</button>
  </div>
  <p class="counter-note">{framework}: $state</p>
</div>
// Preact without a compiler: htm reads the template literal at run time.
import { html } from 'htm/preact';
import { useState } from 'preact/hooks';

export default function Counter({ start, framework }) {
  const [count, setCount] = useState(start);
  return html`
    <div class="counter">
      <div class="counter-value">${count}</div>
      <div class="counter-buttons">
        <button onClick=${() => setCount(count - 1)} aria-label="One less">โˆ’</button>
        <button onClick=${() => setCount(count + 1)} aria-label="One more">+</button>
      </div>
      <p class="counter-note">${framework}: useState, no build step</p>
    </div>`;
}
import { createSignal } from 'solid-js';

export default function Counter(props) {
  const [count, setCount] = createSignal(props.start);
  return (
    <div class="counter">
      <div class="counter-value">{count()}</div>
      <div class="counter-buttons">
        <button onClick={() => setCount(count() - 1)} aria-label="One less">โˆ’</button>
        <button onClick={() => setCount(count() + 1)} aria-label="One more">+</button>
      </div>
      <p class="counter-note">{props.framework}: createSignal</p>
    </div>
  );
}