
Modernisere kodebasen? Her er Erik-Andrés beste tips
– Jeg har lært meg å sette pris på pleie av kodebasen, fremfor bare å lage nye ting, skriver frontend-utvikler Erik-André Mamen.
Som en programmerer har jeg tradisjonelt elsket å lage nye ting, og få ting til å fungere, men ikke vært spesielt interessert i alt rundt av infrastruktur, byggeverktøy og lignende. Ofte har jeg vært prisgitt andre som har satt opp ting, og jeg har bare latt det surre og gå, og forsøkt å ikke røre ting som fungerer.
De siste 15 årene av karrieren har jeg omtrent utelukkende drevet med frontend-programmering. JavaScript er notorisk kjent for å være ganske “Texas” med masse fleksibilitet og lite struktur. Til tross for at jeg egentlig satt pris på dette en gang i tiden, var jeg ganske kjapt ute med å hoppe på TypeScript for 10 år siden.
Ofte hørte jeg kritikere si “Det blir så mye boilerplate bare for å skrive veldig enkel kode”, eller “TypeScript er bare en måte å gjøre JavaScript mer likt Java på.”
Men jeg hadde sansen. Jeg visste hvor lett det var å glemme å parse en string til tall slik at “1”+”2"=”12".
Det er klart, bedre IDE-programvare, og linte-verktøy har også gjort hverdagen til utviklere mye bedre de siste årene.
Strevde med TypeScript
Som mange andre rookies, hadde jeg også mitt strev med TypeScript.
Jeg visste koden fungerte, men jeg fikk ikke “typene til å gå opp”. Jeg var ikke god nok på generics, og forsto ikke alle kombinasjoner av “keyof typeof”, og mye annet. Det finnes massevis av TypeScript-guider på nett, men bare for å gi et krasjkurs i disse så er det altså slik:
firstName: 'Ola',
lastName: 'Nordmann',
age: 30
};
type Person = typeof person; // { firstName: string; lastName: string; age: number }
type KeyNames = keyof typeof person; // 'firstName' | 'lastName' | 'age'
type Values = Person[KeyNames]; // string | number
På samme måte som at noen liker å løse sudoku, har jeg hatt en iver etter å få mest mulige riktig typer, og unngå alle “any” og “//ts-ignore” for å sikre meg at koden er så robust som mulig. Det reduserer også behovet for en del tester. For eksempel er det ikke vits i å teste hva som skjer om man forsøker å sende inn en “string” når input-parameteren er spesifisert som “number”. Det finnes selvsagt unntak, eksempelvis om konsumenten av funksjonene IKKE bruker typescript selv, eller input kommer rett fra en server.
Kanskje forsto jeg det ikke der og da, men min nye forelskelse i TypeScript var starten på en større forståelse av hvordan koden jeg skriver er på en ganske lang reise før den ender opp i nettleseren til en sluttbruker.
Alt som skjer av bygging, transpilering, og lignende var som sagt tidligere noe jeg nærmest fraskrev meg ansvaret for. Prosjektene jeg har jobbet på har mer eller mindre eksklusivt blitt brukt på store desktop-maskiner med raskt nett, så ting som mobiltilpasning, og små bundle-sizes har blitt neglisjert av meg.
Jeg overforenkler min egen reise litt her, men for et års tid siden bestemte jeg meg for å gjøre alvor av mine drømmer om å modernisere kodebasen.
Riktignok har jeg alltid vært flink til å holde avhengigheter i package.json ajur med nyeste versjon. Men å endre hele oppsettet har alltid sittet litt lengre inne.
#1: Bort fra Create React App
Min største frustrasjon hadde vært CRA (Create React App), et fantastisk verktøy til sånne som meg som bare ga meg alt mulig av konfigurasjon ut av boksen, så jeg kunne gå rett på kodingen.
Ettersom siste major release kom på tampen av 2021, var jeg var smertelig klar over at prosjektet i praksis var dødt. Men fordelen med å slippe å ta stilling til masse config-filer blir fort en ulempe når du nettopp ønsker å endre enkelte konfigurasjoner.
Selv om CRA benytter seg av Webpack, er det ikke lett å overstyre det som CRA har lagt opp til, og Eject-funksjonen gjør at man får fryktelig mange filer å måtte forholde seg til. Et veldig enkelt eksempel er at det er relativt vanskelig å sette opp støtte for @aliaser i import paths med CRA:
import { Button } from '@components/Button'
Ikke bare skal det vise seg å finnes fryktelig mange alternativer til CRA, men etter en kjapp vurdering ender jeg med å “safe” å bruke populære Vite. Men det går ikke helt problemfritt. Ettersom vi bruker Protobuf med gRPC til å kommunisere mellom frontend og backend, er vi avhengig av genererte filer. Men kombinasjonen av ts-proto var dessverre ikke direkte kompatibel med Vite. Så da kom jeg til første store hinder.
Overraskende nok finnes det et lite knippe med alternativer, men jeg endte med Protobuf-ES. Det førte til en ganske stor omskriving, hvor hvert eneste kall måtte endres. Det er slike ting som ofte ville bli sett på som en show-stopper, men jeg følte det var helt nødvendig å ta meg tiden til å rydde opp slik at ikke kodebasen var fastlåst av gamle valg i all evighet.
Det finnes nok av guider på nettet om hvordan man migrerer fra CRA, til Vite, så det gikk relativt problemfritt, selv om også dette krevde en del mindre endringer i ganske mange filer.
#2: Rydding i package.json
Neste steg var å rydde i package.json, for å titte på alle pakkeavhengighetene.
Det er verdt å merke seg at tree-shaking fungerer veldig godt slik at man ikke får noen skikkelig stor bundle om man har ubrukte pakker. Selv ikke om man flytter alle pakkene fra devDependencies til dependencies. Faktisk ender man opp med identisk størrelse på bygget.

Jeg sa tidligere at jeg er veldig flink til å bumpe versjonene jevnlig. Likevel er jeg ikke sikker på at det alltid er en god idé. Selvsagt er det lurt å sørge for at man ikke har sikkerhetshull i kodebasen sin, og man kan risikere å få en enorm migreringsjobb dersom man lenge har utsatt breaking changes.
Likevel, dersom man har et veldig lite behov, er det kanskje ikke nødvendig med mange nye features i en pakke heller. Eksempelvis om man kun ønsker en pakke som kan vise XML på en fin måte, så er det kanskje ikke nødvendig å oppgradere til en nyere versjon med masse ekstra funksjoner som i tillegg kan redigere XML, når du ikke har det behovet.
Et konkret eksempel på dette er Axios v0.27.2 som jeg installerte for årevis siden på 19.2kb, mens den nyeste i skrivende stund (v1.9.0) er på 35,8 kb. Jeg skal ikke påstå at det ikke er viktige oppdateringer i mellomtiden, men det er garantert mye jeg heller ikke trenger.
Axios var forøvrig en av pakkene jeg bestemte meg for å sparke ut. Som tidligere nevnt brukes gRPC i all kommunikasjon med backend, så det var bare noe småtteri som fortsatt gikk utenom, og da kunne jeg like godt bruke fetch APIet, selv om det har litt mer overhead. Jeg fant også noen andre pakker jeg lett kunne kvitte meg med fordi det egentlig ikke var noe stor behov for dem.
#3: Mindre "chunker"
Jeg hadde plutselig begynt å se på chunk-sizes i bygge-prosessen. Som tidligere nevnt har jeg ikke brydd meg nevneverdig om det, fordi brukerne primært har lynkjapt internett fra datamaskiner, men jeg så at det var noen åpenbare forbedringer jeg kunne gjøre:
build/assets/index-Cp3TTP2i.css 107.24 kB │ gzip: 18.80 kB
build/assets/index-FB7z_soS.js 2,176.48 kB │ gzip: 674.56 kB
Alt over 500kb gir en fin warning når jeg bygger, og jeg bestemte meg for å se om det var noe som var verdt å ta tak i. Absolutt alt av JavaScript, inkludert tredjepartsbiblioteker ble bundlet sammen. Det var relativt enkelt å sette opp noe slikt i vite.config.ts
build: {
outDir: 'build',
rollupOptions: {
output: {
manualChunks: {
react: ['react', 'react-dom'],
router: ['react-router-dom'],
oidc: ['oidc-client-ts'],
zod: ['zod'],
date: ['date-fns'],
grpc: [
'@bufbuild/protobuf',
'@connectrpc/connect',
'@connectrpc/connect-web'
],
floatingui: ['@floating-ui/react'],
hookform: ['react-hook-form', '@hookform/resolvers'],
redux: ['react-redux', '@reduxjs/toolkit']
}
}
}
}
Merk at selv om chunken blir mindre er det ikke nødvendigvis lurt å splitte ut alle tredjepartsbiblioteker og rammeverk. React er for eksempel i bruk på alle sider, og må dermed lastes inn initiellt uansett. Grunnen til at det likevel kan være verdt å gjøre det er at filene kan caches av nettleseren. På den måten trenger absolutt alt lastes inn på nytt selv om du bare har endret en skrivefeil på en side.
Deretter har man lazy-loading i React. Det er også med på å splitte opp en ellers stor bundle, og gir mening om man kan utsette lasting til en bruker trenger det. Gir veldig mening om en komponent kun er i bruk noen få steder.
Her er et enkelt eksempel:
import { LoadingSpinner } from '@components/LoadingSpinner';
import React, { lazy, Suspense } from 'react';
const GoogleMapsComponents = lazy(() => import('./GoogleMapsComponents'));
export const MapView: React.FC = () => (
<section className='map-view'>
<Suspense
fallback={
<div>
<LoadingSpinner /> Laster kart...
</div>
}
>
<GoogleMapsComponents />
</Suspense>
</section>
);
Her skal det sies at jeg først gikk litt i overkant bananas som først ga meg en slags lykkerus av å få ned bundlesizen betraktelig.
Men etter å ha hatt dette noen dager i prod ble lykkerusen til frustrasjon når jeg opplevde at veldig ofte når jeg trykket på en lenke ga det et betydelig “lagg” fordi mange nye filer måtte lastes ned for hver side. Sånn sett synes jeg det er verdt å ha en noe lengre ventetid ved første gangs lasting, enn å oppleve at hver side bruker tid på å laste inn. Det gir likevel mening å splitte ut sider som man sjelden er innom, eller kun noen brukere har rettigheter til å se.
Forøvrig er vite-bundle-analyzer gull verdt om man skal finne store syndere i bygget sitt.
Neste steg..?
Hva er neste steg av forbedringer og forenklinger i kodebasen?
En naturlig kandidat er å titte på Biome, som er et verktøy som kan erstatte ESLint og Prettier, og i tillegg være ekstremt mye raskere. Da ESLint gikk til versjon 9, endret de config-formatet, noe som har gjort at jeg har utsatt oppgraderingen.
I tillegg er det lenge siden jeg har gått igjennom reglene i kodebasen, og endel henger igjen fra CRA-tiden. Tilsammen gjør dette at jeg er mer enn klar nok til å ta tiden fatt for å rydde opp!
Biome 2 er i beta i skrivende stund så regner med at jeg kaster meg over denne så fort den er lansert.