منذ وقت طويل، تمكّنت المتصفّحات من التعامل مع الملفات والأدلة. توفّر 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 فقط، بل يمكن استخدامه أيضًا في متصفّح EdgeHTML القديم و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();
};
المشكلة
من بين الجوانب السلبية الكبيرة لطريقة التنزيل أنّه لا يمكن تنفيذ الخطوات الكلاسيكية التالية: الفتح→التعديل→الحفظ، أي أنّه لا يمكن استبدال الملف الأصلي. بدلاً من ذلك، ستحصل على نسخة جديدة من الملف الأصلي في مجلد "عمليات التنزيل" التلقائي في نظام التشغيل عند "الحفظ".
واجهة برمجة التطبيقات File System Access API
تسهِّل واجهة برمجة التطبيقات File System Access 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);
}
};
تقديم browser-fs-access
على الرغم من أنّ واجهة برمجة التطبيقات File System Access API جيدة تمامًا، إلا أنّها غير متاحة على نطاق واسع بعد.
لهذا السبب، أرى أنّ File System Access API هي ميزة تحسين تدريجي. ولهذا السبب، أريد استخدامها عندما يكون المتصفّح متوافقًا معها، واستخدام النهج التقليدي في حال عدم توافقه، مع عدم تحميل المستخدم لأي رمز JavaScript غير متوافق. مكتبة browser-fs-access هي الحلّ الذي أقدّمه لهذا التحدي.
فلسفة التصميم
بما أنّه من المحتمل أن تتغيّر واجهة برمجة التطبيقات File System Access API في المستقبل،
لم يتم وضع واجهة برمجة التطبيقات browser-fs-access API على أساسها.
وهذا يعني أنّ المكتبة ليست polyfill،
بل هي ponyfill.
يمكنك استيراد أي وظائف تحتاجها حصريًا (سواءً بشكل ثابت أو ديناميكي) للحفاظ على حجم تطبيقك صغيرًا قدر الإمكان.
الطرق المتاحة هي
fileOpen()
و
directoryOpen()
و
fileSave()
.
في المكتبة، ترصد الميزة ما إذا كانت واجهة برمجة التطبيقات File System Access API متوافقة، ثم تستورد مسار الرمز البرمجي المقابل.
استخدام مكتبة browser-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. يتوفّر أيضًا رمز المصدر هناك. لأسباب تتعلق بالأمان، لا يُسمح للإطارات الفرعية من مصادر مختلفة بعرض أداة اختيار ملفات، وبالتالي، لا يمكن تضمين العرض الترويجي في هذه المقالة.
مكتبة browser-fs-access في الاستخدام الفعلي
في وقت فراغي، أساهم قليلاً في تطوير تطبيق متوافق مع الأجهزة الجوّالة (PWA) قابل للتثبيت يُسمى Excalidraw، وهو أداة لوح معلومات تتيح لك بسهولة رسم مخطّطات بيانية بأسلوب يشبه الرسم اليدوي. وهو متجاوب تمامًا ويعمل بشكل جيد على مجموعة من الأجهزة، بدءًا من الهواتف الجوّالة الصغيرة ووصولاً إلى أجهزة الكمبيوتر ذات الشاشات الكبيرة. وهذا يعني أنّه يجب التعامل مع الملفات على جميع الأنظمة الأساسية المختلفة سواء كانت متوافقة مع واجهة برمجة التطبيقات File System Access API أم لا. وهذا يجعله مرشحًا رائعًا لمكتبة browser-fs-access.
على سبيل المثال، يمكنني بدء رسم على هاتف iPhone، وحفظه (من الناحية الفنية: تنزيله، لأنّ Safari لا يتيح استخدام واجهة برمجة التطبيقات File System Access API) في مجلد "عمليات التنزيل" على هاتف iPhone، وفتح الملف على الكمبيوتر المكتبي (بعد نقله من هاتفي)، وتعديل الملف واستبداله بتغييراتي، أو حتى حفظه كملف جديد.
عيّنة تعليمات برمجية من واقع الحياة
في ما يلي مثال فعلي على استخدام واجهة برمجة التطبيقات browser-fs-access في Excalidraw.
تم اقتباس هذا المقتطف من
/src/data/json.ts
.
من المثير للاهتمام معرفة كيفية تمرير الطريقة saveAsJSON()
إما معرّف ملف أو null
إلى الطريقة
fileSave()
في browser-fs-access، ما يؤدي إلى استبدال الملف عند تقديم معرّف،
أو حفظه في ملف جديد في حال عدم تقديم معرّف.
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 على عملهم في المشروع ومراجعتهم لطلبات سحب المشارِكين. الصورة الرئيسية من أعمال إيليا بافلوف على Unsplash