דפדפנים יכולים לטפל בקבצים ובספריות במשך תקופה ארוכה. ב-File API מספק תכונות לייצוג אובייקטים של קבצים באפליקציות אינטרנט, וגם בחירה בהם באופן פרוגרמטי וגישה לנתונים שלהם. אבל ברגע שתסתכלו מקרוב, כל הנוצץ הוא לא זהב.
הדרך המסורתית לטפל בקבצים
פתיחת קבצים
כמפתח, אתה יכול לפתוח ולקרוא קבצים דרך
<input type="file">
לרכיב מסוים.
בצורתו הפשוטה ביותר, פתיחת קובץ יכולה להיראות כמו דוגמת הקוד שבהמשך.
האובייקט input
נותן FileList
,
במקרה הזה, הוא מורכב רק ממחרוזת אחת
File
.
File
הוא סוג ספציפי של Blob
,
ואפשר להשתמש בו בכל הקשר שבו Blob יכול.
const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
פתיחת ספריות
כדי לפתוח תיקיות (או ספריות), אפשר להגדיר
<input webkitdirectory>
.
מלבד זאת, כל שאר הפעולות פועלות בדיוק כמו שצוין למעלה.
למרות השם עם קידומת הספק,
אפשר להשתמש ב-webkitdirectory
לא רק בדפדפני Chromium ו-WebKit, אלא גם ב-Edge מבוסס-HTML מדור קודם וגם ב-Firefox.
מתבצעת שמירה של קבצים (במקום הורדה)
כדי לשמור קובץ, בדרך כלל קיימת הגבלה על הורדה של קובץ,
שעובד בזכות
<a download>
.
בהינתן אובייקט מסוג Blob, אפשר להגדיר את מאפיין העוגן href
לכתובת URL מסוג blob:
שניתן לקבל
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();
};
הבעיה
החיסרון המשמעותי של גישת ההורדה הוא שאין דרך ליצור גרסה קלאסית תהליך פתוח ←edit←שמירה, כלומר אין דרך להחליף את הקובץ המקורי. במקום זאת, תקבלו עותק חדש של הקובץ המקורי. בתיקיית ההורדות שמוגדרת כברירת מחדל במערכת ההפעלה בכל פעם ש"שומרים".
File System Access API
File System Access API מאפשר לבצע את שתי הפעולות, את הפתיחה והשמירה, לפשוטות יותר. היא גם מפעילה שמירה אמיתית, כלומר לא ניתן רק לבחור איפה לשמור קובץ, אבל גם להחליף קובץ קיים.
פתיחת קבצים
באמצעות File System Access API,
פתיחת קובץ כרוכה בקריאה אחת ל-method window.showOpenFilePicker()
.
הקריאה הזו מחזירה כינוי לקובץ, שממנו אפשר לקבל את ה-File
בפועל באמצעות ה-method 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);
}
};
פתיחת ספריות
פתיחת ספרייה באמצעות הטלפון
window.showDirectoryPicker()
שמאפשר לבחור ספריות בתיבת הדו-שיח של הקובץ.
מתבצעת שמירה של הקבצים
באותו אופן אפשר לשמור קבצים.
מתוך כינוי של קובץ, אפשר ליצור שידור שניתן לכתיבה דרך createWritable()
,
ואז כותבים את נתוני ה-blob על ידי קריאה ל-method של write()
של השידור,
ולבסוף, אפשר לסגור את הסטרימינג על ידי קריאה ל-method 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);
}
};
חדש: browser-fs-access
בדיוק כמו ש-File System Access API הוא היא עדיין לא זמינה לכולם.
לכן, File System Access API נראה לי כשיפור מתקדם. לכן, אני רוצה להשתמש בו כשהדפדפן תומך בכך, ותשתמשו בגישה המסורתית, אם לא. כל זאת מבלי להעניש את המשתמש על הורדות מיותרות של קוד JavaScript שאינו נתמך. בדפדפן browser-fs-access היא התשובה לאתגר הזה.
פילוסופיית העיצוב
מכיוון שה-File System Access API עדיין צפוי להשתנות בעתיד,
מודל ה-API של browser-fs-access לא מחושב אחריו.
כלומר, הספרייה היא לא polyfill.
אלא פונקציה.
אתם יכולים לייבא (באופן סטטי או דינמי) באופן בלעדי כל פונקציונליות שאתם צריכים כדי שהאפליקציה תהיה קטנה ככל האפשר.
השיטות הזמינות הן
fileOpen()
directoryOpen()
, וגם
fileSave()
.
באופן פנימי, תכונת הספרייה מזהה אם File System Access API נתמך,
ואז מייבאת את נתיב הקוד המתאים.
באמצעות ספריית הדפדפן ו-fs-access
שלוש השיטות אינטואיטיביות לשימוש.
אפשר לציין את הקובץ mimeTypes
או את הקובץ extensions
שאושרו על ידי האפליקציה, ולהגדיר דגל multiple
לאפשר או לא לאפשר בחירה של מספר קבצים או ספריות.
פרטים מלאים זמינים
למסמכי התיעוד שלbrowser-fs-access API.
דוגמת הקוד הבאה מראה איך לפתוח ולשמור קובצי תמונה.
// 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',
});
})();
הדגמה (דמו)
אפשר לראות את הקוד שלמעלה בפעולה בהדגמה ב-Glitch. קוד המקור שלו זמין גם שם. מסיבות אבטחה, מסגרות משנה של מקורות שונים אינן מורשות להציג בורר קבצים, לא ניתן להטמיע את ההדגמה במאמר הזה.
ספריית החיפוש של הדפדפן ו-Fs-access בטבע
בזמני הפנוי, אני תורם/ת בקצת PWA להתקנה שנקרא Excalidraw, כלי לוח אינטראקטיבי שמאפשר לכם לשרטט בקלות דיאגרמות בתחושה של ציור ביד. היא מגיבה באופן מלא ופועלת היטב במגוון מכשירים, מטלפונים ניידים קטנים ועד למחשבים עם מסכים גדולים. כלומר, הוא צריך לטפל בקבצים בכל הפלטפורמות השונות האם הם תומכים ב-File System Access API. לכן הוא מתאים מאוד לשימוש בספריית הדפדפן מסוג fs-access.
לדוגמה, אני יכול להתחיל לצייר באייפון, שומרים אותה (טכנית: מורידים אותה, כי Safari לא תומך ב-File System Access API) לתיקיית ההורדות של iPhone, פתח את הקובץ בשולחן העבודה (לאחר העברתו מהטלפון), ולשנות את הקובץ, ולהחליף אותו בשינויים שלי, או אפילו לשמור אותו כקובץ חדש.
דוגמת קוד בעולם האמיתי
למטה מוצגת דוגמה ממשית לגישת דפדפן-fs-כפי שנעשה בה שימוש ב-Excalidraw.
הקטע הזה נלקח מתוך
/src/data/json.ts
מעניין במיוחד הוא איך השיטה saveAsJSON()
מעבירה כינוי לקובץ או null
ל-'browser-fs-access'
שיטה fileSave()
, שגורמת להחלפה להופיע כאשר ניתן כינוי,
או לשמור אותו בקובץ חדש.
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);
};
שיקולים בקשר לממשק המשתמש
בין אם ב-EXcalidraw או באפליקציה שלכם,
ממשק המשתמש צריך להתאים את עצמו למצב התמיכה של הדפדפן.
אם File System Access API נתמך (if ('showOpenFilePicker' in window) {}
)
אפשר להציג לחצן שמירה בשם בנוסף ללחצן שמירה.
בצילומי המסך שבהמשך רואים את ההבדל בין סרגל הכלים הראשי של האפליקציה הרספונסיבית של Excalidraw ב-iPhone לבין ב-Chrome במחשב.
שימו לב איך הלחצן שמירה בשם ב-iPhone לא מופיע.
מסקנות
מבחינה טכנית, העבודה עם קובצי מערכת עובדת בכל הדפדפנים המתקדמים. בדפדפנים שתומכים ב-File System Access API, אפשר לשפר את החוויה באמצעות מתן הרשאה לצורך שמירה והחלפה אמיתית (לא רק הורדה) של קבצים, היא מאפשרת למשתמשים ליצור קבצים חדשים מכל מקום, כל עוד הם ימשיכו לפעול בדפדפנים שאינם תומכים ב-File System Access API. browser-fs-access מקל על החיים באמצעות התמודדות עם הדקויות של שיפור הדרגתי והפיכת הקוד לפשוט ככל האפשר.
אישורים
המאמר הזה נבדק על ידי ג'ו מדלי קייס בסקית. תודה למומחים של Excalidraw על העבודה שלהם על הפרויקט ועל סקירת בקשות המשיכה שלי. תמונה ראשית (Hero) מאת איליה פבלוב ב-Unspark.