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

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

一群人圍著筆電聚集在一起,筆電放在簡單的桌子上,左側有塑膠椅。背景看起來像是位於開發中國家的學校。

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

關於 Kiwix

根據國際電信聯盟 (International Telecommunication Union) 的資料,在網際網路問世 30 多年後,全球三分之一的人口仍在等待可靠的網際網路連線。這個故事是否就此結束?當然不是。總部位於瑞士的非營利組織 Kiwix 的團隊成員開發了一個開放原始碼應用程式和內容的生態系統,目的是讓無法或無法充分存取網際網路的使用者也能取得知識。其概念是如果無法輕鬆存取網際網路,則使用者可代您下載金鑰資源 (如果有可連線的地方),並將這些資源儲存在本機上以供日後離線使用。許多重要網站 (例如維基百科、Project Gutenberg、Stack Exchange,甚至是 TED 講座) 現在可轉換成高壓縮的 ZIM 檔案 (稱為 ZIM 檔案),並可透過 Kiwix 瀏覽器即時閱讀。

ZIM 封存檔使用高效能的 Zstandard (ZSTD) 壓縮功能 (舊版使用 XZ),主要用於儲存 HTML、JavaScript 和 CSS,而圖片通常會轉換為壓縮的 WebP 格式。每個 ZIM 也包含網址和標題索引。壓縮是關鍵所在,因為在轉換成 ZIM 格式後,維基百科全書 (640 萬篇文章,加上圖片) 都會壓縮到 97 GB。在您察覺到所有人類知識的總和可以儲存在中階 Android 手機之前,聽起來好像很多。我們也提供許多較小的資源,包括數學、醫學等主題的維基百科。

Kiwix 提供多種原生應用程式,可在電腦 (Windows/Linux/macOS) 和行動裝置 (iOS/Android) 上使用。不過,本案例研究將著重於漸進式網頁應用程式 (PWA),這個應用程式旨在為任何裝置提供通用且簡單的解決方案,只要裝置搭載新式瀏覽器即可。

我們將探討開發通用網頁應用程式時所面臨的挑戰,這些應用程式需要提供快速存取大量離線內容檔案的功能,以及一些新式 JavaScript API,特別是 File System Access APIOrigin Private File System,為這些挑戰提供創新且令人振奮的解決方案。

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

Kiwix 使用者是面面俱到的多樣性,他們的需求各不相同,而 Kiwix 還是不太能控制用於存取內容的裝置和作業系統,還是幾乎無法控制。這類裝置有些可能速度緩慢或已過時, 尤其是世界上低收入的地區。雖然 Kiwix 會盡可能涵蓋更多用途,但機構也意識到,如果能在任何裝置上利用「最」通用的軟體 (網路瀏覽器) 來觸及更多使用者,就能吸引更多使用者。因此,受到 Atwood 法則的啟發,其中指出「任何可用 JavaScript 編寫的應用程式,最終都會以 JavaScript 編寫」,因此在約 10 年前,部分 Kiwix 開發人員開始著手將 Kiwix 軟體從 C++ 移植至 JavaScript。

這個移植版本的第一個版本稱為 Kiwix HTML5,適用於現已停用的 Firefox OS 和瀏覽器擴充功能。其核心是 (且) 使用 Emscripten 編譯器,編譯為 ASM.js 中繼 JavaScript 語言及後續 Wasm 或 WebAssembly 的 C++ 解壓縮引擎 (XZ 和 ZSTD)。後來改名為 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 隨身碟或可攜式硬碟上交換內容,如果是行動網際網路較為平緩或昂貴的單位,則有些內容會用 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 解壓縮為止 (通常是文章或資產)。也就是說,大型封存檔永遠不需要完全讀入記憶體。

雖然 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 發現了在 Chrome 78 中進行原點試驗的新型 API,後來稱為「Native File System API」。這個 API 可讓您取得檔案或資料夾的檔案句柄,並將其儲存在 IndexedDB 資料庫中。重要的是,這個句柄會在應用程式工作階段之間保留,因此使用者在重新啟動應用程式時,不會被迫再次選取檔案或資料夾 (但他們必須回答快速權限提示訊息)。在正式推出前,這個 API 已重新命名為 File System Access API,而核心部分則由 WHATWG 標準化為 File System API (FSA)。

那麼,API 的 File System Access 部分如何運作?請注意以下幾個重點:

  • 這是非同步 API (Web Workers 中的專屬函式除外)。
  • 您必須透過程式設計,擷取使用者手勢 (點選或輕觸 UI 元素),才能啟動檔案或目錄挑選器。
  • 使用者如要再次授予權限,以便存取先前選取的檔案 (在新工作階段中),也必須有使用者手勢。事實上,如果不是透過使用者手勢啟動,瀏覽器會拒絕顯示權限提示。

這個程式碼相對簡單明瞭,除了必須使用建構的 IndexedDB API 儲存檔案和目錄控制代碼之外,好消息是,有幾個程式庫可為您處理許多繁重工作,例如 browser-fs-access。在 Kiwix JS 中,我們決定直接使用 API,因為這些 API 有非常完善的文檔。

開啟檔案和目錄挑選器

開啟檔案挑選器的畫面如下所示 (此處使用 Promise,但如果您偏好 async/await sugar,請參閱 Chrome 開發人員教學課程):

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.all().then(...) 陳述式中包裝處理每個句柄的所有 Promise 即可,例如:

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;
  });
}

請注意,您必須提供用於儲存檔案句柄的函式,除非您使用抽象程式庫,否則沒有方便的方法。Kiwix 的實作方式可在檔案 cache.js 中看到,但如果只用於儲存及擷取檔案或資料夾句柄,則可大幅簡化。

處理目錄的操作稍微複雜一些,因為您必須使用非同步 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),就能立即移除所有權限提示。這項新功能就是 Origin Private File System

全面採用原生:Origin 私人檔案系統

Origin Private File System (OPFS) 仍是 Kiwix PWA 中的實驗功能,但團隊非常樂意鼓勵使用者試用,因為這項功能可大幅縮小原生應用程式和網頁應用程式之間的差距。主要優點如下:

  • 您可以不顯示權限提示存取 OPFS 中的封存檔案,即使在啟動時也是如此。使用者可以從上一個工作階段中斷的地方繼續閱讀文章,並且瀏覽封存內容,完全沒有阻礙。
  • 這項服務可提供對儲存檔案的最佳化存取方式:在 Android 上,我們發現速度提升了 5 到 10 倍。

在 Android 中,使用 File API 進行標準檔案存取作業的速度非常慢,尤其是當大型封存檔儲存在 microSD 卡上,而非裝置儲存空間時 (Kiwix 使用者經常遇到這種情況)。這項新 API 將徹底改變這一切。雖然大多數使用者無法將 97 GB 的檔案儲存在 OPFS 中 (會耗用裝置儲存空間,而非 microSD 卡儲存空間),但最適合儲存中小型封存檔。您想查看 WikiProject Medicine 提供的最完整的醫學百科全書嗎?沒問題,1.7 GB 的檔案很容易放入 OPFS!(提示:在應用程式內的資料庫中尋找「其他」→「mdwiki_en_all_maxi」)。

OPFS 的運作方式

OPFS 是瀏覽器提供的檔案系統,每個來源都會獨立提供,可視為類似於 Android 上的應用程式範圍儲存空間。檔案可從使用者可見的檔案系統匯入 OPFS,也可以直接下載至 OPFS (API 也允許在 OPFS 中建立檔案)。一旦進入 OPFS,就會與裝置的其他部分隔離。在以 Chromium 為基礎的瀏覽器中,您也可以將檔案從 OPFS 匯出回使用者能查看的檔案系統。

如要使用 OPFS,第一步是使用 navigator.storage.getDirectory() 要求存取權 (再次提醒,如果您想查看使用 await 的程式碼,請參閱「Origin 私人檔案系統」):

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

您從這個帳號取得的帳號代碼與上述 window.showDirectoryPicker() 提供的 FileSystemDirectoryHandle 類型非常相同,也就是說,您可以重複使用處理該程式碼的程式碼 (而且您更不需要將此程式碼儲存在 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() 挑選的目錄) 的任何目錄。這項功能使用與上述程式碼相同的原則,但會建構 Response,其中包含 ReadableStream 和控制器,可將從遠端檔案讀取的位元組排入佇列。產生的 Response.body管道至 OPFS 內的新檔案寫入器。

在這種情況下,Kiwix 可以計算通過 ReadableStream 的位元組,因此可向使用者提供進度指標,並警告他們不要在下載期間退出應用程式。這段程式碼有點複雜,因此我們不在此處顯示,但由於我們的應用程式是 FOSS 應用程式,因此如果您想執行類似的操作,可以查看原始碼。Kiwix UI 的樣式如下 (下方顯示的進度值不同,因為 Kiwix 只會在百分比變更時更新橫幅,但會更頻繁地更新「下載進度」面板):

Kiwix 使用者介面,底部有一個列,警告使用者不要關閉應用程式,並顯示 .zim 封存檔的下載進度。

由於下載作業可能會耗時很久,Kiwix 會讓使用者在作業期間自由使用應用程式,但會確保橫幅一律顯示,提醒使用者在下載作業完成前不要關閉應用程式。

在應用程式中實作迷你檔案管理工具

此時,Kiwix PWA 開發人員發現無法將檔案新增至 OPFS。應用程式也需要為使用者提供一種方式,讓他們從這個儲存區刪除不再需要的檔案,並在理想情況下,將鎖定在 OPFS 中的任何檔案匯出至使用者可見的檔案系統。因此,您必須在應用程式中實作迷你檔案管理系統

以下為您簡單介紹適用於 Chrome 的 OPFS Explorer 擴充功能 (它也能在 Edge 中運作)。這個工具會在開發人員工具中新增分頁,讓您查看 OPFS 中的確切內容,並刪除惡意或失敗的檔案。這對檢查程式碼是否正常運作、監控下載行為,以及一般清理開發實驗而言,都是非常有用的工具。

匯出檔案取決於能否在 Kiwix 將匯出的檔案或目錄中取得檔案控制代碼,因此只能在可使用 window.showSaveFilePicker() 方法的情況下使用。如果 Kiwix 檔案小於幾 GB,我們就能在記憶體中建構 blob、為其提供網址,然後將其下載至使用者可見的檔案系統。很抱歉,這類大型檔案無法執行這項操作。如果受到支援,匯出就非常簡單:與 OPFS 儲存檔案一樣,相反地,也就是取得要儲存檔案的控制代碼,要求使用者挑選使用 window.showSaveFilePicker() 儲存檔案的位置,然後在 saveHandle 上使用 createWriteable()。您可以在存放區中查看程式碼

所有瀏覽器都支援檔案刪除功能,而且可以透過簡單的 dirHandle.removeEntry('filename') 完成。在 Kiwix 的案例中,我們比較喜歡和之前一樣疊代 OPFS 項目,讓我們可以先檢查所選檔案是否存在,並要求確認,但這不一定適用於所有使用者。再次提醒,如有興趣,您可以檢查我們的程式碼

我們決定不採用提供這些選項的按鈕讓 Kiwix UI 變得雜亂無章,而是將小型圖示直接放在封存清單下方。輕觸其中一個圖示會變更封存清單的顏色,為使用者提供視覺線索,讓他們知道要執行哪些操作。接著,使用者按一下或輕觸其中一個封存項目,系統就會執行相應的作業 (匯出或刪除) (在確認後)。

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

最後,這是上方討論所有檔案管理功能的螢幕側錄示範,也就是新增檔案至 OPFS,直接將檔案下載到 OPFS 中、刪除檔案,以及匯出至使用者可見的檔案系統。

開發人員的工作永遠沒有結束的一天

OPFS 是 PWA 開發人員的絕佳創新,提供功能強大的檔案管理功能,可大幅縮小原生應用程式和網頁應用程式之間的差距。但開發人員實在難以置信,他們永遠都不滿意!OPFS 幾乎完美,但仍有待改進之處。很高興主要功能可在 Chromium 和 Firefox 瀏覽器中運作,而且在 Android 和電腦上皆已實作。我們希望很快就能在 Safari 和 iOS 中實作完整的功能組合。以下問題仍未解決:

  • Firefox 目前會將 OPFS 配額上限設為 10 GB,不論底層磁碟空間有多大。雖然對大多數 PWA 作者來說,這可能已足夠,但對 Kiwix 來說,這項限制相當嚴格。幸好,Chromium 瀏覽器提供的資訊量相當豐富。
  • 由於未實作 window.showSaveFilePicker(),目前無法將大型檔案從 OPFS 匯出至行動瀏覽器或電腦版 Firefox 的使用者可見檔案系統。在這些瀏覽器中,大型檔案會有效地儲存在 OPFS 中。這違反 Kiwix 開放內容存取權的理念,以及使用者之間分享封存檔的功能,特別是在網路連線不穩定或費用昂貴的地區。
  • 使用者無法控制 OPFS 虛擬檔案系統會使用的儲存空間。這在行動裝置上尤其成問題,因為使用者可能在 microSD 卡上有大量空間,但在裝置儲存空間上只有很少空間。

不過,這些都是小問題,對 PWA 檔案存取功能來說,這項功能的進展已相當顯著。Kiwix PWA 團隊非常感謝 Chromium 開發人員和倡議者,他們首先提出並設計了檔案系統存取 API,並在瀏覽器供應商之間達成共識,認為原始私人檔案系統的重要性。對於 Kiwix JS PWA,這項功能解決了許多過去阻礙應用程式運作的使用者體驗問題,並協助我們達成目標,讓所有人都能更輕鬆地存取 Kiwix 內容。請試用 Kiwix PWA,並告訴開發人員你的想法!

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