Lab · 2026
Una drum machine, quattro front end.
Una drum machine TR-808 da suonare. Il motore audio è scritto una volta sola, in Haxe. L'interfaccia è costruita quattro volte: codice DOM puro in Haxe, React, Vue e Angular. Poi le ho messe alla prova tutte e quattro, ed ecco i risultati.
Tocca un tasto per aggiungere un passo; trascinalo in su o in giù per il volume, di lato per anticiparlo o ritardarlo. Gira le manopole trascinandole. La barra spaziatrice avvia e ferma. Gli otto pulsanti dei pattern ne contengono uno ciascuno: toccane uno per modificarlo mentre la sequenza continua; Select, Copy e Clear accanto al titolo Sequence decidono cosa suona, ne copiano uno o ne svuotano uno. Cambia front end e i tuoi pattern ti seguono.
L'idea
Volevo per il Lab qualcosa con cui giocare che mostrasse anche come lavoro. Quasi tutte le mie interfacce sono in Haxe compilato in JavaScript puro, quindi ho costruito una piccola app in quattro modi, per vedere quanto costa davvero, e cosa offre, ciascuno dei framework più diffusi.
Tutto tranne l'interfaccia è condiviso. Un solo motore, scritto in Haxe e compilato in JavaScript, gestisce il pattern, il tempo e il suono tramite Tone.js. Le quattro versioni usano lo stesso foglio di stile e lo stesso markup, e superano gli stessi 30 test nel browser. Così ogni differenza misurabile viene dal framework.
Perché Haxe
Haxe è un linguaggio open source con un solo compilatore, rigoroso, e molte destinazioni: JavaScript, PHP, C++, Python, Lua, la JVM e altre. Se scrivi JavaScript o TypeScript lo sai già leggere: classi, arrow function, più o meno la stessa sintassi. In più c'è un compilatore che controlla tutto prima che qualcosa giri.
- I tipi, senza doverli scrivere. Ogni valore ha un tipo, ma il compilatore ne deduce quasi tutti, quindi il codice resta corto come il JavaScript. Un campo scritto male o un argomento sbagliato è un errore di compilazione, non un bug che trova un visitatore.
- Cose che JavaScript non ha. Enum il cui
switchdeve coprire ogni caso, pattern matching, tipi astratti che a runtime non costano nulla e macro che eseguono codice durante la compilazione. - Codice prodotto piccolo. L'eliminazione del codice morto toglie tutto ciò che il programma non chiama mai: qui l'intero motore pesa 11 kB, minificato e compresso con gzip.
- Scritto una volta, usato da entrambe le parti. Una stessa classe può essere compilata per un backend PHP o Node e per il JavaScript nel browser, così regole come la validazione dei form si scrivono una volta sola e non possono divergere. Nel mio lavoro le stesse classi Haxe girano sul server e nel client.
Per questo era adatto a questo progetto. Il motore è scritto una volta sola in Haxe e compilato in un unico, piccolo modulo JavaScript. React, Vue e Angular lo installano come qualsiasi pacchetto npm, e un breve file di tipi permette al loro TypeScript di controllare ogni chiamata. La versione Haxe va oltre: viene compilata insieme al sorgente del motore, quindi un solo compilatore controlla tutto, dai pulsanti fino al grafo audio. Il motore è anche testato da solo, su Node con un sostituto della libreria audio: 259 controlli girano prima che venga costruita qualsiasi interfaccia.
La macchina
Il pannello si ispira alla mia Korg volca beats, nei colori della roccia norvegese che trovi nel resto del sito. I suoni sono quelli della TR-808: 68 registrazioni, e girando le manopole 808 di una parte scegli la registrazione fatta con quella regolazione.
- Ogni passo ha il suo volume (trascina il tasto in su o in giù), un nudge che sposta il passo un po' prima o un po' dopo (trascinalo di lato) e una linguetta che suona il campione al contrario.
- Il drive è una saturazione calda, come quella delle valvole; il riverbero è una mandata, come su un mixer.
- Otto pattern, da suonare in qualsiasi sequenza (versione 2, più sotto).
- Tutto il pattern sta nell'indirizzo della pagina, quindi il pulsante di condivisione in alto a destra manda un ritmo come link: chi lo apre trova questa pagina con il tuo ritmo nella macchina, pronto da suonare.
Le regole
Le stesse regole valgono per ogni versione: funzioni di al massimo quindici righe, nomi che raccontano cosa succede, e la prima versione di ciascuna scritta come la scriverebbero le guide ufficiali di quel framework. Tutto è stato minificato e compresso con gzip allo stesso modo, poi misurato sulla stessa macchina.
Cosa ho misurato
Chrome headless, una versione alla volta. Il “telefono” è una finestra delle dimensioni di uno smartphone con il processore rallentato quattro volte: è il modo in cui Chrome simula un telefono di fascia media. Ogni valore è quello centrale di tre o cinque prove. In tutti i grafici, più corto è meglio. Sono i numeri della versione 2 (più sotto); quelli della versione 1 rientravano nello stesso margine di variazione tra una prova e l'altra.
Codice aggiunto da ciascun front end
Minificato e compresso con gzip, escluso ciò che tutte le versioni condividono: Tone.js (81 kB) e il motore (11 kB).
Il codice DOM puro non aggiunge quasi nulla. Ogni framework si porta dietro il proprio runtime: Vue è il più leggero dei tre, React il più pesante.
Pannello visibile, telefono in 4G lento
Il largest paint (il momento in cui compare l'elemento più grande), dal test mobile di Lighthouse.
Il pannello Haxe è semplice HTML, quindi compare appena arriva la pagina. I framework lo disegnano da JavaScript, che prima va scaricato ed eseguito. Il rendering lato server colmerebbe la differenza.
Play funzionante, telefono in 4G lento
Da quando si chiede la pagina a quando si può premere Play: script eseguiti, suoni scaricati e decodificati.
Valori vicini per tutti e quattro. Quasi tutto il tempo se ne va per scaricare i suoni della batteria, qualunque framework disegni il pannello.
Script per ogni passo del playhead, telefono
A 200 BPM, oltre a ciò che serve a una pagina essenziale con lo stesso motore e nessun framework.
Tutti e quattro tengono il passo senza fatica: un passo dura 75 ms. React ridisegna tutti e sedici i tasti a ogni passo; Vue e Angular ricontrollano solo ciò che dipende dal playhead; la versione Haxe sposta una sola classe, a mano.
Lavoro per ogni movimento del puntatore durante il trascinamento, telefono
Tutto ciò che il browser fa per un movimento di una manopola o di un tasto (la media dei due), dato che ogni movimento ridisegna il pannello.
Ecco la sorpresa, qui sotto.
La sorpresa
La prima versione Haxe era la più lenta da trascinare: circa 21 ms per ogni movimento del puntatore sul telefono rallentato, contro circa 13 ms dei framework.
Ridisegnava due testi, la riga di stato e i nomi delle manopole 808, a ogni modifica, anche quando non erano cambiati. Scrivere lo stesso testo in una pagina lo sostituisce comunque, e il browser ricalcola il layout dell'intera pagina. React, Vue e Angular saltano da soli le scritture inutili. Il codice DOM puro deve farlo da sé.
La correzione è stata di tre righe: scrivere il testo solo quando è diverso. Dopo, la versione Haxe è diventata la più leggera delle quattro da trascinare. Il grafico qui sopra mostra entrambe le versioni, perché scoprire cose così è proprio il motivo per misurare.
Conclusioni
La più leggera: il codice DOM puro, di gran lunga. La versione Haxe aggiunge 4,2 kB a ciò che tutte condividono. Vue aggiunge 24 kB, Angular 48 kB e React 66 kB. Un framework deve inviare il proprio runtime prima di poter fare qualsiasi cosa, mentre Haxe sparisce nella compilazione e lascia solo il JavaScript che la pagina esegue davvero.
La più rapida a comparire: di nuovo il DOM puro. Il suo pannello è normale HTML, quindi un telefono con una connessione lenta lo mostra in meno di 2 secondi. I framework costruiscono il pannello da JavaScript, quindi non può comparire finché il loro codice non è stato scaricato ed eseguito: circa 5 secondi. Il rendering lato server colmerebbe gran parte della differenza, al prezzo di un server.
La più reattiva: un pareggio, se il DOM puro è scritto con cura. Dopo la correzione, la versione Haxe fa a ogni trascinamento tanto poco lavoro quanto il migliore dei framework, e quasi niente a ogni passo del playhead, perché ogni modifica va dritta all'unico elemento della pagina che è cambiato. Gli aggiornamenti mirati di Vue e Angular fanno più o meno lo stesso, entro il margine di variazione delle misure; il ridisegno di React fa un po' più di lavoro a ogni passo e a ogni trascinamento. La prima versione Haxe mostra il rovescio della medaglia: senza un framework, evitare il lavoro inutile tocca a te, ed è facile non accorgersene finché non misuri.
Quindi qual è la migliore? Per un pezzo piccolo e molto interattivo come questo, dove su un telefono contano ogni kilobyte e ogni millisecondo, il codice DOM puro è il più leggero e il più rapido a comparire, a patto che lo scriva qualcuno attento. In un'app più grande con un team sceglierei Vue: il framework più leggero qui, il meno codice e aggiornamenti efficienti senza doverci pensare. Angular offre la struttura più solida e di gran lunga più strumenti. Il vastissimo ecosistema di React si accompagna al runtime più pesante e al maggior lavoro per ogni aggiornamento.
Questi sono i risultati di una sola piccola app, misurati in un browser headless con un processore rallentato al posto di un telefono. Sono un'indicazione, non una legge.
Tutto il codice, i test e gli script delle misure sono su GitHub, così puoi verificare ogni numero da te.
Versione 2: una funzione nuova, quattro volte
Le app vere cambiano, così per la versione 2 ho dato a bümbap una funzione della mia volca: otto pulsanti dei pattern e una sequenza in cui suonarli. Ogni pulsante contiene un pattern intero, ogni passo con il suo volume, nudge e verso, e il suono di ogni parte. Toccane uno per modificarlo, anche mentre la sequenza continua a suonare. Tre piccoli pulsanti accanto al titolo fanno il resto: Select (tocchi i pattern nell'ordine in cui devono suonare, anche ripetuti, e ognuno mostra il suo posto; una nuova sequenza parte alla fine della battuta), Copy e Clear. Ci sono anche le scorciatoie: Maiusc+clic per aggiungere alla sequenza, sul telefono tenerne premuto uno e toccarne altri, e trascinarne uno su un altro per copiarlo.
Poi ho misurato quanto è costata la funzione a ciascuna interfaccia, rispetto alla versione 1: i numeri sono nella tabella qui sotto.
La maggior parte del lavoro è andata dove deve stare: nel motore. Quale pattern suona dopo, il cambio a fine battuta, cosa fanno Select, Copy e Clear con un tocco, un link che porta tutti e otto i pattern (e apre ancora i link della versione 1) e perfino i gesti dei pulsanti sono scritti una volta sola, in Haxe. Così ogni interfaccia ha dovuto solo disegnare i pulsanti e passarne i tocchi.
Così le quattro sono arrivate vicine. Vue ha richiesto di nuovo meno righe, poi Angular e React. La versione Haxe ne ha richieste di più, perché il suo markup sta nella sua pagina HTML, e ha ricevuto un piccolo aiuto che salta le scritture invariate: la lezione della versione 1. Ogni versione è cresciuta di qualche centinaio di byte.
In nessun framework si è rotto nulla. L'unico bug trovato dai test condivisi nel browser era nel motore (un link condiviso chiedeva una registrazione del clap che non esiste), quindi una sola correzione le ha sistemate tutte e quattro. È il motivo per tenere la logica in un posto solo: una funzione costa tanto una volta, e poco per ogni interfaccia.
Quanto sono costati i pattern a ciascuna interfaccia
| Righe dell'interfaccia | Codice in più | |
|---|---|---|
| Haxe | +99 / −0 | +0,4 kB |
| React | +76 / −1 | +0,5 kB |
| Vue | +53 / −1 | +0,5 kB |
| Angular | +60 / −2 | +0,7 kB |
Costruirle una per una
Haxe. La più rapida da scrivere, e l'unica compilata insieme al sorgente del motore. Se rinomino qualcosa nel motore, l'interfaccia non compila invece di rompersi nel browser. Il compilatore ha trovato due errori prima che girasse qualunque cosa. Il prezzo: dettagli di prestazioni come quello qui sopra devi scovarli tu.
React. Il motore fornisce istantanee congelate dello stato, esattamente ciò che vuole useSyncExternalStore di React, quindi non è servito codice di raccordo. Il JSX cresce in fretta, e la regola delle quindici righe l'ha diviso in tanti piccoli componenti. Gli eventi della rotella di React non possono impedire lo scorrimento della pagina, quindi ogni manopola aggiunge un proprio listener.
Vue. Il meno codice dei quattro. Uno shallowRef è tutto l'adattatore verso il motore, e i template contengono direttamente il markup. Il suo controllo dei tipi non funziona ancora con il nuovo TypeScript 7.
Angular. La configurazione più impegnativa e l'installazione di gran lunga più grande, ma con i signal il codice dello stato è corto quanto quello di Vue. È stata l'unica versione a fallire dei test al primo tentativo: gli a capo in un template avevano messo degli spazi nei nomi delle parti, e senza zone.js le manopole leggevano un valore vecchio tra due pressioni rapide dei tasti.
Codice e strumenti
| Righe di codice UI | Build da zero | Pacchetti installati | |
|---|---|---|---|
| Haxe | 671 | 0,4 s | 7 |
| React | 567 | 1,0 s | 32 |
| Vue | 447 | 3,7 s | 53 |
| Angular | 525 | 6,4 s | 218 |
Suoni: il set gratuito di campioni TR-808 di Michael Fischer (1994). Motore e front end scritti con Claude, un assistente di programmazione basato sull'IA, secondo il mio design, le mie specifiche e le mie regole di codice.

