קריאה וכתיבה של קבצים וספריות באמצעות ספריית הדפדפן-fs-access

דפדפנים יכולים לטפל בקבצים ובספריות מזה זמן רב. 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 מבוסס-EdgeHTML מדור קודם וב-Firefox.

שומר (במקום: מוריד) קבצים

כששומרים קובץ, בדרך כלל אפשר להוריד אותו רק באמצעות המאפיין <a download>. בהתאם ל-blob, אפשר להגדיר את המאפיין href של העוגן לכתובת URL מסוג blob:, שמקבלים מ-method 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();
};

הבעיה

חיסרון אדיר של גישת ההורדה הוא שאין דרך לבצע תהליך קלאסי של פתיחה ← עריכה ← שמירה, כלומר, אין דרך להחליף את הקובץ המקורי. במקום זאת, בכל פעם ששומרים, מתקבל עותק חדש של הקובץ המקורי בתיקיית ההורדות שמוגדרת כברירת מחדל במערכת ההפעלה.

ממשק ה-API של גישה למערכת קבצים

ממשק ה-API של גישה למערכת קבצים הופך את הפעולות, הפתיחה והשמירה, לפשוטות הרבה יותר. היא גם מאפשרת שמירה אמיתית, כלומר לא רק לבחור איפה לשמור קובץ, אלא גם להחליף קובץ קיים.

פתיחת קבצים

בעזרת File System Access API, פתיחת קובץ היא קריאה אחת לשיטה window.showOpenFilePicker(). הקריאה הזו מחזירה כינוי לקובץ, שממנו אפשר לקבל את File בפועל באמצעות שיטת 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 על ידי קריאה לשיטה write() של השידור, ולבסוף סוגרים את השידור על ידי קריאה לשיטה 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);
  }
};

חדש: דפדפן-fs-access

באותה מידה כמו File System Access API הוא עדיין לא זמין לכולם.

טבלת תמיכה בדפדפן ל-File System Access API. כל הדפדפנים מסומנים כ &#39;ללא תמיכה&#39; או &#39;מאחורי סימון&#39;.
טבלת תמיכה בדפדפן ל-File System Access API. (מקור)

לכן אני רואה את File System Access API כשיפור הדרגתי. לכן כדאי להשתמש בו כשהדפדפן תומך בכך, ואז להשתמש בגישה המסורתית, אבל לא להעניש את המשתמש באמצעות הורדות מיותרות של קוד JavaScript שאינו נתמך. הספרייה browser-fs-access היא התשובה שלי לאתגר הזה.

פילוסופיית העיצוב

מאחר ש-File System Access API עדיין צפוי להשתנות בעתיד, ה-browser-fs-access לא מבוסס על מודל כלומר, הספרייה היא לא polyfill, אלא ponyfill. אפשר לייבא (באופן סטטי או דינמי) באופן בלעדי את הפונקציונליות הדרושה כדי להקטין את האפליקציה ככל האפשר. השיטות הזמינות הן שקיבלו את השמות המתאימים: 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. לכן הוא מועמד מצוין לספריית ה-browser-fs-access.

לדוגמה, אני יכול להתחיל ציור ב-iPhone שלי, לשמור אותו (טכנית: להוריד אותו, מכיוון ש-Safari לא תומך ב-File System Access API) בתיקיית ההורדות ב-iPhone, לפתוח את הקובץ בשולחן העבודה (לאחר העברתי מהטלפון), לשנות את הקובץ ולהחליף אותו בשינויים שלי, או אפילו לשמור אותו כקובץ חדש.

ציור אקסטרה ב-iPhone.
התחלת שרטוט החרגה ב-iPhone שבו אין תמיכה ב-File System Access API, אבל אפשר לשמור קובץ (להוריד אותו) בתיקיית ההורדות.
שרטוט &#39;אקספלור&#39; שהשתנה ב-Chrome במחשב.
פתיחה ושינוי של שרטוט EXAlidraw במחשב שבו יש תמיכה ב-File System Access API, ולכן אפשר לגשת לקובץ דרך ה-API.
החלפת הקובץ המקורי בשינויים.
החלפת הקובץ המקורי עם השינויים בקובץ המקורי של exalidraw. בדפדפן מופיעה תיבת דו-שיח ששואלת אם זה בסדר.
שמירת השינויים בקובץ שרטוט חדש.
שמירת השינויים בקובץ החרגה חדש. הקובץ המקורי יישאר ללא שינוי.

דוגמת קוד בעולם האמיתי

בהמשך מוצגת דוגמה ממשית ל-browser-fs-access כפי שנעשה בו שימוש ב-Excalidraw. הקטע לקוח מתוך /src/data/json.ts. מעניין במיוחד הוא האופן שבו השיטה saveAsJSON() מעבירה כינוי של קובץ או null ל-method 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) {}), תוכלו להציג את הלחצן Save As (שמירה בשם) בנוסף ללחצן Save (שמירה). צילומי המסך הבאים מראים את ההבדל בין סרגל הכלים הראשי של האפליקציה הרספונסיבית ב-iPhone לבין סרגל הכלים הראשי של האפליקציה ב-Chrome. לתשומת ליבך, הלחצן שמירה בשם חסר ב-iPhone.

אפשר להסיר את סרגל הכלים של האפליקציה ב-iPhone בלחיצה על לחצן &#39;שמירה&#39; בלבד.
אפשר לבטל הסרה של סרגל הכלים של האפליקציה ב-iPhone באמצעות לחצן שמירה בלבד.
אפשר להסיר את סרגל הכלים של האפליקציות מ-Chrome בשולחן העבודה בעזרת הלחצן &#39;שמירה&#39; והלחצן &#39;שמירה בשם&#39;.
אפשר להסיר את סרגל הכלים של האפליקציות ב-Chrome בעזרת לחיצה על שמירה ולחצן שמירה בשם ממוקד.

מסקנות

מבחינה טכנית, העבודה עם קובצי מערכת עובדת בכל הדפדפנים המודרניים. בדפדפנים שתומכים ב-File System Access API, אפשר לשפר את החוויה באמצעות שמירה והחלפה (לא רק הורדה) של קבצים, ומתן אפשרות למשתמשים ליצור קבצים חדשים מכל מקום שירצו, וכל זאת תוך שמירה על פונקציונליות בדפדפנים שלא תומכים ב-File System Access API. תכונת browser-fs-access מאפשרת לכם להתמודד עם הדקויות של שיפור הדרגתי ולהפוך את הקוד שלכם לפשוט ככל האפשר, וכך להקל על חייכם.

אישורים

המאמר הזה נכתב על ידי Joe Medley ו-Kayce Basques. תודה לתורמי התוכן להסרת תוכן על עבודתם בפרויקט ועל בדיקת בקשות המשיכה שלי. תמונה ראשית (Hero) מאת איליה פבלוב בתוכנית UnFlood.