Các 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 trình bày đối tượng tệp trong ứng dụng web, cũng như chọn chúng và truy cập dữ liệu của chúng theo phương thức lập trình. Tuy nhiên, khi bạn nhìn gần hơn thì tất cả những thứ lấp lánh đó không phải là vàng.
Cách truyền thống để xử lý tệp
Mở tệp
Là nhà phát triển, bạn có thể mở và đọc tệp qua
<input type="file">
.
Ở dạng đơn giản nhất, việc mở một tệp có thể có dạng như mã mẫu dưới đây.
Đối tượng input
cung cấp cho bạn một FileList
,
trong trường hợp bên dưới chỉ bao gồm một
File
.
File
là một loại cụ thể của Blob
,
và có thể dùng trong bất kỳ ngữ cảnh nào Blob có thể làm.
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
<input webkitdirectory>
.
Ngoài ra, mọi tính năng khác đều hoạt động tương tự như trên.
Mặc dù có tiền tố tên nhà cung cấp,
webkitdirectory
không chỉ dùng được trong các trình duyệt Chromium và WebKit, mà còn dùng được trong Edge dựa trên EdgeHTML cũ cũng như trong Firefox.
Lưu (thay vì: tải xuống) tệp
Để lưu một tệp, thông thường, bạn bị giới hạn tải xuống một tệp,
Quảng cáo này hoạt động nhờ vào
<a download>
.
Với một Blob, bạn có thể đặt thuộc tính href
của liên kết thành một URL blob:
mà bạn có thể lấy từ
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 đề
Nhược điểm lớn của phương pháp tải xuống là không có cách nào để tạo tệp open→chỉnh sửa→ lưu luồng xảy ra, tức là không có cách nào để ghi đè tệp gốc. Thay vào đó, bạn sẽ tạo ra 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 mỗi khi bạn "lưu".
API Truy cập hệ thống tệp
API Truy cập hệ thống tệp giúp cả thao tác, mở và lưu đơn giản hơn rất nhiều. Thao tác này cũng bật tính năng lưu thực, tức là bạn không chỉ có thể chọn vị trí lưu tệp, nhưng cũng ghi đè lên tệp hiện có.
Mở tệp
Với API Truy cập hệ thống tệp,
mở tệp là vấn đề một lệnh gọi đến phương thức window.showOpenFilePicker()
.
Lệnh gọi này trả về một tên người dùng tệp mà từ đó bạn có thể nhận 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()
giúp bạn có thể chọn các thư mục trong hộp thoại tệp.
Đang lưu tệp
Cách lưu tệp cũng tương tự.
Từ tên người dùng tệp, bạn tạo một luồng có thể ghi thông qua createWritable()
,
sau đó bạn phải 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 là đóng luồng bằng cách gọi phương thức close()
.
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 browser-fs-access
Hoàn toàn tốt như API Truy cập hệ thống tệp, nên chưa được cung cấp rộng rãi.
Đây là lý do tại sao tôi thấy API Truy cập hệ thống tệp là nâng cao tăng dần. Do đó, tôi muốn sử dụng công cụ này khi trình duyệt hỗ trợ, và nếu không thì dùng phương pháp truyền thống; và không bao giờ phạt người dùng bằng việc tải xuống mã JavaScript không được hỗ trợ một cách không cần thiết. Tham số browser-fs-access là câu trả lời của tôi cho thách thức này.
Triết lý thiết kế
Vì API Truy cập hệ thống tệp vẫn có thể thay đổi trong tương lai,
browser-fs-access API không được mô hình hóa theo nó.
Tức là thư viện này không phải là một polyfill,
mà là ponyfill.
Bạn có thể (theo cách tĩnh hoặc động) nhập độc quyền mọi chức năng mà bạn cần để giữ cho ứng dụng của mình nhỏ nhất có thể.
Các phương thức có sẵn được đặt tên phù hợp
fileOpen()
!
directoryOpen()
và
fileSave()
.
Trong nội bộ, tính năng của thư viện sẽ phát hiện xem API Truy cập hệ thống tệp có được hỗ trợ hay không,
rồi nhập đường dẫn mã tương ứng.
Sử dụng thư viện browser-fs-access
Đây là 3 phương thức 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 toàn bộ thông tin chi tiết, hãy xem
tài liệu về API browser-fs-access.
Mã mẫu dưới đây cho biết 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ã ở trên trong thực tế trong bản minh hoạ trên Glitch. Mã nguồn của thư viện này cũng có sẵn ở đó. Vì lý do bảo mật, các khung phụ trên nhiều nguồn gốc không được phép hiển thị bộ chọn tệp, không thể nhúng bản minh hoạ vào bài viết này.
Thư viện browser-fs-access trong môi trường
Trong thời gian rảnh, tôi đóng góp một chút công sức cho PWA có thể cài đặt có tên là Excalidraw, một công cụ bảng trắng giúp các em dễ dàng phác thảo biểu đồ bằng tay. API này hoàn toàn phản hồi và hoạt động tốt trên nhiều loại 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 nghĩa là giải pháp này cần xử lý các tệp trên mọi nền tảng chúng có hỗ trợ API Truy cập hệ thống tệp hay không. Điều này khiến thư viện này trở thành một ứng viên tuyệt vời cho thư viện browser-fs-access.
Ví dụ: tôi có thể bắt đầu vẽ trên iPhone của mình, lưu tệp (về mặt kỹ thuật: tải xuống, vì Safari không hỗ trợ API Truy cập hệ thống tệp) vào thư mục Downloads (Tệp đã tải xuống) trên iPhone, mở tệp trên máy tính của tôi (sau khi chuyển tệp từ điện thoại của tôi), sửa đổi tệp và ghi đè bằng các thay đổi của tôi hoặc thậm chí lưu tệp dưới dạng tệp mới.
Mã mẫu thực tế
Dưới đây là một ví dụ thực tế về browser-fs-access khi được sử dụng trong Excalidraw.
Phần trích dẫn này được lấy từ
/src/data/json.ts
.
Điều đặc biệt là cách phương thức saveAsJSON()
truyền dữ liệu xử lý tệp hoặc null
đến browser-fs-access'
fileSave()
giúp phương thức này ghi đè khi bạn chỉ định một tên người dùng,
hoặc để lưu vào tệp mới nếu khô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ểm cần lưu ý 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 phải thích ứng với tình huố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ể hiển thị nút Save As (Lưu dưới dạng) cùng với nút Save (Lưu).
Các ảnh chụp màn hình dưới đây cho thấy sự khác biệt giữa thanh công cụ thích ứng của ứng dụng chính của Excalidraw trên iPhone và trên Chrome dành cho máy tính.
Lưu ý rằng trên iPhone thiếu nút Save As (Lưu dưới dạng).
Kết luận
Về mặt kỹ thuật, làm việc với các tệp hệ thống hoạt động trên tất cả các trình duyệt hiện đại. Trên các trình duyệt hỗ trợ API Truy cập hệ thống tệp, 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) tệp và bằng cách cho phép người dùng tạo tệp mới ở bất cứ nơi nào họ muốn, tất cả trong khi vẫn hoạt động trên các trình duyệt không hỗ trợ API Truy cập Hệ thống Tệp. browser-fs-access giúp cuộc sống của bạn dễ dàng hơn bằng cách xử lý các tính chất tinh vi của cải tiến tăng dần và làm cho mã của bạn trở nên đơn giản nhất có thể.
Xác nhận
Bài viết này do Joe Medley xem xét và Kayce Basques. Nhờ những người đóng góp cho Excalidraw cho dự án của họ và để xem xét Yêu cầu kéo của tôi. Hình ảnh chính của Ilya Pavlov trên Unsplash.