الفرق بين require و import في TypeScript: دليل شامل للمبتدئين

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

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

ابدأ الدورة

require(x) vs import x: لماذا يمثل هذا صداعًا دائمًا لمطوري TypeScript

\n\n

إذا سبق لك أن كتبت كودًا بلغة TypeScript ورأيت خطأ مثل Cannot use import statement outside a module أو require is not defined — فأنت لست وحدك. على موقع Stack Overflow، سؤال "الفرق بين 'require(x)' و 'import x'" حصد عشرات الآلاف من المشاهدات. وهذا ليس من قبيل الصدفة: الخلط بين نظامي الوحدات (modules) هو أحد أكثر الأخطاء شيوعًا التي يقع فيها حتى المطورون ذوو الخبرة.

\n\n

في هذا الدليل التعليمي، لن نقتصر على سرد الاختلافات فقط. بل سنأخذك في رحلة من عدم الفهم التام إلى الاختيار الواثق للصيغة الصحيحة. ستتعلم كيف يقوم TypeScript بترجمة الكود الخاص بك، ولماذا لا يزال require مستخدمًا حتى اليوم، ومتى يجب استخدام import.

\n\n

1. السياق التاريخي: من أين أتى النظامان

\n\n

لفهم الفرق، يجب أن نلقي نظرة على تاريخ JavaScript. في البداية، لم تكن Node.js تحتوي على نظام وحدات مدمج. ابتكر المطورون CommonJS — وهي طريقة متزامنة (synchronous) لاستيراد الوحدات باستخدام require(). هذا الأسلوب يعمل بشكل جيد على الخادم (server-side)، لكنه لم يكن مناسبًا للمتصفحات التي تحتاج إلى تحميل غير متزامن (asynchronous).

\n\n

لاحقًا، ظهر معيار ES Modules (ESM) — وهو صيغة الوحدات الرسمية لـ ECMAScript. TypeScript، باعتبارها لغة مبنية فوق JavaScript، تدعم كلا الخيارين. ولكن هنا يبدأ الارتباك: تكتب import، والمترجم (compiler) يحوله إلى require — أو العكس.

\n\n

2. الصيغة (Syntax): الفروق الرئيسية

\n\n

دعنا نوضح الفروق بين الأسلوبين.

\n\n

CommonJS (require)

\n\n
// استيراد الوحدة بأكملها\nconst fs = require('fs');\n\n// استيراد جزء من الوحدة (تفكيك)\nconst { readFile, writeFile } = require('fs');\n\n// التصدير\nmodule.exports = { myFunction, myVariable };\nexports.myFunction = () => { ... };
\n\n

ES Modules (import)

\n\n
// استيراد الوحدة بأكملها\nimport fs from 'fs';\n\n// استيراد الصادرات المسماة\nimport { readFile, writeFile } from 'fs';\n\n// استيراد الكل ككائن\nimport * as fs from 'fs';\n\n// التصدير\nexport const myVariable = 42;\nexport function myFunction() { ... }\nexport default class MyClass { }
\n\n

للوهلة الأولى، الفرق يبدو فقط في الصيغة. لكن دعنا نتعمق أكثر.

\n\n

3. الاستيراد الثابت (Static) مقابل الاستيراد الديناميكي (Dynamic)

\n\n

هذا هو الفرق الجوهري الذي يؤثر على طريقة عمل الكود الخاص بك.

\n\n
    \n
  • require — ديناميكي. يمكنك استدعاؤه في أي مكان، داخل شرط أو دالة. يتم تحميل الوحدة في وقت الاستدعاء.
  • \n
  • import — ثابت. يتم حل (resolve) عمليات الاستيراد في مرحلة الترجمة (compile time)، قبل تنفيذ الكود. يجب أن تكون دائمًا في المستوى الأعلى من الملف.
  • \n
\n\n

مثال على require الديناميكي:

\n\n
let myModule;\nif (process.env.NODE_ENV === 'production') {\n  myModule = require('./production-config');\n} else {\n  myModule = require('./dev-config');\n}
\n\n

هذه الحيلة لا تعمل مع import — ستظهر أخطاء نحوية. ولكن لهذا الغرض يوجد import() الديناميكي (الذي يُرجع Promise):

\n\n
if (condition) {\n  const module = await import('./some-module');\n  module.doSomething();\n}
\n\n

4. كيف يقوم TypeScript بترجمة الوحدات

\n\n

كل شيء يعتمد على إعداد module في ملف tsconfig.json. إليك الخيارات الرئيسية:

\n\n
    \n
  • "module": "commonjs" — سيتم تحويل import الخاص بك إلى require. سيعمل الكود في Node.js دون أي إعدادات إضافية.
  • \n
  • "module": "esnext" — سيبقى import كما هو. مناسب للمتصفحات الحديثة أو أدوات التجميع (مثل Webpack و Vite).
  • \n
  • "module": "node16" أو "nodenext" — سيقوم TypeScript بتحديد النظام المناسب تلقائيًا.
  • \n

المدونات

توصيات الكتب