WebCatalog 如何利用 APFS 複製功能,將 Mac App 的磁碟用量最多降低 8 倍

WebCatalog 桌面版應用程式現在會在 macOS 上使用 APFS 複製功能,大幅減少磁碟空間用量。相同的 Electron 和 Photon 資料只需儲存一次,因此每新增一個應用程式,通常只會額外占用 1–2 MB 的實體儲存空間,而不是約 320 MB。

2026年9月23日

Nguyen Tran · Software Engineer

WebCatalog 如何利用 APFS 複製功能,將 Mac App 的磁碟用量最多降低 8 倍

過去,在 macOS 上使用 WebCatalog 桌面應用程式建立的每個應用程式,大約會占用 320 MB 的磁碟空間。對以 Electron 為基礎的應用程式來說,這並不罕見;但 WebCatalog 是為了讓使用者能將許多網頁應用程式當作桌面應用程式使用而設計的。安裝十個應用程式,就可能占用超過 3 GB,儘管這些應用程式內的大部分資料其實完全相同。

我們最近改變了 WebCatalog 桌面應用程式在 macOS 上建立應用程式的方式。現在,第一個應用程式大約會使用 340 MB 的實際磁碟空間,其中包含一份儲存在本機、預先準備好的應用程式引擎。此後,每新增一個應用程式,通常只會增加 1–2 MB 的實際磁碟用量。在我們的測試中,十個應用程式的用量從超過 3 GB 降至約 360 MB。

我們使用的是 macOS 內建的功能:APFS 複製(APFS clones)。關鍵不只是複製檔案,而是讓複製機制發揮作用,同時確保每個應用程式仍然獨立、程式碼簽署正確,且功能與舊流程建立的應用程式相同。

為什麼每個應用程式都會再占用 320 MB

WebCatalog 桌面應用程式會將網站轉換成獨立的桌面應用程式。你建立的每個應用程式都在 Photon 上執行;Photon 是我們以 Electron 打造的應用程式引擎。在 macOS 上,每個應用程式都是一般的 .app 套件,內含 Electron(Chromium 和 Node.js)、Photon,以及該應用程式專屬的檔案。

以 Slack 和 Discord 兩個應用程式為例。它們的名稱、圖示、套件識別碼和設定各不相同,但體積龐大的 Electron 框架和 Photon 的大部分內容都相同。

在此之前,每個應用程式都是以完全獨立的副本建立。

Slack.app
  Electron
  Photon
  Slack 專屬檔案

Discord.app
  Electron
  Photon
  Discord 專屬檔案

兩個應用程式中的 Electron 框架和 Photon 可能連每個位元組都相同,但 macOS 仍會為每個應用程式儲存另一份實際副本。因此,十個應用程式就代表大約十份幾乎相同、大小約 320 MB 的應用程式套件。

不過,這種架構有一項我們想保留的特性:每個應用程式都完全獨立。刪除其中一個不會影響其他應用程式;已安裝的應用程式也不依賴 WebCatalog 桌面應用程式持續存在於系統中。

我們想消除的是底層不必要的重複資料。

APFS 已經具備我們需要的基本機制

現代 Mac 使用 Apple 的 APFS 檔案系統。APFS 支援複製:這是一種寫入時複製機制,讓兩個獨立檔案在其中一個發生變更前,共用相同的實體資料。

假設要複製一個 300 MB 的檔案。使用一般複製方式,macOS 會再將 300 MB 寫入磁碟,因此兩個檔案合計會占用約 600 MB。

使用 APFS 複製時,新檔案看起來和運作起來仍是一個完整的 300 MB 檔案,但起初會指向與原始檔案相同的實體區塊。

檔案 A ─────┐
            ├── 共用的實體區塊
檔案 B ─────┘

如果檔案 B 的一部分日後發生變更,APFS 只會為變更的資料寫入新區塊。其餘仍相同的內容則可以繼續共用原本的區塊。

檔案 A ───────── 共用區塊

檔案 B ───────── 共用區塊
       └──────── 已變更的區塊

從應用程式的角度來看,檔案 A 和檔案 B 是兩個獨立檔案;從 SSD 的角度來看,相同的資料只需要儲存一次。

這幾乎就是我們需要的運作方式。

從共同基礎建立應用程式

現在,WebCatalog 桌面應用程式會為每種 Electron 與 Photon 版本組合準備一個基礎版本。Photon 基礎應用程式包含各應用程式之間相同的部分,包括 Electron、Photon、框架結構和共用的簽章。

當桌面應用程式使用相同版本建立另一個應用程式時,不再從頭解壓縮並建立另一份完整副本,而是對 Photon 基礎應用程式進行 APFS 複製,再只針對需要不同的部分進行客製化。

這些部分包括應用程式名稱、圖示、套件識別碼、設定、執行檔名稱、建置識別碼,以及其他應用程式專屬的中繼資料。

由於套件的大部分內容維持不變,APFS 會繼續共用包含 Electron 和 Photon 的實體區塊。只有相對少量的應用程式專屬資料會占用新的空間。

為什麼不直接共用 Electron?

一個更直覺的做法,是只安裝一次 Electron,讓每個應用程式都指向它。

我們考慮過這個方法,但它會從根本上改變可靠性模式。如果共用的 Electron 安裝項目消失,所有依賴它的應用程式都可能無法運作。快取清理工具可能會移除它;解除安裝 WebCatalog 桌面應用程式也可能移除它;移動或還原個別應用程式則會變得更加複雜。

此外,我們還得追蹤哪些已安裝的應用程式依賴哪些執行階段版本,才能安全地清理舊版本。

macOS 程式碼簽署也帶來另一個問題:嚴格簽章驗證會拒絕指向應用程式套件外部的符號連結。

APFS 複製讓我們取得共用資料的好處,卻不必引入這種依賴關係。已安裝的應用程式可以共用實體磁碟區塊,同時維持完整、獨立的應用程式套件。

刪除 Photon 基礎應用程式,不會讓由它建立的應用程式故障;刪除其中一個已安裝的應用程式,也不會影響其他應用程式。檔案系統負責處理資料共用,而應用程式架構依然保持獨立。

程式碼簽署差點抵銷省下的空間

複製應用程式只是問題的一部分。macOS 應用程式還需要經過程式碼簽署。

我們舊有的建置流程會在客製化完成後,對每個應用程式套件進行深度重新簽署。對一般副本而言,這沒有問題;但在寫入時複製的儲存機制下,修改大型二進位檔可能導致 APFS 為它分配新的實體區塊。

在開發期間,我們測得重新簽署 Electron 框架會使每個應用程式增加約 194 MB 的實體儲存用量。我們雖然成功避免在建立應用程式時複製 Electron,卻又在簽署時複製了其中一大部分。

因此,簽署流程也必須改變。

現在,大型共用框架會在 Photon 基礎應用程式中完成簽署。當桌面應用程式複製這個基礎版本時,我們會避免修改那些大型共用二進位檔,只重新簽署各應用程式之間確實需要有所差異的較小部分。

應用程式完成後,我們會對最終套件執行嚴格簽章驗證,確保所有內容仍然有效。

這讓我們能同時保有 macOS 程式碼簽署的保障,以及 APFS 帶來的儲存空間節省效果。

要求 Node.js 複製,結果卻沒有真正複製

還有另一個意料之外的問題:執行複製作業本身。

Node.js 提供旨在要求寫入時複製行為的複製旗標,包括 COPYFILE_FICLONE 和 COPYFILE_FICLONE_FORCE。從規格上看,這些似乎正是我們需要的 API。

在使用 Node.js 24 的測試中,我們以幾種方法複製同一個 288 MB 的 Electron 應用程式,並測量每次操作額外占用了多少實體磁碟空間:

fs.cpSync                              +288 MB
fs.cpSync + COPYFILE_FICLONE           +288 MB
fs.cpSync + COPYFILE_FICLONE_FORCE     +288 MB
/bin/cp -c -R                          ~0 MB

在我們的測試中,Node.js 的兩種複製模式都產生了完整的實體副本。即使是 FORCE 版本,當預期的複製行為沒有發生時,也沒有回報錯誤。

macOS 原生的 cp -c 操作則如預期運作,因此 WebCatalog 桌面應用程式直接使用這個機制。

這也提醒我們,檔案系統最佳化應該透過測量檔案系統本身來驗證。呼叫要求寫入時複製的 API,不一定代表產生的檔案真的共用實體儲存空間。

讓最佳化即使失敗也不影響安裝

節省磁碟空間是一項最佳化;成功安裝應用程式則是必要條件。

我們將新流程設計成即使複製失敗,也不會影響應用程式安裝。如果準備 Photon 基礎應用程式失敗、快取中的基礎版本損壞、複製失敗,或簽章驗證未通過,桌面應用程式就會退回先前的建置流程,進行完整解壓縮與完整簽署。

因此,最壞的情況只是磁碟用量與以往相同,而不是安裝失敗。

我們也必須處理並行作業的情況。Photon 基礎應用程式會先在暫存目錄中準備好,完成後才移至最終位置。這樣一來,即使同時建置兩個應用程式,也不會不小心讀取到尚未建置完成的基礎版本。

舊的基礎版本也能安全移除。已安裝的 APFS 複製項目不依賴原始基礎版本持續存在。即使基礎版本被刪除,複製項目仍會保留它所使用的實體區塊。

這項機制適用於 APFS,也就是所有現代 Mac 的預設檔案系統。如果你的應用程式存放在無法進行複製的磁碟上,例如使用 HFS+ 或 exFAT 的外接硬碟,WebCatalog 桌面應用程式就會退回一般複製方式。因此,應用程式仍會像以前一樣運作,只是不會節省空間。Windows 和 Linux 目前則維持不變。

邏輯大小與實體大小不是同一回事

有一個可能稍令人困惑的副作用:Finder 可能仍會顯示每個應用程式約為 320 MB。

這個數字代表應用程式的邏輯大小。應用程式確實包含約 320 MB 的檔案;如果將這些檔案複製到其他地方而不使用複製機制,大致就需要寫入這麼多資料。

改變的是實體大小,也就是這些檔案在磁碟上實際占用了多少不重複的區塊。

如果兩個應用程式包含相同的 300 MB 框架,而 APFS 允許它們共用底層區塊,那麼每個應用程式在邏輯上都可以包含 300 MB,但第二個應用程式幾乎不會增加新的實體資料。

Finder 的「取得資訊」視窗和 du 等工具,不一定能呈現這種共用情形。要觀察節省效果,最明顯的指標是磁碟實際剩餘的可用空間。

我們使用七個實際應用程式驗證了這一點:Discord、Facebook、Instagram、Messenger、TikTok,以及兩個自訂應用程式。與舊建置流程相比,以這種方式建立它們約可節省 1.9 GB 的磁碟空間。

我們也檢查了底層的實體磁碟偏移位置,確認這些應用程式在區塊層級共用了相同的 Electron 和 Photon 資料。使用舊建置流程建立的應用程式則沒有共用任何這些區塊,為我們提供了有用的對照。

最終成果

對只使用 WebCatalog 桌面應用程式建立一個應用程式的人來說,差異很小。第一個應用程式實際上比以前稍微占用更多磁碟空間,因為桌面應用程式還會保留一份 Photon 基礎應用程式,以供後續複製使用。

但從那之後,效益會迅速增加。

先前

1 個應用程式       ~320 MB
10 個應用程式      >3 GB

使用 APFS 複製後:

現在

1 個應用程式       ~340 MB
10 個應用程式      ~360 MB

建立初始基礎版本後,每新增一個應用程式,通常只會增加 1–2 MB 的實體磁碟用量。

重要的是,我們在達成這項成果的同時,沒有引入對共用執行階段的依賴。每個應用程式仍是一般、獨立完整的 macOS 應用程式,而 APFS 只需儲存一次相同的 Electron 和 Photon 資料。安裝約十個應用程式時,實際磁碟用量可以降低至原本的八分之一以下。

這項功能已在最新版 macOS WebCatalog 桌面應用程式中提供。你不需要啟用任何設定:新建立的應用程式會自動使用這項功能,現有應用程式也會在下次更新時縮減磁碟用量。