كيف توفر الأسماء المستعارة في TypeScript ساعات من حياتك
\nهل سبق لك أن كتبت شيئًا مثل import UserService from '../../../../../services/UserService'؟ إذا كان الأمر كذلك، فأنت تعرف هذا الألم. تتشتت العيون، ويتحول إعادة الهيكلة إلى كابوس، وأي نقل لملف يكسر سلسلة الاستيراد بأكملها. لحسن الحظ، يقدم TypeScript (وملف التهيئة الخاص به tsconfig.json) حلاً أنيقًا — paths.
ولكن السؤال هو: كيف يعمل هذا تحت الغطاء؟ لماذا، بعد كتابة الاسم المستعار "@/*"، يمكنك كتابة import { UserService } from '@/services/UserService'؟ والأهم من ذلك — لماذا لا يعمل هذا أحيانًا بدون إعدادات إضافية لأداة التجميع (webpack, Vite, Rollup)؟
في هذه المقالة، لن نعرض فقط كيفية إعداد paths في tsconfig.json. سننظر داخل الآلية، ونتعرف على تعيين المسار (path mapping)، ونفهم كيف يقوم TypeScript بحل الوحدات (modules)، ونتعلم كيفية تجنب الأخطاء النموذجية.
\n\nما هو paths في tsconfig.json؟
\nPaths هو قسم في tsconfig.json يسمح لك بتعيين ارتباطات (mapping) مخصصة بين الأسماء المستعارة للاستيراد والمسارات الفعلية للملفات. ببساطة، أنت تقول لـ TypeScript: «عندما ترى استيرادًا يبدأ بـ @utils/، ابحث في الواقع عن الملف في مجلد src/utils/».
إليك كيف يبدو ذلك في الكود:
\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
- التحقق من المسار المطلق (يبدأ بـ
/) — إذا كان كذلك، يبحث من جذر نظام الملفات. \n - التحقق من المسار النسبي (يبدأ بـ
./أو../) — يحل بالنسبة للملف الحالي. \n - التحقق من الاسم المستعار من paths — إذا بدأ الاستيراد ببادئة محددة في
paths، يقوم TypeScript باستبدالها بالمسار المقابل من التعيين. \n - البحث في node_modules — إذا لم ينطبق أي مما سبق، يبحث عن الوحدة كحزمة npm. \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
- tsc-alias — أداة مساعدة تقوم بعد التجميع باستبدال الأسماء المستعارة بالمسارات الفعلية. \n
- module-alias — حزمة npm لتسجيل الأسماء المستعارة في Node.js. \n