Hopp til hovedinnhold

Hvordan skal vi bygge fremtidens React Native apper?

Publisert:5. mars

I React Native 0.84 ble Hermes V1 plutselig standard – en ny versjon av motoren som allerede lå under panseret. Men hvorfor finnes Hermes i det hele tatt, og hva er det egentlig en JavaScript-motor gjør? For å svare på det må vi starte helt nederst i teknologistakken.

I React Native 0.82 ble en ny, eksperimentell JavaScript-motor kalt Hermes V1 annonsert. Da versjon 0.84 kom i februar, ble den allerede satt som standard.

Det reiste flere spørsmål: Hvis Hermes allerede er motoren i React Native – hvorfor trengs en “V1”? Hvorfor har React Native i det hele tatt en egen JavaScript-motor? Og kanskje viktigst: Hva er egentlig en JavaScript-motor?

Jeg prøver å finne svar på disse spørsmålene – i motsatt rekkefølge.

Det som skjer under panseret

Hvis du drev med web-utvikling for 10 år siden så blir denne delen fort en liten “trip down memory lane” du fint kan hoppe over. For sånne som meg derimot – som er relativt ferske i gamet – så er ikke det helt naturlig å ha et forhold til hva som skjer med JavaScript- eller forhåpentligvis TypeScript-koden du skriver som får den til å kjøre på enheten din.

Verktøyene våre tar seg av mer og mer for oss, og lagene i frontend-stakken vokser i takt med kompleksiteten i applikasjonene vi lager. Meta-rammeverk som Next.js håndterer code splitting, server-side rendering og routing, oppå React som allerede abstraherer bort DOM-manipulasjon, rendering og tilstand. Det er ikke så rart at man sjelden tenker på hva som skjer helt nederst i stakken.

Nesten helt nederst kommer JavaScript-motorer inn i bildet – de som har ansvar for å gjøre koden din om til noe kjørbart. Så, hva er en motor og hvordan funker de?

En mindre brikke i et større spill

At det kalles en JavaScript-motor er i seg selv et litt forvirrende navn på noe som var et ganske så enkelt konsept. De gjør det samme som program som kompilerer kode (compilers) eller tolker kode (interpreters): de oversetter kodespråket du skriver til instruksjoner som maskiner kan utføre (maskinkode).

Så, hvorfor kaller vi det en motor i JavaScript? Det kommer av at JavaScript ble designet for web-teknologi. Da JavaScript kom på banen i 1995 så ble det skapt for Netscape Navigator, og det som tolket koden var innebygd i selve nettleseren. Det var ikke noe frittstående program, men heller en kompleks mekanisme i et større system. Litt som en motor i en bil.

Den ville ville web-vesten

I starten var det så enkelt som at JavaScript var et tolket språk, og den første JavaScript-moteren leste koden linje-for-linje uten noe særlig mer enn det. Men, etter hvert som maskinene ble kraftigere så skapte det også mer forventninger til opplevelsen av å bruke dem. Nettlesere måtte bli raskere, så å få koden til å kjøre mer effektivt var super viktig.

En unik utfordring som oppstod med en gang var at det eksisterte mer enn bare én nettleser. Hvis JavaScript-moteren bor i nettleseren, så betyr det også at hver nettleser har sin egen implementasjon. Det gjorde det utrolig vanskelig å skrive kode som funket på tvers av nettlesere. I starten var det bare to motorer: Mocha (Netscape Navigator) og JScript (Internet Explorer). Etter hvert kom det flere nye på banen som SpiderMonkey (Firefox), V8 (Google Chrome) og JavaScriptCore (Safari) med hver sine former for innovasjoner. Det ble helt umulig å teste at koden din funket på tvers, og jQuery – et bibliotek som lagde API-er som funket på tvers av nettleser – ble en bransjestandard som fortsatt dominerer internettet.

Takk til enorme forbedringer i web-standarder så har forskjellene mellom nettlesere blitt nesten helt obskure, og ferskere utviklere som meg hører bare om jQuery i kontekst av noe som virker som en fjern fortid. Så utrolig mye har skjedd for å gjøre utviklingen lettere, men også i hvordan en JavaScript-motor fungerer.

Den moderne JavaScript-motoren – spekulativ optimering

Jeg skal ikke gå detalj for detalj i hvordan motorer for JavaScript på web funker, men vi bør gå gjennom de to viktigste detaljene av hva en moderne JavaScript-motor på web gjør.

Bytecode – middelveien mellom menneske- og maskinkode

En JavaScript-motor er programmet som tar JavaScript-koden din og gjør den om til instruksjoner maskinen kan kjøre. JavaScript-moteren gjør ikke lenger oversettelsen fra kildekode til maskinkode direkte, men oversetter først til noe som heter Bytecode. Bytecode gir noen veldig konkrete fordeler:

  • Det er et enklere og mer kompakt instruksjonspråk å kjøre på den virtuelle maskinen til motoren, som betyr at det kjører raskere.
  • Det går fortere å generere Bytecode fra kildekoden enn å gå direkte til maskinkode. Da starter nettsiden din opp fortere.
  • Bytecode er plattformuavhengig og kan kjøre på forskjellige arkiteturer.

Bytecode kan sees på som et mellomsteg – et enklere instruksjonsspråk som motoren sin virtuelle maskin forstår, før det eventuelt oversettes videre til maskinkode.

Just-in-time kompilering

JavaScript kode blir ikke lenger tolket linje for linje når du kjører den, men i stedet blir den også i noen tilfeller kompilert! Det høres kanskje litt rart ut. Hvordan gir det mening hvis man får shippet kildekode i JavaScript når man går inn på en side, og ikke en forhåndsgenert binary-fil?

Her kommer Just-in-time (JIT) delen. I motsetning til tradisjonal kompilering så kompileres lynrask maskinkode fra Bytecode som lagres direkte i cache, og blir brukt umiddelbart. Alt sammen skjer under kjøretid.

Dette høres ut som litt for godt til å være sant. Det spiser vel opp noe helt enormt med minne hvis alt skal kompileres og caches? I tillegg ville det vel tatt veldig mye tid hvis man likevel skal kompilere all kode med en gang i kjøretid.

Heldigvis har motoren blitt litt mer sofistikert enn som så! Motoren overvåker Bytecoden som blir kjørt og analyserer hvilke funksjoner og variabler som ofte blir kalt – så kalt “hot code”. Det er bare disse delene av programmet som blir aggresivt optimalisert gjennom kompilering, så man slipper kosten av å optimalisere sjelden og ubrukt kode! Dette er det man kaller spekulativ optimering. Motoren “gjetter” altså hvilke antagelser som holder i praksis, og optimaliserer deretter.

Deoptimalisering – når man trår av stien

For å få til en slik spekulativ optimering så må motoren altså ta noen antagelser. JavaScript er ikke et statisk typet språk, så man kan ikke garantere at en funksjon aldri vil kunne få uventede verdier som argumenter. Ta et eksempel hvor man regner ut en pris:

function kalkulerTotal(pris: number, antall: number): number {
  return pris * antall ;
}

// Kalles på 10,000 ganger med argumenter av typen number
kalkulerTotal(29.99, 3);
kalkulerTotal(9.99, 10);
kalkulerTotal(149.00, 1);

Da observerer motoren at her kommer det alltid typen number som argument, og kompilerer da en versjon av funksjonen som dropper typesjekken og kan utnytte maskinens mulitplikasjonsinstruksjon. På et senere tidspunkt – ut av det blå – så kommer det et par strings i stedet for numbers:

// Etter 10,001 kall
kalkulerTotal("29.99", "3");

Da bryter vi plutselig den antagelsen, og motoren velger derfor å forkaste den optimaliserte versjonen som ikke lenger stemmer, og velger å gå tilbake til Bytecode tolkningen. Avhengig av hvor mye det koster å kjøre uoptimalisert kode så kan det hende den prøver et nytt forsøk på å optimalisere for både number og string.

Det store bildet

JavaScript-motorer har mildt sagt blitt ganske så sofistikerte skapninger som gjøre mye for å optimalisere hvordan vi kjører programmene våre. Det store poenget å ta med seg er at det meste skjer under kjøretid:

Beskrivelse av stegene som gjennomføres av en standard JavaScript mtor
Spekulativ optimering i JavaScript motorer

JavaScript er i bunn og grunn et dynamisk typet språk. Typesjekking kan ikke gjøres på forhånd. Motoren må sjekke hva slags type verdier man gjør operasjoner på når de blir kallet på. Så all den sofistiskerte logikken som har blitt lagt til har ikke fundamental endret hvordan koden pakkes sammen og sendes ut til brukeren. Alt skjer i etterkant.

Kort sagt: moderne JavaScript-motorer på web optimaliserer kode aggressivt under kjøretid. Men disse strategiene passer ikke nødvendigvis like godt på mobile enheter.

Hermes – ny plattform, nye behov

React Native – et kryssplattformrammeverk skrevet i JavaScript – bruker naturligvis også en motor for å oversette det du skriver om til maskinkode for plattformene koden din kjører på. I dag shipper alle nye versjoner av React Native med Hermes – en JavaScript-moter optimalisert for React Native. Men, man begynner jo å lure. Hvorfor trenger React Native en egen motor?

Det var ikke alltid sånn

Hermes har ikke alltid eksistert som en del av React Native økosystemet. Den ble først annonsert i 2019, og ble ikke standarden før sommeren 2022. Orginalt så var det faktisk JavaScriptCore – motoren Safari bruker – som ble brukt i de første versjonene.

Da lastet man inn kildekode ved oppstart av appen, og først etter det parset man og startet oversettingen til Bytecode. Dette funker greit i en nettleser, men å gjøre dette på mobiltelefoner viste seg å skape kjempelang ventetid under oppstart – spesielt for litt rimeligere Android telefoner. Forskjellen på hvor rask og smooth en native app og en React Native app ble ekstremt merkbar.

JIT-kompileringen var heller ikke like bra som på web. Det fungerte på samme måte, men å lagre mye optimalisert kode under kjøretid er ikke like trivielt når minnebruk er mye mer dyrebart på en mobil enhet. Enda verre er det at det bare funket på Android, siden iOS apper ikke har noen form for minne som kan både skrives til og kjøres pga sikkerhetsfunksjonalitet!

Premissene for å kjøre JavaScript er helt forskjellige mellom en nettleser og i en app. Det viste seg at egenskapene som er nødvendige for en motor i nettleseren, å kunne kjøre en drøss med forskjellige Java-Script programmer (les nettsider), ikke overførte til en ny kontekst så bra. En motor som skal kjøre i en app trenger bare å forholde seg til akkurat den koden du har laget for appen, og kunne derfor blitt mer optimalisert basert på det premisset!

React Native som et kryssplattform-rammeverk har alltid strebet etter å yte såpass godt at man ikke legger merke til om det er en native app eller ikke, så noe måtte gjøres. Det krevdes at man tok hensyn til de nye utfordringene som kom av nye plattformer å kjøre på.

Hermes – Optimalisering før kjøretid

Hermes ble laget med mål om å optimalisere for to ting: raskere oppstart og lavere minnebruk. Løsningen ble å flytte det tunge arbeidet til før kjøretid, ved å fundamentalt skifte når i programmets livsløp man oversetter fra kildekode til Bytecode. I stedet for å oversette under kjøretid og bruke JIT-kompilering, så gjør man Ahead-Of-Time (AOT) kompilering. Dette er mer tradisjonelt kompilerte språk gjør det, hvor det da er en bundlet Hermes Bytecode-binary som sendes til brukerens enhet i stedet for kildekode.

Overordnet steg-for-steg beskrivelse av hvordan Hermes-motoren funker
Forenklet bilde av hvordan Hermes fungerer

Dette er et fundamentalt skifte fra hvordan motorer for web fungerer. På noen måter så kan må si at den er mye enklere konseptuelt. Mange av de intrikate elementene for å oppnå “hot-code” optimaliseringene ved hjelp av JIT og deoptimalisering er ikke en greie lenger. I tillegg så skjer hele kompleksiteten av å optimalisere bare én gang, og er sentralisert hos utvikleren under bygging.

Den største forskjellen mellom en nettlesermotor og Hermes er derfor når optimaliseringen skjer: web-motorer optimaliserer under kjøretid, mens Hermes gjør mesteparten av arbeidet før appen starter.

Samtidig, så får den ikke utnytte alle fordelene andre AOT-kompilatorer har som er skrevet for andre språk som for eksempel C++. I JavaScript så kan man ikke gjøre statisk analyse av typene, så man ender opp med å kompilere operasjonene, ikke implementasjonen av typene:

// funksjon i JavaScript
function(a, b) {
	return a * b;
}

// eksempel på kompilert Bytecode av funksjonen over
LoadParam 1 // Last a (ukjent type)
LoadParam 2 // Last b (ukjent type)
Multiply // <- Gjør * operasjonen (slå opp type og
         //    passende operasjon under kjøretid)
Return

Det blir en slags hybrid greie. Det meste har blitt kompilert, men man må fortsatt gjøre en del sjekker under kjøretid. Det gjør også at man når et tak på hvor optimalisert koden din blir. Den er begrenset til å være så god som ytelesen på Bytecoden er, som ikke når helt opp til native kode. Selvfølgeligvis er det noe skaperne av Hermes har lyst til å gjøre noe med.

Hermes V1 – React Native i fremtiden

I fjor høst (oktober 2025) slapp teamet bak Hermes en ny eksperimentell versjon av motoren: Hermes V1. Per nå er det ikke noen revolusjonerende nye ting tilgjengelig, bortsett fra mange små forbedringer som jeg skal komme tilbake til etterpå. Det som er veldig spennende derimot, er de store tingene de jobber på som som skal bli lettere å rulle ut når økosystemet adopterer Hermes V1.

Å bruke typer i optimaliseringen

Hermes V1 var lenge kjent under et annet navn – Static Hermes. Den store tanken var at man kunne bruke type annoteringene fra Flow eller TypeScript kode for å lage typet Bytecode. Da ville man også kunne kompilert type-spesifikke operasjoner uten å måtte gjøre oppslag under kjøretid.

Problemet per nå er at det ikke eksisterer noen JavaScript bygge-verktøy som beholder typene. “Type Erasure” er en standard operasjon hvor man hvisker bort alle den statiske typingen i TypeScript før man lager JavaScript bundlen som skal kjøres. Det gjør at Hermes, som bare støtter JavaScript, aldri ser typene dine når den skal generere Bytecoden.

Hermes V1 skal støtte TypeScript og Flow og dermed kunne utnytte informasjonen til å lage høy-optimalisert statisk kode. De har ikke tenkt å kvitte seg med standard JavaScript støtte, så man får beholde friheten til å velge hvordan man vil skrive koden. De som skriver i standard JavaScript vil få litt ytelsesfordeler, mens de som skriver i TypeScript vil få mye. La oss være ærlige; hvis du skriver React-kode i 2026 så bør du skrive TypeScript. For ditt eget beste.

JIT… men ikke egentlig?

Når man hører dette, så kommer fort spørsmål: “Hvis man nå kan lage statisk typet Bytecode i byggesteget, hvorfor ikke gå hele veien til å kompilere native kode?” Native kode vil jo absolutt være det med best ytelse, men det kommer ikke uten sine trade-offs.

Mange React Native utviklere i dag nyter godt av at koden deres er delt i to deler: native kode og JavaScript bundlen. De store aktørene som eier markedsplassene vi publiserer appene våre på er ikke fan av at vi oppdaterer appene våre uten at de får sjekket gjennom dem først. Morsomt nok, så gjelder dette bare på om det har kommet noen endringer i native koden, ikke i JS bundlen.

Dette gjør at man kan gjøre noe som kalles Over-The-Air (OTA) oppdateringer. I stedet for å måtte sende inn en ny appversjon til review hos Apple eller Google når man har endret JS koden sin, så kan man i stedet sette opp en kobling til en server og lytte på oppdateringer som laster ned en ny bundle. Det kan så klart være litt risky og by på nye bugs, men det gir mye større fleksibiltet i å patche og hotfixe småting uten å trenge potensiell 1-2 dagers venteperiode. Hvis vi hadde bygget til native kode direkte så hadde dette vært umulig, og alle slike arbeidsflyter som er godt spredt i økosystemet hadde kollapset.

Løsningen for å ta et steg frem, er å… ta et steg tilbake. Hva om man sendte bundlen til brukeren og så kompilerte til native kode under kjøretid? Det høres jo veldig ut som JIT-kompilering slik vi ser JavaScript-motorer gjør det på web? Jo, men ikke helt.

Siden man allerede har statisk typet Bytecode så slippes nesten all den sofistikerte delen av spekulativ optimalisering. Når vi vet nøyaktig hvilke maskinkode operasjoner vi skal gjøre for alle typene så trenger vi ikke å gjøre antagelser, og hele loopen med deoptimalisering forsvinner. Skaperne liker derfor ikke å kalle det en fullverdig JIT.

Visning av Hermes V1 steg for steg med maskinkode oversettelse til slutt
Fremtidig Hermes V1 hvor man oversetter til maskinkode i siste steg

Det er bare en oversetting, og blir en liten brikke i det store bildet. Det vil nok koste litt mer minne, men vil tilby enda bedre ytelse for “hot code". Så det blir ikke en revolusjonerende prosess som kommer til å endre på hele arkitekturen til Hermes, siden resten av stegene holder seg relativt likt med og uten maskinkode steget.

Hermes V1 – den nye standarden

Funksjonalitetene jeg har nevnt så langt er de store linjene som definerer hvor React Natives motor er på vei. De er ikke på plass enda, men likevel ble Hermes V1 standarden fra og med React Native versjon 0.84 (februar 2026). Hva var poenget med å oppgradere nå?

De har virkelig jobbet på når det kommer til ytelsen:

Stolpediagram som viser at Hermes V1 har 1,5x til 2x ytelse på enkelte målinger
Sammenligning av Hermes og Hermes V1 ved lansering / Jakub Piasecki, React Native bidragsyter

Små forbedringer i motoren som gjør den bedre rigget for å implementere typet Bytecode og JIT gjør at den kjører 1,5 til 2,5 ganger bedre på tvers av forskjellige benchmarks. Bare dette er jo en kjempeforbedring for motoren, og det beste av alt er at du ikke trenger å gjøre noe annet enn å oppgradere React Native. Så det er bare å hive seg rundt og komme seg på nyeste versjon!

For å runde av

Det er utrolig kult hvordan React Native fortsetter å utvikle seg fremover. Fra å være et slags strikk og binders rammeverk for å få JavaScript utviklere inn i mobil-økosystemet, til å utvikle verktøyene og teknologi som løser ting nøyaktig slik React Native bør gjøre det.

Endelig blir den fundamentale forskjellen som oppstår mellom native og web – optimalisering før kjøretid – utforsket for å skape enda bedre ytelse for app brukere. Det kommer nok til å ta en god stund til vi ser de store featurene som typet kompilering og oversettelse til maskinkode bli stabilt, men Hermes V1 som nettopp kom ut er en endring som tar oss noen lange steg på veien. Hermes V1 er kanskje ikke revolusjonen ennå – men det er et tydelig signal om hvor React Native-plattformen er på vei. De baner sin egen vei, og går for gull!