Kiwix PWA 如何讓使用者儲存網際網路上的 GB 資料以供離線使用

Geoffrey Kantaris
Geoffrey Kantaris
Stéphane Coillet-Matillon
Stéphane Coillet-Matillon

許多人圍坐在一台筆電旁邊,桌子上擺著一張塑膠椅。背景看起來像是一個開發中國家的學校。

本個案研究將探討非營利組織 Kiwix 如何運用漸進式網頁應用程式技術和 File System Access API,讓使用者下載及儲存大型網際網路封存檔供離線使用。瞭解實作程式碼處理來源私人檔案系統 (OPFS) 的技術實作程序。OPFS 是 Kiwix PWA 中的全新瀏覽器功能,可強化檔案管理,提昇在沒有權限提示的情況下存取封存更佳。本文將探討這個新檔案系統中的挑戰,並強調潛在的未來發展。

關於 Kiwix

在網路誕生超過 30 年之後,全球有三分之一的人口仍在等待國際電信聯盟取得可靠的網際網路存取權。故事結束了嗎?當然不是瑞士非營利機構 Kiwix 的人們發展出一套開放原始碼應用程式和內容生態系統,旨在協助部分或完全沒有網路的使用者取得知識。重點在於,如果「無法」輕鬆存取網際網路,他人就能為您下載金鑰資源 (可在可連線的地方),然後儲存在本機供離線使用。許多重要網站 (例如維基百科、 Gutenberg、Stack Exchange,甚至是 TED 講座) 現在都可以轉換為經過壓縮的封存檔 (稱為 ZIM 檔案),並透過 Kiwix 瀏覽器即時閱讀。

ZIM 封存使用高效率的 Zstandard (ZSTD) 壓縮 (舊版使用的 XZ),主要用於儲存 HTML、JavaScript 和 CSS,而圖片則通常會轉換為壓縮的 WebP 格式。每個 ZIM 也都包含網址和標題索引。壓縮就是關鍵,因為英文 Wikipedia 的所有英文內容 (640 萬篇報導加上圖片) 在轉換為 ZIM 格式後會壓縮為 97 GB,在您發現中階 Android 手機現在也能融入各種人類知識,聽起來似乎十分龐大。另提供許多較小的資源,包括維基百科的主題版本,例如數學和醫學等。

Kiwix 提供一系列原生應用程式,以電腦 (Windows/Linux/macOS) 和行動裝置 (iOS/Android) 使用為目標。不過,本個案研究將著重在漸進式網頁應用程式 (PWA),目的是讓任何具備新式瀏覽器的裝置都能使用,並打造通用的簡易解決方案。

我們會探討開發需在離線狀態下快速存取大型內容封存檔案的通用網頁應用程式,還有一些新型 JavaScript API (尤其是 File System Access API) 和來源私人檔案系統,這些挑戰為這些挑戰提供創新、令人期待的解決方案。

要離線使用的網頁應用程式嗎?

Kiwix 使用者有許多不同的需求,Kiwix 則幾乎無法控制用於存取其內容的裝置和作業系統。某些裝置可能速度較慢或過時,特別是在全球低收入地區。雖然 Kiwix 盡力涵蓋更多用途,但該機構也瞭解到,可以在任何裝置上使用「最通用的軟體」軟體 (網路瀏覽器) 觸及更多使用者。因此,受到 Atwood's 法律啟發,要求任何可透過 JavaScript 編寫的應用程式最終都會以 JavaScript 編寫 (部分 Kiwix 開發者),大約在 10 年前將 Kiwix 軟體從 C++ 移植到 JavaScript。

本通訊埠的第一個版本稱為 Kiwix HTML5,是針對 Firefox OS 和瀏覽器擴充功能已終止的版本。其核心原為 (且是) 將 C++ 解壓縮引擎 (XZ 和 ZSTD) 編譯為 ASM.js 中繼 JavaScript 語言,以及較新版本的 Wasm (或稱 WebAssembly),需使用 Emscripten 編譯器進行編譯。稍後更名為 Kiwix JS,我們仍在主動開發瀏覽器擴充功能

Kiwix JS 離線瀏覽器

進入漸進式網頁應用程式 (PWA)。為瞭解這項技術的潛力,Kiwix 開發人員建構了 Kiwix JS 專屬的 PWA 版本,並設定新增 OS 整合,讓應用程式能夠提供類似原生的功能,特別是在離線使用、安裝、檔案處理和檔案系統存取等方面。

離線優先 PWA 極輕巧,因此非常適合在不穩定或昂貴的行動網際網路環境中使用。背後的技術就是 Service Worker API 和相關的 Cache API,供以 Kiwix JS 為基礎的所有應用程式使用。這些 API 可讓應用程式擔任伺服器,攔截使用者檢視的主要文件或文章中的擷取要求,並將這些要求重新導向至 (JS) 後端,以從 ZIM 封存中擷取及建構回應。

儲存空間,隨處儲存

由於 ZIM 檔案十分龐大,儲存和存取空間也龐大,因此或許是 Kiwix 開發人員最大的問題。許多 Kiwix 使用者會在有網際網路可用的情況下在應用程式中下載內容,以便之後離線使用。其他使用者則使用 Torrent 在電腦上下載檔案,再傳輸至行動裝置或平板電腦裝置;另外,有些使用者則透過 USB 隨身碟或可攜式硬碟,在設有修補或昂貴行動網際網路的區域交換內容。您必須讓 Kiwix JS 和 Kiwix PWA 支援從任意使用者存取的位置存取內容的所有方法。

最初即使在低記憶體裝置上,Kiwix JS 讀取了數百 GB 的大量封存資料 (我們一個 ZIM 封存檔一個 166 GB!),即使在記憶體不足的裝置上,也是能使用 File API。不管是舊版瀏覽器,所有瀏覽器都能使用這個 API,而且在不支援新版 API 的情況下,可做為通用備用 API。就如同在 Kiwix 的案例中,在 HTML 中定義 input 元素一樣簡單:

<input
  type="file"
  accept="application/octet-stream,.zim,.zimaa,.zimab,.zimac, ..."
  value="Select folder with ZIM files"
  id="archiveFilesLegacy"
  multiple
/>

選取之後,輸入元素會保留 File 物件,而檔案物件基本上是參照儲存空間中基礎資料的中繼資料。從技術上,Kiwix 的物件導向後端 (以純用戶端 JavaScript 編寫) 會視需求讀取大型封存檔案的小型資料片段。如果需要解壓縮這些配量,後端會將這些配量傳送至 Wasm 解壓縮器,若收到要求則取得進一步的配量,直到完整的 blob 解壓縮為止 (通常是文章或資產)。這表示不需要將大型封存檔完全讀取到記憶體中。

和通用 API 一樣,File API 具有缺點,使 Kiwix JS 應用程式比原生應用程式看起來變得雜亂無章,像是使用者需要使用檔案選擇器挑選封存,或者將檔案拖放至應用程式,每次啟動應用程式時,由於這個 API 無法保留從一個工作階段到下一個工作階段的權限。

如同許多開發人員一樣,Kiwix JS 開發人員最初只是朝 Electron 路徑前進,為了減少使用者體驗不佳的問題,ElectronJS 是一個很棒的架構,它提供強大的功能,包括使用 Node API 的完整存取檔案系統。不過,這項工具也有一些已知的缺點:

  • 只能在電腦作業系統上執行。
  • 這大小龐大且龐大 (70 MB 至 100 MB)。

Electron 應用程式的大小非常大,因為每個應用程式都有 Chromium 的完整版本,因此與最小化和隨附的 PWA 相比,兩者相比 5.1 MB 非常不理想!

那麼 Kiwix 是否有辦法改善 PWA 使用者的情境?

提供 File System Access API 救援

大約在 2019 年,Kiwix 注意到某個 API 即將在 Chrome 78 進行來源試用,隨後更名為「Native File System API」。它承諾能夠取得檔案或資料夾的檔案控制代碼,並將其儲存在索引資料庫資料庫中。至關重要的,這個帳號代碼會在應用程式工作階段之間保持不變,因此在重新啟動應用程式時,使用者無須再次選取檔案或資料夾 (不過他們必須回答快速權限提示)。到了實際運作環境時,它已重新命名為 File System Access API,而核心部分則由 WHATWG 標準化為 File System API (FSA)。

那麼,API 的檔案系統存取部分是如何運作的?重要注意事項:

  • 這是非同步的 API (Web Worker 中的特殊函式除外)。
  • 擷取使用者手勢 (點選或輕觸 UI 元素) 時,檔案或目錄挑選器必須透過程式輔助方式啟動。
  • 為了讓使用者再次授予權限,以便存取先前選取的檔案 (在新工作階段中),使用者也必須做出使用者手勢,事實上,如果未由使用者手勢啟動,瀏覽器會拒絕顯示權限提示。

除了使用複雜的 IndexedDB API 來儲存檔案和目錄控制代碼之外,程式碼相對簡單。好消息是有些程式庫可為您處理大部分的繁瑣作業,例如 browser-fs-access。在 Kiwix JS 上,我們決定直接使用 API,這些 API 都有詳盡記錄。

開啟檔案和目錄選擇器

開啟檔案選擇器大致如下 (請使用 Promise,但如果您想要使用 async/await 糖,請參閱 Chrome for Developers 教學課程):

return window
  .showOpenFilePicker({ multiple: false })
  .then(function (fileHandles) {
    return processFileHandle(fileHandles[0]);
  })
  .catch(function (err) {
    // This is normal if app is launching
    console.warn(
      'User cancelled, or cannot access fs without user gesture',
      err,
    );
  });

請注意,為求簡化,這個程式碼只會處理第一個選取的檔案 (並禁止挑選多個檔案)。如果您想使用 { multiple: true } 選擇多個檔案,只要將處理每個控制代碼的所有 Promise 納入 Promise.all().then(...) 陳述式即可,例如:

let promisesForFiles = fileHandles.map(function (fileHandle) {
    return processFileHandle(fileHandle);
});
return Promise.all(promisesForFiles).then(function (arrayOfFiles) {
    // Do something with the files array
    console.log(arrayOfFiles);
}).catch(function (err) {
    // Handle any errors that occurred during processing
    console.error('Error processing file handles!', err);
)};

不過,挑選多個檔案當然比較好,方法是要求使用者選擇包含這些檔案的目錄,而不是其中個別檔案的目錄,特別是因為 Kiwix 使用者傾向將所有 ZIM 檔案彙整到同一個目錄中。啟動目錄挑選器的程式碼與上述程式碼幾乎相同,唯一差別在於您使用 window.showDirectoryPicker.then(function (dirHandle) { … });

正在處理檔案或目錄控制代碼

取得帳號代碼後,您必須加以處理,讓 processFileHandle 函式如下所示:

function processFileHandle(fileHandle) {
  // Serialize fileHandle to indexedDB
  serializeFSHandletoIdxDB('pickedFSHandle', fileHandle, function (val) {
    console.debug('IndexedDB responded with ' + val);
  });
  return fileHandle.getFile().then(function (file) {
    // Do something with the file
    return file;
  });
}

請注意,您必須提供函式來儲存檔案控制代碼,除非您使用抽象程式庫,否則不必提供簡便的方法。您可以在 cache.js 檔案中看到 Kiwix 實作此做法,但若僅用於儲存及擷取檔案或資料夾控制代碼,則可大幅簡化。

處理目錄相對來說比較複雜,因為您必須使用非同步的 entries.next() 疊代選取目錄中的項目,才能找出所需檔案或檔案類型。執行這項操作的方法有很多種,但以下概述 Kiwix PWA 中使用的程式碼:

let iterableEntryList = dirHandle.entries();
return iterateAsyncDirEntries(iterableEntryList, []).then(function (entryList) {
  // Do something with the entry list
  return entryList;
});

/**
 * Iterates FileSystemDirectoryHandle iterator and adds entries to an array
 * @param {Iterator} entries An asynchronous iterator of entries
 * @param {Array} archives An array to which to add the entries (may be empty)
 * @return {Promise<Array>} A Promise for an array of entries in the directory
 */
function iterateAsyncDirEntries(entries, archives) {
  return entries
    .next()
    .then(function (result) {
      if (!result.done) {
        let entry = result.value[1];
        // Filter for the files you want
        if (/\.zim(\w\w)?$/i.test(entry.name)) {
          archives.push(entry);
        }
        return iterateAsyncDirEntryArray(entries, archives);
      } else {
        // We've processed all the entries
        if (!archives.length) {
          console.warn('No archives found in the picked directory!');
        }
        return archives;
      }
    })
    .catch(function (err) {
      console.error('There was an error processing the directory!', err);
    });
}

請注意,針對 entryList 中的每個項目,您稍後需要使用 entry.getFile().then(function (file) { … }) 取得檔案;或在 async function 中使用 const file = await entry.getFile() 取得對等的檔案。

我們可以進一步改善嗎?

如果規定使用者在後續啟動應用程式時,利用使用者手勢授予權限,會增加檔案和資料夾的阻力,但檔案和資料夾開啟時仍會稍微增加,但比起強制重新選擇檔案,這種操作仍能大幅簡化。Chromium 開發人員目前正在完成程式碼,藉此允許已安裝 PWA 的永久權限。這可是許多 PWA 開發人員一直以來對抗的回報,而且他們期待看到這些努力。

但如果我不必等待,該怎麼辦?Kiwix 開發人員最近發現,可以使用 Chromium 和 Firefox 瀏覽器支援的全新 File Access API 功能 (且在 Safari 中部分支援,但仍缺少 FileSystemWritableFileStream) 消除所有權限提示。這項新功能是來源私人檔案系統

完全原生:來源私人檔案系統

來源私人檔案系統 (OPFS) 仍是 Kiwix PWA 中的實驗功能,但團隊非常高興鼓勵使用者試用,因為這可大幅縮短原生應用程式和網頁應用程式之間的差距。主要優點如下:

  • 即使在啟動時,我仍可透過無權限提示存取 OPFS 中的封存檔。使用者可以從上一個工作階段中斷的地方繼續閱讀文章並瀏覽封存內容,完全不會妨礙閱讀。
  • 對其中儲存的檔案提供高度最佳化的存取方式:Android 平台的速度加快了 5 到 10 倍之多。

使用 File API 在 Android 中存取標準檔案會非常緩慢,尤其是如果大型封存檔案儲存於 microSD 卡 (而非裝置儲存空間中),更是如此。使用這個新的 API 的所有變更。雖然多數使用者無法在 OPFS 中儲存 97 GB 的檔案 (這會耗用裝置儲存空間,而非 microSD 卡儲存空間),但最適合用來儲存中小型封存檔。您希望 WikiProject Medicine 中最完善的醫學百科全書嗎?沒問題,大小為 1.7 GB,就能輕鬆在 OPFS 中!(提示:請在應用程式內程式庫中尋找「other」→「mdwiki_en_all_maxi」)。

OPFS 的運作方式

OPFS 是由瀏覽器提供的檔案系統,每個來源都各自獨立,這類似於 Android 上的應用程式限定範圍儲存空間。您可以從使用者可見的檔案系統將檔案匯入 OPFS,或是直接下載至 OPFS (該 API 也允許在 OPFS 中建立檔案)。進入 OPFS 後,就會與裝置的其他部分隔離。在電腦版 Chromium 瀏覽器中,您也可以將檔案從 OPFS 匯出回使用者可見的檔案系統。

如要使用 OPFS,第一步是使用 navigator.storage.getDirectory() 要求存取權 (同樣地,如果您想使用 await 查看程式碼,請參閱來源私人檔案系統):

return navigator.storage
  .getDirectory()
  .then(function (handle) {
    return processDirHandle(handle);
  })
  .catch(function (err) {
    console.warn('Unable to get the OPFS directory entry', err);
  });

從這個帳號中取得的帳號代碼與上述FileSystemDirectoryHandle相同,window.showDirectoryPicker()代表您可以重複使用用於處理該程式碼的程式碼 (而且不必在 indexedDB 中儲存這些資料,只需要在需要時使用)。假設 OPFS 中已有部分檔案,而且您想使用這些檔案,則使用先前顯示的 iterateAsyncDirEntries() 函式,即可執行以下操作:

return navigator.storage.getDirectory().then(function (dirHandle) {
  let entries = dirHandle.entries();
  return iterateAsyncDirEntries(entries, [])
    .then(function (archiveList) {
      return archiveList;
    })
    .catch(function (err) {
      console.error('Unable to iterate OPFS entries', err);
    });
});

請注意,您仍須針對要從 archiveList 陣列處理的任何項目使用 getFile()

將檔案匯入 OPFS

那麼,該如何先將檔案進入 OPFS?不要這麼快!首先,您必須估算可使用的儲存空間容量,並確保使用者不會嘗試放置 97 GB 的檔案,以免超出空間大小。

取得預估配額很簡單:navigator.storage.estimate().then(function (estimate) { … });。我們稍微辛苦思考如何向使用者顯示這項資訊。在 Kiwix 應用程式中,我們選擇在核取方塊旁邊顯示少量的應用程式內面板,讓使用者試用 OPFS:

這個面板顯示儲存空間用量 (百分比) 和剩餘可用儲存空間 (以 GB 為單位)。

這個面板會使用 estimate.quotaestimate.usage 填入,例如:

let OPFSQuota; // Global variable, so we don't have to keep checking it
return navigator.storage.estimate().then(function (estimate) {
  const percent = ((estimate.usage / estimate.quota) * 100).toFixed(2);
  OPFSQuota = estimate.quota - estimate.usage;
  document.getElementById('OPFSQuota').innerHTML =
    '<b>OPFS storage quota:</b><br />Used:&nbsp;<b>' +
    percent +
    '%</b>; ' +
    'Remaining:&nbsp;<b>' +
    (OPFSQuota / 1024 / 1024 / 1024).toFixed(2) +
    '&nbsp;GB</b>';
});

如您所見,還有一個按鈕,可讓使用者透過使用者可見的檔案系統將檔案新增至 OPFS。好消息是,您可以直接使用 File API 取得要匯入的檔案物件 (或物件)。事實上,不要使用 window.showOpenFilePicker(),因為 Firefox 不支援這個方法,而 OPFS 卻「確實」支援這個方法。

上方螢幕截圖中顯示的「Add file(s)」按鈕不是舊版檔案選擇器,但會在使用者點選或輕觸時執行 click() 隱藏的舊版挑選器 (<input type="file" multiple … /> 元素)。接著,應用程式只會擷取隱藏檔案輸入的 change 事件、檢查檔案大小,並在檔案超過配額時拒絕檔案。如果一切正常,請詢問使用者是否要新增:

archiveFilesLegacy.addEventListener('change', function (files) {
  const filesArray = Array.from(files.target.files);
  // Abort if user didn't select any files
  if (filesArray.length === 0) return;
  // Calculate the size of the picked files
  let filesSize = 0;
  filesArray.forEach(function (file) {
    filesSize += file.size;
  });
  // Check the size of the files does not exceed the quota
  if (filesSize > OPFSQuota) {
    // Oh no, files are too big! Tell user...
    console.log('Files would exceed the OPFS quota!');
  } else {
    // Ask user if they're sure... if user said yes...
    return importOPFSEntries(filesArray)
      .then(function () {
        // Tell user we successfully imported the archives
      })
      .catch(function (err) {
        // Tell user there was an error (error catching is important!)
      });
  }
});

詢問使用者是否要將 .zim 檔案清單新增至來源私人檔案系統的對話方塊。

在部分作業系統 (例如 Android) 中,匯入封存並非最快作業,因此 Kiwix 也會在匯入封存檔案時顯示橫幅和小型旋轉圖示。團隊無法為這項作業新增進度指標:如果您知道了,歡迎透過明信片回答問題!

那麼 Kiwix 如何實作 importOPFSEntries() 函式?這包括使用 fileHandle.createWriteable() 方法,可有效地將每個檔案串流至 OPFS。所有工作都是由瀏覽器負責處理。(Kiwix 目前將 Promise 用於我們的舊版程式碼集,但必須說明此情況,await 會產生更簡單的語法,且能避免產生毀滅的金字塔)。

function importOPFSEntries(files) {
  // Get a handle on the OPFS directory
  return navigator.storage
    .getDirectory()
    .then(function (dir) {
      // Collect the promises for each file that we want to write
      let promises = files.map(function (file) {
        // Create the file and get a writeable handle on it
        return dir
          .getFileHandle(file.name, { create: true })
          .then(function (fileHandle) {
            // Get a writer for the file
            return fileHandle.createWritable().then(function (writer) {
              // Show a banner / spinner, then write the file
              return writer
                .write(file)
                .then(function () {
                  // Finished with this writer
                  return writer.close();
                })
                .catch(function (err) {
                  console.error('There was an error writing to the OPFS!', err);
                });
            });
          })
          .catch(function (err) {
            console.error('Unable to get file handle from OPFS!', err);
          });
      });
      // Return a promise that resolves when all the files have been written
      return Promise.all(promises);
    })
    .catch(function (err) {
      console.error('Unable to get a handle on the OPFS directory!', err);
    });
}

將檔案串流直接下載至 OPFS

還有一種做法,就是將檔案直接從網際網路串流至 OPFS,或將檔案串流至您擁有目錄控制代碼的任何目錄 (亦即使用 window.showDirectoryPicker() 挑選的目錄)。程式碼的原理與上述程式碼相同,只不過會建構由 ReadableStream 和將遠端檔案讀取位元組的控制器加入佇列的 Response。產生的 Response.body 隨後會管道到 OPFS 中新檔案的寫入器。

在此情況下,Kiwix 可以計算通過 ReadableStream 的位元組數,因此為使用者提供進度指標,並警告使用者不要在下載期間退出應用程式。程式碼稍微太多,無法在這裡顯示,但我們的應用程式是 FOSS 應用程式,因此如果您想執行類似操作,可以查看原始碼。Kiwix UI 的外觀如下 (下方顯示的不同進度值只是在百分比變化時更新橫幅,但會定期更新「Download progress」面板):

Kiwix 使用者介面,底部有長條圖示,警告使用者不要退出應用程式,並顯示 .zim 封存檔的下載進度。

由於下載作業可能需要相當長的時間,因此 Kiwix 可讓使用者在作業期間任意使用應用程式,但可確保系統一律會顯示橫幅,因此在下載作業完成前,提醒使用者不要關閉應用程式。

在應用程式中實作小型檔案管理員

此時,Kiwix PWA 開發人員發現無法將檔案新增至 OPFS。應用程式也必須讓使用者能夠從這個儲存區域刪除不再需要的檔案,在理想情況下,還能將鎖定在 OPFS 中的任何檔案匯出回使用者可見的檔案系統。實際上,您必須在應用程式中實作最小的檔案系統

在此簡單介紹適用於 Chrome 的優質 OPFS Explorer 擴充功能 (此擴充功能也適用於 Edge)。這項工具會在開發人員工具中新增一個分頁,方便您查看 OPFS 的具體內容,還能刪除惡意或失敗的檔案。只要檢查程式碼是否正常運作、監控下載行為,以及大致清除開發實驗,這種做法非常實用。

檔案匯出取決於是否能在 Kiwix 要儲存匯出檔案的目標檔案或目錄中取得檔案控制代碼,所以只有在可使用 window.showSaveFilePicker() 方法的情況下才適用這個做法。如果 Kiwix 檔案小於數 GB,我們就能在記憶體中建立 blob,提供網址,然後將 blob 下載至使用者可見的檔案系統。 遺憾的是,這類封存資料數量龐大。如果受支援,匯出檔案非常簡單:方法與將檔案儲存到 OPFS 方法大致相同 (取得要儲存檔案的控制代碼,請使用者選擇要儲存檔案的位置,然後在 saveHandle 上使用 createWriteable())。您可以在存放區中查看程式碼window.showSaveFilePicker()

所有瀏覽器都支援檔案刪除功能,只要使用簡單的 dirHandle.removeEntry('filename') 即可。在 Kiwix 的案例中,我們偏好按照上述方式疊代 OPFS 項目,讓系統先檢查所選檔案是否存在並要求確認,但並非所有人的必要檔案。同樣地,如有需要,您可以檢查程式碼

該團隊決定在提供這些選項的按鈕後,不讓 Kiwix UI 過於雜亂,而是將小型圖示直接放在封存清單下方。輕觸其中一個圖示會變更封存清單的顏色,方便使用者瞭解即將執行的操作。接著,使用者點選或輕觸其中一個封存檔,系統就會執行相應的作業 (匯出或刪除)。

詢問使用者是否要刪除 .zim 檔案的對話方塊。

最後,以下提供上述所有檔案管理功能的螢幕側錄示範,包括將檔案新增至 OPFS、將檔案直接下載到檔案中、刪除檔案,以及匯出至使用者可看到的檔案系統。

開發人員的工作永遠不會結束

OPFS 是 PWA 開發人員的一大創新成果,提供強大的檔案管理功能,並有助於大幅縮短原生應用程式和網頁應用程式之間的差距。但開發人員可說是件令人苦惱的事,卻無法滿足他們的需求!OPFS 幾乎都完美無缺,但主要功能在 Chromium 和 Firefox 瀏覽器中都能正常運作,而且也在 Android 和電腦上執行。我們希望 Safari 和 iOS 版本也能支援完整功能。以下問題仍然存在:

  • 無論底面磁碟空間有多大,Firefox 的 OPFS 配額上限都是 10 GB。雖然大多數 PWA 作者都限制了 10 GB,但這對 Kiwix 來說相當嚴格。所幸 Chromium 瀏覽器非常強大
  • 由於未實作 window.showSaveFilePicker(),因此目前無法將大型檔案從 OPFS 匯出至行動瀏覽器或電腦版 Firefox 中向使用者顯示的檔案系統。在這些瀏覽器中,大型檔案實際上會卡在 OPFS 中。這違反了 Kiwix 對內容開放自由的倫理,且使用者之間能夠共用封存檔,特別是在網路連線不穩定或所費不貲的地區。
  • 使用者無法控制 OPFS 虛擬檔案系統會使用的儲存空間。這對行動裝置來說特別問題,因為使用者的 microSD 卡可能存有大量空間,但裝置儲存空間卻很少。

但這些都只是個微不足道的問題,因為 PWA 將以截然不同的方式存取檔案。Kiwix PWA 團隊非常感謝 Chromium 開發人員和倡導者,率先提出及設計 File System Access API,也致力在瀏覽器廠商之間達成共識,瞭解原始私人檔案系統的重要性。對 Kiwix JS PWA 而言,此程式已解決過去曾對應用程式造成影響的許多使用者體驗問題,讓我們得以繼續為所有人改善 Kiwix 內容的無障礙設計。請讓 Kiwix PWA 旋轉,並告知開發人員您的想法!

如需 PWA 功能的實用資源,請參閱下列網站: