มาตรฐานระบบไฟล์ได้เปิดตัวระบบไฟล์ส่วนตัวของต้นทาง (OPFS) เป็นปลายทางการจัดเก็บข้อมูลที่ส่วนตัวสำหรับต้นทางของหน้าเว็บ และผู้ใช้จะมองไม่เห็น ซึ่งจะให้สิทธิ์เข้าถึงไฟล์พิเศษที่ได้รับการเพิ่มประสิทธิภาพอย่างมากเพื่อประสิทธิภาพ
การสนับสนุนเบราว์เซอร์
เบราว์เซอร์สมัยใหม่รองรับระบบไฟล์ส่วนตัวของต้นทาง และกลุ่มการทำงานด้านเทคโนโลยีแอปพลิเคชันไฮเปอร์เท็กซ์บนเว็บ (WHATWG) ได้กำหนดมาตรฐานไว้ในมาตรฐานระบบไฟล์
แรงจูงใจ
เมื่อนึกถึงไฟล์ในคอมพิวเตอร์ คุณอาจนึกถึงลำดับชั้นของไฟล์ ซึ่งก็คือไฟล์ที่จัดระเบียบไว้ในโฟลเดอร์ที่คุณสำรวจได้ด้วย File Explorer ของระบบปฏิบัติการ เช่น ใน Windows สำหรับผู้ใช้ชื่อ Tom รายการสิ่งที่ต้องทำอาจอยู่ใน C:\Users\Tom\Documents\ToDo.txt
ในตัวอย่างนี้ ToDo.txt
คือชื่อไฟล์ และ Users
, Tom
และ Documents
คือชื่อโฟลเดอร์ `C:` ใน Windows แสดงถึงไดเรกทอรีรากของไดรฟ์
วิธีดั้งเดิมในการทำงานกับไฟล์บนเว็บ
หากต้องการแก้ไขรายการสิ่งที่ต้องทำในเว็บแอปพลิเคชัน ให้ทำตามขั้นตอนปกติ ดังนี้
- ผู้ใช้อัปโหลดไฟล์ไปยังเซิร์ฟเวอร์หรือเปิดไฟล์ในไคลเอ็นต์ด้วย
<input type="file">
- ผู้ใช้ทำการเปลี่ยนแปลง แล้วดาวน์โหลดไฟล์ผลลัพธ์ที่มี
<a download="ToDo.txt>
ที่คุณตั้งโปรแกรมให้click()
ผ่าน JavaScript - สำหรับการเปิดโฟลเดอร์ คุณต้องใช้แอตทริบิวต์พิเศษใน
<input type="file" webkitdirectory>
ซึ่งแม้จะมีชื่อที่เป็นกรรมสิทธิ์ แต่ก็ได้รับการรองรับจากเบราว์เซอร์แทบทั้งหมด
วิธีที่ทันสมัยในการทำงานกับไฟล์บนเว็บ
โฟลว์นี้ไม่ได้แสดงถึงวิธีที่ผู้ใช้คิดเกี่ยวกับการแก้ไขไฟล์ และหมายความว่าผู้ใช้จะได้รับสำเนาของไฟล์อินพุตที่ดาวน์โหลด ดังนั้น File System Access API จึงได้เปิดตัวเมธอดตัวเลือก 3 รายการ ได้แก่ showOpenFilePicker()
, showSaveFilePicker()
และ showDirectoryPicker()
ซึ่งทำงานตรงตามชื่อของเมธอด โดยจะเปิดใช้โฟลว์ดังนี้
- เปิด
ToDo.txt
ด้วยshowOpenFilePicker()
แล้วรับออบเจ็กต์FileSystemFileHandle
- จากออบเจ็กต์
FileSystemFileHandle
ให้รับFile
โดยเรียกใช้เมธอดgetFile()
ของแฮนเดิลไฟล์ - แก้ไขไฟล์ แล้วเรียกใช้
requestPermission({mode: 'readwrite'})
ในแฮนเดิล - หากผู้ใช้ยอมรับคำขอสิทธิ์ ให้บันทึกการเปลี่ยนแปลงกลับไปยังไฟล์ต้นฉบับ
- หรือจะเรียกใช้
showSaveFilePicker()
แล้วให้ผู้ใช้เลือกไฟล์ใหม่ก็ได้ (หากผู้ใช้เลือกไฟล์ที่เปิดก่อนหน้านี้ ระบบจะเขียนทับเนื้อหาของไฟล์) สำหรับการบันทึกซ้ำ คุณสามารถเก็บแฮนเดิลไฟล์ไว้ได้เพื่อไม่ต้องแสดงกล่องโต้ตอบการบันทึกไฟล์อีกครั้ง
ข้อจำกัดในการทำงานกับไฟล์บนเว็บ
ไฟล์และโฟลเดอร์ที่เข้าถึงได้ผ่านวิธีการเหล่านี้จะอยู่ในระบบไฟล์ที่เรียกว่าผู้ใช้มองเห็น ระบบจะทำเครื่องหมายไฟล์ที่บันทึกจากเว็บและไฟล์ที่เรียกใช้งานได้โดยเฉพาะด้วยเครื่องหมายของเว็บ เพื่อให้มีคำเตือนเพิ่มเติมที่ระบบปฏิบัติการแสดงได้ก่อนที่จะมีการเรียกใช้ไฟล์ที่อาจเป็นอันตราย ไฟล์ที่ได้จากเว็บจะได้รับการปกป้องโดย Safe Browsing ซึ่งเป็นฟีเจอร์ความปลอดภัยเพิ่มเติม โดยคุณสามารถคิดว่าฟีเจอร์นี้เป็นการสแกนไวรัสบนระบบคลาวด์เพื่อให้เข้าใจได้ง่ายและสอดคล้องกับบริบทของบทความนี้ เมื่อเขียนข้อมูลลงในไฟล์โดยใช้ File System Access API การเขียนจะไม่เกิดขึ้นในตำแหน่งเดิม แต่จะใช้ไฟล์ชั่วคราว ระบบจะไม่แก้ไขตัวไฟล์เอง เว้นแต่ไฟล์จะผ่านการตรวจสอบความปลอดภัยทั้งหมดนี้ ดังที่ทราบกันดีว่าการดำเนินการนี้ทำให้การดำเนินการกับไฟล์ค่อนข้างช้า แม้ว่าจะมีการปรับปรุงในกรณีที่ทำได้ เช่น ใน macOS อย่างไรก็ตาม การเรียกใช้ write()
ทุกครั้งจะอยู่ในตัวเอง ดังนั้นเบื้องหลังจึงเปิดไฟล์ ค้นหาออฟเซ็ตที่ระบุ และเขียนข้อมูลในที่สุด
ไฟล์เป็นพื้นฐานของการประมวลผล
ในขณะเดียวกัน ไฟล์ก็เป็นวิธีที่ยอดเยี่ยมในการบันทึกข้อมูล เช่น SQLite จะจัดเก็บฐานข้อมูลทั้งหมดไว้ในไฟล์เดียว อีกตัวอย่างหนึ่งคือ Mipmap ที่ใช้ในการประมวลผลรูปภาพ Mipmap คือลำดับของรูปภาพที่ได้รับการคำนวณล่วงหน้าและเพิ่มประสิทธิภาพแล้ว โดยแต่ละรูปภาพจะมีความละเอียดต่ำลงเรื่อยๆ จากรูปภาพก่อนหน้า ซึ่งทำให้การดำเนินการหลายอย่าง เช่น การซูม เร็วขึ้น แล้วเว็บแอปพลิเคชันจะได้รับประโยชน์จากไฟล์ได้อย่างไรโดยไม่ต้องเสียค่าใช้จ่ายด้านประสิทธิภาพในการประมวลผลไฟล์บนเว็บ คำตอบคือระบบไฟล์ส่วนตัวของต้นทาง
ระบบไฟล์ส่วนตัวของต้นทางเทียบกับระบบไฟล์ที่ผู้ใช้มองเห็นได้
ระบบไฟล์ส่วนตัวของต้นทางไม่ได้มีไว้ให้ผู้ใช้เห็น ซึ่งแตกต่างจากระบบไฟล์ที่ผู้ใช้มองเห็นซึ่งเรียกดูได้โดยใช้ File Explorer ของระบบปฏิบัติการ พร้อมทั้งไฟล์และโฟลเดอร์ที่คุณอ่าน เขียน ย้าย และเปลี่ยนชื่อได้ ไฟล์และโฟลเดอร์ในระบบไฟล์ส่วนตัวของต้นทางจะมีความเป็นส่วนตัวตามชื่อ และมีความเป็นส่วนตัวสำหรับต้นทางของเว็บไซต์ ค้นหาต้นทางของหน้าเว็บโดยพิมพ์ location.origin
ในคอนโซล DevTools ตัวอย่างเช่น ต้นทางของหน้า https://developer.chrome.com/articles/
คือ https://developer.chrome.com
(นั่นคือส่วน /articles
ไม่ใช่ส่วนหนึ่งของต้นทาง) คุณอ่านเพิ่มเติมเกี่ยวกับทฤษฎีของต้นทางได้ในทำความเข้าใจ "เว็บไซต์เดียวกัน" และ "ต้นทางเดียวกัน" หน้าเว็บทั้งหมดที่แชร์ต้นทางเดียวกันจะดูข้อมูลระบบไฟล์ส่วนตัวของต้นทางเดียวกันได้ ดังนั้น https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/
จะดูรายละเอียดเดียวกันกับตัวอย่างก่อนหน้าได้ แต่ละต้นทางจะมีระบบไฟล์ส่วนตัวของต้นทางที่เป็นอิสระของตนเอง ซึ่งหมายความว่าระบบไฟล์ส่วนตัวของต้นทางของ https://developer.chrome.com
จะแตกต่างจากระบบไฟล์ส่วนตัวของต้นทางของ https://web.dev
โดยสิ้นเชิง ใน Windows ไดเรกทอรีรูทของระบบไฟล์ที่ผู้ใช้มองเห็นคือ C:\\
สำหรับระบบไฟล์ส่วนตัวของต้นทางเทียบเท่าคือไดเรกทอรีรูทที่ว่างเปล่าในตอนแรกต่อต้นทางที่เข้าถึงโดยการเรียกใช้เมธอดแบบอะซิงโครนัส
navigator.storage.getDirectory()
ดูการเปรียบเทียบระบบไฟล์ที่ผู้ใช้มองเห็นและระบบไฟล์ส่วนตัวของแหล่งที่มาได้ในแผนภาพต่อไปนี้ แผนภาพแสดงให้เห็นว่านอกเหนือจากไดเรกทอรีรากแล้ว ทุกอย่างจะเหมือนกันในเชิงแนวคิด โดยมีลำดับชั้นของไฟล์และโฟลเดอร์เพื่อจัดระเบียบและจัดเรียงตามความจำเป็นสำหรับความต้องการด้านข้อมูลและการจัดเก็บ
รายละเอียดของระบบไฟล์ส่วนตัวของต้นทาง
เช่นเดียวกับกลไกการจัดเก็บข้อมูลอื่นๆ ในเบราว์เซอร์ (เช่น localStorage หรือ IndexedDB) ระบบไฟล์ส่วนตัวของต้นทางจะอยู่ภายใต้ข้อจํากัดโควต้าของเบราว์เซอร์ เมื่อผู้ใช้ล้างข้อมูลการท่องเว็บทั้งหมดหรือข้อมูลเว็บไซต์ทั้งหมด ระบบจะลบระบบไฟล์ส่วนตัวของต้นทางด้วย เรียกใช้ navigator.storage.estimate()
และในออบเจ็กต์การตอบกลับที่ได้ ให้ดูรายการ usage
เพื่อดูว่าแอปใช้พื้นที่เก็บข้อมูลไปแล้วเท่าใด ซึ่งจะแบ่งตามกลไกการจัดเก็บในออบเจ็กต์ usageDetails
โดยคุณจะต้องดูรายการ fileSystem
โดยเฉพาะ เนื่องจากผู้ใช้มองไม่เห็นระบบไฟล์ส่วนตัวของต้นทาง จึงไม่มีข้อความแจ้งขอสิทธิ์และไม่มีการตรวจสอบ Google Safe Browsing
การขอสิทธิ์เข้าถึงไดเรกทอรีราก
หากต้องการเข้าถึงไดเรกทอรีราก ให้เรียกใช้คำสั่งต่อไปนี้ คุณจะได้รับแฮนเดิลไดเรกทอรีที่ว่างเปล่า หรือกล่าวอย่างเจาะจงคือ FileSystemDirectoryHandle
const opfsRoot = await navigator.storage.getDirectory();
// A FileSystemDirectoryHandle whose type is "directory"
// and whose name is "".
console.log(opfsRoot);
เทรดหลักหรือ Web Worker
การใช้ระบบไฟล์ส่วนตัวของต้นทางทำได้ 2 วิธี ได้แก่ ในเธรดหลักหรือใน Web Worker Web Worker จะบล็อกเทรดหลักไม่ได้ ซึ่งหมายความว่าในบริบทนี้ API จะซิงโครนัสได้ ซึ่งเป็นรูปแบบที่โดยทั่วไปแล้วไม่อนุญาตในเทรดหลัก API แบบซิงโครนัสอาจเร็วกว่าเนื่องจากไม่ต้องจัดการกับ Promise และโดยปกติแล้วการดำเนินการกับไฟล์จะเป็นแบบซิงโครนัสในภาษาต่างๆ เช่น C ซึ่งคอมไพล์เป็น WebAssembly ได้
// This is synchronous C code.
FILE *f;
f = fopen("example.txt", "w+");
fputs("Some text\n", f);
fclose(f);
หากต้องการดำเนินการกับไฟล์ให้เร็วที่สุดเท่าที่จะเป็นไปได้ หรือหากคุณใช้ WebAssembly ให้ข้ามไปที่ใช้ระบบไฟล์ส่วนตัวของต้นทางใน Web Worker หรือจะอ่านต่อก็ได้
ใช้ระบบไฟล์ส่วนตัวของต้นทางในเทรดหลัก
สร้างไฟล์และโฟลเดอร์ใหม่
เมื่อมีโฟลเดอร์รูทแล้ว ให้สร้างไฟล์และโฟลเดอร์โดยใช้เมธอด getFileHandle()
และ getDirectoryHandle()
ตามลำดับ หากส่ง {create: true}
ระบบจะสร้างไฟล์หรือโฟลเดอร์ให้หากยังไม่มี สร้างลำดับชั้นของไฟล์โดยเรียกใช้ฟังก์ชันเหล่านี้โดยใช้ไดเรกทอรีที่สร้างขึ้นใหม่เป็นจุดเริ่มต้น
const fileHandle = await opfsRoot
.getFileHandle('my first file', {create: true});
const directoryHandle = await opfsRoot
.getDirectoryHandle('my first folder', {create: true});
const nestedFileHandle = await directoryHandle
.getFileHandle('my first nested file', {create: true});
const nestedDirectoryHandle = await directoryHandle
.getDirectoryHandle('my first nested folder', {create: true});
เข้าถึงไฟล์และโฟลเดอร์ที่มีอยู่
หากทราบชื่อ ให้เข้าถึงไฟล์และโฟลเดอร์ที่สร้างไว้ก่อนหน้านี้โดยเรียกใช้เมธอด getFileHandle()
หรือ getDirectoryHandle()
แล้วส่งชื่อไฟล์หรือโฟลเดอร์
const existingFileHandle = await opfsRoot.getFileHandle('my first file');
const existingDirectoryHandle = await opfsRoot
.getDirectoryHandle('my first folder');
รับไฟล์ที่เชื่อมโยงกับแฮนเดิลไฟล์สำหรับการอ่าน
FileSystemFileHandle
แสดงถึงไฟล์ในระบบไฟล์ หากต้องการรับ File
ที่เชื่อมโยง ให้ใช้วิธี getFile()
ออบเจ็กต์ File
เป็นBlob
ประเภทหนึ่ง และใช้ได้ในทุกบริบทที่ Blob
ใช้ได้ โดยเฉพาะอย่างยิ่ง FileReader
, URL.createObjectURL()
, createImageBitmap()
และ XMLHttpRequest.send()
ยอมรับทั้ง Blobs
และ Files
หากคุณได้รับ File
จาก FileSystemFileHandle
จะเป็นการ "ปลด" ข้อมูลเพื่อให้คุณเข้าถึงและทำให้ข้อมูลพร้อมใช้งานในระบบไฟล์ที่ผู้ใช้มองเห็นได้
const file = await fileHandle.getFile();
console.log(await file.text());
เขียนไปยังไฟล์โดยการสตรีม
สตรีมข้อมูลลงในไฟล์โดยเรียกใช้ createWritable()
ซึ่งจะสร้าง FileSystemWritableFileStream
จากนั้นคุณจะ write()
เนื้อหา เมื่อสิ้นสุดแล้ว คุณต้องclose()
สตรีม
const contents = 'Some text';
// Get a writable stream.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the stream, which persists the contents.
await writable.close();
ลบไฟล์และโฟลเดอร์
ลบไฟล์และโฟลเดอร์โดยเรียกใช้เมธอด remove()
ที่เฉพาะเจาะจงของแฮนเดิลไฟล์หรือไดเรกทอรี หากต้องการลบโฟลเดอร์รวมถึงโฟลเดอร์ย่อยทั้งหมด ให้ส่งตัวเลือก {recursive: true}
await fileHandle.remove();
await directoryHandle.remove({recursive: true});
อีกวิธีหนึ่งคือหากคุณทราบชื่อไฟล์หรือโฟลเดอร์ที่จะลบในไดเรกทอรี ให้ใช้วิธี removeEntry()
directoryHandle.removeEntry('my first nested file');
การย้ายและเปลี่ยนชื่อไฟล์และโฟลเดอร์
เปลี่ยนชื่อและย้ายไฟล์และโฟลเดอร์โดยใช้เมธอด move()
การย้ายและการเปลี่ยนชื่ออาจเกิดขึ้นพร้อมกันหรือแยกกันก็ได้
// Rename a file.
await fileHandle.move('my first renamed file');
// Move a file to another directory.
await fileHandle.move(nestedDirectoryHandle);
// Move a file to another directory and rename it.
await fileHandle
.move(nestedDirectoryHandle, 'my first renamed and now nested file');
แก้ไขเส้นทางของไฟล์หรือโฟลเดอร์
หากต้องการทราบตำแหน่งของไฟล์หรือโฟลเดอร์ที่กำหนดเมื่อเทียบกับไดเรกทอรีอ้างอิง ให้ใช้วิธี resolve()
โดยส่ง FileSystemHandle
เป็นอาร์กิวเมนต์ หากต้องการรับเส้นทางแบบเต็มของไฟล์หรือโฟลเดอร์ในระบบไฟล์ส่วนตัวของต้นทาง ให้ใช้ไดเรกทอรีรากเป็นไดเรกทอรีอ้างอิงที่ได้รับผ่าน navigator.storage.getDirectory()
const relativePath = await opfsRoot.resolve(nestedDirectoryHandle);
// `relativePath` is `['my first folder', 'my first nested folder']`.
ตรวจสอบว่าแฮนเดิลของไฟล์หรือโฟลเดอร์ 2 รายการชี้ไปยังไฟล์หรือโฟลเดอร์เดียวกันหรือไม่
บางครั้งคุณอาจมีแฮนเดิล 2 รายการและไม่ทราบว่าแฮนเดิลทั้ง 2 รายการชี้ไปยังไฟล์หรือโฟลเดอร์เดียวกันหรือไม่ หากต้องการตรวจสอบว่ากรณีดังกล่าวเกิดขึ้นหรือไม่ ให้ใช้วิธีisSameEntry()
fileHandle.isSameEntry(nestedFileHandle);
// Returns `false`.
แสดงรายการเนื้อหาของโฟลเดอร์
FileSystemDirectoryHandle
คือตัววนซ้ำแบบอะซิงโครนัสที่คุณวนซ้ำด้วยลูป for await…of
ในฐานะเครื่องวนซ้ำแบบไม่พร้อมกัน เครื่องมือนี้ยังรองรับเมธอด entries()
, values()
และ keys()
ซึ่งคุณเลือกใช้ได้ตามข้อมูลที่ต้องการ
for await (let [name, handle] of directoryHandle) {}
for await (let [name, handle] of directoryHandle.entries()) {}
for await (let handle of directoryHandle.values()) {}
for await (let name of directoryHandle.keys()) {}
แสดงเนื้อหาของโฟลเดอร์และโฟลเดอร์ย่อยทั้งหมดแบบเรียกซ้ำ
การจัดการกับลูปและฟังก์ชันแบบไม่พร้อมกันที่จับคู่กับการเรียกซ้ำนั้นทำได้ง่าย ฟังก์ชันด้านล่างนี้สามารถใช้เป็นจุดเริ่มต้นในการแสดงเนื้อหาของโฟลเดอร์และโฟลเดอร์ย่อยทั้งหมด รวมถึงไฟล์ทั้งหมดและขนาดของไฟล์ คุณสามารถทำให้ฟังก์ชันง่ายขึ้นได้หากไม่ต้องการขนาดไฟล์ โดยไม่ต้องส่งพรอมิส handle.getFile()
แต่ส่ง handle
โดยตรงแทนdirectoryEntryPromises.push
const getDirectoryEntriesRecursive = async (
directoryHandle,
relativePath = '.',
) => {
const fileHandles = [];
const directoryHandles = [];
const entries = {};
// Get an iterator of the files and folders in the directory.
const directoryIterator = directoryHandle.values();
const directoryEntryPromises = [];
for await (const handle of directoryIterator) {
const nestedPath = `${relativePath}/${handle.name}`;
if (handle.kind === 'file') {
fileHandles.push({ handle, nestedPath });
directoryEntryPromises.push(
handle.getFile().then((file) => {
return {
name: handle.name,
kind: handle.kind,
size: file.size,
type: file.type,
lastModified: file.lastModified,
relativePath: nestedPath,
handle
};
}),
);
} else if (handle.kind === 'directory') {
directoryHandles.push({ handle, nestedPath });
directoryEntryPromises.push(
(async () => {
return {
name: handle.name,
kind: handle.kind,
relativePath: nestedPath,
entries:
await getDirectoryEntriesRecursive(handle, nestedPath),
handle,
};
})(),
);
}
}
const directoryEntries = await Promise.all(directoryEntryPromises);
directoryEntries.forEach((directoryEntry) => {
entries[directoryEntry.name] = directoryEntry;
});
return entries;
};
ใช้ระบบไฟล์ส่วนตัวของต้นทางใน Web Worker
ดังที่กล่าวไว้ก่อนหน้านี้ Web Worker จะบล็อกเทรดหลักไม่ได้ จึงอนุญาตให้ใช้วิธีการแบบซิงโครนัสในบริบทนี้
การรับแฮนเดิลการเข้าถึงแบบซิงโครนัส
จุดแรกเข้าสำหรับการดำเนินการกับไฟล์ที่เร็วที่สุดเท่าที่จะเป็นไปได้คือ FileSystemSyncAccessHandle
ซึ่งได้มาจาก FileSystemFileHandle
ปกติโดยการเรียกใช้ createSyncAccessHandle()
const fileHandle = await opfsRoot
.getFileHandle('my highspeed file.txt', {create: true});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
วิธีการไฟล์แบบซิงโครนัสในตำแหน่ง
เมื่อมีแฮนเดิลการเข้าถึงแบบซิงโครนัส คุณจะได้รับสิทธิ์เข้าถึงเมธอดไฟล์แบบแทนที่ที่รวดเร็วซึ่งเป็นแบบซิงโครนัสทั้งหมด
getSize()
: แสดงผลขนาดของไฟล์ในหน่วยไบต์write()
: เขียนเนื้อหาของบัฟเฟอร์ลงในไฟล์ โดยอาจเขียนที่ออฟเซ็ตที่ระบุ และแสดงผลจำนวนไบต์ที่เขียน การตรวจสอบจำนวนไบต์ที่เขียนแล้วซึ่งส่งคืนจะช่วยให้ผู้โทรตรวจหาและจัดการข้อผิดพลาดและการเขียนบางส่วนได้read()
: อ่านเนื้อหาของไฟล์ลงในบัฟเฟอร์ โดยอาจระบุออฟเซ็ตที่ต้องการหรือไม่ก็ได้truncate()
: ปรับขนาดไฟล์เป็นขนาดที่ระบุflush()
: ตรวจสอบว่าเนื้อหาของไฟล์มีการแก้ไขทั้งหมดที่ดำเนินการผ่านwrite()
close()
: ปิดแฮนเดิลการเข้าถึง
ต่อไปนี้เป็นตัวอย่างที่ใช้วิธีการทั้งหมดที่กล่าวถึงข้างต้น
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle('fast', {create: true});
const accessHandle = await fileHandle.createSyncAccessHandle();
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode('Some text');
// Write the content at the beginning of the file.
accessHandle.write(content, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();
// Encode more content to write to the file.
const moreContent = textEncoder.encode('More content');
// Write the content at the end of the file.
accessHandle.write(moreContent, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();
// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));
// Read the entire file into the data view.
accessHandle.read(dataView);
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));
// Read starting at offset 9 into the data view.
accessHandle.read(dataView, {at: 9});
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));
// Truncate the file after 4 bytes.
accessHandle.truncate(4);
คัดลอกไฟล์จากระบบไฟล์ส่วนตัวต้นทางไปยังระบบไฟล์ที่ผู้ใช้มองเห็น
ดังที่กล่าวไว้ข้างต้น คุณจะย้ายไฟล์จากระบบไฟล์ส่วนตัวต้นทางไปยังระบบไฟล์ที่ผู้ใช้มองเห็นไม่ได้ แต่สามารถคัดลอกไฟล์ได้ เนื่องจาก showSaveFilePicker()
จะแสดงในเทรดหลักเท่านั้น แต่จะไม่แสดงในเทรด Worker คุณจึงต้องเรียกใช้โค้ดในเทรดหลัก
// On the main thread, not in the Worker. This assumes
// `fileHandle` is the `FileSystemFileHandle` you obtained
// the `FileSystemSyncAccessHandle` from in the Worker
// thread. Be sure to close the file in the Worker thread first.
const fileHandle = await opfsRoot.getFileHandle('fast');
try {
// Obtain a file handle to a new file in the user-visible file system
// with the same name as the file in the origin private file system.
const saveHandle = await showSaveFilePicker({
suggestedName: fileHandle.name || ''
});
const writable = await saveHandle.createWritable();
await writable.write(await fileHandle.getFile());
await writable.close();
} catch (err) {
console.error(err.name, err.message);
}
แก้ไขข้อบกพร่องของระบบไฟล์ส่วนตัวของต้นทาง
จนกว่าจะมีการเพิ่มการรองรับเครื่องมือสำหรับนักพัฒนาเว็บในตัว (ดู crbug/1284595) ให้ใช้ส่วนขยาย Chrome OPFS Explorer เพื่อแก้ไขข้อบกพร่องของระบบไฟล์ส่วนตัวของต้นทาง ภาพหน้าจอด้านบนจากส่วนการสร้างไฟล์และโฟลเดอร์ใหม่มาจากส่วนขยายโดยตรง
หลังจากติดตั้งส่วนขยายแล้ว ให้เปิดเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เลือกแท็บ OPFS Explorer จากนั้นคุณก็พร้อมที่จะตรวจสอบลำดับชั้นของไฟล์ บันทึกไฟล์จากระบบไฟล์ส่วนตัวของต้นทางไปยังระบบไฟล์ที่ผู้ใช้มองเห็นได้โดยคลิกชื่อไฟล์ และลบไฟล์และโฟลเดอร์โดยคลิกไอคอนถังขยะ
สาธิต
ดูระบบไฟล์ส่วนตัวของต้นทางในการทำงาน (หากคุณติดตั้งส่วนขยาย OPFS Explorer) ในการสาธิตที่ใช้เป็นแบ็กเอนด์สำหรับฐานข้อมูล SQLite ที่คอมไพล์เป็น WebAssembly อย่าลืมดูซอร์สโค้ดใน Glitch โปรดสังเกตว่าเวอร์ชันที่ฝังด้านล่างไม่ได้ใช้แบ็กเอนด์ระบบไฟล์ส่วนตัวของต้นทาง (เนื่องจาก iframe เป็นแบบข้ามต้นทาง) แต่เมื่อคุณเปิดเดโมในแท็บแยกต่างหาก ระบบจะใช้แบ็กเอนด์ดังกล่าว
บทสรุป
ระบบไฟล์ส่วนตัวของต้นทางตามที่ WHATWG ระบุได้กำหนดวิธีที่เราใช้และโต้ตอบกับไฟล์บนเว็บ ซึ่งทำให้เกิด Use Case ใหม่ๆ ที่ไม่สามารถทำได้ด้วยระบบไฟล์ที่ผู้ใช้มองเห็น ผู้ให้บริการเบราว์เซอร์รายใหญ่ทั้งหมด ได้แก่ Apple, Mozilla และ Google ต่างก็เข้าร่วมและมีวิสัยทัศน์ร่วมกัน การพัฒนา Origin Private File System เป็นความพยายามร่วมกันอย่างมาก และความคิดเห็นจากนักพัฒนาซอฟต์แวร์และผู้ใช้เป็นสิ่งสำคัญต่อความคืบหน้า ในขณะที่เราปรับแต่งและปรับปรุงมาตรฐานอย่างต่อเนื่อง เรายินดีรับฟังความคิดเห็นเกี่ยวกับที่เก็บ whatwg/fs ในรูปแบบของปัญหาหรือคำขอผสานรวม
ลิงก์ที่เกี่ยวข้อง
- ข้อกำหนดมาตรฐานของระบบไฟล์
- ที่เก็บมาตรฐานของระบบไฟล์
- File System API พร้อมโพสต์ Origin Private File System ของ WebKit
- ส่วนขยาย OPFS Explorer
การรับทราบ
บทความนี้ได้รับการตรวจสอบโดย Austin Sully, Etienne Noël และ Rachel Andrew รูปภาพหลักโดย Christina Rumpf ใน Unsplash