純前端也可以存取檔案系統!

2023-09-10 21:00:12

前言

週末逛github的時候,發現我們只需要在github域名上加上1s他就能夠開啟一個vscode視窗來閱讀程式碼,比起在github倉庫中檢視更加方便

然後我就想網頁端vscode能不能開啟我原生的專案呢,帶著這個疑惑我開啟了網頁版vscode,它居然真的可以開啟我原生的專案程式碼!

難道又出了新的API讓前端的能力更進一步了?開啟MDN查了一下相關檔案,發現了幾個新的API

showOpenFilePicker

用來選擇檔案

語法

showOpenFilePicker()

引數

  • options:(可選)包含以下屬性
    • multiple:布林值,預設為false。為true表示允許使用者選擇多個檔案
    • excludeAcceptAllOption:布林值,預設為false。預設情況下,檔案選擇器帶有一個允許使用者選擇所有型別檔案的過濾選項(展開於檔案型別選項中)。設定此選項為 true 以使該過濾選項不可用。
    • types:表示允許選擇的檔案型別的陣列

返回值

返回一個promise物件,會兌現一個包含 FileSystemFileHandle 物件的 Array 陣列。

體驗

<template>
  <div class="open_file" @click="openFile">開啟檔案</div>
</template>

<script setup lang="ts">
const openFile = async () => {
  const res = await window.showOpenFilePicker();
  console.log(res);
};
</script>

預設只能開啟一個檔案,可以傳入multiple:true開啟多個檔案

showDirectoryPicker

用來選擇目錄

語法

屬於瀏覽器全域性方法,直接呼叫即可

showDirectoryPicker()

引數

  • options:(可選)包含以下屬性
    • multiple:布林值,預設為false。為true表示允許使用者選擇多個檔案
    • excludeAcceptAllOption:布林值,預設為false。預設情況下,檔案選擇器帶有一個允許使用者選擇所有型別檔案的過濾選項(展開於檔案型別選項中)。設定此選項為 true 以使該過濾選項不可用。
    • types:表示允許選擇的檔案型別的陣列

返回值

返回一個promise物件,會兌現一個包含 FileSystemFileHandle 物件的 Array 陣列。

體驗

<template>
  <div class="open_file" @click="openFile">開啟檔案</div>
  <div class="open_file" @click="openDir">開啟資料夾</div>
</template>

<script setup lang="ts">
const openFile = async () => {
  const res = await window.showOpenFilePicker({
    // multiple: true,
  });
  console.log(res.length);
};

const openDir = async () => {
  const res = await window.showDirectoryPicker();
  console.log(res);
};
</script>

擴充套件

FileSystemFileHandle

FileSystemFileHandle提供了一些方法可以用來獲取和操作檔案

  • getFile:返回一個Promise物件,用於獲取檔案;

  • createSyncAccessHandle:返回一個FileSystemSyncAccessHandle物件,用於同步存取檔案;

  • createWritable:返回一個Promise物件,用於建立一個可寫流,用於寫入檔案;

FileSystemDirectoryHandle

FileSystemDirectoryHandle物件是一個代表檔案系統中的目錄的物件,它同樣提供了方法來獲取和操作目錄

  • entries:返回一個AsyncIterable物件,用於獲取目錄中的所有檔案和目錄;
  • keys:返回一個AsyncIterable物件,用於獲取目錄中的所有檔案和目錄的名稱;
  • values:返回一個AsyncIterable物件,用於獲取目錄中的所有檔案和目錄的FileSystemHandle物件;
  • getFileHandle:返回一個Promise物件,用於獲取目錄中的檔案;
  • getDirectoryHandle:返回一個Promise物件,用於獲取目錄中的目錄;
  • removeEntry:返回一個Promise物件,用於刪除目錄中的檔案或目錄;
  • resolve:返回一個Promise物件,用於獲取目錄中的檔案或目錄;

entrieskeysvalues這三個方法都是用來獲取目錄中的所有檔案和目錄的,它們返回的都是一個AsyncIterable物件,我們可以通過for await...of語法來遍歷它。

開發編輯器

瞭解完這些知識點,我們就可以來開發一個簡陋網頁版編輯器了,初期只包含開啟檔案、開啟資料夾、檢視檔案、切換檔案

編輯器大概長這樣:

開啟資料夾

const openDir = async () => {
  const res = await window.showDirectoryPicker({});
  const detalAction = async (obj: any) => {
    if (obj.entries) {
      const dirs = obj.entries();
      for await (const entry of dirs) {
        if (entry[1].entries) {
          // 資料夾,遞迴處理
          detalAction(entry[1]);
        } else {
          // 檔案
          fileList.value.push({
            name: entry[0],
            path: obj.name,
            fileHandle: entry[1],
          });
        }
      }
    }
  };
  await detalAction(res);
  showCode(fileList.value[0], 0);
  console.log("--fileList--", fileList);
};

這裡主要是遞迴處理資料夾,返回一個檔案列表

讀取檔案內容

const showCode = async (item: any, index: number) => {
  const file = await item.fileHandle.getFile();
  const text = await file.text();
  codeText.value = text;
  currentIndex.value = index;
};

展示檔案內容

使用highlight.js來高亮展示程式碼

<div class="show_code">
  <pre v-highlight>
        <code class="lang-dart">
            {{ codeText }}
        </code>
   </pre>
</div>

最終效果如下:

想不到吧,這種功能現在純前端就能夠實現了,當然還可以做的更復雜一點,包括修改儲存等功能,儲存可以使用showSaveFilePickerAPI,它可以寫入檔案,同樣是返回一個promise。感興趣的可以試著完善編輯器的功能。