使用 Browser-fs-access 程式庫讀取及寫入檔案和目錄

瀏覽器處理檔案和目錄已久, File API 提供了在網頁應用程式中表示檔案物件的功能。 以及透過程式輔助方式選取這些項目並存取其資料 但眼前的一刻,光澤反映出來的不是金色相片。

處理檔案的傳統方法

開啟檔案

開發人員可以透過以下方法開啟及讀取檔案: <input type="file">敬上 元素。 開啟檔案最簡單的方式,看起來會像下方的程式碼範例。 input 物件提供 FileList、 在以下範例中,只包含一個 FileFile 是特定的 Blob 種類, 而且可在 Blob 可執行的任何環境中使用。

const openFile = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

開啟目錄

如果是開啟資料夾 (或目錄),您可以設定 <input webkitdirectory>敬上 屬性。 除此之外,其他動作的運作方式都維持不變。 先前採用的供應商名稱 webkitdirectory 不僅適用於 Chromium 和 WebKit 瀏覽器,也能在舊版 EdgeHTML 架構和 Firefox 中使用。

儲存中 (而非下載) 檔案

儲存檔案時,以往僅限於下載檔案、 這個做法之所以有效 <a download>敬上 屬性。 有了 Blob,您可以將錨定的 href 屬性設為 blob: 網址,可從 URL.createObjectURL()敬上 方法。

const saveFile = async (blob) => {
  const a = document.createElement('a');
  a.download = 'my-file.txt';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

問題

下載方法有很大的缺點,就是無法重複使用 開啟→編輯→儲存流程會發生,也就是說,您無法覆寫原始檔案。 而是改為建立原始檔案的新副本 每次您「儲存」時,都會先出現在作業系統的預設「下載」資料夾中。

File System Access API

File System Access API 使得開啟與儲存這兩種作業更加簡單。 這麼做也會啟用真實儲存功能,也就是說,您不僅可以選擇檔案儲存位置 也能覆寫現有檔案

開啟檔案

有了 File System Access API 開啟檔案是指對 window.showOpenFilePicker() 方法發出的一次呼叫。 這個呼叫會傳回檔案控制代碼,您可以透過 getFile() 方法取得實際的 File

const openFile = async () => {
  try {
    // Always returns an array.
    const [handle] = await window.showOpenFilePicker();
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

開啟目錄

呼叫 以開啟目錄 window.showDirectoryPicker(),可讓系統在檔案對話方塊中選取目錄。

儲存檔案

儲存檔案的方法也很簡單。 透過檔案控制代碼,您可以透過 createWritable() 建立可寫入的串流。 然後,您必須呼叫串流的 write() 方法,以便寫入 Blob 資料。 最後,呼叫其 close() 方法即可關閉串流。

const saveFile = async (blob) => {
  try {
    const handle = await window.showSaveFilePicker({
      types: [{
        accept: {
          // Omitted
        },
      }],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
    return handle;
  } catch (err) {
    console.error(err.name, err.message);
  }
};

隆重推出 Browser-fs-access

如同 File System Access API 這項服務尚未全面開放使用

File System Access API 的瀏覽器支援表格。所有瀏覽器都會標示為「不支援」或「標記後面」
File System Access API 的瀏覽器支援表格。 (資料來源)

這就是為什麼 File System Access API 是漸進式強化功能。 因此,我想要在瀏覽器支援的情況下使用 如果不是,請採用傳統做法 使用不支援的 JavaScript 程式碼下載,絕不會拖累使用者。 browser-fs-access 程式庫是我回答這項挑戰的問題

設計理念

由於 File System Access API 未來可能還會變更 而不是針對 Browser-fs-access API 進行建模。 也就是說,這個程式庫並不是 polyfill。 而是「ponyfill」 您可以 (靜態或動態) 單獨匯入任何所需功能,讓應用程式盡可能縮小。 可用方法經過適當命名 fileOpen()directoryOpen()fileSave()。 在內部,程式庫功能可偵測是否支援 File System Access API。 然後匯入對應的程式碼路徑

使用 Browser-fs-access 程式庫

這三種方法十分直觀。 您可以指定應用程式可接受的 mimeTypesextensions 檔案,並設定 multiple 標記 允許或禁止使用者選取多個檔案或目錄。 如需完整詳細資料,請參閱 browser-fs-access API 說明文件。 以下程式碼範例顯示如何開啟及儲存圖片檔。

// The imported methods will use the File
// System Access API or a fallback implementation.
import {
  fileOpen,
  directoryOpen,
  fileSave,
} from 'https://unpkg.com/browser-fs-access';

(async () => {
  // Open an image file.
  const blob = await fileOpen({
    mimeTypes: ['image/*'],
  });

  // Open multiple image files.
  const blobs = await fileOpen({
    mimeTypes: ['image/*'],
    multiple: true,
  });

  // Open all files in a directory,
  // recursively including subdirectories.
  const blobsInDirectory = await directoryOpen({
    recursive: true
  });

  // Save a file.
  await fileSave(blob, {
    fileName: 'Untitled.png',
  });
})();

示範

您可以在 Glitch 的示範中,查看上述程式碼的實際運作情形。 其原始碼同樣可用。 基於安全考量,跨來源子頁框不允許顯示檔案選擇器, 文章中無法嵌入示範影片。

公開的 Browser-fs-access 程式庫

我的閒暇時間 可安裝的 PWA 名為 Excalidraw 一款白板工具,可讓使用者透過手繪風格輕鬆繪製圖表。 不僅回應速度飛快,而且在各種裝置 (小手機和大螢幕電腦) 上都適用。 這代表必須處理所有平台上的檔案 是否支援 File System Access API。 因此很適合使用 Browser-fs-access 程式庫。

例如,我可以在 iPhone 上開始繪圖 儲存 (技術上:由於 Safari 不支援 File System Access API,因此請下載這個檔案) 移至 iPhone 的「下載」資料夾,在我的電腦上開啟檔案 (從我的手機傳輸後), 修改檔案、以我的變更內容覆寫,或另存為新檔案。

iPhone 上的 Excalidraw 繪圖。
在不支援 File System Access API 的 iPhone 上開始執行 Excalidraw 繪圖,但檔案可儲存至「下載」資料夾。
,瞭解如何調查及移除這項存取權。
電腦版 Chrome 中的修改版 Excalidraw 繪圖。
在支援 File System Access API 的電腦上開啟並修改 Excalidraw 繪圖,因此可透過 API 存取該檔案。
,瞭解如何調查及移除這項存取權。
用修改內容覆寫原始檔案。
根據修改的原始 Excalidraw 繪圖檔案覆寫原始檔案。瀏覽器會顯示對話方塊,詢問我是否沒有問題。
,瞭解如何調查及移除這項存取權。
將修改內容儲存至新的 Excalidraw 繪圖檔案。
將修改內容儲存到新的 Excalidraw 檔案。原始檔案則維持不變。

實際程式碼範例

下方是 Browser-fs-access 實際使用範例。 這份節錄的資料來源 /src/data/json.ts。 其中特別值得注意的是 saveAsJSON() 方法如何將檔案控制代碼或 null 傳遞至 Browser-fs-access fileSave() 方法,可在指定控制代碼時覆寫該方法。 或是另存為新檔案

export const saveAsJSON = async (
  elements: readonly ExcalidrawElement[],
  appState: AppState,
  fileHandle: any,
) => {
  const serialized = serializeAsJSON(elements, appState);
  const blob = new Blob([serialized], {
    type: "application/json",
  });
  const name = `${appState.name}.excalidraw`;
  (window as any).handle = await fileSave(
    blob,
    {
      fileName: name,
      description: "Excalidraw file",
      extensions: ["excalidraw"],
    },
    fileHandle || null,
  );
};

export const loadFromJSON = async () => {
  const blob = await fileOpen({
    description: "Excalidraw files",
    extensions: ["json", "excalidraw"],
    mimeTypes: ["application/json"],
  });
  return loadFromBlob(blob);
};

使用者介面注意事項

無論是在 Excalidraw 還是您的應用程式中 使用者介面應會根據瀏覽器支援的情況進行調整。 如果支援 File System Access API (if ('showOpenFilePicker' in window) {}) 除了「Save」(儲存) 按鈕外,您還可以顯示「Save As」按鈕。 下方的螢幕截圖顯示 Excalidraw 支援在 iPhone 和 Chrome 電腦版上的回應式主應用程式工具列的差異。 注意:iPhone 上缺少「另存新檔」按鈕。

iPhone 上的 Excalidraw 應用程式工具列,只顯示「Save」按鈕。
在 iPhone 上只需使用「Save」按鈕,即可執行應用程式工具列。
,瞭解如何調查及移除這項存取權。
Chrome 電腦版 Excalidraw 應用程式工具列顯示「儲存」選項和「另存新檔」按鈕。
在 Chrome 中使用「Save」和聚焦的「Save As」按鈕,執行應用程式工具列。

結論

技術上來說,使用系統檔案完全支援所有新型瀏覽器。 在支援 File System Access API 的瀏覽器上,只要允許 適用於實際儲存及覆寫檔案 (不只是下載) 讓使用者在任何位置建立新檔案 仍可在不支援 File System Access API 的瀏覽器中正常運作。 browser-fs-access 可讓您的生活更輕鬆 處理漸進增強的細微差異,並盡可能簡化程式碼。

特別銘謝

本文經過 Joe MedleyKayce Basques。 感謝 Excalidraw 貢獻者 處理專案工作 並檢閱我的提取要求 主頁橫幅製作者: Ilya Pavlov 的 Unsplash。