Skip to content

cjohansen.no

Å organisere større JavaScript-prosjekter

Når mengden JavaScript stadig øker blir det hele tiden viktigere å organisere koden på et vis. Gjennom to artikler skal vi se hvordan vi kan oppnå kode som både er lettere å vedlikeholde og å jobbe flere sammen på.

Livet i ad hoc-verden

JavaScript har tradisjonelt sett blitt utviklet på en ad hoc-aktig måte. Jeg tror følgende er beskrivende for mye kode som finnes der ute:

Man bygger en webside, og får på et tidspunkt behov for å krydre opp interaksjonen med litt JavaScript. Kanskje noen linker som skal sende inn et skjema, litt enkel skjemavalidering og et par Ajax-dingser.

Noen funksjoner blir rablet ned, stappet i <head/>, og disse aksesseres fra noen inline eventhandlere. Etterhvert blir det mer, og man flytter litt kode ut i en egen scriptfil. På sikt blir denne stor, og man deler kanskje opp i to filer: generiske komponenter i lib.js og noe mer site-spesifikk kode i site.js eller lignende.

Langs veien kommer det til et js-rammeverk, og noen slipper inn litt autogenerert kode fra serverside-rammeverket og ting begynner å vokse ut av proporsjoner. To utviklere som jobber på hver sin "lib"-komponent må merge endringene hver gang de commiter fordi begge komponentene er i samme fil.

Und so weiter. Denne strategien kommer fort til kort. Ikke minst gir den et lite intuitivt miljø for nye utviklere å tre inn i.

JavaScripts mangler

Noe av skylda for at man ender opp med slike lite intuitive oppsett kan man tillegge JavaScript. JavaScript tilbyr ingen verktøy for å strukturere koden din. Her er det ikke noe pakkesystem ala Javas, ikke noe require eller import. JavaScript har - som vi tidligere har sett - ikke engang et opplegg for navnerom - all kode evalueres i det globale miljøet.

Det eneste JavaScript byr oss på av verktøy er <script/> (...) og fleksible objekter. It ain't much, men det er nok til at vi med litt disiplin kan organisere oss på en skikkelig måte.

Mål

Det er greit å ha en formening om hva vi ønsker å oppnå. Her er forslag til noen mål med koden vår:

I resten av artikkelen, og i morgen, skal jeg forsøke å lansere ett forslag til løsning. Siden det ikke er noen "den ene JavaScript-måten" å gjøre dette på så blir mitt forslag bare, vel, et forslag. Det er sikkert mange andre praksiser der ute som fungerer fint, og jeg hører gjerne fra dere som leser hvordan dere løser organisering av kode.

Forutsetninger

Jeg forutsetter i mitt forslag at man jobber på et prosjekt hvor det er mer enn minimalt med JavaScript. Hva som definerer en slik minimumsmengde er vanskelig å si. Men jeg har altså ikke de helt minste prosjektene i tankene her. I tillegg forutsetter jeg en hang til objektorientert design. Det er helt mulig å skrive funksjonsorientert JavaScript bra, men for nå fokuserer jeg altså på en objektorientert tilnærming.

Globalt skop og navnerom

Jeg har tidligere i kalenderen gått gjennom en måte å løse problematikken rundt det globale skopet på. Hvorvidt du bruker namespace()-metoden fra tidligere, noe lignende, eller rett og slett bare manuelt håndterer objektene er hipp som happ. Fordelen med å bruke noe ala namespace() er at man får litt løsere kopling mellom komponentene.

namespace() gir deg et objekt uansett om objekter i "stien" er definert eller ikke, og dette gjør at man ikke trenger å stokke filer i en forhåndsdefinert rekkefølge for å få navnerommene til å laste riktig. Sålenge objektene da ikke på andre måter avhenger av hverandre står man litt friere i forhold til rekkefølge.

Objekter og filer

Vi deler altså koden vår opp i logiske komponenter. Disse komponentene kan for enkle tilfeller bli enkeltobjekter ("klasser"), eller for mer sammensatte tilfeller bli en gruppe objekter ("klasser"). Her kan man enten skjære helt igjennom og gjøre som man gjør i Java: hver komponent/"klasse" i hver sin fil. Eller, man kan tillate å samle flere enn en "klasse" i én fil når disse sammen er en feature, eller er i samme navnerom. Litt skjønn kan man utvise.

Hensikten med denne oppdelingen er selvfølgelig å få bedre oversikt over koden, og å skille enkeltdelene bedre. Med et oppsett som dette er det også langt lettere å skru av enkeltfunksjonalitet skulle det være aktuelt.

Nettopp fordi oversikt og utviklervennlige filer er et viktig poeng er det greit å utøve skjønn i forhold til hvordan man deler kode opp i filer. Mange filer med småobjekter på 10-20 linjer kan bidra til mer rot enn oversikt. Her må teamet bli enige om hva som passer utviklerne best.

Dette er vel og bra, hadde det bare ikke vært for at JavaScript ikke har noen enkel måte å inkludere et script fra et annet script på, og at flere filer alltid betyr dårligere ytelse. Dette problemet takler vi først i morgendagens artikkel.

Hvordan kjøre ikke-påtrengende kode?

Når vi skriver ikke-påtrengende JavaScript får vi mange fordeler, men også noen nye utfordringer. Den mest åpenbare går på at vi nå trenger et nytt "lag" der det settes eventhandlere på elementer, og forskjellige script-elementer settes opp. Fordi vi ikke ønsker forsinkelse i denne fasen (det vil resultere i flikking og andre uønskede effekter) så gjøres dette gjerne på den raskeste måten: gjennom en implementasjon av DOMContentLoaded-eventen.

Å vite hvordan vi skal hekte på funksjonaliteten er greit nok - men det er ihvertfall to måter å gjøre det på:

  1. En eventhandler for DOMContentLoaded i hver enkelt fil som inisierer komponenten
  2. En eventhandler i en fil som "starter" alle komponentene

Den første løsningen har en ulempe i at den kobler sidespesifik funksjonalitet med våre (forhåpentligvis) godt abstraherte komponenter. Dessuten sprer den "bootup"-logikk rundt i mange filer.

Den andre løsningen har en ulempe i at den skiller logikken bak en komponent fra logikken for å kjøre den i forskjellige filer. Derimot har den en styrke i at det blir en lett tilgjengelig plass å finne ut av hva som kjøres når siden lastes.

Noen praktiske løsninger på disse utfordringene, samt noen forslag til løsning på problemer med mange js-filer ser vi på i morgendagens noe mer praktisk orienterte artikkel. Vel møtt!

Possibly related

2006 - 2010 Christian Johansen Creative Commons License