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 Truy cập 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.

API Truy cập hệ thống tệp là gì?

File System Access API (API Hệ thống tệp gốc) (trước đây gọi là API Hệ thống tệp gốc và trước đây gọi là API Tệp có thể ghi) cho phép nhà phát triển xây dựng các ứng dụng web mạnh mẽ có khả năng 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 vào ứng dụng web, API này sẽ cho phép họ đọc hoặc lưu trực tiếp các thay đổi đối với 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 trước đây bạn đã từng đọc và ghi tệp, thì nhiều nội dung tôi sắp chia sẻ sẽ quen thuộc với bạn. Tôi khuyến khích bạn đọc bất kỳ báo cáo nào vì không phải hệ thống nào cũng giống nhau.

API Truy cập hệ thống tệp hiện đượ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 hiện chỉ có sẵn sau cờ. Android hỗ trợ một phần hệ thống tệp riêng tư của API kể từ Chromium 109. Hiện chưa có kế hoạch nào cho các phương thức chọn, nhưng bạn có thể theo dõi tiến trình tiềm năng bằng cách gắn dấu sao crbug.com/1011535.

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

Để chứng minh 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 tệp trình chỉnh sửa văn bản duy nhất. Dịch vụ này cho phép bạn mở tệp văn bản, chỉnh sửa, lưu thay đổi trở lại ổ đĩa hoặc bắt đầu một tệp mới và lưu thay đổi vào ổ đĩa. Công cụ này không có gì phức tạp nhưng cung cấp đủ để giúp bạn hiểu các khái niệm.

Hỗ trợ trình duyệt

Hỗ trợ trình duyệt

  • 86
  • 86
  • x
  • x

Nguồn

Thử ngay

Xem API Truy cập hệ thống tệp trong thực tế trong bản minh hoạ trình chỉnh sửa 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, hệ thống 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 gồm các trình xử lý tệp. Tham 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, chẳng hạn như bằng cách cho phép người dùng chọn nhiều tệp, thư mục hoặc nhiều loại tệp. Nếu không có bất kỳ tuỳ chọn nào được chỉ định, bộ chọn tệp 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 bối cảnh bảo mật và phải được gọi từ trong 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 tên người dùng. Trong trường hợp này, 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 giữ tham chiếu đến tên người dùng tệp để có thể sử dụng sau này. Bạn sẽ cần 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 tệp từ hệ thống tệp

Giờ đây, khi đã có tên người dùng đối với một tệp, bạn có thể xem các thuộc tính của tệp hoặc truy cập vào tệp đó. Bây giờ, tôi sẽ chỉ đọc nội dung của cuộc gọi. Việc gọi handle.getFile() sẽ trả về một đối tượng File, trong đó 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();

Đối tượng File do FileSystemFileHandle.getFile() trả về chỉ có thể đọc được 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 sẽ 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 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à đặt 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 chỉnh sửa văn bản, có hai cách để lưu tệp: LưuLưu dưới dạng. Lưu chỉ cần ghi lại các thay đổi vào tệp gốc bằng cách sử dụng tên người dùng tệp được truy xuất trước đó. Tuy nhiên, Lưu dưới dạng sẽ tạo một tệp mới và do đó yêu cầu một tên người dùng mới của tệp.

Tạo tệp mới

Để lưu một tệp, hãy gọi showSaveFilePicker(). Thao tác này sẽ hiển thị bộ chọn tệp ở chế độ "lưu", cho phép người dùng chọn một tệp mới họ muốn 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 phần mở rộng .txt, vì vậy, tôi đã cung cấp thêm một số tham số.

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

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

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

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

Việc ghi dữ liệu vào ổ đĩa sử dụng đối tượng FileSystemWritableFileStream, một lớp con của WritableStream. Hãy tạo luồng bằng cách gọi createWritable() trên đối tượng xử lý 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ó cấp quyền ghi vào tệp hay không. Nếu 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 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 chỉnh sửa văn bản, đối tượng DOMException được xử lý trong phương thức saveFile().

Phương thức write() nhận một chuỗi. Đây là chuỗi cần thiết cho trình chỉnh sửa văn bản. Tuy nhiên, quá trình này cũng có thể lấy BufferSource hoặc Blob. Ví dụ: bạn có thể dùng đường dẫn trực tiếp vào luồng dữ liệu đó:

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 của mình đề xuất tên hoặc vị trí tệp mặc định. Ví dụ: trình chỉnh sửa văn bản nên đề 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 thuộc tính suggestedName như một phần của các tuỳ chọn showSaveFilePicker.

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

Thư mục bắt đầu mặc định cũng tương tự như vậy. Nếu đang tạo một 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 bắt đầu mặc định bằng cách chuyển thuộc tính startIn vào các phương thức showSaveFilePicker, showDirectoryPicker() hoặc showOpenFilePicker như vậy.

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àn hình của người dùng, nếu có.
  • documents: Thư mục thường lưu trữ tài liệu do người dùng tạo.
  • downloads: Thư mục thường được lưu trữ các tệp đã tải xuống.
  • music: Thư mục thường lưu trữ các 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/phim.

Ngoài các thư mục hệ thống phổ biến, bạn cũng có thể truyền tệp hoặc ô điều khiển thư mục hiện có dưới dạng giá trị cho startIn. Khi đó, hộp thoại sẽ mở ra trong cùng một 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ó cá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 cũng như nhập hình ảnh. Theo mặc định, mỗi bộ chọn tệp sẽ mở ở vị trí được nhớ lần cuối. Bạn có thể tránh né việc 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ớ thư mục riêng được sử 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ữ trình xử lý tệp hoặc trình xử lý thư mục trong IndexedDB

Trình xử lý tệp và trình xử lý thư mục có thể chuyển đổi tuần tự, tức là bạn có thể lưu một tệp hoặc nút xử lý thư mục vào IndexedDB hoặc gọi postMessage() để gửi chúng giữa cùng một nguồn gốc cấp cao nhất.

Việc lưu tệp hoặc thư mục xử lý vào IndexedDB có nghĩa là bạn có thể lưu trữ trạng thái hoặc nhớ những tệp hoặc thư mục mà người dùng đang làm việc. Điều này giúp bạn có thể giữ danh sách các tệp đã mở hoặc chỉnh sửa gần đây, đề xuất mở lại tệp gần đây nhất khi ứng dụng được mở, khôi phục thư mục đang hoạt động trước đó và làm nhiều việc khác. Trong trình chỉnh sửa 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ở, giúp dễ dàng truy cập lại vào các tệp đó.

Ví dụ về mã bên dưới cho thấy cách lưu trữ và truy xuất một xử lý tệp và một xử lý 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 để 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);
  }
});

Xử lý và quyền đối với tệp hoặc thư mục được lưu trữ

Vì các quyền hiện khô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ấp quyền vào tệp hoặc thư mục bằng queryPermission() hay chưa. Nếu chưa, hãy gọi requestPermission() để (lại) yêu cầu. Thao tác này cũng tương tự như đối với trình xử lý tệp và 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 thấy một lời nhắc khi mở tệp và cấp quyền đọ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ả cá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, 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.

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

Chẳng hạn, nếu bạn cần truy cập vào từng tệp qua getFile() để lấy kích thước tệp riêng lẻ, đừng sử dụng await cho từng kết quả theo tuần tự, mà hãy xử lý tất cả các tệp song song, chẳng hạn như thông qua Promise.all().

const butDir = document.getElementById('butDirectory');
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 các 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 các tệp và thư mục bằng phương thức getFileHandle() hoặc getDirectoryHandle() tương ứng. Bằng cách truyền vào một đối tượng options tuỳ chọn với khoá là create và giá trị boolean là true hoặc false, bạn có thể xác định xem có nên tạo tệp hoặc thư mục mới nếu không có tệp hay không.

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

Xử lý đườ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, có thể bạn nên phân giải đường dẫn của mục được đề cập. Bạn có thể thực hiện việc này bằng phương thức resolve() được đặt tên hợp lý. Để phân giải, mục này có thể là phần tử 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ó 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 chứa bằng phương thức removeEntry(). Đối với các thư mục, bạn có thể tuỳ ý xoá thao tác đệ quy và bao gồm tất cả cá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 một tệp hoặc thư mục

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

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

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

Bạn có thể đổi tên hoặc di chuyển các tệp và thư mục đến vị trí mới bằng cách gọi move() trên giao diện FileSystemHandle. FileSystemHandle có giao diện con FileSystemFileHandleFileSystemDirectoryHandle. Phương thức move() nhận một hoặc hai tham số. Đầu tiên có thể là một chuỗi có tên mới hoặc FileSystemDirectoryHandle đến thư mục đích. Trong trường hợp sau, tham số thứ hai không bắt buộc là một chuỗi có tên mới, vì vậy, việc di chuyển và đổi tên có thể diễn ra 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');

Quy trình tích hợp 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 tệp được kéo và thả trên trang web. Trong khi thực hiện thao tác kéo và thả, các mục tệp và thư mục đã kéo tương ứng được liên kết với các mục nhập tệp và mục nhập thư mục. Phương thức DataTransferItem.getAsFileSystemHandle() sẽ trả về một lời hứa với một đối tượng FileSystemFileHandle nếu mục được kéo là một tệp và một lời hứa với đối tượng FileSystemDirectoryHandle nếu mục được kéo là một thư mục. Danh sách dưới đây minh hoạ điều này trong thực tế. 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ư của nguồn gốc

Hệ thống tệp riêng tư gốc là một điểm cuối lưu trữ, đúng như tên gọi, là một điểm cuối 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 ở một nơi nào đó, nhưng bạn không nên tập trung vào việc nội dung đó có thể dễ dàng truy cập. Tương tự, bạn không nên 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ể khiến cho 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ư theo nguồn gốc), trình duyệt có thể lưu trữ các "tệp" này trong 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, không nên mong đợi tìm thấy các tệp được tạo trùng khớp với nhau ở đâ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

  • 86
  • 86
  • 111
  • 15,2

Nguồn

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

Hệ thống tệp riêng tư gốc cho phép 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 cấp quyền ghi tại chỗ và quyền ghi độ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 vào tệp: createSyncAccessHandle() (dành cho các thao tác đọc và ghi đồng bộ). API này hiển thị trên FileSystemFileHandle, nhưng chỉ có trong Web Workers.

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

Vải polyfilling

Không thể thay thế hoàn toàn 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">.
  • Có thể mô phỏng phương thức showSaveFilePicker() bằng phần tử <a download="file_name">, mặc dù phương thức này kích hoạt hoạt động tải xuống 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 phần nào 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 File System Access API (API Truy cập hệ thống tệp) bất cứ khi nào có thể và sẽ quay lại sử dụng các lựa chọn tốt nhất tiếp theo sau đây trong mọi trường hợp khác.

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

Nhóm Chrome đã thiết kế và triển khai API Truy cập hệ thống tệp dựa trên các nguyên tắc cốt lõi được xác định trong Kiểm soát quyền truy cập vào các tính năng nền tảng web mạnh mẽ, 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ư sự thoải mái cho người dùng.

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

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

Khi mở một tệp, người dùng sẽ cấp quyền đọc tệp hoặc thư mục thông qua bộ chọn tệp. Bộ chọn tệp đang mở chỉ có thể hiển thị thông qua cử chỉ của người dùng khi được phân phát trong một bối cảnh bảo mật. Nếu đổi ý, 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">.

Bộ chọn tệp để lưu tệp vào ổ đĩa.
Một 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 một tệp mới, trình duyệt sẽ hiển thị bộ chọn tệp lưu để 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ể giới hạn khả năng lưu vào một số thư mục, chẳng hạn như các thư mục hệ điều hành chính như Windows, thư mục Thư viện macOS, v.v. 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 khi chưa được người dùng cho phép rõ rà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 tệp mà trước đó họ đã cấp quyền đọc, thì 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 các 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 hiển thị trước khi lưu tệp.
Lời nhắc sẽ xuất hiện 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 sẽ không thể lưu các thay đổi đối với tệp cục bộ. Ứng dụng sẽ 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 xuống" tệp, lưu dữ liệu vào đám mây, v.v.

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 quyền cho trang web để lưu vào một tệp cục bộ.

Sau khi người dùng cấp cho một ứng dụng web quyền lưu tệp cục bộ, trình duyệt sẽ hiển thị một biểu tượng trong thanh URL. Khi người dùng nhấp vào biểu tượng đó, một cửa sổ bật lên sẽ bật lên cho thấy 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ể dễ dàng thu hồi quyền truy cập đó nếu muốn.

Khả năng lưu trữ cố định quyền

Ứng dụng web có thể tiếp tục lưu các thay đổi vào 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 ứng dụng đã đóng. Sau khi bạn đóng một thẻ, trang web sẽ mất tất cả quyền truy cập. Lần tiếp theo khi sử dụng ứng dụng web, người dùng sẽ được nhắc lại quyền truy cập vào các tệp.

Ý kiến 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.

Cho chúng tôi biết về thiết kế của API

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

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

Bạn có phát hiện thấy lỗi khi triển khai Chrome không? Hay cách triển khai có khác với quy cách không?

  • Báo cáo lỗi tại https://new.crbug.com. Hãy nhớ cung cấp nhiều chi tiết nhất có thể, hướng dẫn đơn giản để tái tạo và đặt Thành phần thành Blink>Storage>FileSystem. Sự cố rất hữu ích trong việc chia sẻ các bản sao nhanh và dễ dàng.

Bạn định sử dụng API?

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

Các đường liên kết hữu ích

Xác nhận

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