المسارات في tsconfig.json: ما يخفى تحت غطاء TypeScript

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

كيف توفر الأسماء المستعارة في TypeScript ساعات من حياتك

\n

هل سبق لك أن كتبت شيئًا مثل import UserService from '../../../../../services/UserService'؟ إذا كان الأمر كذلك، فأنت تعرف هذا الألم. تتشتت العيون، ويتحول إعادة الهيكلة إلى كابوس، وأي نقل لملف يكسر سلسلة الاستيراد بأكملها. لحسن الحظ، يقدم TypeScript (وملف التهيئة الخاص به tsconfig.json) حلاً أنيقًا — paths.

\n

ولكن السؤال هو: كيف يعمل هذا تحت الغطاء؟ لماذا، بعد كتابة الاسم المستعار "@/*"، يمكنك كتابة import { UserService } from '@/services/UserService'؟ والأهم من ذلك — لماذا لا يعمل هذا أحيانًا بدون إعدادات إضافية لأداة التجميع (webpack, Vite, Rollup)؟

\n

في هذه المقالة، لن نعرض فقط كيفية إعداد paths في tsconfig.json. سننظر داخل الآلية، ونتعرف على تعيين المسار (path mapping)، ونفهم كيف يقوم TypeScript بحل الوحدات (modules)، ونتعلم كيفية تجنب الأخطاء النموذجية.

\n\n

ما هو paths في tsconfig.json؟

\n

Paths هو قسم في tsconfig.json يسمح لك بتعيين ارتباطات (mapping) مخصصة بين الأسماء المستعارة للاستيراد والمسارات الفعلية للملفات. ببساطة، أنت تقول لـ TypeScript: «عندما ترى استيرادًا يبدأ بـ @utils/، ابحث في الواقع عن الملف في مجلد src/utils/».

\n

إليك كيف يبدو ذلك في الكود:

\n\n
// tsconfig.json\n{\n  "compilerOptions": {\n    "baseUrl": ".",\n    "paths": {\n      "@/*": ["src/*"],\n      "@utils/*": ["src/utils/*"],\n      "@components/*": ["src/components/*"]\n    }\n  }\n}\n
\n\n

الآن، بدلاً من المسارات النسبية المروعة، تكتب:

\n
// كان (كابوس)\nimport { formatDate } from '../../../../utils/dateHelpers';\n\n// أصبح (جمال)\nimport { formatDate } from '@utils/dateHelpers';\n
\n\n

توافقون الرأي، الفرق هائل. ولكن كيف يفهم TypeScript إلى أين يذهب؟

\n\n

كيف يعمل حل الوحدات تحت الغطاء

\n

لفهم تعيين المسار (path mapping) في TypeScript، تحتاج إلى فهم الآلية الأساسية لـ resolution (حل الوحدات). عندما يقوم TypeScript بتجميع الكود الخاص بك، فإنه يستخدم استراتيجية مشابهة لـ Node.js، ولكن مع إمكانيات إضافية.

\n

تبدو العملية كالتالي:

\n
    \n
  1. التحقق من المسار المطلق (يبدأ بـ /) — إذا كان كذلك، يبحث من جذر نظام الملفات.
  2. \n
  3. التحقق من المسار النسبي (يبدأ بـ ./ أو ../) — يحل بالنسبة للملف الحالي.
  4. \n
  5. التحقق من الاسم المستعار من paths — إذا بدأ الاستيراد ببادئة محددة في paths، يقوم TypeScript باستبدالها بالمسار المقابل من التعيين.
  6. \n
  7. البحث في node_modules — إذا لم ينطبق أي مما سبق، يبحث عن الوحدة كحزمة npm.
  8. \n
\n

النقطة الأساسية: TypeScript نفسه لا يغير المسارات الفعلية في JavaScript المترجمة. إنه فقط يتحقق من الأنواع ويصدر أخطاء إذا لم يتم العثور على الملف. أما استبدال الأسماء المستعارة بالمسارات الفعلية في وقت التشغيل (runtime) فهي مهمة أداة التجميع (webpack, Vite, tsc-alias).

\n\n

لماذا لا تعمل paths «خارج الصندوق»؟

\n

هذا هو السؤال الأكثر شيوعًا على Stack Overflow: «لقد قمت بإعداد paths، ولكن عند تشغيل التطبيق يظهر خطأ "Cannot find module"». السبب بسيط: TypeScript هو مترجم (transpiler)، وليس أداة تجميع (bundler).

\n

عند تشغيل tsc، يقوم بإنشاء ملفات JS، لكنه يترك الأسماء المستعارة كما هي. على سبيل المثال، import { foo } from '@utils/bar' سيبقى في JS كما هو. Node.js (أو المتصفح) لا يعرف ما هو @utils، ويصدر خطأ.

\n

لإصلاح ذلك، تحتاج إلى استخدام أحد الحلول التالية:

\n
    \n
  • tsc-alias — أداة مساعدة تقوم بعد التجميع باستبدال الأسماء المستعارة بالمسارات الفعلية.
  • \n
  • module-alias — حزمة npm لتسجيل الأسماء المستعارة في Node.js.
  • \n

المدونات

توصيات الكتب