API Truy cập hệ thống tệp: đơn giản hoá quyền truy cập vào các tệp cục bộ

API Quyền truy cập vào hệ thống tệp cho phép các ứng dụng web đọc hoặc lưu trực tiếp các thay đổi vào tệp và thư mục trên thiết bị của người dùng.

File System Access API (API truy cập hệ thống tệp) cho phép nhà phát triển xây dựng các ứng dụng web mạnh mẽ tương tác với các tệp trên thiết bị cục bộ của người dùng, chẳng hạn như IDE, trình chỉnh sửa ảnh và video, trình chỉnh sửa văn bản, v.v. Sau khi người dùng cấp quyền truy cập cho một ứng dụng web, API này cho phép họ đọc hoặc lưu trực tiếp các thay đổi vào tệp và thư mục trên thiết bị của người dùng. Ngoài việc đọc và ghi tệp, API Truy cập hệ thống tệp còn cung cấp khả năng mở thư mục và liệt kê nội dung của thư mục đó.

Nếu đã từng làm việc với việc đọc và ghi tệp, bạn sẽ quen thuộc với nhiều nội dung mà tôi sắp chia sẻ. Tuy nhiên, bạn vẫn nên đọc tài liệu này vì không phải hệ thống nào cũng giống nhau.

API Truy cập hệ thống tệp được hỗ trợ trên hầu hết các trình duyệt Chromium trên Windows, macOS, ChromeOS và Linux. Một ngoại lệ đáng chú ý là Brave, trong đó tính năng này hiện chỉ có sau một cờ. Chúng tôi đang nỗ lực hỗ trợ Android theo ngữ cảnh crbug.com/1011535.

Sử dụng API Truy cập hệ thống tệp

Để thể hiện sức mạnh và tính hữu ích của API Truy cập hệ thống tệp, tôi đã viết một trình chỉnh sửa văn bản duy nhất. Tệp này cho phép bạn mở một tệp văn bản, chỉnh sửa tệp đó, lưu các thay đổi trở lại ổ đĩa hoặc bắt đầu một tệp mới và lưu các thay đổi vào ổ đĩa. Ứng dụng này không có gì đặc biệt nhưng cung cấp đủ thông tin để giúp bạn hiểu các khái niệm.

Hỗ trợ trình duyệt

Hỗ trợ trình duyệt

  • Chrome: 86.
  • Edge: 86.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Phát hiện tính năng

Để tìm hiểu xem API Quyền truy cập vào hệ thống tệp có được hỗ trợ hay không, hãy kiểm tra xem phương thức bộ chọn mà bạn quan tâm có tồn tại hay không.

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

Dùng thử

Xem API Truy cập hệ thống tệp trong thực tế trong bản minh hoạ trình soạn thảo văn bản.

Đọc một tệp từ hệ thống tệp cục bộ

Trường hợp sử dụng đầu tiên mà tôi muốn giải quyết là yêu cầu người dùng chọn một tệp, sau đó mở và đọc tệp đó từ ổ đĩa.

Yêu cầu người dùng chọn một tệp để đọc

Điểm truy cập vào API Truy cập hệ thống tệp là window.showOpenFilePicker(). Khi được gọi, phương thức này sẽ hiển thị hộp thoại bộ chọn tệp và nhắc người dùng chọn một tệp. Sau khi người dùng chọn một tệp, API sẽ trả về một mảng các handle tệp. Thông số options không bắt buộc cho phép bạn tác động đến hành vi của bộ chọn tệp, ví dụ: bằng cách cho phép người dùng chọn nhiều tệp, thư mục hoặc các loại tệp khác nhau. Nếu không chỉ định bất kỳ tuỳ chọn nào, bộ chọn tệp sẽ cho phép người dùng chọn một tệp. Đây là tính năng hoàn hảo cho trình chỉnh sửa văn bản.

Giống như nhiều API mạnh mẽ khác, việc gọi showOpenFilePicker() phải được thực hiện trong ngữ cảnh bảo mật và phải được gọi từ trong một cử chỉ của người dùng.

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

Sau khi người dùng chọn một tệp, showOpenFilePicker() sẽ trả về một mảng các tên người dùng. Trong trường hợp này, là mảng một phần tử có một FileSystemFileHandle chứa các thuộc tính và phương thức cần thiết để tương tác với tệp đó.

Bạn nên lưu giữ thông tin tham chiếu đến tay cầm tệp để có thể sử dụng sau. Bạn sẽ cần đến quyền này để lưu các thay đổi đối với tệp hoặc để thực hiện bất kỳ thao tác nào khác đối với tệp.

Đọc một tệp từ hệ thống tệp

Giờ đây, bạn đã có một handle (tên nhận dạng) cho một tệp, bạn có thể lấy các thuộc tính của tệp hoặc truy cập vào chính tệp đó. Bây giờ, tôi sẽ đọc nội dung của thư. Việc gọi handle.getFile() sẽ trả về một đối tượng File chứa một blob. Để lấy dữ liệu từ blob, hãy gọi một trong các phương thức của blob (slice(), stream(), text() hoặc arrayBuffer()).

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

Chỉ đọc được đối tượng File do FileSystemFileHandle.getFile() trả về, miễn là tệp cơ sở trên ổ đĩa không thay đổi. Nếu tệp trên ổ đĩa bị sửa đổi, đối tượng File sẽ không đọc được và bạn cần gọi lại getFile() để lấy đối tượng File mới nhằm đọc dữ liệu đã thay đổi.

Kết hợp kiến thức đã học

Khi người dùng nhấp vào nút Open (Mở), trình duyệt sẽ hiển thị một bộ chọn tệp. Sau khi chọn một tệp, ứng dụng sẽ đọc nội dung và đưa các nội dung đó vào <textarea>.

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

Ghi tệp vào hệ thống tệp cục bộ

Trong trình soạn thảo văn bản, có hai cách để lưu tệp: LưuLưu dưới dạng. Thao tác Lưu sẽ ghi các thay đổi trở lại tệp gốc bằng cách sử dụng handle tệp được truy xuất trước đó. Tuy nhiên, thao tác Save As (Lưu dưới dạng) sẽ tạo một tệp mới và do đó cần có một handle tệp mới.

Tạo tệp mới

Để lưu tệp, hãy gọi showSaveFilePicker() để cho thấy bộ chọn tệp ở chế độ "lưu", cho phép người dùng chọn một tệp mới mà họ muốn sử dụng để lưu. Đối với trình chỉnh sửa văn bản, tôi cũng muốn trình chỉnh sửa này tự động thêm tiện ích .txt, vì vậy, tôi đã cung cấp một số tham số bổ sung.

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

Lưu các thay đổi vào ổ đĩa

Bạn có thể tìm thấy tất cả mã để lưu các thay đổi cho một tệp trong bản minh hoạ trình chỉnh sửa văn bản trên GitHub. Các hoạt động tương tác với hệ thống tệp cốt lõi nằm trong fs-helpers.js. Đơn giản nhất, quy trình này sẽ có dạng như mã sau. Tôi sẽ giải thích từng bước và giải thích từng bước.

// 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();
}

Quá trình ghi dữ liệu vào ổ đĩa sử dụng đối tượng FileSystemWritableFileStream là một lớp con của WritableStream. Tạo luồng bằng cách gọi createWritable() trên đối tượng handle (trình xử lý) của tệp. Khi createWritable() được gọi, trước tiên, trình duyệt sẽ kiểm tra xem người dùng đã cấp quyền ghi vào tệp hay chưa. Nếu bạn chưa cấp quyền ghi, trình duyệt sẽ nhắc người dùng cấp quyền. Nếu không cấp quyền, createWritable() sẽ gửi một DOMException và ứng dụng sẽ không thể ghi vào tệp. Trong trình soạn thảo văn bản, các đối tượng DOMException được xử lý trong phương thức saveFile().

Phương thức write() sẽ lấy một chuỗi, đây là điều cần thiết cho trình soạn thảo văn bản. Nhưng nó cũng có thể lấy một BufferSource hoặc một Blob. Ví dụ: bạn có thể chuyển luồng trực tiếp đến luồng đó:

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.
}

Bạn cũng có thể seek() hoặc truncate() trong luồng để cập nhật tệp ở một vị trí cụ thể hoặc đổi kích thước tệp.

Chỉ định tên tệp và thư mục bắt đầu được đề xuất

Trong nhiều trường hợp, bạn có thể muốn ứng dụng đề xuất tên tệp hoặc vị trí mặc định. Ví dụ: trình chỉnh sửa văn bản có thể đề xuất tên tệp mặc định là Untitled Text.txt thay vì Untitled. Bạn có thể thực hiện việc này bằng cách truyền một thuộc tính suggestedName trong các tuỳ chọn showSaveFilePicker.

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

Điều này cũng áp dụng cho thư mục khởi động mặc định. Nếu đang tạo trình chỉnh sửa văn bản, bạn nên bắt đầu hộp thoại lưu tệp hoặc mở tệp trong thư mục documents mặc định, còn đối với trình chỉnh sửa hình ảnh, bạn nên bắt đầu trong thư mục pictures mặc định. Bạn có thể đề xuất thư mục khởi động mặc định bằng cách truyền thuộc tính startIn đến các phương thức showSaveFilePicker, showDirectoryPicker() hoặc showOpenFilePicker như sau.

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

Danh sách các thư mục hệ thống phổ biến là:

  • desktop: Thư mục trên máy tính của người dùng, nếu có.
  • documents: Thư mục thường lưu trữ các tài liệu do người dùng tạo.
  • downloads: Thư mục thường lưu trữ các tệp đã tải xuống.
  • music: Thư mục thường lưu trữ tệp âm thanh.
  • pictures: Thư mục thường lưu trữ ảnh và các hình ảnh tĩnh khác.
  • videos: Thư mục thường lưu trữ video hoặc phim.

Ngoài các thư mục hệ thống phổ biến, bạn cũng có thể truyền một tệp hoặc tay điều khiển thư mục hiện có dưới dạng giá trị cho startIn. Sau đó, hộp thoại sẽ mở ra trong cùng thư mục đó.

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

Chỉ định mục đích của các bộ chọn tệp khác nhau

Đôi khi, các ứng dụng có bộ chọn khác nhau cho các mục đích khác nhau. Ví dụ: trình chỉnh sửa văn bản đa dạng thức có thể cho phép người dùng mở tệp văn bản, nhưng cũng có thể nhập hình ảnh. Theo mặc định, mỗi bộ chọn tệp sẽ mở tại vị trí được ghi nhớ gần đây nhất. Bạn có thể tránh vấn đề này bằng cách lưu trữ các giá trị id cho từng loại bộ chọn. Nếu bạn chỉ định id, thì quá trình triển khai bộ chọn tệp sẽ ghi nhớ một thư mục riêng được dùng gần đây nhất cho id đó.

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

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

Lưu trữ các tên người dùng tệp hoặc tên người dùng thư mục trong IndexedDB

Tên tệp và tên thư mục có thể chuyển đổi tuần tự, nghĩa là bạn có thể lưu tên tệp hoặc tên thư mục vào IndexedDB hoặc gọi postMessage() để gửi các tên đó giữa cùng một nguồn cấp cao nhất.

Việc lưu tên tệp hoặc tên thư mục vào IndexedDB có nghĩa là bạn có thể lưu trữ trạng thái hoặc ghi nhớ tệp hoặc thư mục mà người dùng đang xử lý. Điều này cho phép bạn giữ lại danh sách các tệp đã mở hoặc chỉnh sửa gần đây, đề nghị mở lại tệp gần đây nhất khi mở ứng dụng, khôi phục thư mục đang hoạt động trước đó, v.v. Trong trình soạn thảo văn bản, tôi lưu trữ danh sách 5 tệp gần đây nhất mà người dùng đã mở, cho phép truy cập lại các tệp đó.

Ví dụ về mã sau đây cho thấy cách lưu trữ và truy xuất một handle tệp và một handle thư mục. Bạn có thể xem ví dụ thực tế trên Glitch. (Tôi sử dụng thư viện idb-keyval để viết ngắn gọn.)

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);
  }
});

Các quyền và tên tệp hoặc thư mục được lưu trữ

các quyền không phải lúc nào cũng tồn tại giữa các phiên, nên bạn nên xác minh xem người dùng có cấp quyền cho tệp hoặc thư mục bằng queryPermission() hay không. Nếu chưa, hãy gọi requestPermission() để (yêu cầu lại) quyền đó. Điều này cũng áp dụng cho các tên tệp và tên thư mục. Bạn cần chạy fileOrDirectoryHandle.requestPermission(descriptor) hoặc fileOrDirectoryHandle.queryPermission(descriptor) tương ứng.

Trong trình chỉnh sửa văn bản, tôi đã tạo một phương thức verifyPermission() để kiểm tra xem người dùng đã cấp quyền hay chưa và đưa ra yêu cầu (nếu cần).

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;
}

Bằng cách yêu cầu quyền ghi cùng với yêu cầu đọc, tôi đã giảm số lượng lời nhắc cấp quyền; người dùng sẽ thấy một lời nhắc khi mở tệp và cấp quyền cho cả việc đọc và ghi vào tệp đó.

Mở một thư mục và liệt kê nội dung của thư mục đó

Để liệt kê tất cả tệp trong một thư mục, hãy gọi showDirectoryPicker(). Người dùng chọn một thư mục trong bộ chọn, sau đó FileSystemDirectoryHandle sẽ được trả về, cho phép bạn liệt kê và truy cập vào các tệp của thư mục. Theo mặc định, bạn sẽ có quyền đọc các tệp trong thư mục này, nhưng nếu cần quyền ghi, bạn có thể truyền { mode: 'readwrite' } vào phương thức này.

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

Ngoài ra, nếu bạn cần truy cập từng tệp bằng getFile() để chẳng hạn như để lấy từng kích thước tệp, đừng sử dụng await trên từng kết quả theo tuần tự mà hãy xử lý song song tất cả các tệp, chẳng hạn như sử dụng 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));
});

Tạo hoặc truy cập vào tệp và thư mục trong một thư mục

Từ một thư mục, bạn có thể tạo hoặc truy cập vào các tệp và thư mục bằng cách sử dụng phương thức getFileHandle() hoặc tương ứng là phương thức getDirectoryHandle(). Bằng cách truyền vào một đối tượng options không bắt buộc có khoá là create và giá trị boolean là true hoặc false, bạn có thể xác định xem có tạo tệp hoặc thư mục mới hay không nếu không có tệp hoặc thư mục đó.

// 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 });

Giải quyết đường dẫn của một mục trong thư mục

Khi làm việc với các tệp hoặc thư mục trong một thư mục, bạn có thể giải quyết đường dẫn của mục cần xử lý. Bạn có thể thực hiện việc này bằng phương thức resolve() được đặt tên phù hợp. Để phân giải, mục có thể là mục con trực tiếp hoặc gián tiếp của thư mục.

// 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"]

Xoá tệp và thư mục trong thư mục

Nếu đã có được quyền truy cập vào một thư mục, bạn có thể xoá các tệp và thư mục bên trong bằng phương thức removeEntry(). Đối với thư mục, bạn có thể tuỳ ý xoá đệ quy và bao gồm tất cả thư mục con cũng như các tệp có trong đó.

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

Xoá trực tiếp tệp hoặc thư mục

Nếu bạn có quyền truy cập vào một tệp hoặc tay cầm thư mục, hãy gọi remove() trên FileSystemFileHandle hoặc FileSystemDirectoryHandle để xoá tệp hoặc tay cầm thư mục đó.

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

Đổi tên và di chuyển tệp và thư mục

Bạn có thể đổi tên hoặc di chuyển tệp và thư mục sang một vị trí mới bằng cách gọi move() trên giao diện FileSystemHandle. FileSystemHandle có các giao diện con FileSystemFileHandleFileSystemDirectoryHandle. Phương thức move() có một hoặc hai tham số. Giá trị đầu tiên có thể là một chuỗi có tên mới hoặc FileSystemDirectoryHandle dẫn đến thư mục đích. Trong trường hợp sau, thông số thứ hai (không bắt buộc) là một chuỗi có tên mới, vì vậy, bạn có thể di chuyển và đổi tên trong một bước.

// 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');

Tích hợp tính năng kéo và thả

Giao diện Kéo và thả HTML cho phép các ứng dụng web chấp nhận các tệp được kéo và thả trên trang web. Trong quá trình kéo và thả, các mục tệp và thư mục được kéo sẽ được liên kết với các mục tệp và mục thư mục tương ứng. Phương thức DataTransferItem.getAsFileSystemHandle() trả về một lời hứa có đối tượng FileSystemFileHandle nếu mục được kéo là tệp và một lời hứa có đối tượng FileSystemDirectoryHandle nếu mục được kéo là thư mục. Trang thông tin sau đây cho thấy cách thực hiện. Lưu ý rằng DataTransferItem.kind của giao diện Kéo và thả là "file" đối với cả tệp thư mục, trong khi FileSystemHandle.kind của API Truy cập hệ thống tệp là "file" đối với tệp và "directory" đối với thư mục.

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}`);
    }
  }
});

Truy cập vào hệ thống tệp riêng tư gốc

Hệ thống tệp riêng tư gốc là một điểm cuối bộ nhớ, như tên gọi cho thấy, là riêng tư đối với nguồn gốc của trang. Mặc dù các trình duyệt thường triển khai việc này bằng cách lưu trữ nội dung của hệ thống tệp riêng tư gốc này vào ổ đĩa ở đâu đó, nhưng không có ý định cho phép người dùng truy cập vào nội dung đó. Tương tự, không có thể kỳ vọng rằng các tệp hoặc thư mục có tên khớp với tên của phần tử con của hệ thống tệp riêng tư gốc. Mặc dù trình duyệt có thể trông có vẻ như có các tệp, nhưng trong nội bộ (vì đây là hệ thống tệp riêng tư gốc), nên trình duyệt có thể lưu trữ những "tệp" này trong một cơ sở dữ liệu hoặc bất kỳ cấu trúc dữ liệu nào khác. Về cơ bản, nếu bạn sử dụng API này, hãy không mong đợi tìm thấy các tệp đã tạo được so khớp một với một ở đâu đó trên ổ đĩa cứng. Bạn có thể hoạt động như bình thường trên hệ thống tệp riêng tư gốc sau khi có quyền truy cập vào FileSystemDirectoryHandle gốc.

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 });

Hỗ trợ trình duyệt

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

Nguồn

Truy cập vào các tệp được tối ưu hoá cho hiệu suất từ hệ thống tệp riêng tư gốc

Hệ thống tệp riêng gốc cung cấp quyền truy cập không bắt buộc vào một loại tệp đặc biệt được tối ưu hoá cao cho hiệu suất, chẳng hạn như bằng cách cung cấp quyền ghi tại chỗ và độc quyền vào nội dung của tệp. Trong Chromium 102 trở lên, có một phương thức bổ sung trên hệ thống tệp riêng tư gốc để đơn giản hoá việc truy cập tệp: createSyncAccessHandle() (dành cho các thao tác đọc và ghi đồng bộ). Phương thức này được hiển thị trên FileSystemFileHandle, nhưng chỉ dành riêng cho Trình chạy web.

// (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 });

Tự động bổ sung tính năng

Không thể hoàn toàn polyfill các phương thức API Truy cập hệ thống tệp.

  • Bạn có thể ước chừng phương thức showOpenFilePicker() bằng phần tử <input type="file">.
  • Bạn có thể mô phỏng phương thức showSaveFilePicker() bằng phần tử <a download="file_name">, mặc dù điều này sẽ kích hoạt quá trình tải xuống theo phương thức lập trình và không cho phép ghi đè các tệp hiện có.
  • Phương thức showDirectoryPicker() có thể được mô phỏng một phần bằng phần tử <input type="file" webkitdirectory> không chuẩn.

Chúng tôi đã phát triển một thư viện có tên là browser-fs-access. Thư viện này sử dụng API Truy cập hệ thống tệp bất cứ khi nào có thể và quay lại các tuỳ chọn tốt nhất tiếp theo trong mọi trường hợp khác.

Tính bảo mật và quyền truy cập

Nhóm Chrome đã thiết kế và triển khai API Truy cập hệ thống tệp bằng các nguyên tắc cốt lõi được xác định trong bài viết Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm cả quyền kiểm soát và tính minh bạch của người dùng cũng như tính công thái học của người dùng.

Mở tệp hoặc lưu tệp mới

Bộ chọn tệp để mở tệp để đọc
Bộ chọn tệp dùng để mở một tệp hiện có để đọc.

Khi mở tệp, người dùng cấp quyền đọc tệp hoặc thư mục bằng bộ chọn tệp. Bộ chọn tệp đang mở chỉ có thể hiển thị bằng cử chỉ của người dùng khi được phân phát từ một ngữ cảnh an toàn. Nếu thay đổi ý định, người dùng có thể huỷ lựa chọn trong bộ chọn tệp và trang web sẽ không có quyền truy cập vào bất kỳ nội dung nào. Đây là hành vi tương tự như hành vi của phần tử <input type="file">.

Công cụ chọn tệp để lưu tệp vào ổ đĩa.
Bộ chọn tệp dùng để lưu tệp vào ổ đĩa.

Tương tự, khi một ứng dụng web muốn lưu tệp mới, trình duyệt sẽ hiển thị bộ chọn tệp lưu, cho phép người dùng chỉ định tên và vị trí của tệp mới. Vì họ đang lưu một tệp mới vào thiết bị (thay vì ghi đè tệp hiện có), nên bộ chọn tệp sẽ cấp cho ứng dụng quyền ghi vào tệp đó.

Thư mục bị hạn chế

Để giúp bảo vệ người dùng và dữ liệu của họ, trình duyệt có thể hạn chế khả năng người dùng lưu vào một số thư mục nhất định, chẳng hạn như các thư mục hệ điều hành cốt lõi như Windows, thư mục Thư viện macOS. Khi điều này xảy ra, trình duyệt sẽ hiển thị lời nhắc và yêu cầu người dùng chọn một thư mục khác.

Sửa đổi tệp hoặc thư mục hiện có

Ứng dụng web không thể sửa đổi tệp trên ổ đĩa nếu không có sự cho phép rõ ràng của người dùng.

Lời nhắc cấp quyền

Nếu một người muốn lưu các thay đổi vào một tệp mà trước đó họ đã cấp quyền đọc, trình duyệt sẽ hiển thị lời nhắc cấp quyền, yêu cầu trang web cấp quyền ghi thay đổi vào ổ đĩa. Yêu cầu cấp quyền chỉ có thể được kích hoạt bằng cử chỉ của người dùng, chẳng hạn như bằng cách nhấp vào nút Lưu.

Lời nhắc cấp quyền xuất hiện trước khi lưu tệp.
Lời nhắc hiển thị cho người dùng trước khi trình duyệt được cấp quyền ghi trên một tệp hiện có.

Ngoài ra, một ứng dụng web chỉnh sửa nhiều tệp, chẳng hạn như IDE, cũng có thể yêu cầu quyền lưu các thay đổi tại thời điểm mở.

Nếu người dùng chọn Huỷ và không cấp quyền ghi, thì ứng dụng web không thể lưu các thay đổi vào tệp cục bộ. Ứng dụng phải cung cấp một phương thức thay thế để người dùng lưu dữ liệu của họ, chẳng hạn như bằng cách cung cấp cách "tải tệp xuống" hoặc lưu dữ liệu vào đám mây.

Sự minh bạch

Biểu tượng thanh địa chỉ
Biểu tượng thanh địa chỉ cho biết người dùng đã cấp cho trang web quyền lưu vào tệp cục bộ.

Sau khi người dùng cấp quyền cho ứng dụng web lưu tệp trên máy, trình duyệt sẽ hiển thị biểu tượng trong thanh địa chỉ. Khi nhấp vào biểu tượng này, một cửa sổ bật lên sẽ hiển thị danh sách các tệp mà người dùng đã cấp quyền truy cập. Người dùng có thể thu hồi quyền truy cập đó bất cứ lúc nào nếu họ muốn.

Quyền ổn định

Ứng dụng web có thể tiếp tục lưu các thay đổi đối với tệp mà không cần nhắc cho đến khi tất cả các thẻ cho nguồn gốc của tệp đó đóng lại. Sau khi một thẻ bị đóng, trang web sẽ mất tất cả quyền truy cập. Vào lần tiếp theo người dùng sử dụng ứng dụng web, họ sẽ được nhắc lại để truy cập vào các tệp.

Phản hồi

Chúng tôi muốn biết trải nghiệm của bạn với API Truy cập hệ thống tệp.

Giới thiệu cho chúng tôi về thiết kế API

Có điều gì về API không hoạt động như bạn mong đợi không? Hay có phương thức hoặc thuộc tính nào bị thiếu mà bạn cần để triển khai ý tưởng của mình không? Bạn có câu hỏi hoặc nhận xét về mô hình bảo mật không?

Bạn gặp vấn đề khi triển khai?

Bạn có phát hiện lỗi trong quá trình triển khai Chrome không? Hay cách triển khai có khác với thông số kỹ thuật không?

  • Gửi lỗi tại https://new.crbug.com. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn cách tái hiện và đặt Components (Thành phần) thành Blink>Storage>FileSystem. Glitch rất hữu ích để chia sẻ các bản tái hiện nhanh.

Bạn có dự định sử dụng API này không?

Bạn có dự định sử dụng API Truy cập hệ thống tệp trên trang web của mình không? Sự ủng hộ công khai của bạn giúp chúng tôi ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó.

Đường liên kết hữu ích

Lời cảm ơn

Thông số API Truy cập hệ thống tệp là do Marijn Kruisselbrink viết.