كيف قدّمت أول مساهمة لي إلى مستودع DefinitelyTyped لتعريفات TypeScript مفتوحة المصدر

دقائق القراءة: 7

مقدمة: لماذا كانت هذه المساهمة مهمة؟

تُعد TypeScript من أفضل الأدوات التي يمكن إضافتها إلى أي مشروع واجهات حديث، لأنها تجعل الشيفرة أكثر أماناً، وأسهل في التتبع، وأوضح من حيث التوثيق والتعامل مع الأخطاء. وبالنسبة لكثير من المطورين، أصبح استخدام TypeScript جزءاً أساسياً من سير العمل اليومي داخل مشاريع React وGatsby وغيرها.

لكن رغم وفرة الحزم الجاهزة في النظام التقني المفتوح، قد تصادف أحياناً مكتبة مفيدة لا تحتوي بعد على تعريفات أنواع جاهزة. هذا ما حدث مع إضافة gatsby-plugin-breakpoints، وهي إضافة بسيطة لكنها عملية. وبدلاً من حذفها من المشروع أو إعادة كتابة منطقها بالكامل، كان الحل الأفضل هو إنشاء تعريفات الأنواع اللازمة لها، ثم مشاركتها مع المجتمع عبر مستودع DefinitelyTyped.

واجهة توضيحية لمستودع DefinitelyTyped الخاص بتعريفات TypeScript مفتوحة المصدر

كيف بدأت الفكرة: حل عملي بدل إزالة الاعتماديات

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

لكن في الواقع، كانت الإضافة مستخدمة بالفعل في أجزاء متعددة من المشروع. وهذا يعني أن حذفها سيفرض إعادة هيكلة قائمة فقط من أجل التخلص من اعتماد خارجي، مع ضرورة محاكاة الواجهة البرمجية نفسها API حتى لا يتعطل الكود الحالي. هنا ظهر الخيار الأكثر منطقية: الاحتفاظ بالإضافة وكتابة تعريفات الأنواع الخاصة بها.

وبعد إنجاز ذلك محلياً داخل المشروع، ظهرت فرصة أفضل: لماذا لا تتحول هذه المعالجة المحلية إلى مساهمة حقيقية يستفيد منها الجميع؟

ما هو مستودع DefinitelyTyped؟

يُعد DefinitelyTyped المستودع الأشهر لتعريفات الأنواع الخاصة بالحزم المكتوبة أو المستخدمة مع TypeScript. وعندما لا توفر مكتبة ما تعريفات رسمية مرفقة بها، يكون هذا المستودع غالباً هو الوجهة الأولى لإضافة تلك التعريفات ومشاركتها مع المجتمع.

الميزة المهمة هنا أن المساهمة لا تتطلب بالضرورة تعديلات ضخمة أو خبرة عميقة في البنية الداخلية للمكتبة؛ أحياناً يكفي أن تفهم الواجهة العامة للمكتبة وتوثيقها لتكتب تعريفات صحيحة وقابلة للاستخدام.

إعداد بيئة العمل للمساهمة في DefinitelyTyped

إنشاء القالب الأولي للتعريفات

بعد عمل نسخة Fork من المستودع، يمكن توليد البنية الأولية للحزمة المستهدفة بالأمر التالي:

npx dts-gen --dt --name gatsby-plugin-breakpoints --template module

سينشئ هذا الأمر مجلداً جديداً داخل مشروع DefinitelyTyped يحتوي عادة على الملفات التالية:

  • gatsby-plugin-breakpoints-test.ts
  • index.d.ts
  • tsconfig.json
  • tslint.json

الملف index.d.ts هو المكان الأساسي لكتابة تعريفات الأنواع، بينما يساعد ملف الاختبار في التأكد من أن هذه التعريفات تعمل كما هو متوقع.

تعديل إعدادات tsconfig.json لمشاريع React

بما أن هذه الإضافة تتعامل مع React، فهناك تعديل بسيط لكنه ضروري:

  1. إضافة الخاصية "jsx": "react" داخل compilerOptions.
  2. تغيير اسم ملف الاختبار من gatsby-plugin-breakpoints-test.ts إلى gatsby-plugin-breakpoints-tests.tsx.
  3. تحديث الإشارة إلى ملف الاختبار داخل الحقل files.

يمكن أن يصبح ملف tsconfig.json بالشكل التالي:

{
  "compilerOptions": {
    "module": "commonjs",
    "lib": ["es6", "DOM"],
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictFunctionTypes": true,
    "strictNullChecks": true,
    "baseUrl": "../",
    "typeRoots": ["../"],
    "types": [],
    "noEmit": true,
    "forceConsistentCasingInFileNames": true,
    "jsx": "react",
    "esModuleInterop": true
  },
  "files": [
    "index.d.ts",
    "gatsby-plugin-breakpoints-tests.tsx"
  ]
}

تحليل المكتبة قبل كتابة تعريفات TypeScript

قبل البدء في كتابة الأنواع، من المهم الرجوع إلى التوثيق الرسمي وشيفرة المصدر الخاصة بالمكتبة. هذه الخطوة تساعد في معرفة كل الأجزاء العامة التي يجب تمثيلها داخل ملف التعريفات.

في حالة gatsby-plugin-breakpoints، كانت العناصر الأساسية التي تحتاج إلى تعريف كما يلي:

  • إعدادات الإضافة Plugin config
  • الدالة الخطافية useBreakpoint
  • المكوّن عالي الرتبة withBreakpoints
  • BreakpointProvider
  • BreakpointContext

كتابة تعريفات إعدادات الإضافة

لنبدأ بتعريف كائنات الإعداد الخاصة بالمكتبة. هذا الجزء يوضح شكل الخيارات التي يمكن تمريرها داخل إعدادات Gatsby.

// index.d.ts
// Type definitions for gatsby-plugin-breakpoints 1.3
// Project: https://github.com/JimmyBeldone/gatsby-plugin-breakpoints
// Definitions by: Iva Kop <https://github.com/IvaKop>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

/**
 * @see https://www.gatsbyjs.com/plugins/gatsby-plugin-breakpoints/
 */
export type QueriesObject = Record<string, string>;

export interface BreakpointOptions {
  queries?: QueriesObject;
}

export interface BreakpointConfig {
  resolve: 'gatsby-plugin-breakpoints';
  options?: BreakpointOptions;
}

في هذا المثال، يُستخدم النوع Record<string, string> لتمثيل كائن يحتوي على أسماء نقاط التوقف وقيمها النصية مثل (max-width: 720px).

اختبار تعريف إعدادات الإضافة

بعد كتابة النوع، من الأفضل اختباره مباشرة داخل ملف الاختبارات:

// gatsby-plugin-breakpoints-tests.tsx
import React from 'react';
import {
  BreakpointConfig,
} from 'gatsby-plugin-breakpoints';

const defaultQueries = {
  xs: '(max-width: 320px)',
  sm: '(max-width: 720px)',
  md: '(max-width: 1024px)',
  l: '(max-width: 1536px)',
};

const plugins: BreakpointConfig = {
  resolve: `gatsby-plugin-breakpoints`,
  options: {
    queries: defaultQueries,
  },
};

تعريف الدالة الخطافية useBreakpoint

الخطوة التالية هي تعريف Hook التي تعيد كائناً منطقياً يوضح حالة كل نقطة توقف. هذا مناسب جداً لاستخدامات الواجهات التفاعلية التي تحتاج إلى إظهار أو إخفاء عناصر حسب حجم الشاشة.

// index.d.ts
// ...
export type BreakpointsObject = Record<string, boolean>;

export function useBreakpoint(): BreakpointsObject;

اختبار useBreakpoint داخل مكوّن React

// gatsby-plugin-breakpoints-tests.tsx
import React from 'react';
import {
  // ...
  useBreakpoint
} from 'gatsby-plugin-breakpoints';

function HookComponent() {
  const breakpoints = useBreakpoint();

  return <div>{breakpoints.xs ? <div /> : null}</div>;
}

هذا الاختبار البسيط يثبت أن الكائن الناتج من useBreakpoint() يسمح بالوصول إلى الخصائص المنطقية بصورة صحيحة.

تعريف withBreakpoints كمكوّن عالي الرتبة

توفر المكتبة أيضاً نمط HOC أو Higher Order Component، وهو أسلوب شائع لحقن خصائص إضافية في المكونات. هنا نحتاج إلى تعريف الخصائص التي سيضيفها هذا المكوّن تلقائياً.

// index.d.ts
// ...
export interface BreakpointProps {
  breakpoints: BreakpointsObject;
}

export function withBreakpoints<P extends BreakpointProps>(
  Component: React.ComponentType<P>
): React.ComponentType<P>;

اختبار withBreakpoints مع مكوّن دالي ومكوّن صنفي

// gatsby-plugin-breakpoints-tests.tsx
import React from 'react';
import {
  // ...
  withBreakpoints,
  BreakpointProps,
} from 'gatsby-plugin-breakpoints';

const EnhancedFunctionalComponent = withBreakpoints(
  function Component({ breakpoints }) {
    return breakpoints.xs ? <div /> : <div>Content hidden only on mobile</div>;
  }
);

class Component extends React.Component<BreakpointProps> {
  render() {
    const { breakpoints } = this.props;
    return breakpoints.xs ? <div /> : <div>Content hidden only on mobile</div>;
  }
}

const EnhancedClassComponent = withBreakpoints(Component);

هذا النوع من الاختبارات مهم لأنه يضمن أن التعريفات تعمل مع أكثر من أسلوب شائع لكتابة مكونات React.

تعريف BreakpointContext وBreakpointProvider

بقي عنصران أساسيان في المكتبة: Context والمزوّد Provider. تعريفهما ضروري حتى يتمكن المطور من استخدام سياق React بطريقة سليمة مع الأنواع.

// index.d.ts
// ...
export type QueriesObject = Record<string, string>;

export const BreakpointContext: React.Context<QueriesObject>;
export const BreakpointProvider: React.Provider<QueriesObject>;

اختبار السياق والمزوّد

// gatsby-plugin-breakpoints-tests.tsx
import React from 'react';
import {
  // ...
  BreakpointProvider,
  BreakpointContext,
} from 'gatsby-plugin-breakpoints';

// BreakpointContext
function useContext() {
  const context = React.useContext(BreakpointContext);
  return context;
}

// BreakpointProvider
const ProviderComponent: React.FC = ({ children }) => {
  return <BreakpointProvider value={defaultQueries}>{children}</BreakpointProvider>;
};

بهذه الخطوة تكون معظم واجهة المكتبة العامة قد غُطيت بتعريفات قابلة للاستخدام والاختبار.

تشغيل الفحص والاختبارات قبل رفع Pull Request

بعد الانتهاء من كتابة الأنواع والاختبارات، تأتي مرحلة التحقق النهائي. أولاً، يمكن تشغيل أداة التدقيق الخاصة بالمستودع:

npm run lint gatsby-plugin-breakpoints

إذا مرّ الفحص بنجاح، ننتقل إلى الاختبارات:

npm run test gatsby-plugin-breakpoints

حل مشكلة Cannot find module 'csstype'

قد تظهر أحياناً أخطاء مرتبطة بالاعتماديات الداخلية، ومن أشهرها الخطأ التالي:

Cannot find module 'csstype' or its corresponding type declarations.

هذا النوع من المشاكل ليس نادراً في بيئات العمل الكبيرة، وغالباً يكون له حل موثق مسبقاً داخل المشكلات المفتوحة في المستودع. في هذه الحالة، كان الحل بسيطاً: تثبيت الحزم المطلوبة داخل مجلد types/react ثم إعادة تشغيل الاختبار.

cd types/react && npm install && cd ../../ && npm run test gatsby-plugin-breakpoints

بعد ذلك، يفترض أن تعمل الاختبارات بشكل طبيعي، وتصبح المساهمة جاهزة لرفع PR ومراجعتها من قبل القائمين على المشروع.

ما الذي يحدث بعد إرسال المساهمة؟

بعد رفع طلب الدمج Pull Request، تبدأ أنظمة التكامل المستمر CI في تنفيذ الفحوصات الآلية. إذا كانت التعريفات صحيحة، والاختبارات ناجحة، والتنسيق مطابقاً لمعايير المستودع، فعادة تكون الموافقة سريعة نسبياً، خصوصاً إذا كانت المساهمة صغيرة وواضحة الهدف.

الجميل في هذا النوع من المساهمات أن أثره يتجاوز المشروع الشخصي؛ فبمجرد دمج التعريفات ونشرها، يستفيد منها كل مطور يستخدم الحزمة نفسها لاحقاً.

دروس مهمة من أول مساهمة مفتوحة المصدر

  • ليست كل مساهمة مفتوحة المصدر معقدة أو كبيرة الحجم.
  • إضافة تعريفات TypeScript قد تكون من أكثر الطرق العملية لرد الجميل للمجتمع التقني.
  • قراءة التوثيق وشيفرة المصدر تكفي غالباً لبناء تعريفات دقيقة ومفيدة.
  • وجود اختبارات مرافقة للتعريفات يزيد فرص قبولها بسرعة.
  • حل المشكلات الصغيرة، مثل ضبط الإعدادات أو تثبيت الاعتماديات الناقصة، جزء طبيعي من رحلة المساهمة.

لماذا تُعد هذه المساهمة ذات قيمة حقيقية؟

ربما تبدو إضافة تعريفات أنواع لحزمة صغيرة مساهمة محدودة، لكنها في الحقيقة تقدم فائدة مباشرة للمطورين الذين سيستخدمون هذه المكتبة مستقبلاً. فكل تعريف صحيح يقلل الأخطاء، ويحسن الإكمال التلقائي، ويجعل تجربة التطوير أكثر سلاسة.

وهذا بالضبط ما يمنح المشاريع المفتوحة المصدر قوتها: تراكم التحسينات الصغيرة القادمة من أفراد مختلفين، حتى لو بدأ كل واحد منهم بخطوة متواضعة.

الخلاصة التقنية

إذا واجهت مكتبة مفيدة لا تحتوي على تعريفات TypeScript، فلا تتسرع في استبدالها أو حذفها. في كثير من الحالات، يكون إنشاء ملف index.d.ts مدعوماً باختبارات دقيقة داخل DefinitelyTyped حلاً عملياً وسريع الأثر. تقنياً، هذا النوع من المساهمات يرفع جودة النظام البيئي بأكمله، ويحوّل مشكلة محلية داخل مشروعك إلى فائدة عامة للمجتمع البرمجي.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *