Ngày xuất bản: 27 tháng 7 năm 2020
Trình duyệt đã có thể xử lý các tệp và thư mục trong một thời gian dài. File API cung cấp các tính năng để biểu thị các đối tượng tệp trong các ứng dụng web, cũng như chọn các đối tượng đó theo phương pháp lập trình và truy cập vào dữ liệu của chúng. Tuy nhiên, khi bạn nhìn kỹ hơn, bạn sẽ thấy không phải thứ gì lấp lánh cũng là vàng.
Cách xử lý tệp theo cách truyền thống
Mở tệp
Bạn có thể mở và đọc tệp bằng phần tử <input type="file">.
Ở dạng đơn giản nhất, việc mở một tệp có thể trông giống như mẫu mã.
Đối tượng input cung cấp cho bạn một FileList. Trong trường hợp ví dụ của chúng ta, đối tượng này chỉ bao gồm một File.
File là một loại Blob cụ thể và có thể được dùng trong mọi ngữ cảnh mà Blob có thể.
const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
Mở thư mục
Để mở thư mục (hoặc thư mục), bạn có thể đặt thuộc tính <input webkitdirectory>.
Ngoài ra, mọi thứ khác đều hoạt động giống như trên.
Mặc dù có tên có tiền tố của nhà cung cấp, webkitdirectory không chỉ dùng được trong các trình duyệt Chromium và WebKit, mà còn trong Edge dựa trên EdgeHTML cũ cũng như trong Firefox.
Lưu và tải tệp xuống
Để lưu một tệp, theo truyền thống, bạn chỉ có thể tải một tệp xuống. Điều này có thể thực hiện được nhờ thuộc tính <a download>.
Với một Blob, bạn có thể đặt thuộc tính href của neo thành một URL blob: mà bạn có thể lấy từ phương thức URL.createObjectURL().
const saveFile = async (blob) => {
const a = document.createElement('a');
a.download = 'my-file.txt';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', (e) => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
Vấn đề
Một nhược điểm lớn của phương pháp tải xuống là không có cách nào để thực hiện quy trình mở→chỉnh sửa→lưu theo cách truyền thống, tức là không có cách nào để ghi đè tệp gốc. Thay vào đó, bạn sẽ có một bản sao mới của tệp gốc trong thư mục Tải xuống mặc định của hệ điều hành bất cứ khi nào bạn "lưu".
File System Access API
File System Access API giúp cả hai thao tác mở và lưu trở nên đơn giản hơn nhiều. Tính năng này cũng cho phép tiết kiệm thực sự. Điều này có nghĩa là bạn có thể chọn nơi lưu tệp và ghi đè một tệp hiện có.
Mở tệp
Với File System Access API, việc mở một tệp chỉ cần một lệnh gọi đến phương thức window.showOpenFilePicker().
Lệnh gọi này trả về một mã nhận dạng tệp. Từ mã nhận dạng này, bạn có thể nhận được File thực tế thông qua phương thức getFile().
const openFile = async () => {
try {
// Always returns an array.
const [handle] = await window.showOpenFilePicker();
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
Mở thư mục
Mở một thư mục bằng cách gọi window.showDirectoryPicker() để có thể chọn thư mục trong hộp thoại tệp.
Lưu tệp
Việc lưu tệp cũng tương tự như vậy.
Từ một mã nhận dạng tệp, bạn tạo một luồng có thể ghi thông qua createWritable(), sau đó bạn ghi dữ liệu Blob bằng cách gọi phương thức write() của luồng và cuối cùng bạn đóng luồng bằng cách gọi phương thức close() của luồng.
const saveFile = async (blob) => {
try {
const handle = await window.showSaveFilePicker({
types: [{
accept: {
// Omitted
},
}],
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
} catch (err) {
console.error(err.name, err.message);
}
};
Giới thiệu về browser-fs-access
Mặc dù File System Access API hoạt động hoàn toàn bình thường, nhưng chưa được cung cấp rộng rãi.
Đó là lý do tôi xem File System Access API là một nâng cao tăng dần. Do đó, tôi muốn sử dụng tính năng này khi trình duyệt hỗ trợ và sử dụng phương pháp truyền thống nếu không; tất cả trong khi không bao giờ phạt người dùng bằng cách tải xuống không cần thiết mã JavaScript không được hỗ trợ. Thư viện browser-fs-access là câu trả lời của tôi cho thử thách này.
Triết lý thiết kế
Vì File System Access API vẫn có khả năng thay đổi trong tương lai, nên browser-fs-access API không được mô hình hoá theo API này.
Tức là thư viện này không phải là một polyfill mà là một ponyfill.
Bạn có thể nhập (tĩnh hoặc động) riêng chức năng mà bạn cần để giữ cho ứng dụng của mình có kích thước nhỏ nhất có thể.
Các phương thức có sẵn là fileOpen(), directoryOpen() và fileSave().
Về nội bộ, thư viện sẽ phát hiện tính năng nếu API Truy cập hệ thống tệp được hỗ trợ, sau đó nhập đường dẫn mã tương ứng.
Sử dụng thư viện
Ba phương thức này đều dễ sử dụng.
Bạn có thể chỉ định mimeTypes hoặc tệp extensions được chấp nhận của ứng dụng và đặt cờ multiple để cho phép hoặc không cho phép chọn nhiều tệp hoặc thư mục.
Để biết thông tin chi tiết đầy đủ, hãy xem tài liệu về API browser-fs-access.
Mã mẫu này cho thấy cách bạn có thể mở và lưu tệp hình ảnh.
// The imported methods will use the File
// System Access API or a fallback implementation.
import {
fileOpen,
directoryOpen,
fileSave,
} from 'https://unpkg.com/browser-fs-access';
(async () => {
// Open an image file.
const blob = await fileOpen({
mimeTypes: ['image/*'],
});
// Open multiple image files.
const blobs = await fileOpen({
mimeTypes: ['image/*'],
multiple: true,
});
// Open all files in a directory,
// recursively including subdirectories.
const blobsInDirectory = await directoryOpen({
recursive: true
});
// Save a file.
await fileSave(blob, {
fileName: 'Untitled.png',
});
})();
Bản minh hoạ
Bạn có thể xem mã này hoạt động trong một bản minh hoạ trên GitHub. Mã nguồn của hệ điều hành này cũng có sẵn ở đó.
Thư viện browser-fs-access trong tự nhiên
Vào thời gian rảnh, tôi đóng góp một chút cho một PWA có thể cài đặt có tên là Excalidraw, một công cụ bảng trắng cho phép bạn phác thảo sơ đồ theo cách vẽ tay. Thư viện này hoàn toàn thích ứng và hoạt động tốt trên nhiều thiết bị, từ điện thoại di động nhỏ đến máy tính có màn hình lớn. Điều này có nghĩa là ứng dụng cần xử lý các tệp trên tất cả các nền tảng, bất kể nền tảng đó có hỗ trợ File System Access API hay không. Điều này khiến nó trở thành một lựa chọn lý tưởng cho thư viện browser-fs-access.
Ví dụ: tôi có thể bắt đầu vẽ trên iPhone, lưu bản vẽ (về mặt kỹ thuật: tải bản vẽ xuống, vì Safari không hỗ trợ File System Access API) vào thư mục Tải xuống trên iPhone, mở tệp trên máy tính (sau khi chuyển tệp từ điện thoại), sửa đổi tệp và ghi đè tệp bằng nội dung thay đổi của mình, hoặc thậm chí lưu tệp dưới dạng một tệp mới.
Đoạn mã mẫu trong thực tế
Dưới đây là một ví dụ thực tế về browser-fs-access được dùng trong Excalidraw.
Đoạn trích này được lấy từ /src/data/json.ts.
Điều đặc biệt thú vị là cách phương thức saveAsJSON() truyền một mã nhận dạng tệp hoặc null đến phương thức fileSave() của browser-fs-access, khiến phương thức này ghi đè khi có mã nhận dạng hoặc lưu vào một tệp mới nếu không có mã nhận dạng.
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
fileHandle: any,
) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
type: "application/json",
});
const name = `${appState.name}.excalidraw`;
(window as any).handle = await fileSave(
blob,
{
fileName: name,
description: "Excalidraw file",
extensions: ["excalidraw"],
},
fileHandle || null,
);
};
export const loadFromJSON = async () => {
const blob = await fileOpen({
description: "Excalidraw files",
extensions: ["json", "excalidraw"],
mimeTypes: ["application/json"],
});
return loadFromBlob(blob);
};
Những điều cần cân nhắc về giao diện người dùng
Cho dù trong Excalidraw hay ứng dụng của bạn, giao diện người dùng đều phải thích ứng với khả năng hỗ trợ của trình duyệt.
Nếu API Truy cập hệ thống tệp được hỗ trợ (if ('showOpenFilePicker' in window) {}), bạn có thể cho thấy nút Lưu dưới dạng ngoài nút Lưu.
Ảnh chụp màn hình bên dưới cho thấy sự khác biệt giữa thanh công cụ chính thích ứng của ứng dụng Excalidraw trên iPhone và trên Chrome dành cho máy tính.
Lưu ý rằng nút Lưu dưới dạng không xuất hiện trên iPhone.
Kết luận
Về mặt kỹ thuật, việc sử dụng các tệp hệ thống hoạt động trên mọi trình duyệt hiện đại. Trên những trình duyệt hỗ trợ File System Access API, bạn có thể cải thiện trải nghiệm bằng cách cho phép lưu và ghi đè (không chỉ tải xuống) các tệp thực sự, đồng thời cho phép người dùng tạo tệp mới ở bất cứ nơi nào họ muốn, trong khi vẫn duy trì chức năng trên những trình duyệt không hỗ trợ File System Access API. browser-fs-access giúp cuộc sống của bạn dễ dàng hơn bằng cách xử lý những điểm tinh tế của tính năng cải tiến tăng dần và giúp mã của bạn đơn giản nhất có thể.
Lời cảm ơn
Nội dung này được Joe Medley và Kayce Basques xem xét. Cảm ơn những người đóng góp cho Excalidraw vì công sức của họ trong dự án này và vì đã xem xét các Yêu cầu kéo của tôi.