本個案研究探討非營利組織 Kiwix 如何使用漸進式網頁應用程式技術和 File System Access API,讓使用者下載及儲存大型網際網路封存檔,以供離線使用。瞭解與 Origin Private File System (OPFS) 相關的程式碼技術導入方式。Kiwix PWA 中的這項新瀏覽器功能可強化檔案管理,提供更佳的封存檔存取權,無須顯示權限提示。本文將討論這項新檔案系統的挑戰,並強調未來可能的發展。
關於 Kiwix
根據國際電信聯盟 (International Telecommunication Union) 的資料,網際網路問世超過 30 年後,全球三分之一的人口仍在等待可靠的網際網路連線。這個故事是否就此結束?當然不是。總部位於瑞士的非營利組織 Kiwix 的團隊成員開發了一個開放原始碼應用程式和內容的生態系統,目的是讓無法或無法充分存取網際網路的使用者也能取得知識。他們的想法是,如果您無法輕鬆存取網際網路,他們可以為您在有網路連線的地方和時間下載重要資源,並將這些資源儲存在本機,以便日後離線使用。許多重要網站 (例如 Wikipedia、Project 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 和 Origin Private File System,為這些挑戰提供創新且令人振奮的解決方案。
離線使用的網頁應用程式?
Kiwix 使用者來自各行各業,需求各異,而 Kiwix 對使用者存取內容所用的裝置和作業系統幾乎沒有控制權。其中部分裝置可能速度較慢或過時,特別是世界各地的低收入地區。Kiwix 雖然會盡量涵蓋最多用途,但也意識到,如果使用任何裝置上最普遍的軟體,也就是網路瀏覽器,就能接觸更多使用者。因此,受到 Atwood 法則的啟發,該法則指出「任何可用 JavaScript 編寫的應用程式,最終都會以 JavaScript 編寫」,因此在約 10 年前,部分 Kiwix 開發人員開始著手將 Kiwix 軟體從 C++ 移植至 JavaScript。
這個移植版本的第一個版本稱為 Kiwix HTML5,適用於現已停用的 Firefox OS 和瀏覽器擴充功能。其核心 (現在也是) 是使用 Emscripten 編譯器,將 C++ 解壓縮引擎 (XZ 和 ZSTD) 編譯為 ASM.js 中間 JavaScript 語言,以及後來的 Wasm 或 WebAssembly。後來改名為 Kiwix JS,瀏覽器擴充功能仍在積極開發中。
輸入漸進式網頁應用程式 (PWA)。為了發揮這項技術的潛力,Kiwix 開發人員打造了 Kiwix JS 的專屬 PWA 版本,並著手新增OS 整合,讓應用程式提供類似原生的功能,尤其是在離線使用、安裝、檔案處理和檔案系統存取方面。
離線優先 PWA 非常輕巧,因此非常適合在行動網路連線不穩定或費用昂貴的情況下使用。這項技術背後的技術是 Service Worker API 和相關的 Cache API,所有以 Kiwix JS 為基礎的應用程式都會使用這項技術。這些 API 可讓應用程式充當伺服器,從正在查看的主要文件或文章中攔截擷取要求,並將這些要求重新導向至 (JS) 後端,從 ZIM 封存檔中擷取並建構回應。
無所不在的儲存空間
考量到 ZIM 封存檔的大小,Kiwix 開發人員可能會對存取和儲存 ZIM 封存檔 (尤其是在行動裝置上) 感到頭痛。許多 Kiwix 使用者會在有網路時下載應用程式內容,以便日後離線使用。其他使用者會在電腦上使用 torrent 進行下載,然後將內容轉移到行動裝置或平板電腦,並在行動網路連線不穩定或費用昂貴的地區,透過 USB 隨身碟或行動硬碟交換內容。Kiwix JS 和 Kiwix PWA 必須支援從任意使用者可存取位置存取內容的所有方式。
最初讓 Kiwix JS 能夠讀取數百 GB 的龐大封存檔 (其中一個 ZIM 封存檔為 166 GB),甚至在低記憶體裝置上也能讀取,就是File 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 上,我們發現速度提升了五到十倍。
在 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:
系統會使用 estimate.quota
和 estimate.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: <b>' +
percent +
'%</b>; ' +
'Remaining: <b>' +
(OPFSQuota / 1024 / 1024 / 1024).toFixed(2) +
' GB</b>';
});
如您所見,還有一個按鈕可讓使用者從使用者可見的檔案系統,將檔案新增至 OPFS。好消息是,您可以直接使用 File API 取得要匯入的所需 File 物件 (或物件)。事實上,不使用 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!)
});
}
});
在某些作業系統 (例如 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 會允許使用者在作業期間自由使用應用程式,但會確保橫幅一律顯示,提醒使用者在下載作業完成前不要關閉應用程式。
在應用程式中實作迷你檔案管理工具
此時,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 因為提供這些選項的按鈕而顯得雜亂,而是直接在存檔清單下方放置小圖示。輕觸其中一個圖示會變更封存清單的顏色,為使用者提供視覺提示,說明他們將要執行的動作。接著,使用者會點選或輕觸其中一個封存檔案,然後執行相應的作業 (匯出或刪除) (在確認後執行)。
最後,以下是上述所有檔案管理功能的螢幕錄影示範,包括將檔案新增至 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 的最初提案者和設計者,也為瀏覽器供應商達成 Origin Private File System 重要性的共識而努力不懈。對於 Kiwix JS PWA,這項功能解決了許多過去阻礙應用程式運作的使用者體驗問題,並協助我們達成目標,讓 Kiwix 內容更易於所有人存取。請試用 Kiwix PWA,並告訴開發人員你的想法!
如需關於 PWA 功能的實用資源,請參閱下列網站:
- Project Fugu API 展示內容:一系列網路應用程式展示功能,可縮小原生應用程式和 PWA 之間的差距。
- PWA 目前可執行的功能:展示 PWA 目前可執行的功能。