هياكل المكتبات في TypeScript

من موسوعة حسوب

مقدمة

طريقة هيكلة ملفّ التصريحات الخاص بك تعتمد عمومًا على كيفيّة اعتماد المستخدمين على المكتبة. هناك عدّة طرق يُمكن بها توفير المكتبة ليستخدمها الآخرون في JavaScript. وستحتاج إلى كتابة ملفّ تصريحاتٍ ملائم حسب هيكل المكتبة التي ترغب بكتابة ملفّ تصريحات لها. يغطي هذا الدليل كيفيّة التعرّف على أنماط المكتبات الشائعة، وكيفيّة كتابة ملف تصريحاتٍ مناسب لنمط المكتبة.

يوجد لكل نمط من أنماط هيكلة المكتبات الشائعة ملفٌّ ملائم له في قسم القوالب. يمكنك البدء بهذه القوالب لكتابة ملف التصريحات بشكل أسرع.

التعرّف على أنواع المكتبات

سنلقي أولًا نظرةً على أنواع المكتبات التي يمكن تمثيلها باستخدام ملفّات تصريحات TypeScript. سنستعرض بشكل وجيز كيف تُستخدَم كل مكتبة، وكيف تُكتَب، وسنعرض بعض الأمثلة لمكتبات JavaScript مشهورة.

التعرّف على هيكل مكتبةٍ أوّلُ خطوةٍ لكتابة ملفّ تصريحاتٍ لها. سنعطيك بعض النصائح التي ستساعدك على التعرف على هيكل المكتبة اعتمادًا على كلّ من طريقة استخدام المكتبة من طرف مستخدميها وعن طريق شيفرتها المصدريّة. ويمكن لأحد هاتين الطريقتين أن تكون أسهل من الأخرى حسب توثيق المكتبة وتنظيمها. وننصح بالاعتماد على ما ترتاح إليه أكثر.

المكتبات العامّة (Global Libraries)

المكتبة العامّة هي كل مكتبةٍ يُمكن الوصول إليها من المجال العام (دون اللجوء إلى أي شكل من أشكال الاستيراد). توفّر العديد من المكتبات ببساطة متغيّرًا عامّا واحدًا أو أكثر. على سبيل المثال، إن كنت تستخدم مكتبة jQuery، فتستطيع استخدام المتغيّر ‎$‎ عبر الإشارة إليه ببساطة:

$(() => { console.log('hello!'); } );

عادةً ما ستجد في التوثيق إرشادًا لكيفيّة استخدام مكتبةٍ عامّة عبر استخدام وسم ‎<script>‎ في لغة HTML كالتالي:

<script src="http://a.great.cdn.for/someLib.js"></script>

معظم المكتبات القابلة للوصول إليها من المجال العام حاليًّا مكتوبة في شكل مكتبات UMD (انظر أدناه). من الصّعب التفريق بين توثيق مكتبات UMD وتوثيق المكتبات العامّة. تأكّد أولًا من أنّ المكتبة ليست مكتبة UMD قبل محاولة كتابة ملفّ تصريحاتٍ عامّ.

التعرّف على المكتبات العامّة من شيفرتها

عادةً ما تكون شيفرة المكتبات العامّة في غاية البساطة. يمكن لمكتبة عامّة بسيطة أن تشبه ما يلي:

function createGreeting(s) {
    return "Hello, " + s;
}

أو ما يلي:

window.createGreeting = function(s) {
    return "Hello, " + s;
}

عند النظر إلى شيفرة مكتبة عامّة عادةً ما ستلاحظ التالي:

  • جمل ‎var‎ أو تصريحات ‎function‎ على المستوى الأعلى (Top-level).
  • تعيين واحد أو أكثر إلى ‎window.someName‎.
  • افتراض أنّ كائنات DOM أوليّة مثل ‎document‎ أو ‎window‎ موجودة مسبقًا.

ولن تلاحظ ما يلي:

  • التحقق من وجود محملات الوحدات (module loaders) أو استخدامها مثل ‎require‎ أو ‎define‎.
  • استيرادات CommonJS وNode.js ذات الشكل ‎var fs = require("fs");‎.
  • استدعاءات ‎define(...)‎.
  • توثيقٌ يشرح كيفيّة استيراد المكتبة باستخدام ‎require‎ أو ‎import‎.

أمثلة على مكتبات عامّة

لأنّ تحويل مكتبة عامّة إلى مكتبة UMD أمر سهل، فقد انتقلت معظم المكتبات المشهورة إلى صيغة UMD، وقد أصبحت المكتبات العامّة قليلة جدًّا. لكن المكتبات الصغيرة والتي تتطلّب تقنيّة DOM (أو تلك التي لا تحتاج إلى أية اعتماديّات) قد تبقى مكتبات عامّة.

قالب المكتبات العامّة

يعرِّف ملفّ القالب ‎global.d.ts مثالًا على مكتبةٍ باسم ‎myLib‎. لكن اقرأ أولًا قسم تجنّب صراعات الأسماء أدناه.

المكتبات الوحدية (Modular Libraries)

بعض المكتبات تعمل فقط في بيئة محمّل وحدات (module loader environment). على سبيل المثال، لأنّ مكتبة ‎express‎ تعمل فقط على منصّة Node.js فيجب تحميلها باستخدام الدالّة ‎require‎ الخاصّة بنظام CommonJS.

لدى كلّ من نسخة ECMAScript 2015 (المعروفة كذلك بالأسماء ES2015 وECMAScript 6 وES6)، وCommonJS، وRequireJS طرقٌ متشابهة لاستيراد الوحدات. في CommonJS (منصّة Node.js) على سبيل المثال، يمكن استيراد وحدةٍ كالتالي:

var fs = require("fs");

في TypeScript أو ES6، يمكن استخدام الكلمة المفتاحيّة ‎import‎ لنفس الغرض:

import fs = require("fs");

عادةً ما سترى أحد الأسطر التاليّة في توثيق المكتبات الوحديّة:

var someLib = require('someLib');

أو:

define(..., ['someLib'], function(someLib) {

});

قد ترى هذه الأمثلة في توثيق وحدة UMD كذلك كما هي الحال مع الوحدات العامّة، لذا تحقّق من شيفرة المكتبة أو توثيقها.

التعرّف على مكتبة وحدية من شيفرتها

أغلب الظن أنّك ستجد على الأقل بعض النقاط التاليّة في المكتبات الوحديّة:

  • استدعاءاتُ ‎require‎ أو ‎define‎ دون جمل شرطيّة.
  • تصريحاتٌ مثل ‎import * as a from 'b';‎ أو ‎export c;‎.
  • تعييناتٌ إلى ‎exports‎ أو ‎module.exports‎.

ومن النادر أن تجد ما يلي:

  • تعييناتٌ إلى ‎window‎ أو ‎global‎.

أمثلة على مكتبات وحدية

العديد من مكتبات Node.js المشهورة مكتباتٌ وحديّة مثل express، وgulp، وrequest.

وحدات UMD

تجمع وحدات UMD بين الشكلين السابقين، إذ تكون قابلة للاستخدام على شكل وحدة (عبر استيرادها)، أو يُمكن كذلك استخدامها كمكتبة عامّة (عند استخدامها في بيئة دون محمّل وحدات). العديد من المكتبات المشهورة مثل Moment.js مكتوبة بهذه الطريقة. على سبيل المثال، قد تكتب ما يلي في Node.js أو عند استخدام RequireJS:

import moment = require("moment");
console.log(moment.format());

أمّا في بيئة متصفّحٍ فقد تكتب ما يلي:

console.log(moment.format());

التعرّف على مكتبة UMD

تتحقّق وحدات UMD من وجود بيئة محمّل وحدات. وهذا نمط يسهل التعرّف عليه، ويبدو مشابهًا لما يلي:

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["libName"], factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory(require("libName"));
    } else {
        root.returnExports = factory(root.libName);
    }
}(this, function (b) {

إذا وجدت اختبارات ‎typeof define‎، أو ‎typeof window‎، أو ‎typeof module‎ في شيفرة مكتبةٍ ما، خاصّة في أعلى الملفّ، فأغلب الظن أنّها مكتبة UMD.

سيحتوي توثيق مكتبات UMD كذلك شرحًا لكيفيّة "استخدام المكتبة في Node.js" بمثالٍ يعتمد على ‎require‎، إضافةً إلى قسمٍ يشرح كيفيّة "استخدام المكتبة في المتصفّح" بالوسم ‎<script>‎ لتحميل السكربت.

أمثلة لمكتبات UMD

أصبحت معظم المكتبات المشهورة متوفرة على شكل حزم UMD. والأمثلة تشمل مكتبة jQuery، وMoment.js، وlodash، والعديد من المكتبات الأخرى.

القوالب

هناك ثلاثة قوالب للوحدات، module.d.ts، وmodule-class.d.ts، وmodule-function.d.ts.

استعمل module-function.d.ts إن أمكن استدعاء الوحدة كدالة:

var x = require("foo");
// لاحظ استدعاء الوحدة كدالة
var y = x(42);

لكن اقرأ أولًا تأثير ES6 على تواقيع استدعاء الوحدات أدناه.

استعمل module-class.d.ts إن كان يُمكن بناء الوحدة باستخدام ‎new‎:

var x = require("bar");

// لاحظ أنّنا نستخدم العامل
// 'new'
// على المتغيّر المستورَد
var y = new x("hello");

تنطبق نفس الملاحظة على هذه الوحدات كذلك.

إذا لم تكن الوحدة قابلةً للاستدعاء ولا للبناء، فاستخدم الملفّ ‎module.d.ts.

إضافةُ (Plugin) وحدةٍ أو إضافةُ UMD

إضافة الوحدة تُغيّر شكل وحدة ما (سواء أكانت وحدةً عاديّة أو وحدة UMD). على سبيل المثال، في Moment.js، تُضيف الإضافة ‎moment-range‎ تابعًا جديدًا باسم ‎range‎ إلى الكائن ‎moment‎.

ولكتابة ملفّ تصريحات، عليك كتابة نفس الشيفرة سواء أكانت الوحدة المُعدَّلَة وحدةً عاديّة أو وحدة UMD.

القالب

استعمل القالب module-plugin.d.ts.

الإضافات العامّة (Global Plugins)

الإضافات العامّة هي شيفرةٌ عامّة تُغيّر شكل متغيّر أو كائن عامّ. وقد تنشئ هذه الإضافات صراعا أثناء التنفيذ (runtime conflict) كما هي الحال في الوحدات التي تُغيّر المجال العام.

على سبيل المثال، تضيف بعض المكتبات دوالًا جديدة إلى ‎Array.prototype‎ أو ‎String.prototype‎.

التعرّف على الإضافات العامّة

عادة ما يسهل التعرّف على الإضافات العامّة من توثيقها.

قد تجد أمثلة مشابهة لما يلي:

var x = "hello, world";
// تضيف الإضافة توابع جديدة إلى الأنواع المضمَّنة
console.log(x.startsWithHello());

var y = [1, 2, 3];
// تضيف الإضافة توابع جديدة إلى الأنواع المضمَّنة
console.log(y.reverseAndSort());

القوالب

استعمل القالب global-plugin.d.ts.

الوحدات التي تغيّر المجال العام

الوحدات التي تغيّر المجال العام هي وحداتٌ تغيّرُ عند استيرادها القيمَ الموجودة في المجال العام. على سبيل المثال، يُمكن لمكتبةٍ إضافة عناصر جديدة إلى ‎String.prototype‎ عند استيرادها. هذا النمط خطير نوعًا ما بسبب إمكانيّة وقوع صراعات أثناء التنفيذ، لكن مع ذلك لا زال بالإمكان كتابة ملفّات تصريحات لمثل هذه المكتبات.

التعرّف على الوحدات التي تغيّر المجال العام

من السهل التعرّف على الوحدات التي تغيّر المجال العام من توثيقها. وتكون عمومًا مشابهة للإضافات العامّة، لكنّها تحتاج إلى استدعاء ‎require‎ لتفعيل تأثيراتها.

قد ترى توثيقًا كالتالي:

// استدعاء
// 'require'
// دون استخدام قيمتها المعادة
var unused = require("magic-string-time");
/* أو دون تعيينها إلى متغيّر */
require("magic-string-time");

var x = "hello, world";
// تضيف توابع جديدة إلى الأنواع المضمَّنة
console.log(x.startsWithHello());

var y = [1, 2, 3];
// تضيف توابع جديدة إلى الأنواع المضمَّنة
console.log(y.reverseAndSort());

القالب

استعمل القالب global-modifying-module.d.ts.

استخدام الاعتماديات

هناك عدّة أنواع من الاعتماديات التي قد تعتمد عليها:

الاعتماد على المكتبات العامة

إذا كانت مكتبتك تعتمد على مكتبة عامّة، فاستخدم التعليمة ‎/// <reference types="..." />‎ (انظر توثيق تعليمات الشرطات الثلاث):

/// <reference types="someLib" />

function getThing(): someLib.thing;

الاعتماد على الوحدات

إذا كانت مكتبتك تعتمد على وحدةٍ فاستخدم الجملة ‎import‎:

import * as moment from "moment";

function getThing(): moment;

الاعتماد على مكتبات UMD

من مكتبة عامّة

إذا كانت مكتبتك العامة تعتمد على وحدة UMD، فاستخدم تعليمة ‎/// <reference types‎:

/// <reference types="moment" />

function getThing(): moment;

من وحدة أو مكتبة UMD

إذا كانت وحدتك أو مكتبة UMD الخاصة بك تعتمد على مكتبة UMD، فاستخدم جملة ‎import‎:

import * as someLib from 'someLib';

تنبيه: لا تستعمل تعليمة ‎/// <reference‎ للتصريح عن اعتمادٍ على مكتبة UMD!

ملاحظات

تجنّب صراعات الأسماء (Name Conflicts)

لاحظ أنّه من الممكن تعريف عدّة أنواع في المجال العام عند كتابة ملفّ تصريحات عام (global declaration file). هذا غير منصوح به بشدّة لأنّه قد يؤدي إلى صراعات أسماء لا يمكن حلها عندما تتواجد عدة ملفات تصريحات في مشروع واحد.

استعمال مجالات الأسماءِ حصرًا قاعدةٌ بسيطة ينبغي اتباعها، إذ يجب التصريح عن الأنواع وإحاطتها بمجال أسماء يُسمّى بنفس اسم المتغيّر العام الذي تعرّفه المكتبة. على سبيل المثال، إذا عرّفت المكتبة القيمة العامّة ‎cats‎، فعليك كتابة ما يلي:

declare namespace cats {
    interface KittySettings { }
}

ولا يجب عليك كتابة الواجهة مباشرة كالتالي:

// في المستوى الأعلى للملفّ
interface CatsKittySettings { }

هذا الإرشاد يتحقّق كذلك من أنّ نقل المكتبة إلى UMD ممكن دون كسر ملفات التصريحات لمستخدميها.

تأثير ES6 على إضافات الوحدات

بعض الإضافات تضيف أو تعدّل تصديرات في المستوى الأعلى في الوحدات الأصلية. يُعدّ هذا مسموحًا به في CommonJS وبقيّة محملات الوحدات، لكن رغم هذا فوحدات ES6 غير قابلة للتعديل (immutable) وهذا النمط غير مسموح به. ولأن TypeScript تعمل مع جميع محملات الوحدات، فلا يوجد تطبيق لهذه السياسة أثناء الترجمة، لذا ينبغي معرفة هذا على المطورين الذين يريدون التحويل إلى محمّل وحدات ES6.

تأثير ES6 على تواقيع استدعاء الوحدات

تتوفّر العديد من المكتبات المشهورة مثل Express كدالة يمكن استدعاؤها عند استيرادها. على سبيل المثال، عادةً ما تُستخدَم مكتبة Express كالتالي:

import exp = require("express");
var app = exp();

في محمّلات وحدات ES6، يمكن للكائن الموجود في المستوى الأعلى (المستورَد هنا باسم ‎exp‎) أن يمتلك خاصياتٍ فقط؛ ولا يكون كائن الوحدة في المستوى الأعلى قابلًا للاستدعاء أبدًا. أشهر حلّ لهذه المشكلة هو تعريف تصدير افتراضيّ بالكلمة المفتاحية ‎default‎ لتصدير كائن قابل للاستدعاء أو كائن قابل للبناء (constructable). تتعرّف بعض حشوات (shims) محملات الوحدات تلقائيا على هذه المشكلة وتستبدل الكائن في المستوى الأعلى بتصدير افتراضيّ.

مصادر