
Varje app du skapade med WebCatalogs skrivbordsapp brukade ta upp omkring 320 MB diskutrymme på macOS. Det är inte ovanligt för en Electron-baserad app, men WebCatalog är utformat för personer som använder många webbappar som skrivbordsappar. Installerade du tio kunde de ta upp mer än 3 GB, trots att det mesta av innehållet i apparna var exakt detsamma.
Vi har nyligen ändrat hur WebCatalogs skrivbordsapp bygger appar på macOS. Den första appen använder nu omkring 340 MB fysiskt diskutrymme, inklusive en lokalt lagrad, förberedd kopia av appmotorn. Därefter lägger varje ytterligare app vanligtvis bara till 1–2 MB faktisk diskanvändning. I våra tester gick tio appar från mer än 3 GB till omkring 360 MB.
Vi gjorde detta med en funktion som redan finns inbyggd i macOS: APFS-kloner. Det intressanta var inte bara att klona filer, utan att få kloningen att fungera samtidigt som varje app förblev oberoende, korrekt kodsignerad och funktionellt likvärdig med en app byggd enligt den gamla processen.
Varför varje app tog upp ytterligare 320 MB
WebCatalogs skrivbordsapp gör webbplatser till fristående skrivbordsappar. Varje app du skapar körs på Photon, vår appmotor som bygger på Electron. På macOS är varje app ett vanligt .app-paket som innehåller Electron (Chromium och Node.js), Photon och filerna för just den appen.
Ta två appar som exempel: Slack och Discord. Deras namn, ikoner, paketidentifierare och konfiguration skiljer sig åt, men det stora Electron-ramverket och det mesta av Photon är identiska.
Fram tills nu byggdes varje app som en helt separat kopia.
Slack.app
Electron
Photon
Slack-specifika filer
Discord.app
Electron
Photon
Discord-specifika filer
Electron-ramverket och Photon kunde vara identiska byte för byte i båda apparna, men macOS lagrade ändå en ny fysisk kopia för varje app. Tio appar innebar därför ungefär tio kopior av nästan samma app-paket på 320 MB.
Den arkitekturen hade dock en egenskap som vi ville behålla: varje app var helt oberoende. Att ta bort en app kunde inte påverka en annan, och installerade appar var inte beroende av att WebCatalogs skrivbordsapp fanns kvar på systemet.
Det vi ville få bort var den onödiga dupliceringen bakom kulisserna.
APFS hade redan funktionen vi behövde
Moderna Mac-datorer använder Apples filsystem APFS. APFS stöder kloning, en mekanism för kopiering vid skrivning som låter två oberoende filer dela samma fysiska data tills en av dem ändras.
Tänk dig att du kopierar en fil på 300 MB. Vid en vanlig kopiering skriver macOS ytterligare 300 MB till disken, så de två filerna tar upp omkring 600 MB sammanlagt.
Med en APFS-klon ser den nya filen fortfarande ut och fungerar som en fullständig fil på 300 MB, men till en början pekar den på samma fysiska block som originalet.
Fil A ─────┐
├── delade fysiska block
Fil B ─────┘
Om en del av fil B ändras senare skriver APFS nya block endast för de data som ändrats. Allt som förblir identiskt kan fortsätta att dela de ursprungliga blocken.
Fil A ───────── delade block
Fil B ───────── delade block
└──────── ändrade block
Ur appens perspektiv är fil A och fil B separata filer. Ur SSD-enhetens perspektiv behöver identiska data bara lagras en gång.
Det är nästan precis det beteende vi behövde.
Bygga appar från en gemensam bas
WebCatalogs skrivbordsapp förbereder nu en bas för varje kombination av Electron- och Photon-versioner. Photon-basappen innehåller de delar som är identiska mellan apparna, däribland Electron, Photon, ramverksstrukturen och de gemensamma signaturerna.
När skrivbordsappen skapar ytterligare en app med samma versioner packar den inte längre upp och bygger en ny fullständig kopia från grunden. I stället skapar den en APFS-klon av Photon-basappen och anpassar bara de delar som behöver vara annorlunda.
Det gäller bland annat appens namn, ikon, paketidentifierare, inställningar, namn på körbara filer, byggidentifierare och andra appspecifika metadata.
Eftersom större delen av paketet förblir oförändrad fortsätter APFS att dela de fysiska block som innehåller Electron och Photon. Endast den relativt lilla mängden appspecifika data tar upp nytt utrymme.
Varför inte bara dela Electron?
En mer uppenbar lösning vore att installera Electron en gång och låta varje app peka på den installationen.
Vi övervägde det, men det skulle i grunden förändra tillförlitlighetsmodellen. Om den delade Electron-installationen försvann skulle alla appar som var beroende av den kunna sluta fungera. Ett rensningsverktyg skulle kunna ta bort den, en avinstallation av WebCatalogs skrivbordsapp skulle kunna ta bort den, och det skulle bli mer komplicerat att flytta eller återställa en enskild app.
Det skulle också kräva att vi höll reda på vilka installerade appar som är beroende av vilka runtime-versioner, så att gamla versioner kunde tas bort på ett säkert sätt.
Kodsignering i macOS skapar ytterligare ett problem. Strikt signaturverifiering avvisar symboliska länkar som pekar utanför app-paketet.
APFS-kloner ger oss fördelen med delning utan att införa det beroendet. Installerade appar kan dela fysiska diskblock och samtidigt förbli fullständiga, oberoende app-paket.
Att ta bort Photon-basappen gör inte att appar som skapats från den slutar fungera. Att ta bort en installerad app påverkar inte någon annan. Filsystemet sköter delningen medan apparkitekturen förblir oberoende.
Kodsigneringen höll nästan på att utradera besparingen
Att klona appen var bara en del av problemet. macOS-appar behöver också kodsigneras.
Vår gamla byggprocess signerade om hela app-paketet, inklusive dess underliggande delar, efter anpassningen. För en vanlig kopia fungerar det bra. Med lagring som använder kopiering vid skrivning kan en ändring av en stor binärfil däremot få APFS att tilldela den nya fysiska block.
Under utvecklingen mätte vi att omsignering av Electron-ramverket lade till ungefär 194 MB fysisk lagring per app. Vi hade lyckats undvika att kopiera Electron när appen skapades, bara för att duplicera en stor del av det igen vid signeringen.
Därför behövde även signeringsprocessen ändras.
Det stora, gemensamma ramverket signeras nu som en del av Photon-basappen. När skrivbordsappen klonar basen undviker vi att ändra de stora, delade binärfilerna och signerar bara om de mindre delar som faktiskt behöver skilja sig mellan apparna.
När appen är klar kör vi en strikt signaturverifiering av det färdiga paketet för att säkerställa att allt fortfarande är giltigt.
På så sätt kan vi behålla både macOS garantier för kodsignering och utrymmesbesparingen från APFS.
När vi bad Node.js att klona utan att något faktiskt klonades
Ett annat oväntat problem var att utföra själva kloningen.
Node.js har kopieringsflaggor avsedda att begära kopiering vid skrivning, däribland COPYFILE_FICLONE och COPYFILE_FICLONE_FORCE. På papperet lät de som precis de API:er vi behövde.
I våra tester med Node.js 24 kopierade vi samma Electron-app på 288 MB med flera metoder och mätte hur mycket ytterligare fysiskt diskutrymme varje åtgärd tog upp:
fs.cpSync +288 MB
fs.cpSync + COPYFILE_FICLONE +288 MB
fs.cpSync + COPYFILE_FICLONE_FORCE +288 MB
/bin/cp -c -R ~0 MB
Båda kloningslägena i Node.js skapade fortfarande en fullständig fysisk kopia i vårt test, och inte ens FORCE-varianten rapporterade ett fel när den förväntade kloningen uteblev.
Det inbyggda macOS-kommandot cp -c fungerade som förväntat, så WebCatalogs skrivbordsapp använder den mekanismen direkt.
Det var också en nyttig påminnelse om att filsystemsoptimeringar bör verifieras genom att mäta själva filsystemet. Att anropa ett API som begär kopiering vid skrivning betyder inte nödvändigtvis att filerna faktiskt delar fysisk lagring.
Göra optimeringen säker även när den misslyckas
Att spara diskutrymme är en optimering. Att kunna installera en app är ett krav.
Vi utformade den nya processen så att kloningen kan misslyckas utan att appinstallationen gör det. Om förberedelsen av Photon-basappen misslyckas, om den cachade basen är skadad, om kloningen misslyckas eller om signaturverifieringen inte godkänns, återgår skrivbordsappen till den tidigare byggprocessen med fullständig uppackning och fullständig signering.
I värsta fall blir diskanvändningen alltså densamma som tidigare, inte en misslyckad installation.
Vi behövde också ta hänsyn till samtidighet. En Photon-basapp förbereds först i en tillfällig katalog och flyttas till sin slutliga plats först när den är färdig. På så sätt kan två appbyggen som pågår samtidigt inte av misstag använda en halvfärdig bas.
Äldre baser kan också tas bort säkert. En installerad APFS-klon är inte beroende av att den ursprungliga basen finns kvar. Om basen tas bort behåller klonen de fysiska block som den fortfarande använder.
Detta fungerar på APFS, standardfilsystemet på alla moderna Mac-datorer. Om dina appar ligger på en disk som inte stöder kloning, till exempel en extern HFS+- eller exFAT-enhet, återgår WebCatalogs skrivbordsapp till vanlig kopiering. Apparna fungerar då som tidigare, men sparar inte utrymme. Windows och Linux är oförändrade tills vidare.
Logisk storlek och fysisk storlek är inte samma sak
En något förvirrande bieffekt är att Finder fortfarande kan visa att varje app är omkring 320 MB stor.
Den siffran anger appens logiska storlek. Appen innehåller faktiskt filer på sammanlagt ungefär 320 MB, och om filerna kopierades någonstans utan kloning är det ungefär så mycket data som skulle behöva skrivas.
Det som har förändrats är den fysiska storleken, alltså hur många unika block filerna faktiskt upptar på disken.
Om två appar innehåller samma ramverk på 300 MB och APFS låter dem dela samma underliggande block, kan varje app logiskt innehålla 300 MB medan den andra appen nästan inte tillför några nya fysiska data alls.
Finders fönster Visa info och verktyg som du visar inte nödvändigtvis den här delningen. Besparingen märks tydligast på hur mycket ledigt utrymme som faktiskt finns kvar på disken.
Vi verifierade detta med sju riktiga appar: Discord, Facebook, Instagram, Messenger, TikTok och två anpassade appar. Byggda på det här sättet sparade de omkring 1,9 GB diskutrymme jämfört med den gamla byggprocessen.
Vi kontrollerade också de underliggande fysiska diskpositionerna och bekräftade att apparna delade sina gemensamma Electron- och Photon-data på blocknivå. En app som skapats med den gamla byggprocessen delade inga av dessa block, vilket gav oss en användbar jämförelse.
Resultatet
För den som bara skapar en app med WebCatalogs skrivbordsapp är skillnaden liten. Den första appen använder faktiskt lite mer diskutrymme än tidigare, eftersom skrivbordsappen också behåller Photon-basappen som används för att skapa efterföljande kloner.
Därefter ökar nyttan snabbt.
Tidigare
1 app ~320 MB
10 appar >3 GB
Med APFS-kloning:
Nu
1 app ~340 MB
10 appar ~360 MB
När den första basen väl har skapats lägger varje ytterligare app vanligtvis bara till 1–2 MB fysisk diskanvändning.
Det viktiga är att vi har åstadkommit detta utan att införa ett beroende av en delad körmiljö. Varje app förblir en vanlig, fristående macOS-app, samtidigt som APFS bara lagrar identiska Electron- och Photon-data en gång. Med omkring tio installerade appar kan det minska den faktiska diskanvändningen med mer än åtta gånger.
Detta finns i den senaste versionen av WebCatalogs skrivbordsapp för macOS. Du behöver inte aktivera något: nya appar använder det automatiskt, och appar du redan har tar upp mindre utrymme nästa gång de uppdateras.