PWA 檔案系統存取實作 運用 API 讀取與寫入本機文件以強化編輯器功能

me
林彥成
2021-09-18 | 3 min.
文章目錄
  1. 1. 什麼是 File System Access API?
  2. 2. 什麼是 File System Access API?PWA 檔案系統存取解析
  3. 3. 網頁讀取檔案教學:傳統 Input vs. 現代 API
    1. 3.1. 1. 傳統方式:使用 <input type="file">
    2. 3.2. 2. 現代方式:File System Access API 實作
  4. 4. 網頁寫入檔案教學:如何儲存內容到本機?
    1. 4.1. 1. 傳統方式:模擬下載
    2. 4.2. 2. 現代方式:showSaveFilePicker 範例
  5. 5. 1. 刪除本機檔案
  6. 6. 2. 開啟目錄與列舉檔案
  7. 7. File System Access API 權限驗證
  8. 8. FAQ:File System Access API 常見問題
    1. 8.1. Q1:File System Access API 的安全性如何保障?
    2. 8.2. Q2:網頁重新整理後,我需要重新請求權限嗎?
    3. 8.3. Q3:這項 API 支援哪些瀏覽器?

什麼是 File System Access API?

File System Access API 是一種現代 Web API,允許 Progressive Web App (PWA) 直接讀取、寫入或管理使用者本機裝置上的檔案與目錄。這項技術打破了傳統網頁只能透過下載或上傳來操作檔案的限制,讓 Web 應用能像桌面軟體(如 VS Code、Photoshop)一樣提供「開啟檔案」、「儲存修改」甚至「管理整個專案目錄」的功能。它透過 showOpenFilePickershowSaveFilePicker 等方法,在確保使用者授權的前提下,大幅提升了網頁應用的生產力與實用性。


什麼是 File System Access API?PWA 檔案系統存取解析

透過 File System Access API,網頁程式也能像桌面應用程式一樣,直接操作用戶本機上的檔案。無論是開啟文件進行編輯,還是將修改後的內容儲存回本機,都能透過這套 API 實現。

小編覺得這項功能對於開發 IDE、圖片編輯器或筆記 App 的 Progressive Web App 來說,簡直是如虎添翼!Google 提供了一個很棒的文字編輯器 Demo,大家可以去玩玩看:

小編在幾年前剛學習 SPA 時也寫了個單頁筆記應用,雖然當時是用舊方法實作,但這裡也附上原始碼和 Demo 連結給大家參考:


網頁讀取檔案教學:傳統 Input vs. 現代 API

在進行 網頁讀取檔案教學 時,小編觀察到目前主要有兩種實踐方式:

1. 傳統方式:使用 <input type="file">

這是最常見的做法,透過監聽 change 事件並使用 FileReader 讀取內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var fileInput = document.getElementById("fileInput");

fileInput.addEventListener("change", function (e) {
var file = fileInput.files[0];
var textType = /text.*/;

if (file.type.match(textType)) {
var reader = new FileReader();

reader.onload = function (e) {
maindata.gridData = [];
var objText = JSON.parse(reader.result.toString());
objText.gridData.forEach(function (element, index) {
maindata.gridData.push(element);
});
console.log(objText);
};

reader.readAsText(file);
} else {
fileDisplayArea.innerText = "File not supported!";
}
});

2. 現代方式:File System Access API 實作

  • slice()
  • stream()
  • text()
  • arrayBuffer()
1
2
3
4
5
6
7
8
9
10
let fileHandle;
butOpenFile.addEventListener("click", async () => {
[fileHandle] = await window.showOpenFilePicker({
// 可以提供預設開啟位置
startIn: "pictures",
});
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});

showOpenFilePicker 中的 startIn 提供的預設值是常見的幾個資料夾,能夠提供指定:

  • desktop
  • documents
  • downloads
  • music
  • pictures
  • videos

網頁寫入檔案教學:如何儲存內容到本機?

同樣地,File System Access API 實作 讓儲存流程變得更專業:

1. 傳統方式:模擬下載

將內容轉為 Blob 並生成下載連結,將 Blob 檔案透過下載的方式寫入本機。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function saveFile(text) {
var url = null;
var data = new Blob([text], {
type: "text/plain;",
});
// 如果正在替換先前生成的檔案,需手動撤銷 URL 以避免記憶體洩漏
if (url !== null) {
window.URL.revokeObjectURL(url);
}

url = window.URL.createObjectURL(data);

var link = document.getElementById("exportText");
link.href = url;
var filename = window.prompt("輸入檔名") || "export";
link.download = filename + ".txt";
link.click();
}

2. 現代方式:showSaveFilePicker 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const fileHandle = await self.showSaveFilePicker({
suggestedName: "Untitled Text.txt",
types: [
{
description: "Text documents",
accept: {
"text/plain": [".txt"],
},
},
],
});

async function writeFile(fileHandle, contents) {
// 建立一個 FileSystemWritableFileStream 以供寫入
const writable = await fileHandle.createWritable();
// 將內容寫入資料流
await writable.write(contents);
// 關閉資料流並將內容寫入磁碟
await writable.close();
}

1. 刪除本機檔案

透過 File System Access API 的 removeEntry() 是可以做到刪除檔案和刪除資料夾所有內容的效果。

1
2
3
4
// 刪除檔案
await directoryHandle.removeEntry("Abandoned Projects.txt");
// 刪除資料夾所有內容,包含子目錄
await directoryHandle.removeEntry("Old Stuff", { recursive: true });

2. 開啟目錄與列舉檔案

showDirectoryPicker() 提供了我們操作目錄並列舉內容的功能。

1
2
3
4
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}

File System Access API 權限驗證

部分瀏覽器環境還是會有權限的問題,所以偵測權限相關就變成也是要寫在程式碼裡,邏輯如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = "readwrite";
}
// 檢查是否已獲得同意
if ((await fileHandle.queryPermission(options)) === "granted") {
return true;
}
// 若未獲得同意,主動發起權限請求
if ((await fileHandle.requestPermission(options)) === "granted") {
return true;
}
// 被使用者拒絕
return false;
}

FAQ:File System Access API 常見問題

Q1:File System Access API 的安全性如何保障?

A:API 有極其嚴格的安全限制:1. 必須在 HTTPS 環境下執行;2. 必須由使用者手動觸發(如點擊按鈕);3. 在存取重要系統資料夾(如 Windows 資料夾)時,瀏覽器會直接禁止;4. 每次寫入都需要使用者明確授權。

Q2:網頁重新整理後,我需要重新請求權限嗎?

A:是的。為了安全起見,檔案控制權(File Handle)不會在重新整理後自動保留權限。不過,您可以將 File Handle 存入 IndexedDB,在使用者下次開啟頁面時重新呼叫 requestPermission(),這樣使用者只需點擊「同意」即可恢復存取,無需再次開啟檔案選取器。

Q3:這項 API 支援哪些瀏覽器?

A:目前主要支援桌面版的 ChromeEdgeOpera。Safari 與 Firefox 基於隱私與安全考量,目前尚未完整支援。在這些不支援的瀏覽器上,建議回退(Fallback)至傳統的 <input type="file"> 方式。


掌握了 File System Access API,您就能為 PWA 注入強大的本機處理能力。這不僅優化了用戶體驗,更拓寬了 Web 應用在專業領域的無限可能!


喜歡這篇文章,請幫忙拍拍手喔 🤣