File System Access API: ลดความซับซ้อนในการเข้าถึงไฟล์ในเครื่อง

File System Access API ช่วยให้เว็บแอปอ่านหรือบันทึกการเปลี่ยนแปลงลงในไฟล์และโฟลเดอร์ในอุปกรณ์ของผู้ใช้ได้โดยตรง

เผยแพร่: 19 สิงหาคม 2024

File System Access API ช่วยให้นักพัฒนาแอปสร้างเว็บแอปที่มีประสิทธิภาพซึ่งโต้ตอบกับไฟล์ในอุปกรณ์ของผู้ใช้ได้ เช่น IDE, โปรแกรมแก้ไขรูปภาพและวิดีโอ, โปรแกรมแก้ไขข้อความ และอื่นๆ หลังจาก ผู้ใช้ให้สิทธิ์เข้าถึงเว็บแอปแล้ว API นี้จะอนุญาตให้ผู้ใช้อ่านหรือบันทึกการเปลี่ยนแปลงลงในไฟล์และ โฟลเดอร์ในอุปกรณ์ของผู้ใช้ได้โดยตรง นอกเหนือจากการอ่านและเขียนไฟล์แล้ว File System Access API ยังมี ความสามารถในการเปิดไดเรกทอรีและแสดงเนื้อหาของไดเรกทอรี

หากคุณเคยทำงานกับไฟล์การอ่านและการเขียนมาก่อน สิ่งที่ฉันกำลังจะแชร์ต่อไปนี้จะคุ้นเคยกับคุณเป็นอย่างดี อย่างไรก็ตาม เราขอแนะนำให้คุณอ่านคู่มือนี้ เนื่องจากระบบแต่ละระบบอาจแตกต่างกัน

File System Access API รองรับในเบราว์เซอร์ Chromium ส่วนใหญ่บน Windows, macOS, ChromeOS, Linux และ Android ข้อยกเว้นที่สำคัญคือ Brave ซึ่งขณะนี้ใช้ได้เฉพาะเมื่อเปิดใช้แฟล็ก

การใช้ File System Access API

ฉันได้เขียนโปรแกรมแก้ไข ข้อความแบบไฟล์เดียวเพื่อแสดงให้เห็นถึงประสิทธิภาพและประโยชน์ของ File System Access API ซึ่งช่วยให้คุณเปิดไฟล์ข้อความ แก้ไข บันทึกการเปลี่ยนแปลงกลับลงในดิสก์ หรือเริ่ม ไฟล์ใหม่และบันทึกการเปลี่ยนแปลงลงในดิสก์ได้ ซึ่งอาจไม่ได้ซับซ้อนมากนัก แต่ก็เพียงพอที่จะช่วยให้คุณ เข้าใจแนวคิดต่างๆ

การสนับสนุนเบราว์เซอร์

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: not supported.
  • Safari: not supported.

Source

การตรวจหาฟีเจอร์

หากต้องการดูว่าระบบรองรับ File System Access API หรือไม่ ให้ตรวจสอบว่ามีเมธอดตัวเลือก ที่คุณสนใจหรือไม่

if ('showOpenFilePicker' in self) {
  // The `showOpenFilePicker()` method of the File System Access API is supported.
}

ลองใช้

ดู File System Access API ในการทำงานในเดโมโปรแกรมแก้ไขข้อความ

อ่านไฟล์จากระบบไฟล์ในเครื่อง

กรณีการใช้งานแรกที่ฉันต้องการจัดการคือการขอให้ผู้ใช้เลือกไฟล์ จากนั้นเปิดและอ่านไฟล์นั้นจากดิสก์

ขอให้ผู้ใช้เลือกไฟล์ที่จะอ่าน

จุดแรกเข้าของ File System Access API คือ window.showOpenFilePicker() เมื่อเรียกใช้ จะแสดงกล่องโต้ตอบตัวเลือกไฟล์ และแจ้งให้ผู้ใช้เลือกไฟล์ หลังจากเลือกไฟล์แล้ว API จะแสดงอาร์เรย์ของแฮนเดิลไฟล์ พารามิเตอร์ options (ไม่บังคับ) ช่วยให้คุณมีอิทธิพลต่อลักษณะการทำงานของเครื่องมือเลือกไฟล์ เช่น อนุญาตให้ผู้ใช้เลือกไฟล์หรือไดเรกทอรีหลายรายการ หรือเลือกไฟล์ประเภทต่างๆ หากไม่ได้ระบุตัวเลือกใดๆ เครื่องมือเลือกไฟล์จะอนุญาตให้ผู้ใช้เลือกไฟล์เดียว ซึ่งเหมาะที่จะใช้กับเครื่องมือแก้ไขข้อความ

เช่นเดียวกับ API อื่นๆ ที่มีประสิทธิภาพ การเรียกใช้ showOpenFilePicker() ต้องดำเนินการในบริบทที่ปลอดภัย และต้องเรียกใช้จากภายในท่าทางสัมผัสของผู้ใช้

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  // Destructure the one-element array.
  [fileHandle] = await window.showOpenFilePicker();
  // Do something with the file handle.
});

เมื่อผู้ใช้เลือกไฟล์ showOpenFilePicker() จะแสดงอาร์เรย์ของแฮนเดิล ในกรณีนี้คืออาร์เรย์ที่มีองค์ประกอบเดียวซึ่งมี FileSystemFileHandle ที่มีพร็อพเพอร์ตี้และ เมธอดที่จำเป็นในการโต้ตอบกับไฟล์

การเก็บข้อมูลอ้างอิงไปยังแฮนเดิลของไฟล์จะเป็นประโยชน์เพื่อให้ใช้ในภายหลังได้ คุณจะต้องใช้รหัสนี้เพื่อบันทึกการเปลี่ยนแปลงในไฟล์หรือดำเนินการอื่นๆ กับไฟล์

อ่านไฟล์จากระบบไฟล์

เมื่อมีแฮนเดิลของไฟล์แล้ว คุณจะดูพร็อพเพอร์ตี้ของไฟล์หรือเข้าถึงไฟล์นั้นได้ ตอนนี้จะอ่านเนื้อหาให้ฟังนะ การเรียก handle.getFile() จะแสดงผลออบเจ็กต์ File ซึ่งมี Blob หากต้องการรับข้อมูลจาก Blob ให้เรียกใช้เมธอดใดเมธอดหนึ่ง (slice(), stream(), text() หรือ arrayBuffer())

const file = await fileHandle.getFile();
const contents = await file.text();

ออบเจ็กต์ File ที่ FileSystemFileHandle.getFile() ส่งคืนจะอ่านได้ก็ต่อเมื่อไฟล์พื้นฐานในดิสก์ยังไม่มีการเปลี่ยนแปลง หากมีการแก้ไขไฟล์ในดิสก์ ออบเจ็กต์ File จะอ่านไม่ได้ และคุณจะต้องเรียกใช้ getFile() อีกครั้งเพื่อรับออบเจ็กต์ File ใหม่เพื่ออ่านข้อมูลที่เปลี่ยนแปลง

สรุปข้อมูลทั้งหมด

เมื่อผู้ใช้คลิกปุ่มเปิด เบราว์เซอร์จะแสดงเครื่องมือเลือกไฟล์ เมื่อเลือกไฟล์แล้ว แอปจะอ่านเนื้อหาและใส่ลงใน <textarea>

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

เขียนไฟล์ลงในระบบไฟล์ในเครื่อง

ในโปรแกรมแก้ไขข้อความ คุณบันทึกไฟล์ได้ 2 วิธี ได้แก่ บันทึกและบันทึกเป็น บันทึก จะเขียนการเปลี่ยนแปลงกลับไปยังไฟล์ต้นฉบับโดยใช้แฮนเดิลไฟล์ที่ดึงมาเมื่อก่อนหน้านี้ แต่บันทึกเป็นจะสร้างไฟล์ใหม่ จึงต้องมีแฮนเดิลไฟล์ใหม่

สร้างไฟล์ใหม่

หากต้องการบันทึกไฟล์ ให้เรียกใช้ showSaveFilePicker() ซึ่งจะแสดงเครื่องมือเลือกไฟล์ ในโหมด "บันทึก" เพื่อให้ผู้ใช้เลือกไฟล์ใหม่ที่ต้องการใช้บันทึกได้ สำหรับโปรแกรมแก้ไขข้อความ ฉันต้องการให้เพิ่มส่วนขยาย .txt โดยอัตโนมัติด้วย จึงระบุพารามิเตอร์เพิ่มเติม

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

บันทึกการเปลี่ยนแปลงลงในดิสก์

คุณดูโค้ดทั้งหมดสำหรับการบันทึกการเปลี่ยนแปลงในไฟล์ได้ในเดโมโปรแกรมแก้ไขข้อความของฉันบน GitHub การโต้ตอบหลักของระบบไฟล์อยู่ใน fs-helpers.js กระบวนการที่ง่ายที่สุดจะมีลักษณะดังโค้ดต่อไปนี้ ฉันจะอธิบายแต่ละขั้นตอนให้คุณ

// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Write the contents of the file to the stream.
  await writable.write(contents);
  // Close the file and write the contents to disk.
  await writable.close();
}

การเขียนข้อมูลลงดิสก์จะใช้ออบเจ็กต์ FileSystemWritableFileStream ซึ่งเป็นคลาสย่อย ของ WritableStream สร้างสตรีมโดยเรียกใช้ createWritable() ในออบเจ็กต์ แฮนเดิลของไฟล์ เมื่อมีการเรียกใช้ createWritable() เบราว์เซอร์จะตรวจสอบก่อนว่าผู้ใช้ได้ให้สิทธิ์ เขียนไฟล์หรือไม่ หากยังไม่ได้รับสิทธิ์เขียน เบราว์เซอร์จะแจ้งให้ผู้ใช้ขอสิทธิ์ หากไม่ได้รับสิทธิ์ createWritable()จะส่งDOMException และแอปจะเขียนลงในไฟล์ไม่ได้ ในเครื่องมือแก้ไขข้อความ ระบบจะจัดการออบเจ็กต์ DOMException ในเมธอด saveFile()

write() เมธอดจะใช้สตริง ซึ่งเป็นสิ่งที่จำเป็นสำหรับเครื่องมือแก้ไขข้อความ แต่ยังรับ BufferSource หรือ Blob ได้ด้วย เช่น คุณสามารถส่งสตรีมไปยัง ได้โดยตรง

async function writeURLToFile(fileHandle, url) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Make an HTTP request for the contents.
  const response = await fetch(url);
  // Stream the response into the file.
  await response.body.pipeTo(writable);
  // pipeTo() closes the destination pipe by default, no need to close it.
}

นอกจากนี้ คุณยังseek()หรือtruncate()ภายในสตรีมเพื่ออัปเดตไฟล์ที่ตำแหน่งที่เฉพาะเจาะจง หรือปรับขนาดไฟล์ได้ด้วย

การระบุชื่อไฟล์และไดเรกทอรีเริ่มต้นที่แนะนำ

ในหลายกรณี คุณอาจต้องการให้แอปแนะนำชื่อไฟล์หรือตำแหน่งเริ่มต้น เช่น โปรแกรมแก้ไขข้อความ อาจต้องการแนะนำชื่อไฟล์เริ่มต้นเป็น Untitled Text.txt แทน Untitled คุณ ทำได้โดยส่งพร็อพเพอร์ตี้ suggestedName เป็นส่วนหนึ่งของshowSaveFilePicker ตัวเลือก

const fileHandle = await self.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [{
    description: 'Text documents',
    accept: {
      'text/plain': ['.txt'],
    },
  }],
});

เช่นเดียวกับไดเรกทอรีเริ่มต้น หากคุณกำลังสร้างโปรแกรมแก้ไขข้อความ คุณอาจต้องการ เริ่มกล่องโต้ตอบการบันทึกหรือเปิดไฟล์ในโฟลเดอร์ documents เริ่มต้น ในขณะที่โปรแกรมแก้ไขรูปภาพ อาจต้องการเริ่มในโฟลเดอร์ pictures เริ่มต้น คุณสามารถแนะนำไดเรกทอรีเริ่มต้นได้โดยส่งพร็อพเพอร์ตี้ startIn ไปยังเมธอด showSaveFilePicker, showDirectoryPicker() หรือ showOpenFilePicker ดังนี้

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

รายการไดเรกทอรีระบบที่รู้จักกันดีมีดังนี้

  • desktop: ไดเรกทอรีเดสก์ท็อปของผู้ใช้ หากมี
  • documents: ไดเรกทอรีที่มักจะใช้จัดเก็บเอกสารที่ผู้ใช้สร้างขึ้น
  • downloads: ไดเรกทอรีที่มักจะจัดเก็บไฟล์ที่ดาวน์โหลด
  • music: ไดเรกทอรีที่มักจะจัดเก็บไฟล์เสียง
  • pictures: ไดเรกทอรีที่มักจะจัดเก็บรูปภาพและภาพนิ่งอื่นๆ
  • videos: ไดเรกทอรีที่มักใช้จัดเก็บวิดีโอหรือภาพยนตร์

นอกเหนือจากไดเรกทอรีระบบที่รู้จักกันดีแล้ว คุณยังส่งผ่านแฮนเดิลไฟล์หรือไดเรกทอรีที่มีอยู่เป็นค่าสำหรับ startIn ได้ด้วย จากนั้นกล่องโต้ตอบจะเปิดขึ้นในไดเรกทอรีเดียวกัน

// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
  startIn: directoryHandle
});

การระบุวัตถุประสงค์ของเครื่องมือเลือกไฟล์ต่างๆ

บางครั้งแอปพลิเคชันก็มีเครื่องมือเลือกที่แตกต่างกันสำหรับวัตถุประสงค์ต่างๆ ตัวอย่างเช่น โปรแกรมแก้ไข Rich Text อาจอนุญาตให้ผู้ใช้เปิดไฟล์ข้อความ แต่ก็อนุญาตให้นำเข้ารูปภาพด้วย โดยค่าเริ่มต้น ตัวเลือกไฟล์ แต่ละรายการจะเปิดขึ้นในตำแหน่งที่จำได้ล่าสุด คุณหลีกเลี่ยงปัญหานี้ได้โดยการจัดเก็บidค่า สำหรับเครื่องมือเลือกแต่ละประเภท หากมีการระบุ id การติดตั้งใช้งานเครื่องมือเลือกไฟล์จะจดจำ ไดเรกทอรีที่ใช้ล่าสุดแยกต่างหากสำหรับ id นั้น

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});

การจัดเก็บแฮนเดิลไฟล์หรือแฮนเดิลไดเรกทอรีใน IndexedDB

FileHandle และ DirectoryHandle สามารถทำให้เป็นแบบอนุกรมได้ ซึ่งหมายความว่าคุณสามารถบันทึก FileHandle หรือ DirectoryHandle ลงใน IndexedDB หรือเรียกใช้ postMessage() เพื่อส่งระหว่างต้นทางระดับบนสุดเดียวกันได้

การบันทึกแฮนเดิลไฟล์หรือไดเรกทอรีไปยัง IndexedDB หมายความว่าคุณสามารถจัดเก็บสถานะหรือจดจำไฟล์หรือไดเรกทอรีที่ผู้ใช้ทำงานอยู่ได้ ซึ่งจะช่วยให้คุณเก็บรายการไฟล์ที่เปิดหรือแก้ไขล่าสุดไว้ได้ เสนอให้เปิดไฟล์ล่าสุดอีกครั้งเมื่อเปิดแอป กู้คืนไดเรกทอรีการทำงานก่อนหน้า และอื่นๆ ในโปรแกรมแก้ไขข้อความ ฉันจะจัดเก็บรายการไฟล์ 5 รายการล่าสุดที่ผู้ใช้เปิด เพื่อให้เข้าถึงไฟล์เหล่านั้นได้อีกครั้ง

ตัวอย่างโค้ดต่อไปนี้แสดงการจัดเก็บและเรียกข้อมูลแฮนเดิลไฟล์และแฮนเดิลไดเรกทอรี คุณดูการทำงานของฟีเจอร์นี้ได้ที่ Glitch (ฉันใช้ไลบรารี idb-keyval เพื่อความกระชับ)

import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';

const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');

// File handle
button1.addEventListener('click', async () => {
  try {
    const fileHandleOrUndefined = await get('file');
    if (fileHandleOrUndefined) {
      pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const [fileHandle] = await window.showOpenFilePicker();
    await set('file', fileHandle);
    pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

// Directory handle
button2.addEventListener('click', async () => {
  try {
    const directoryHandleOrUndefined = await get('directory');
    if (directoryHandleOrUndefined) {
      pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const directoryHandle = await window.showDirectoryPicker();
    await set('directory', directoryHandle);
    pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

แฮนเดิลและสิทธิ์ของไฟล์หรือไดเรกทอรีที่จัดเก็บ

เนื่องจากสิทธิ์ไม่ได้คงอยู่ระหว่างเซสชันเสมอไป คุณจึงควรตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์เข้าถึงไฟล์หรือไดเรกทอรีโดยใช้ queryPermission() หรือไม่ หากยังไม่ได้รับ ให้โทรหา requestPermission() เพื่อขอรหัสอีกครั้ง ซึ่งจะทำงานในลักษณะเดียวกันกับแฮนเดิลไฟล์และไดเรกทอรี คุณต้องเรียกใช้ fileOrDirectoryHandle.requestPermission(descriptor) หรือ fileOrDirectoryHandle.queryPermission(descriptor) ตามลำดับ

ในโปรแกรมแก้ไขข้อความ ฉันสร้างเมธอด verifyPermission() ที่ตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์แล้วหรือไม่ และหากจำเป็น ก็จะส่งคำขอ

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

การขอสิทธิ์เขียนพร้อมกับคำขออ่านช่วยลดจำนวนข้อความแจ้งขอสิทธิ์ได้ ผู้ใช้จะเห็นข้อความแจ้ง 1 รายการเมื่อเปิดไฟล์ และให้สิทธิ์ทั้งอ่านและเขียนไฟล์

การเปิดไดเรกทอรีและการแจงนับเนื้อหา

หากต้องการแสดงรายการไฟล์ทั้งหมดในไดเรกทอรี ให้เรียกใช้ showDirectoryPicker() ผู้ใช้ เลือกไดเรกทอรีในเครื่องมือเลือก จากนั้นระบบจะแสดงผล FileSystemDirectoryHandle ซึ่ง ช่วยให้คุณแสดงรายการและเข้าถึงไฟล์ของไดเรกทอรีได้ โดยค่าเริ่มต้น คุณจะมีสิทธิ์เข้าถึงแบบอ่าน ไฟล์ในไดเรกทอรี แต่หากต้องการสิทธิ์เข้าถึงแบบเขียน คุณสามารถส่ง { mode: 'readwrite' } ไปยังเมธอดได้

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

หากต้องการเข้าถึงแต่ละไฟล์โดยใช้ getFile() เพื่อรับขนาดไฟล์แต่ละไฟล์ เป็นต้น อย่าใช้ await กับแต่ละผลลัพธ์ตามลำดับ แต่ให้ประมวลผลไฟล์ทั้งหมดแบบขนาน เช่น ใช้ Promise.all()

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  const promises = [];
  for await (const entry of dirHandle.values()) {
    if (entry.kind !== 'file') {
      continue;
    }
    promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
  }
  console.log(await Promise.all(promises));
});

การสร้างหรือเข้าถึงไฟล์และโฟลเดอร์ในไดเรกทอรี

จากไดเรกทอรี คุณสามารถสร้างหรือเข้าถึงไฟล์และโฟลเดอร์ได้โดยใช้เมธอด getFileHandle() หรือ getDirectoryHandle() ตามลำดับ การส่งoptionsออบเจ็กต์ที่ไม่บังคับที่มีคีย์เป็น create และค่าบูลีนเป็น true หรือ false จะช่วยให้คุณกำหนดได้ว่าจะสร้างไฟล์หรือโฟลเดอร์ใหม่หรือไม่หากไม่มีอยู่

// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
  create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });

การแก้ไขเส้นทางของรายการในไดเรกทอรี

เมื่อทำงานกับไฟล์หรือโฟลเดอร์ในไดเรกทอรี การแก้ไขเส้นทางของรายการ ที่เป็นปัญหาอาจมีประโยชน์ ซึ่งทำได้โดยใช้วิธีการ resolve() ที่ตั้งชื่อไว้อย่างเหมาะสม สำหรับการแก้ไข รายการอาจเป็นรายการย่อยโดยตรงหรือโดยอ้อมของไดเรกทอรี

// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]

การลบไฟล์และโฟลเดอร์ในไดเรกทอรี

หากได้รับสิทธิ์เข้าถึงไดเรกทอรี คุณจะลบไฟล์และโฟลเดอร์ที่อยู่ในไดเรกทอรีนั้นได้ด้วยวิธี removeEntry() สำหรับโฟลเดอร์ การลบอาจเป็นแบบเรียกซ้ำและรวม โฟลเดอร์ย่อยทั้งหมดและไฟล์ที่อยู่ในนั้นด้วย (ไม่บังคับ)

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

การลบไฟล์หรือโฟลเดอร์โดยตรง

หากคุณมีสิทธิ์เข้าถึงแฮนเดิลไฟล์หรือไดเรกทอรี ให้เรียกใช้ remove() ใน FileSystemFileHandle หรือ FileSystemDirectoryHandle เพื่อนำออก

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();

การเปลี่ยนชื่อและย้ายไฟล์และโฟลเดอร์

คุณเปลี่ยนชื่อหรือย้ายไฟล์และโฟลเดอร์ไปยังตำแหน่งใหม่ได้โดยเรียกใช้ move() ในอินเทอร์เฟซ FileSystemHandle FileSystemHandle มีอินเทอร์เฟซสำหรับเด็ก FileSystemFileHandle และ FileSystemDirectoryHandle เมธอด move() จะใช้พารามิเตอร์ 1 หรือ 2 รายการ โดยตัวแรกอาจเป็นสตริงที่มีชื่อใหม่หรือเป็น FileSystemDirectoryHandle ไปยังโฟลเดอร์ปลายทาง ในกรณีหลัง พารามิเตอร์ที่ 2 ที่ไม่บังคับคือสตริงที่มีชื่อใหม่ ดังนั้นการย้ายและเปลี่ยนชื่อจึงเกิดขึ้นได้ในขั้นตอนเดียว

// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');

การผสานรวมแบบลากและวาง

อินเทอร์เฟซการลากและวางของ HTML ช่วยให้เว็บแอปพลิเคชันยอมรับไฟล์ที่ลากและวาง ในหน้าเว็บได้ ในระหว่างการลากและวาง ระบบจะเชื่อมโยงรายการไฟล์และไดเรกทอรีที่ลากกับรายการไฟล์และรายการไดเรกทอรีตามลำดับ เมธอด DataTransferItem.getAsFileSystemHandle() จะแสดงผล Promise ที่มีออบเจ็กต์ FileSystemFileHandle หากรายการที่ลากเป็นไฟล์ และ Promise ที่มีออบเจ็กต์ FileSystemDirectoryHandle หากรายการที่ลากเป็นไดเรกทอรี รายการต่อไปนี้ แสดงให้เห็นถึงการทำงานนี้ โปรดทราบว่า DataTransferItem.kind ของอินเทอร์เฟซการลากและวางคือ "file" สำหรับทั้งไฟล์และไดเรกทอรี ในขณะที่ FileSystemHandle.kind ของ File System Access API คือ "file" สำหรับไฟล์และ "directory" สำหรับไดเรกทอรี

elem.addEventListener('dragover', (e) => {
  // Prevent navigation.
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    .filter((item) => item.kind === 'file')
    .map((item) => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

การเข้าถึงระบบไฟล์ส่วนตัวของต้นทาง

ระบบไฟล์ส่วนตัวของต้นทางคือปลายทางการจัดเก็บที่ตามชื่อก็บอกอยู่แล้วว่าเป็นส่วนตัวสำหรับ ต้นทางของหน้าเว็บ แม้ว่าโดยปกติเบราว์เซอร์จะใช้การดำเนินการนี้โดยการบันทึกเนื้อหาของระบบไฟล์ส่วนตัวของต้นทางนี้ลงในดิสก์ที่ใดที่หนึ่ง แต่เราไม่ต้องการให้ผู้ใช้เข้าถึงเนื้อหาได้ ในทำนองเดียวกัน ไม่คาดหวังว่าจะมีไฟล์หรือไดเรกทอรีที่มีชื่อตรงกับชื่อของรายการย่อยของระบบไฟล์ส่วนตัวของต้นทาง แม้ว่าเบราว์เซอร์อาจทำให้ดูเหมือนว่ามีไฟล์ แต่ภายในแล้วเนื่องจากนี่คือระบบไฟล์ส่วนตัวของต้นทาง เบราว์เซอร์จึงอาจจัดเก็บ "ไฟล์" เหล่านี้ไว้ในฐานข้อมูลหรือโครงสร้างข้อมูลอื่นๆ กล่าวโดยสรุปคือ หากคุณใช้ API นี้ อย่าคาดหวังว่าจะพบไฟล์ที่สร้างขึ้นซึ่งตรงกันแบบ 1:1 ที่ใดที่หนึ่งในฮาร์ดดิสก์ คุณจะดำเนินการได้ตามปกติในระบบไฟล์ส่วนตัวของต้นทางเมื่อมีสิทธิ์เข้าถึงรูท FileSystemDirectoryHandle

const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 15.2.

Source

การเข้าถึงไฟล์ที่เพิ่มประสิทธิภาพเพื่อประสิทธิภาพจากระบบไฟล์ส่วนตัวของต้นทาง

ระบบไฟล์ส่วนตัวของต้นทางมีสิทธิ์เข้าถึงไฟล์ประเภทพิเศษที่ได้รับการเพิ่มประสิทธิภาพสูง เพื่อประสิทธิภาพ เช่น การให้สิทธิ์เข้าถึงในการเขียนเนื้อหาของไฟล์แบบเฉพาะและในตำแหน่ง ใน Chromium 102 ขึ้นไป จะมีเมธอดเพิ่มเติมในระบบไฟล์ส่วนตัวของต้นทางเพื่อ ลดความซับซ้อนในการเข้าถึงไฟล์: createSyncAccessHandle() (สำหรับการอ่านและการเขียนแบบซิงโครนัส) โดยจะแสดงใน FileSystemFileHandle แต่จะอยู่ใน Web Worker เท่านั้น

// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });

Polyfilling

คุณไม่สามารถ Polyfill เมธอด File System Access API ได้อย่างสมบูรณ์

  • คุณประมาณค่าเมธอด showOpenFilePicker() ได้ด้วยองค์ประกอบ <input type="file">
  • คุณสามารถจำลองวิธีการ showSaveFilePicker() ด้วยองค์ประกอบ <a download="file_name"> แม้ว่าวิธีนี้จะทริกเกอร์การดาวน์โหลดแบบเป็นโปรแกรมและไม่อนุญาตให้เขียนทับไฟล์ที่มีอยู่
  • คุณสามารถจำลองวิธีการ showDirectoryPicker() ได้ในระดับหนึ่งด้วยองค์ประกอบ <input type="file" webkitdirectory> ที่ไม่ใช่มาตรฐาน

เราได้พัฒนาไลบรารีที่ชื่อ browser-fs-access ซึ่งใช้ File System Access API ทุกครั้งที่ทำได้ และจะกลับไปใช้ตัวเลือกที่ดีที่สุดถัดไปในกรณีอื่นๆ ทั้งหมด

ความปลอดภัยและสิทธิ์

ทีม Chrome ได้ออกแบบและใช้ File System Access API โดยใช้หลักการพื้นฐาน ที่กำหนดไว้ในการควบคุมการเข้าถึงฟีเจอร์ที่มีประสิทธิภาพของแพลตฟอร์มเว็บ ซึ่งรวมถึงการควบคุมและความโปร่งใสของผู้ใช้ และการยศาสตร์ของผู้ใช้

การเปิดไฟล์หรือบันทึกไฟล์ใหม่

เครื่องมือเลือกไฟล์เพื่อเปิดไฟล์สำหรับอ่าน
เครื่องมือเลือกไฟล์ที่ใช้เพื่อเปิดไฟล์ที่มีอยู่เพื่ออ่าน

เมื่อเปิดไฟล์ ผู้ใช้จะให้สิทธิ์ในการอ่านไฟล์หรือไดเรกทอรีโดยใช้เครื่องมือเลือกไฟล์ ตัวเลือกไฟล์ที่เปิดอยู่จะแสดงได้โดยใช้ท่าทางของผู้ใช้เมื่อแสดงจากบริบทที่ปลอดภัยเท่านั้น หากผู้ใช้เปลี่ยนใจ ก็สามารถยกเลิกการเลือกในเครื่องมือเลือกไฟล์ได้ และเว็บไซต์จะไม่ได้รับสิทธิ์เข้าถึงสิ่งใด ซึ่งเป็นลักษณะการทำงานเดียวกันกับองค์ประกอบ <input type="file">

เครื่องมือเลือกไฟล์เพื่อบันทึกไฟล์ลงในดิสก์
เครื่องมือเลือกไฟล์ที่ใช้เพื่อบันทึกไฟล์ลงในดิสก์

ในทำนองเดียวกัน เมื่อเว็บแอปต้องการบันทึกไฟล์ใหม่ เบราว์เซอร์จะแสดงเครื่องมือเลือกไฟล์ที่จะบันทึก เพื่อให้ผู้ใช้ระบุชื่อและตำแหน่งของไฟล์ใหม่ได้ เนื่องจากผู้ใช้บันทึกไฟล์ใหม่ลงในอุปกรณ์ (แทนการเขียนทับไฟล์ที่มีอยู่) เครื่องมือเลือกไฟล์จึงให้สิทธิ์แอปในการ เขียนลงในไฟล์

โฟลเดอร์ที่ถูกจำกัด

เบราว์เซอร์อาจจำกัดความสามารถของผู้ใช้ในการบันทึกลงในโฟลเดอร์บางโฟลเดอร์ เช่น โฟลเดอร์ระบบปฏิบัติการหลักอย่าง Windows และโฟลเดอร์ไลบรารีของ macOS เพื่อช่วยปกป้องผู้ใช้และข้อมูลของผู้ใช้ เมื่อเกิดกรณีนี้ขึ้น เบราว์เซอร์จะแสดงข้อความแจ้งและขอให้ผู้ใช้เลือกโฟลเดอร์อื่น

การแก้ไขไฟล์หรือไดเรกทอรีที่มีอยู่

เว็บแอปจะแก้ไขไฟล์ในดิสก์ไม่ได้หากไม่ได้รับสิทธิ์อย่างชัดเจนจากผู้ใช้

ข้อความแจ้งขอสิทธิ์

หากผู้ใช้ต้องการบันทึกการเปลี่ยนแปลงในไฟล์ที่เคยให้สิทธิ์เข้าถึงแบบอ่านไว้ก่อนหน้านี้ เบราว์เซอร์จะแสดงข้อความแจ้งขอสิทธิ์เพื่อให้เว็บไซต์เขียนการเปลี่ยนแปลงลงในดิสก์ คำขอสิทธิ์จะทริกเกอร์ได้ด้วยท่าทางของผู้ใช้เท่านั้น เช่น การคลิกปุ่มบันทึก

ข้อความแจ้งขอสิทธิ์จะแสดงก่อนบันทึกไฟล์
ข้อความแจ้งที่แสดงต่อผู้ใช้ก่อนที่เบราว์เซอร์จะได้รับสิทธิ์เขียน ในไฟล์ที่มีอยู่

หรือเว็บแอปที่แก้ไขไฟล์หลายไฟล์ เช่น IDE ก็ขอสิทธิ์บันทึกการเปลี่ยนแปลงได้ในขณะที่เปิดไฟล์

หากผู้ใช้เลือก "ยกเลิก" และไม่ให้สิทธิ์การเขียน เว็บแอปจะบันทึกการเปลี่ยนแปลงลงใน ไฟล์ในเครื่องไม่ได้ โดยควรมีวิธีการอื่นให้ผู้ใช้บันทึกข้อมูล เช่น การจัดหาวิธี"ดาวน์โหลด" ไฟล์หรือบันทึกข้อมูลไปยังระบบคลาวด์

ความโปร่งใส

ไอคอนแถบอเนกประสงค์
ไอคอนแถบที่อยู่ที่บ่งบอกว่าผู้ใช้ได้ให้สิทธิ์เว็บไซต์ในการ บันทึกลงในไฟล์ในเครื่อง

เมื่อผู้ใช้ให้สิทธิ์เว็บแอปในการบันทึกไฟล์ในเครื่องแล้ว เบราว์เซอร์จะแสดงไอคอน ในแถบที่อยู่ การคลิกไอคอนจะเปิดป๊อปโอเวอร์ที่แสดงรายการไฟล์ที่ผู้ใช้ให้สิทธิ์เข้าถึง ผู้ใช้สามารถเพิกถอนสิทธิ์เข้าถึงดังกล่าวได้ทุกเมื่อหากต้องการ

การคงอยู่ของสิทธิ์

เว็บแอปจะบันทึกการเปลี่ยนแปลงในไฟล์ต่อไปได้โดยไม่ต้องแจ้งเตือนจนกว่าจะปิดแท็บทั้งหมดสำหรับต้นทางของเว็บแอป เมื่อปิดแท็บแล้ว เว็บไซต์จะเสียสิทธิ์เข้าถึงทั้งหมด ในครั้งถัดไปที่ผู้ใช้ใช้เว็บแอป ระบบจะแจ้งให้ผู้ใช้ให้สิทธิ์เข้าถึงไฟล์อีกครั้ง

ความคิดเห็น

เราอยากทราบประสบการณ์ของคุณในการใช้ File System Access API

บอกเราเกี่ยวกับการออกแบบ API

API ทำงานไม่เป็นไปตามที่คุณคาดหวังใช่ไหม หรือมีเมธอด หรือพร็อพเพอร์ตี้ที่ขาดหายไปซึ่งคุณต้องใช้เพื่อนำแนวคิดไปใช้ไหม หากมีคำถามหรือความคิดเห็นเกี่ยวกับโมเดลความปลอดภัย

หากพบปัญหาในการติดตั้งใช้งาน

คุณพบข้อบกพร่องในการใช้งาน Chrome ไหม หรือการติดตั้งใช้งานแตกต่างจากข้อกำหนด

  • รายงานข้อบกพร่องที่ https://new.crbug.com โปรดระบุรายละเอียดให้มากที่สุดเท่าที่จะทำได้ วิธีการจำลองปัญหา และตั้งค่าคอมโพเนนต์เป็น Blink>Storage>FileSystem

หากมีแผนจะใช้ API

วางแผนที่จะใช้ File System Access API ในเว็บไซต์ใช่ไหม การสนับสนุนแบบสาธารณะของคุณช่วยให้เราจัดลำดับความสำคัญของฟีเจอร์ และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นๆ เห็นว่าการรองรับฟีเจอร์เหล่านี้มีความสำคัญเพียงใด

  • แชร์วิธีที่คุณวางแผนจะใช้ในเธรด Discourse ของ WICG
  • ส่งทวีตถึง @ChromiumDev โดยใช้แฮชแท็ก #FileSystemAccess และ แจ้งให้เราทราบว่าคุณใช้ฟีเจอร์นี้ที่ไหนและอย่างไร

ลิงก์ที่มีประโยชน์

คำขอบคุณ

ข้อกำหนดของ File System Access API เขียนโดย Marijn Kruisselbrink