تقرير الوحدات في TypeScript

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


قراءة هذه الصفحة تتطلّب فهمًا عامًّا للوحدات. انظر توثيق الوحدات للاستزادة.

تقريرُ (أو حلُّ) الوحداتِ (Module resolution)، هي العمليّة التي يعتمد عليها المترجم (compiler) لاكتشاف ما يُشير إليه استيرادٌ (import) معيّن. لنفترض مثلًا أنّ لدينا جملة استيرادٍ كما يلي: ‎import { a } from "moduleA"‎‎؛ للتحقّق من أيّ اعتمادٍ على المتغيّر ‎a‎، فإنّ المترجم بحاجةٍ إلى فهم ما يُمثّله المتغيّر بالضبط، وسيتطلّب ذلك التحقق من تعريفه في الوحدة ‎moduleA‎.

سيسأل المُترجم كبدايةٍ عن شكل الوحدة ‎moduleA‎، وقد تكون الوحدة مُعرّفةً داخل أحد ملفّات ‎.ts‎ أو ‎.tsx‎ أو في ملفّ ‎.d.ts‎ من الملفّات التي تعتمد عليها الشيفرة التي تكتبها.

أولًا، سيُحاول المترجم العثور على ملفّ يُمثّل الوحدة المُستَورَدَة. وللقيام بذلك يتّبع المترجم أحدَ استراتيجيّتَيْن: Classic أو Node. تُخبِر هاتان الاستراتيجيّتان المترجمَ عن المكان الذي يجب بدء البحث عن الوحدة ‎moduleA‎ منه.

إذا لم ينجح هذا وإذا كان اسم الوحدة غير نسبيّ (non-relative) (وفي حالة ‎"moduleA"‎ فالاسم غير نسبيّ)، بعدها سيحاول المترجم العثور عن تصريح وحدة محيطة (ambient module declaration). سنُغطّي الاستيرادات غير النسبية تاليًّا.

وإن لم يستطع المترجم تقرير الوحدة (أي العثور على ملفّ يُمثّلها)، فسيُسجَّل خطأ، في هذه الحالة سيكون نصّ الخطأ شبيها بالنّص ‎error TS2307: Cannot find module 'moduleA'‎.

استيرادات الوحدات النسبيّة (Relative) وغير النسبية (Non-relative)

تُقرَّر استيرادات الوحدات بطريقة مختلفة حسب ما إذا كانت الإحالة (reference) نسبيّةً أو غير نسبيّة.

الاستيرادُ النسبيّ هو كلّ ما يبدأ بالمقطع ‎/‎، أو ‎./‎‎، أو ‎../‎‎. وهذه بعض الأمثلة على ذلك:

  • import Entry from "./components/Entry"‎;‎
  • ‎import { DefaultHeaders } from "../constants/http"‎;‎
  • ‎import "/mod";‎

وأيّ استيرادٍ آخر سيُعدّ غير نسبيّ. وهذه بعض الأمثلة على الاستيرادات غير النسبيّة:

  • import * as $ from "jquery";‎
  • ‎import { Component } from "@angular/core"‎;‎

تُقرَّر الاستيرادات النسبيّة نسبةً إلى الملفّ المستورِد ولا يُمكن عندئذٍ التقريرُ إلى تصريح وحدة محيطة. يجب عليك استخدام الاستيرادات النسبيّة لاستيراد وحداتك الخاصّة التي تُدرِك يقينًا أنّ مكانها النسبيّ لن يتغيّر أثناء التنفيذ (runtime).

يُمكن تقرير الاستيرادات غير النسبيّة نسبةً إلى قيمة ‎baseUrl‎، أو عن طريق تخطيط المسار (path mapping)، واللذان سيُشرَحان أدناه. يُمكن كذلك أن تُقرَّر إلى تصريحات وحدات محيطة. استخدم المسارات غير النسبيّة عند استيراد أي من الاعتماديات الخارجيّة (كالمكتبات والوحدات التي تُثبّت خارجيًّا).

استراتيجيتا تقرير الوحدات

هناك استراتيجيتان ممكنتان لتقرير الوحدات: Node و Classic. يُمكنك استخدام الخيار ‎--moduleResolution‎ لتحديد استراتيجيّة تقرير الوحدات. إذا لم تُحدَّد الاستراتيجيّة فالاستراتيجيّة الافتراضيّة هي Classic لأنظمة تحميل الوحدات ‎--module AMD | System | ES2015‎ وتُستَخدَم استراتيجيّة Node في غير ذلك من حالات.

استراتيجيّة Classic

كانت استراتيجيّةُ Classic استراتيجيّةَ التقرير الافتراضيّة في TypeScript. أمّا الآن فهذه الاستراتيجيّة موجودة بشكل رئيسيّ لتوفير التوافقيّة العكسيّة (backward compatibility).

ستُقرَّر الاستيرادات النسبيّة نسبةً للملفّ المستورِد. لذا فالاستيرادُ ‎import { b } from "./moduleB"‎‎ في الملفّ المصدريّ ‎/root/src/folder/A.ts‎‎ سيُنتِج عمليّات البحث التاليّة:

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts

أمّا بالنّسبة لاستيرادات الوحدات غير النّسبيّة، فسيمُرّ المترجم إلى أعلى شجرة المجلّدات (directory tree) بدايةً من المجلّد الذي يحتوي على الملفّ المُستورِد، في مُحاولةٍ لإيجاد ملفّ تعريفٍ متوافِق.

على سبيل المثال:

لنفترض أنّنا في ملفّ مصدريّ ‎/root/src/folder/A.ts‎، ونستورِد من الوحدة ‎moduleB‎ بشكلٍ غير نسبيّ بالجملة ‎import { b } from "moduleB"‎ مثلًا. هذا الاستيراد سيُؤدي إلى محاولة البحث عن ‎الوحدة ‎"moduleB"‎ في المواقع التّاليّة:

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts
  3. /root/src/moduleB.ts
  4. /root/src/moduleB.d.ts
  5. /root/moduleB.ts
  6. /root/moduleB.d.ts
  7. /moduleB.ts
  8. /moduleB.d.ts

استراتيجيّة Node

تسعى استراتيجيّة التقرير Node لمحاكاة آليّة تقرير الوحدات أثناء التنفيذ في Node.js. يُمكنك الاطلاع على كامل خوارزمية التقرير في Node.js في توثيق وحدات Node.js.

كيفية تقرير الوحدات في Node.js

قبل فهم الخطوات التي سيتبعها مترجم TypeScript، من المهمّ أولًا إلقاء نظرة على وحدات Node.js. تتمّ الاستيرادات في Node.js عادةً عبر استدعاء دالّةٍ تُسمّى ‎require‎. يختلف السلوك الذي تسلكه منصّة Node.js حسب ما إذا أُعطِيَت الدالةُ ‎require‎ مسارًا نسبيًّا أو مسارًا غير نسبيّ.

المسارات النسبيّة واضحة. على سبيل المثال، لنفترض أنّ ملفًّا موجودًا في المسار ‎/root/src/moduleA.js‎ يحتوي على جملة الاستيراد ‎var x = require("./moduleB");‎. سوف تُقرّر منصّة Node.js هذا الاستيراد باتباع الخطوات التاليّة:

  1. البحث عن ملفٍّ اسمُه ‎/root/src/moduleB.js‎ والاعتماد عليه إن وُجِد.
  2. اطلب من المجلّد ‎/root/src/moduleB‎ ما إذا كان يحتوي على ملفّ اسمه ‎package.json‎ يُحدِّد وحدة رئيسيّة في الخاصيّة ‎‎"main"‎‎ داخلَه. في مثالنا هذا، إذا وجدت Node.js ملفًّا اسمه ‎/root/src/moduleB/package.json‎ يحتوي على ‎‎{ "main": "lib/mainModule.js" }‎‎، فحينئذٍ سيُحال تقرير الوحدة إلى الملفّ ‎/root/src/moduleB/lib/mainModule.js‎‎.
  3. اطلب من المجلّد ‎/root/src/moduleB‎ ما إذا كان يحتوي على ملفّ اسمه ‎index.js‎. يُعدّ هذا الملفّ الوحدة الرئيسية ("main") لهذا المجلّد ضمنيًّا (implicitly).

يُمكنك معرفة المزيد عن هذا في توثيق Node.js، انظر قسم وحدات الملفّات وقسم المجلدات كوحدات.

لكن تقرير اسم وحدةٍ غير نسبيّ يُحدَّد بشكل مختلف. ستبحث Node عن وحداتك في مجلّداتٍ خاصّةٍ تُسمّى ‎node_modules‎. مجلّدُ ‎node_modules‎ مجلّدٌ يُمكن له أن يكون على نفس مستوى الملفّ الحاليّ أو أعلى من ذلك على سلسلة المجلّدات (أي المجلّد الأب للمجلّد الذي يحتوي على الملفّ الحالي أو مجلّده الأب إلخ...). ستمرّ Node على سلسلة المجلّدات باحثةً في كلّ مجلّد مُسمّى بالاسم ‎node_modules‎ حتى تجد الوحدةَ المرادُ تحميلُها.

لنستأنف المثال أعلاه، لنفترض أنّ الملفّ ‎/root/src/moduleA.js‎ يعتمد على مسار غير نسبيّ عوض ما سبق، وكان الاستيراد فيه على الشّكل ‎var x = require("moduleB");‎‎. ستُحاول Node هنا تقرير الوحدة ‎moduleB‎ في كلّ مسار من المسارات التاليّة حتى ينجح أحدها:

  1. /root/src/node_modules/moduleB.js
  2. /root/src/node_modules/moduleB/package.json‎ (إذا حدّد الملفُّ خاصيّةَ ‎‎"main"‎‎)
  3. /root/src/node_modules/moduleB/index.js

ثمّ نقفز للأعلى في شجرة المجلّدات:

  1. /root/node_modules/moduleB.js
  2. /root/node_modules/moduleB/package.json‎ (إذا حدّد الملفُّ خاصيّةَ ‎‎"main"‎‎)
  3. /root/node_modules/moduleB/index.js

نقفز مجدّدًا للأعلى في شجرة المجلّدات:

  1. /node_modules/moduleB.js
  2. /node_modules/moduleB/package.json
  3. /node_modules/moduleB/index.js

يُمكنك معرفة المزيد حول هذه العمليّة في توثيق Node.js، انظر قسم تحميل الوحدات من مجلّدات ‎node_modules.

كيفية تقرير الوحدات في TypeScript

تُحاكي TypeScript استراتيجيّة التقرير أثناء التنفيذ (run-time resolution strategy) في Node.js للعثور على ملفّات التعريفات (definition files) للوحدات أثناء الترجمة. وللقيام بالأمر فإنّ TypeScript تُراكِبُ امتدادات ملفّات TypeScript المصدريّة (كلّ من ‎.ts‎ و‎.tsx‎ و‎.d.ts‎) على منطِق التقرير في Node. ستعتمد TypeScript كذلك على حقلٍ في ملفّ ‎package.json‎ يُسمّى ‎"types"‎ لمحاكاة الغرض من ‎"main"‎، أي أنّ المترجم سيستخدم الحقل للعثور على ملفّ التعريف الرئيسيّ.

على سبيل المثال، سينتجُ الاستيراد ‎import { b } from "./moduleB"‎ في الملفّ ‎/root/src/moduleA.ts ‎ التحقّق من وجود الوحدة ‎"./moduleB"‎‎ في الأماكن التاليّة:

  1. /root/src/moduleB.ts
  2. /root/src/moduleB.tsx
  3. /root/src/moduleB.d.ts
  4. /root/src/moduleB/package.json‎ (إذا حدّد الملفُّ حقلَ ‎"types"‎‎)
  5. /root/src/moduleB/index.ts
  6. /root/src/moduleB/index.tsx
  7. /root/src/moduleB/index.d.ts

تذكّر أن منصّة Node.js تبحث عن ملفّ اسمُه ‎moduleB.js‎، ثمّ ملفّ ‎package.json‎ يُمكن تطبيقه للبحث، ثمّ البحث عن ملفٍّ يُسمّى ‎index.js‎.

وبالمِثل فإنّ الاستيرادات غير النسبيّة تتّبع منطق تقرير Node.js، بالبحث أولًا عن ملفّ ثمّ عن مجلّد يُمكن البحث فيه. لذا فالاستيرادُ ‎import { b } from "moduleB"‎‎ في الملفّ المصدريّ ‎/root/src/moduleA.ts‎‎ سيُنتِج عمليّات البحث التاليّة:

  1. /root/src/node_modules/moduleB.ts
  2. /root/src/node_modules/moduleB.tsx
  3. /root/src/node_modules/moduleB.d.ts
  4. /root/src/node_modules/moduleB/package.json‎ (إذا حدّد الملفُّ حقلَ ‎"types"‎‎)
  5. /root/src/node_modules/moduleB/index.ts
  6. /root/src/node_modules/moduleB/index.tsx
  7. /root/src/node_modules/moduleB/index.d.ts

ثمّ نقفز للأعلى في شجرة المجلّدات:

  1. /root/node_modules/moduleB.ts
  2. /root/node_modules/moduleB.tsx
  3. /root/node_modules/moduleB.d.ts
  4. /root/node_modules/moduleB/package.json‎ (إذا حدّد الملفُّ حقلَ ‎"types"‎‎)
  5. /root/node_modules/moduleB/index.ts
  6. /root/node_modules/moduleB/index.tsx
  7. /root/node_modules/moduleB/index.d.ts

نقفز مجدّدًا للأعلى في شجرة المجلّدات:

  1. /node_modules/moduleB.ts
  2. /node_modules/moduleB.tsx
  3. /node_modules/moduleB.d.ts
  4. /node_modules/moduleB/package.json‎ (إذا حدّد الملفُّ حقلَ ‎"types"‎‎)
  5. /node_modules/moduleB/index.ts
  6. /node_modules/moduleB/index.tsx
  7. /node_modules/moduleB/index.d.ts

باستثناء امتدادات الملفّات المُضافة، فهذه الخطوات هي نفسها ما كانت منصّة Node.js تقوم به.

خيارات (flags) إضافيّة لتقرير الوحدات

لا يوافِق أحيانا تخطيطُ مشروعٍ مصدريٍّ التخطيط الذي يُنتَج بعد التّرجمة. عادةً ما يُنتَج المُخرج النهائيّ بعد عدّة خطوات بناء (build steps). يشمل هذا ترجمة ملفّات ‎.ts‎ إلى ملفّات ‎.js‎، ونسخ الاعتماديّات من أماكن مصادر (source locations) مختلفة إلى مكان مُخرَج (output location) واحد. والنتيجة النهائيّة هي أنّ الوحدات أثناء التنفيذ قد تختلف أسماؤها عن أسماء الملفّات المصدريّة التي تحتوي على تعريفاتها. أو قد لا تُوافِق مسارات الوحدات في المُخرَج مسارات الملفّات المصدريّة ذات العلاقة أثناء الترجمة.

يمتلك مترجم TypeScript خياراتٍ (أو راياتٍ [flags]) لإعلام المترجم عن التحوّلات التي يُتوقَّع لها أن تحدث للمصادر لتوليد المُخرَج النّهائيّ.

من الجدير بالذّكر أنّ المترجم لن يُؤدّيَ أيًّا من هذه التحوّلات؛ بل يستخدم فقط هذه المعلومات لإرشاد عمليّة تقرير استيرادِ الوحدة إلى ملفِّ تعريفها.

عنوان URL الأساس (Base URL)

استخدام عنوان URL أساس ‎baseUrl‎ شائعٌ في التطبيقات التي تعتمد على محمّلات الوحدات AMD التي تُنشَر (deployed) فيها الوحدات إلى مجلّد واحد أثناء التّنفيذ. يُمكن لمصادر هذه الوحدات أن تكون في مجلّدات مختلفة، لكن سكربت بناء (build script) سيجمعها جميعًا في مكان واحد.

يُعلِمُ تحديد عنوان ‎baseUrl‎ المترجِمَ بالأماكن التي ستوجد فيها الوحدات. تعدّ جميع استيرادات الوحدات غير النسبيّة متعلّقة بالعنوان ‎baseUrl‎.

تُحدَّد قيمة ‎baseUrl‎ على أنّها إمّا:

  • قيمةُ معامل ‎baseUrl‎ في سطر الأوامر (إذا كان المسار المعطى نسبيًّا، فستُحسَب القيمة حسب المجلّد الحاليّ)
  • قيمةُ الخاصيّة ‎baseUrl‎ في الملفّ ‎tsconfig.json‎ (إذا كان المسار المعطى نسبيًّا، فستُحسَب القيمة حسب مكان الملفّ tsconfig.json)

لاحظ أنّ تحديد عنوان ‎baseUrl‎ لا يُؤثِّر على الاستيرادات النسبيّة للوحدات، إذ تُقرَّر دائمًا نسبةً للملفّ الذي يستورِدها.

يُمكنك معرفة المزيد حول baseUrl على توثيق RequireJS وتوثيق SystemJS.

تخطيط المسار (Path mapping)

أحيانًا لا تكون الوحدات موجودة مباشرة في عنوان ‎baseUrl‎. على سبيل المثال، يُمكن لاستيراد الوحدة ‎"jquery"‎ أن يُترجَم أثناء التنفيذ إلى ‎"node_modules/jquery/dist/jquery.slim.min.js"‎. تستخدم محملات الوحدات إعداد تخطيط لتخطيط أسماء الوحدات وربطها بالملفات أثناء التنفيذ، انظر توثيق RequireJs وتوثيق SystemJS.

يدعم مترجم TypeScript التصريح عن هذه التخطيطات باستخدام الخاصيّة ‎"paths"‎ في ملفّات ‎tsconfig.json‎. إليك مثالًا لكيفيّة تحديد خاصيّة ‎"paths"‎ لمكتبة ‎jquery‎:

{
  "compilerOptions": {
    "baseUrl": ".", // من الواجب تحديد العنوان الأساس إذا حُدِّدت الخاصيّة
    "paths": {
      // هذا التخطيط نسبيّ نسبةً إلى قيمة عنوان
      // "baseUrl"
      "jquery": ["node_modules/jquery/dist/jquery"]
    }
  }
}

لاحظ أنّه من الواجب تحديد العنوان الأساس ‎"baseUrl"‎ إذا حُدِّدت الخاصيّة ‎"paths"‎. لاحظ كذلك أنّ مسارات "paths" تقرَّرُ نسبةً إلى عنوان "paths". عند ضبط قيمةِ ‎"baseUrl"‎ لتكون قيمةً مغايرةً للقيمة ‎"."‎‎ (أي المجلّد الذي يحتوي على الملفّ ‎tsconfig.json‎)، فمن الواجب تغيير التخطيطات إلى ما يُلائم. لنقل مثلًا أنّك ضبطتَ العنوان إلى ‎"baseUrl": "./src"‎‎ في المثال أعلاه، هذا يعني أنّ ‎jquery‎ ستُخطَّط لترتبط بالمسار ‎"../node_modules/jquery/dist/jquery"‎.

يسمح استخدام ‎"paths"‎ كذلك بكتابة تخطيطات أدقّ، ما يشمل عدّة أماكن للرجوع إليها في حالة لم يوجد الملفّ المطلوب. لنقل مثلًا أنّ لدينا هيكل مشروعٍ توجد فيه بعض الوحدات في مكان واحد، وبقية الوحدات موجودة في مكان آخر. ستجمعها عمليّة بناء كلّها في مكان واحد. قد يبدو تصميم ملفّات المشروع كما يلي:

projectRoot
├── folder1
   ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
   └── file2.ts
├── generated
   ├── folder1
   └── folder2
       └── file3.ts
└── tsconfig.json

ملفّ ‎tsconfig.json‎ المتعلّق بالمشروع سيبدو كما يلي:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "*": [
        "*",
        "generated/*"
      ]
    }
  }
}

يُخبِر هذا المترجمَ أن يبحث عن أي وحدةٍ تُوافق النمط ‎‎"*"‎‎ (والذي يعني كافّة القيم) في مكانين اثنين:

  1. ‎"*"‎: الذي يُشير إلى نفس الاسم دون تغيير، لذا اِرْبِط ‎<moduleName>‎ مع ‎<baseUrl>/<moduleName>‎.
  2. ‎"generated/*"‎‎: الذي يُشير إلى اسم الوحدة مع سابقة (prefix) ذات الاسم ‎generated‎ في بداية الاسم، لذا اربط ‎<moduleName>‎‎ مع ‎‎<baseUrl>/generated/<moduleName>‎‎.

باتباع هذا المنطق، سيُحاول المترجم تقرير الاستيرادين كما يلي:

استيراد ‎folder1/file2‎:

  1. يُوافَق النمط ‎*‎ وسيُلتَقط كامل اسم الوحدة.
  2. جرّب أوّل إبدال في القائمة: أبدِل ‎*‎ بالاسم ‎folder1/file2‎.
  3. نتيجة الإبدال اسمٌ غير نسبيّ، أضف إليها عنوان ‎baseUrl‎، النتيجة: ‎projectRoot/folder1/file2.ts
  4. الملفّ موجود. انتهى.

استيراد ‎folder2/file3‎:

  1. يُوافَق النمط ‎*‎ وسيُلتَقط كامل اسم الوحدة.
  2. جرّب أوّل إبدال في القائمة: أبدِل ‎*‎ بالاسم ‎folder2/file3‎.
  3. نتيجة الإبدال اسمٌ غير نسبيّ، أضف إليها عنوان ‎baseUrl‎، النتيجة: ‎projectRoot/folder2/file3.ts‎.
  4. الملفّ غير موجود. انتقل إلى الإبدال الثّاني.
  5. الإبدال الثاني، أضف إليها السابقة ‎generated/*‎‎، النتيجة: ‎generated/folder2/file3
  6. نتيجة الإبدال اسمٌ غير نسبيّ، أضف إليها عنوان ‎baseUrl‎، النتيجة: ‎projectRoot/generated/folder2/file3.ts‎.
  7. الملفّ موجود. انتهى.

المجلّدات الوهميّة (Virtual Directories) باستخدام ‎rootDirs

تُجمَع أحيانًا مصادر المشروع من عدّة مجلّداتٍ أثناء التنفيذ لتجتمع جميعًا لتوليد مجلّد مُخرج (output directory) واحد. يُمكن تخيّل هذه الآليّة على أنّ مجموعة من المجلّدات المصدريّة تجتمع لإنشاء مجلّد وهميّ.

يُمكنك باستخدام ‎rootDirs‎ إخبارُ المترجم عن الجذور (roots) التي تُشكّل هذا المجلّد الوهمي؛ وبالتالي سيتمكّن المترجم من تقرير استيرادات الوحدات النسبيّة داخل هذه المجلدات الوهمية وكأنّها مجتمعة جميعًا في مجلّد واحد فقط.

لنأخذ على سبيل المثال بنية المشروع التالي:

 src
 └── views
     └── view1.ts (يستورد './template1')
     └── view2.ts

 generated
 └── templates
         └── views
             └── template1.ts (يستورد './view2')

تحتوي الملفات الموجودة في المجلّد ‎src/views‎ على شيفرة مستخدمٍ لبعض متحكمات واجهة المستخدم (UI controls). الملفّات الموجودة داخل المجلّد ‎generated/templates‎ تحتوي على شيفرة ربط قوالب واجهة المستخدم (UI template binding code) تُولَّد تلقائيًّا من طرف مولِّد قوالب (template generator) كجزء من عمليّة البناء. ستَنسخ عمليّة البناء الملفّات الموجودة في ‎/src/views‎ وتلك الموجودة في ‎/generated/templates/views‎ إلى نفس المجلَّد في المخرَج. ويُمكن أثناء التنفيذ أن يتوقّع عرضٌ (view) أن يكون قالبُه (template) بجانبه في نفس المجلّد، لذا فيجب استيراده باستخدام اسم نسبيّ مثل ‎"./template"‎‎.

استخدم خاصيّة ‎"rootDirs"‎‎ لتحديد هذه العلاقة للمترجم. تُحدِّد الخاصيّة ‎"rootDirs"‎‎ قائمة مجلدات جذور (roots) من المتوقَّع أن تُدمَج محتوياتها أثناء التنفيذ. لذا فباتّباع المثال أعلاه، يجب على الملفّ ‎tsconfig.json‎ أن يبدو كما يلي:

{
  "compilerOptions": {
    "rootDirs": [
      "src/views",
      "generated/templates/views"
    ]
  }
}

في كلّ مرّة يجد فيها المترجم استيراد وحدة نسبيّ في أحد المجلّدات الفرعيّة (subfolder) الموجودة داخل ‎rootDirs‎، فسيُحاول البحث عن هذا الاستيراد داخل كل مسار من المسارات الموجودة في ‎rootDirs‎.

مرونة ‎rootDirs‎ غير محدودة في تحديد قائمة مجلّداتٍ مصدريّةٍ دُمِجَت منطقيًّا فقط. يُمكن للمصفوفة المعطاة أن تحتوي على أي عدد من المجلّدات المخصّصة لأغراض معيّنة، أو أسماء مجلّداتٍ اعتباطية، بغض النظر عمّا إذا كانت هذه المجلّدات موجودة أو لا. يسمح هذا للمترجم بالتقاط ميّزات تجميع دقيقة وميّزات وقت تنفيذٍ معقّدة، مثل التضمين الشرطي (conditional inclusion) وإضافات المحمّلات الخاصّة بالمشروع (project specific loader plugins) بشكل يضمن أمان الأنواع (type safe).

لنفترض مثلًا أنّنا نُحاول تَدْوِيلَ (internationalization) مشروع معيّن ليدعم عدّة لغات، ستكون لدينا أداة بناء تُجمّع حزمات (bundles) خاصّة بكلّ محليّة (locale) عبر تحريف رمز مسار خاصّ، لنقل أنّنا سنستخدم الرّمز الخاصّ ‎#{locale}‎‎، كجزءٍ من مسار الوحدة النسبيّ مثل ‎‎./#{locale}/messages‎‎. في هذا المشروع الذي افترضناه ستقوم الأداة بإحصاء المحليّات المدعومة، وذلك بتخطيط المسار المجرَّد إلى ‎./zh/messages‎‎، و‎./de/messages‎‎، وما شابه ذلك لبقيّة المحليّات المدعومة.

لنقل أنّ كلّ وحدة من هذه الوحدات تُصدّر مصفوفة سلاسل نصيّة. على سبيل المثال قد يحتوي الملفّ ‎./zh/messages‎ على:

export default [
    "您好吗",
    "很高兴认识你"
];

يُمكننا باستخدام ‎rootDirs‎ إخبار المترجم عن هذا التخطيط، ما سيسمح بتقرير مسار ‎./#{locale}/messages‎ بأمان حتى ولو لم يكن المجلّد موجودًا (ولن يكون موجودًا أبدًا لأنّه لا يُمثّل مجلدا معيّنا بل رمزًا للمجلّدات فقط). على سبيل المثال، يُمكن استخدام ملفّ ‎tsconfig.json‎ كالتالي:

{
  "compilerOptions": {
    "rootDirs": [
      "src/zh",
      "src/de",
      "src/#{locale}"
    ]
  }
}

سيُقرِّر المترجم الآن الاستيراد ‎import messages from './#{locale}/messages'‎‎ ليكون ‎import messages from './zh/messages'‎ للأسباب التطويريّة، وبهذا سيكون التطوير قابلًا لدعم العديد من اللغات دون التضحية بدعم وقت التصميم (design time support).

تتبّع عمليّة تقرير الوحدات

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

لنقل أنّ لدينا تطبيقًا يستخدم الوحدة ‎typescript‎. ويحتوي الملفّ ‎app.ts‎ على الاستيراد ‎import * as ts from "typescript"‎:

   tsconfig.json
├───node_modules
   └───typescript
       └───lib
               typescript.d.ts
└───src
        app.ts

تشغيل المترجم بالخيار ‎--traceResolution‎:

tsc --traceResolution

النتيجة المُخرجَة ستكون كما يلي:

======== Resolving module 'typescript' from 'src/app.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'typescript' from 'node_modules' folder.
File 'src/node_modules/typescript.ts' does not exist.
File 'src/node_modules/typescript.tsx' does not exist.
File 'src/node_modules/typescript.d.ts' does not exist.
File 'src/node_modules/typescript/package.json' does not exist.
File 'node_modules/typescript.ts' does not exist.
File 'node_modules/typescript.tsx' does not exist.
File 'node_modules/typescript.d.ts' does not exist.
Found 'package.json' at 'node_modules/typescript/package.json'.
'package.json' has 'types' field './lib/typescript.d.ts' that references 'node_modules/typescript/lib/typescript.d.ts'.
File 'node_modules/typescript/lib/typescript.d.ts' exist - use it as a module resolution result.
======== Module name 'typescript' was successfully resolved to 'node_modules/typescript/lib/typescript.d.ts'. ========
  1. يحاول المترجم أولًا تقرير الوحدة ‎'typescript'‎ التي استورِدَت في الملفّ ‎'src/app.ts'‎.
  2. استراتيجيّة التقرير غير محدّدة، سيستعمل المترجم الخيار ‎'NodeJs'‎.
  3. تحميل الوحدة ‎'typescript'‎ من مجلّد ‎'node_modules'‎.
  4. الملفّ ‎'src/node_modules/typescript.ts'‎ غير موجود.
  5. الملفّ ‎'src/node_modules/typescript.tsx'‎ غير موجود.
  6. الملفّ ‎'src/node_modules/typescript.d.ts'‎ غير موجود.
  7. الملفّ ‎'src/node_modules/typescript/package.json'‎ غير موجود.
  8. الملفّ ‎'node_modules/typescript.ts'‎ غير موجود.
  9. الملفّ ‎'node_modules/typescript.tsx'‎ غير موجود.
  10. الملفّ ‎'node_modules/typescript.d.ts'‎ غير موجود.
  11. وُجِد الملفّ ‎'package.json'‎ في ‎'node_modules/typescript/package.json'‎.
  12. يمتلك الملفّ ‎'package.json'‎ الخاصيّة ‎'types'‎ قيمتها ‎'./lib/typescript.d.ts'‎ تُشير إلى الملفّ ‎'node_modules/typescript/lib/typescript.d.ts'‎.
  13. الملفّ ‎'node_modules/typescript/lib/typescript.d.ts'‎ موجود، استعمله كنتيجة عمليّة تقرير الوحدة.
  14. قُرِّرَت الوحدة ‎'typescript'‎ بنجاح إلى الملفّ ‎'node_modules/typescript/lib/typescript.d.ts'‎.

المعلومات المهمة

- اسم ومكان الاستيراد: تقرير الوحدة ‎'typescript' التي استورِدَت في الملفّ ‎'src/app.ts'

- الاستراتيجيّة التي يستخدمها المترجم: ‎'NodeJs'

- تحميل الأنواع من حزم npm: يمتلك الملفّ ‎'package.json'‎ الخاصيّة 'types' قيمتها ‎'./lib/typescript.d.ts'‎ تُشير إلى الملفّ ‎'node_modules/typescript/lib/typescript.d.ts'‎.

- النتيجة النهائية: قُرِّرَت الوحدة ‎'typescript'بنجاح إلى الملفّ ‎'node_modules/typescript/lib/typescript.d.ts'‎.

استخدام الخيار ‎--noResolve

سيحاول المترجم افتراضيًّا تقرير جميع استيرادات الوحدات قبل بداية عمليّة الترجمة. في كل مرّة ينجح فيها تقرير استيراد ملفّ ما، فسيُضاف الملفّ إلى قائمة الملفّات التي سيُعالجها المترجم لاحقًا.

يُرشِد خيار المترجمِ ‎--noResolve‎ المترجمَ إلى عدم إضافة أي ملفّات إلى عمليّة الترجمة إلّا تلك التي تُمرَّر عبر سطر الأوامر. لكن سيحاول المترجم رغم ذلك تقرير الوحدات إلى الملفّات، لكن إن لم يُحدَّد الملفّ، فلن يُشمَل في عمليّة الترجمة. على سبيل المثال:

(الملفّ ‎app.ts‎)

// حسنًا مُرِّرت الوحدة
// 'moduleA'
// عبر سطر الأوامر
import * as A from "moduleA"

// خطأ TS2307
// لا يمكن إيجاد الوحدة
// 'moduleB'
import * as B from "moduleB"

الترجمة:

tsc app.ts moduleA.ts --noResolve

ستُنتِج ترجمة الملفّ ‎app.ts‎ باستخدام ‎--noResolve‎ النتيجة التالية:

  • العثور على الوحدة ‎moduleA‎ لأنّها مُرِّرَت عبر ‎سطر الأوامر.
  • خطأ لعدم العثور على الوحدة ‎moduleB‎ لأنها لم تُمرَّر.

أسئلة شائعة

لماذا لا تُستبعَد الوحدات الموجودة في قائمة ‎exclude‎ من طرف المترجم؟

يُحوِّل الملفّ ‎tsconfig.json‎ مجلّدًا إلى مشروع (project). إذا لم تُحدَّد الخاصيّتان ‎“exclude”‎ و‎“files”‎، فستشمل الترجمة جميع الملفّات في المجلّد الذي يحتوي على الملفّ ‎tsconfig.json‎ وجميع المجلّدات الفرعيّة داخله. إن أردت استبعاد بعض الملفّات فاستخدم الخاصيّة ‎exclude‎، وإذا أردت عوضًا عن ذلك تحديد جميع الملفّات التي ستُتَرجم وتجاهل بقيّة الملفّات عوضًا عن السماح للمترجم بالبحث عنها فاستعمل الخاصيّة ‎files‎.

هذا هو تضمين ‎tsconfig.json‎ الافتراضيّ. هذا لا يُضمِّن تقرير الوحدات كما هو موضّح أعلاه. إذا تعرَّف المترجم على ملفّ كهدف لأحد استيرادات الوحدات، فسيُضمَّن هذا الملفّ في عمليّة الترجمة بغضّ النظر عمّا إذا استُبعِد في الخطوات السابقة.

لذا فلاستبعاد ملفّ من عمليّة الترجمة، فستحتاج إلى استبعاده واستبعاد جميع الملفّات التي تحتوي على استيراد ‎import‎ يستورده أو إحالة ‎‎/// <reference path="..." />‎‎ تُشير إليه.

مصادر