File System Access API memungkinkan aplikasi web membaca atau menyimpan perubahan langsung ke file dan folder di perangkat pengguna.
Dipublikasikan: 19 Agustus 2024
File System Access API memungkinkan developer membuat aplikasi web canggih yang berinteraksi dengan file di perangkat lokal pengguna, seperti IDE, editor foto dan video, editor teks, dan lainnya. Setelah pengguna memberikan akses ke aplikasi web, API ini memungkinkan aplikasi web membaca atau menyimpan perubahan langsung ke file dan folder di perangkat pengguna. Selain membaca dan menulis file, File System Access API memberikan kemampuan untuk membuka direktori dan menghitung isinya.
Jika Anda pernah bekerja dengan membaca dan menulis file sebelumnya, sebagian besar hal yang akan saya bagikan akan terasa familiar bagi Anda. Namun, sebaiknya Anda tetap membacanya karena tidak semua sistem itu sama.
File System Access API didukung di sebagian besar browser Chromium di Windows, macOS, ChromeOS, Linux, dan Android. Pengecualian yang patut diperhatikan adalah Brave yang saat ini hanya tersedia di balik tanda khusus.
Menggunakan File System Access API
Untuk menunjukkan kecanggihan dan kegunaan File System Access API, saya menulis editor teks satu file. Anda dapat membuka file teks, mengeditnya, menyimpan perubahan kembali ke disk, atau memulai file baru dan menyimpan perubahan ke disk. Tidak ada yang istimewa, tetapi cukup untuk membantu Anda memahami konsepnya.
Dukungan browser
Deteksi fitur
Untuk mengetahui apakah File System Access API didukung, periksa apakah metode alat pilih yang Anda minati ada.
if ('showOpenFilePicker' in self) {
// The `showOpenFilePicker()` method of the File System Access API is supported.
}
Coba
Lihat cara kerja File System Access API dalam demo editor teks.
Membaca file dari sistem file lokal
Kasus penggunaan pertama yang ingin saya tangani adalah meminta pengguna memilih file, lalu membuka dan membaca file tersebut dari disk.
Meminta pengguna memilih file untuk dibaca
Titik entri ke File System Access API adalah
window.showOpenFilePicker(). Saat dipanggil, fungsi ini akan menampilkan dialog pemilih file,
dan meminta pengguna memilih file. Setelah pengguna memilih file, API akan menampilkan array handle file. Parameter options opsional memungkinkan Anda memengaruhi perilaku pemilih file, misalnya, dengan mengizinkan pengguna memilih beberapa file, atau direktori, atau jenis file yang berbeda.
Tanpa opsi yang ditentukan, pemilih file memungkinkan pengguna memilih satu file. Hal ini sangat cocok untuk editor teks.
Seperti banyak API canggih lainnya, panggilan showOpenFilePicker() harus dilakukan dalam konteks
yang aman, dan harus dipanggil dari dalam gestur pengguna.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
Setelah pengguna memilih file, showOpenFilePicker() akan menampilkan array handle, dalam hal ini
array satu elemen dengan satu FileSystemFileHandle yang berisi properti dan
metode yang diperlukan untuk berinteraksi dengan file.
Sebaiknya simpan referensi ke handle file agar dapat digunakan nanti. Izin ini diperlukan untuk menyimpan perubahan pada file, atau untuk melakukan operasi file lainnya.
Membaca file dari sistem file
Setelah memiliki handle ke file, Anda dapat mendapatkan properti file, atau mengakses file itu sendiri.
Untuk saat ini, saya akan membaca isinya. Memanggil handle.getFile() akan menampilkan objek File, yang berisi blob. Untuk mendapatkan data dari blob, panggil salah satu metodenya, (slice(),
stream(),
text(), atau
arrayBuffer()).
const file = await fileHandle.getFile();
const contents = await file.text();
Objek File yang ditampilkan oleh FileSystemFileHandle.getFile() hanya dapat dibaca selama
file yang mendasarinya di disk belum berubah. Jika file di disk diubah, objek File menjadi
tidak dapat dibaca dan Anda harus memanggil getFile() lagi untuk mendapatkan objek File baru guna membaca data yang diubah.
Menggabungkan semuanya
Saat pengguna mengklik tombol Buka, browser akan menampilkan pemilih file. Setelah pengguna memilih file, aplikasi akan membaca konten dan memasukkannya ke dalam <textarea>.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});
Tulis file ke sistem file lokal
Di editor teks, ada dua cara untuk menyimpan file: Simpan, dan Simpan Sebagai. Save menulis kembali perubahan ke file asli menggunakan handle file yang diambil sebelumnya. Namun, Simpan Sebagai membuat file baru, sehingga memerlukan handle file baru.
Membuat file baru
Untuk menyimpan file, panggil showSaveFilePicker(), yang menampilkan pemilih file dalam mode "simpan", sehingga pengguna dapat memilih file baru yang ingin digunakan untuk menyimpan. Untuk editor
teks, saya juga ingin editor tersebut otomatis menambahkan ekstensi .txt, jadi saya memberikan beberapa parameter
tambahan.
async function getNewFileHandle() {
const options = {
types: [
{
description: 'Text Files',
accept: {
'text/plain': ['.txt'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
return handle;
}
Menyimpan perubahan ke disk
Anda dapat menemukan semua kode untuk menyimpan perubahan pada file dalam demo editor teks saya di
GitHub. Interaksi sistem file inti ada di
fs-helpers.js. Dalam bentuknya yang paling sederhana, prosesnya terlihat seperti kode berikut.
Saya akan membahas setiap langkah dan menjelaskannya.
// 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();
}
Penulisan data ke disk menggunakan objek FileSystemWritableFileStream, subclass
dari WritableStream. Buat stream dengan memanggil createWritable() pada objek handle file. Saat createWritable() dipanggil, browser akan memeriksa terlebih dahulu apakah pengguna telah memberikan
izin tulis ke file. Jika izin untuk menulis belum diberikan, browser akan meminta izin kepada pengguna. Jika izin tidak diberikan, createWritable() akan memunculkan
DOMException, dan aplikasi tidak akan dapat menulis ke file. Di editor teks, objek
DOMException ditangani dalam metode saveFile().
Metode write() mengambil string, yang diperlukan untuk editor teks. Namun, metode ini juga dapat menggunakan
BufferSource, atau Blob. Misalnya, Anda dapat menyalurkan aliran langsung ke
sana:
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.
}
Anda juga dapat seek(), atau truncate() dalam aliran untuk memperbarui
file pada posisi tertentu, atau mengubah ukuran file.
Menentukan nama file yang disarankan dan direktori awal
Dalam banyak kasus, Anda mungkin ingin aplikasi Anda menyarankan nama file atau lokasi default. Misalnya, editor
teks mungkin ingin menyarankan nama file default Untitled Text.txt, bukan Untitled. Anda
dapat melakukannya dengan meneruskan properti suggestedName sebagai bagian dari opsi showSaveFilePicker.
const fileHandle = await self.showSaveFilePicker({
suggestedName: 'Untitled Text.txt',
types: [{
description: 'Text documents',
accept: {
'text/plain': ['.txt'],
},
}],
});
Hal yang sama berlaku untuk direktori awal default. Jika Anda membuat editor teks, Anda mungkin ingin memulai dialog penyimpanan file atau pembukaan file di folder documents default, sedangkan untuk editor gambar, Anda mungkin ingin memulai di folder pictures default. Anda dapat menyarankan direktori awal default
dengan meneruskan properti startIn ke metode showSaveFilePicker, showDirectoryPicker(), atau
showOpenFilePicker seperti ini.
const fileHandle = await self.showOpenFilePicker({
startIn: 'pictures'
});
Daftar direktori sistem yang terkenal adalah:
desktop: Direktori desktop pengguna, jika ada.documents: Direktori tempat dokumen yang dibuat oleh pengguna biasanya disimpan.downloads: Direktori tempat file yang didownload biasanya disimpan.music: Direktori tempat file audio biasanya disimpan.pictures: Direktori tempat foto dan gambar diam lainnya biasanya disimpan.videos: Direktori tempat video atau film biasanya disimpan.
Selain direktori sistem yang sudah dikenal, Anda juga dapat meneruskan handle file atau direktori yang ada sebagai
nilai untuk startIn. Dialog kemudian akan terbuka di direktori yang sama.
// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
startIn: directoryHandle
});
Menentukan tujuan pemilih file yang berbeda
Terkadang aplikasi memiliki pemilih yang berbeda untuk tujuan yang berbeda. Misalnya, editor teks kaya
dapat mengizinkan pengguna membuka file teks, tetapi juga mengimpor gambar. Secara default, setiap pemilih file akan terbuka di lokasi yang terakhir diingat. Anda dapat mengatasi masalah ini dengan menyimpan nilai id untuk setiap jenis pemilih. Jika id ditentukan, implementasi pemilih file akan mengingat
direktori terakhir yang digunakan secara terpisah untuk id tersebut.
const fileHandle1 = await self.showSaveFilePicker({
id: 'openText',
});
const fileHandle2 = await self.showSaveFilePicker({
id: 'importImage',
});
Menyimpan handle file atau handle direktori di IndexedDB
Handle file dan handle direktori dapat diserialisasi, yang berarti Anda dapat menyimpan handle file atau direktori ke IndexedDB, atau memanggil postMessage() untuk mengirimkannya di antara origin tingkat teratas yang sama.
Menyimpan handle file atau direktori ke IndexedDB berarti Anda dapat menyimpan status, atau mengingat file atau direktori yang sedang dikerjakan pengguna. Hal ini memungkinkan untuk menyimpan daftar file yang baru saja dibuka atau diedit, menawarkan untuk membuka kembali file terakhir saat aplikasi dibuka, memulihkan direktori kerja sebelumnya, dan lainnya. Di editor teks, saya menyimpan daftar lima file terbaru yang telah dibuka pengguna, sehingga memungkinkan file tersebut diakses lagi.
Contoh kode berikut menunjukkan cara menyimpan dan mengambil handle file dan handle direktori. Anda dapat melihat penerapannya di Glitch. (Saya menggunakan library idb-keyval agar lebih singkat.)
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);
}
});
Izin dan handle file atau direktori yang disimpan
Karena izin tidak selalu dipertahankan di antara sesi, Anda harus memverifikasi apakah pengguna telah memberikan izin ke file atau direktori menggunakan queryPermission(). Jika belum, hubungi
requestPermission() untuk (kembali) memintanya. Hal ini berlaku sama untuk handle file dan direktori. Anda
harus menjalankan fileOrDirectoryHandle.requestPermission(descriptor) atau
fileOrDirectoryHandle.queryPermission(descriptor).
Di editor teks, saya membuat metode verifyPermission() yang memeriksa apakah pengguna telah
memberikan izin, dan jika diperlukan, membuat permintaan.
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;
}
Dengan meminta izin tulis bersama permintaan baca, saya mengurangi jumlah dialog izin; pengguna melihat satu dialog saat membuka file, dan memberikan izin untuk membaca dan menulis file tersebut.
Membuka direktori dan menghitung isinya
Untuk menghitung semua file dalam direktori, panggil showDirectoryPicker(). Pengguna memilih direktori di pemilih, setelah itu FileSystemDirectoryHandle akan ditampilkan, yang memungkinkan Anda menghitung dan mengakses file direktori. Secara default, Anda akan memiliki akses baca ke file dalam direktori, tetapi jika Anda memerlukan akses tulis, Anda dapat meneruskan { mode: 'readwrite' } ke metode.
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}
});
Jika Anda juga perlu mengakses setiap file menggunakan getFile() untuk, misalnya, mendapatkan ukuran file individual, jangan gunakan await pada setiap hasil secara berurutan, tetapi proses semua file secara paralel, misalnya, menggunakan 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));
});
Membuat atau mengakses file dan folder dalam direktori
Dari direktori, Anda dapat membuat atau mengakses file dan folder menggunakan
metode getFileHandle() atau getDirectoryHandle(). Dengan meneruskan objek options opsional dengan kunci create dan nilai boolean true atau false, Anda dapat menentukan apakah file atau folder baru harus dibuat jika tidak ada.
// 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 });
Menyelesaikan jalur item dalam direktori
Saat bekerja dengan file atau folder dalam direktori, Anda dapat menyelesaikan jalur item yang dimaksud. Hal ini dapat dilakukan dengan metode resolve() yang dinamai dengan tepat. Untuk penyelesaian, item dapat berupa turunan langsung atau tidak langsung dari direktori.
// 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"]
Menghapus file dan folder dalam direktori
Jika telah mendapatkan akses ke direktori, Anda dapat menghapus file dan folder yang ada di dalamnya dengan metode
removeEntry(). Untuk folder, penghapusan dapat dilakukan secara rekursif dan menyertakan semua subfolder dan file yang ada di dalamnya.
// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });
Menghapus file atau folder secara langsung
Jika Anda memiliki akses ke handle file atau direktori, panggil remove() di FileSystemFileHandle atau
FileSystemDirectoryHandle untuk menghapusnya.
// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
Mengganti nama dan memindahkan file dan folder
File dan folder dapat diganti namanya atau dipindahkan ke lokasi baru dengan memanggil move() di antarmuka
FileSystemHandle.