Skip to content

حل الوحدات النمطية في JavaScript هو موضوع معقد.

النظام البيئي حاليًا في خضم انتقال طويل الأمد من وحدات CommonJS إلى وحدات ES الأصلية. يفرض TypeScript مجموعة خاصة به من القواعد حول امتدادات الاستيراد التي لا تتوافق مع ESM. تدعم أدوات البناء المختلفة إعادة تعيين المسارات عبر آليات غير متوافقة متباينة.

يهدف Bun إلى توفير نظام حل وحدات نمطية متسق ويمكن التنبؤ به يعمل فقط. لسوء الحظ لا يزال معقدًا إلى حد ما.

الصيغة

ضع في اعتبارك الملفات التالية.

ts
import { hello } from "./hello";

hello();
ts
export function hello() {
  console.log("Hello world!");
}

عند تشغيل index.ts، يطبع "Hello world!".

bash
bun index.ts
Hello world!

في هذه الحالة، نستورد من ./hello، مسار نسبي بدون امتداد. الاستيرادات ذات الامتدادات اختيارية ولكنها مدعومة. لحل هذا الاستيراد، سيتحقق Bun من الملفات التالية بالترتيب:

  • ./hello.tsx
  • ./hello.jsx
  • ./hello.ts
  • ./hello.mjs
  • ./hello.js
  • ./hello.cjs
  • ./hello.json
  • ./hello/index.tsx
  • ./hello/index.jsx
  • ./hello/index.ts
  • ./hello/index.mjs
  • ./hello/index.js
  • ./hello/index.cjs
  • ./hello/index.json

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

ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // هذا يعمل

إذا قمت بالاستيراد from "*.js{x}"، فسيبحث Bun أيضًا عن ملف *.ts{x} مطابق، ليكون متوافقًا مع دعم وحدة ES الخاص بـ TypeScript.

ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // هذا يعمل
import { hello } from "./hello.js"; // هذا يعمل أيضًا

يدعم Bun كلاً من وحدات ES (صيغة import/export) ووحدات CommonJS (require()/module.exports). سيعمل إصدار CommonJS التالي أيضًا في Bun.

js
const { hello } = require("./hello");

hello();
js
function hello() {
  console.log("Hello world!");
}

exports.hello = hello;

مع ذلك، يُنصح بعدم استخدام CommonJS في المشاريع الجديدة.


أنظمة الوحدات النمطية

يدعم Bun وحدات CommonJS و ESModules بشكل أصلي. وحدات ES هي تنسيق الوحدة الموصى به للمشاريع الجديدة، لكن وحدات CommonJS لا تزال مستخدمة على نطاق واسع في نظام Node.js البيئي.

في وقت تشغيل JavaScript الخاص بـ Bun، يمكن استخدام require بواسطة كل من وحدات ESModules ووحدات CommonJS. إذا كانت الوحدة المستهدفة هي وحدة ES، ترجع require كائن مساحة اسم الوحدة (ما يعادل import * as). إذا كانت الوحدة المستهدفة هي وحدة CommonJS، ترجع require كائن module.exports (كما في Node.js).

نوع الوحدةrequire()import * as
وحدة ESمساحة اسم الوحدةمساحة اسم الوحدة
CommonJSmodule.exportsdefault هو module.exports، مفاتيح module.exports هي تصديرات مسماة

استخدام require()

يمكنك require() أي ملف أو حزمة، حتى ملفات .ts أو .mjs.

index.ts
ts
const { foo } = require("./foo"); // الامتدادات اختيارية
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");

ما هي وحدة CommonJS؟">

في عام 2016، أضاف ECMAScript دعم وحدات ES. وحدات ES هي المعيار لوحدات JavaScript. ومع ذلك، لا تزال ملايين حزم npm تستخدم وحدات CommonJS.

وحدات CommonJS هي وحدات تستخدم module.exports لتصدير القيم. عادةً، يتم استخدام require لاستيراد وحدات CommonJS.

ts
const stuff = require("./stuff");
module.exports = { stuff };

أكبر فرق بين CommonJS ووحدات ES هو أن وحدات CommonJS متزامنة، بينما وحدات ES غير متزامنة. هناك فروق أخرى أيضًا.

  • وحدات ES تدعم await على المستوى العلوي ووحدات CommonJS لا تدعمه.
  • وحدات ES دائمًا في الوضع الصارم، بينما وحدات CommonJS ليست كذلك.
  • المتصفحات لا تدعم وحدات CommonJS بشكل أصلي، لكنها تدعم وحدات ES بشكل أصلي عبر <script type="module">.
  • وحدات CommonJS لا يمكن تحليلها ثابتًا، بينما وحدات ES تسمح فقط بالاستيرادات والتصدير الثابت.

وحدات CommonJS: هي نوع من نظام الوحدات المستخدم في JavaScript. إحدى الميزات الرئيسية لوحدات CommonJS هي أنها تُحمّل وتُنفذ بشكل متزامن. هذا يعني أنه عند استيراد وحدة CommonJS، يتم تشغيل الكود في تلك الوحدة فورًا، وينتظر برنامجك حتى ينتهي قبل الانتقال إلى المهمة التالية. يشبه ذلك قراءة كتاب من البداية إلى النهاية دون تخطي الصفحات.

وحدات ES (ESM): هي نوع آخر من نظام الوحدات تم تقديمه في JavaScript. لديها سلوك مختلف قليلاً مقارنة بـ CommonJS. في ESM، الاستيرادات الثابتة (الاستيرادات التي تتم باستخدام عبارات import) متزامنة، تمامًا مثل CommonJS. هذا يعني أنه عند استيراد ESM باستخدام عبارة import عادية، يتم تشغيل الكود في تلك الوحدة فورًا، ويستمر برنامجك خطوة بخطوة. فكر في الأمر مثل قراءة كتاب صفحة تلو الأخرى.

الاستيرادات الديناميكية: الآن، هنا يأتي الجزء الذي قد يكون مربكًا. تدعم وحدات ES أيضًا استيراد الوحدات في اللحظة عبر الدالة import(). هذا يسمى "استيراد ديناميكي" وهو غير متزامن، لذا لا يمنع تنفيذ البرنامج الرئيسي. بدلاً من ذلك، يجلب ويحمّل الوحدة في الخلفية بينما يستمر برنامجك في التشغيل. بمجرد أن تصبح الوحدة جاهزة، يمكنك استخدامها. هذا مثل الحصول على معلومات إضافية من كتاب بينما لا تزال تقرأه، دون الحاجة إلى إيقاف قراءتك.

باختصار:

  • وحدات CommonJS ووحدات ES الثابتة (عبارات import) تعملان بطريقة متزامنة متشابهة، مثل قراءة كتاب من البداية إلى النهاية.
  • توفر وحدات ES أيضًا خيار استيراد الوحدات بشكل غير متزامن باستخدام الدالة import(). هذا مثل البحث عن معلومات إضافية في منتصف قراءة الكتاب دون التوقف.

استخدام import

يمكنك import أي ملف أو حزمة، حتى ملفات .cjs.

index.ts
ts
import { foo } from "./foo"; // الامتدادات اختيارية
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";

استخدام import و require() معًا

في Bun، يمكنك استخدام import أو require في نفس الملف - كلاهما يعمل طوال الوقت.

index.ts
ts
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";

const myStuff = require("./my-commonjs.cjs");

await على المستوى العلوي

الاستثناء الوحيد لهذه القاعدة هو await على المستوى العلوي. لا يمكنك require() ملف يستخدم await على المستوى العلوي، لأن دالة require() متزامنة بطبيعتها.

لحسن الحظ، عدد قليل جدًا من المكتبات تستخدم await على المستوى العلوي، لذا نادرًا ما تكون هذه مشكلة. لكن إذا كنت تستخدم await على المستوى العلوي في كود التطبيق الخاص بك، فتأكد من عدم require() هذا الملف من مكان آخر في تطبيقك. بدلاً من ذلك، يجب استخدام import أو import() الديناميكي.


استيراد الحزم

يطبق Bun خوارزمية حل الوحدات النمطية الخاصة بـ Node.js، لذا يمكنك استيراد الحزم من node_modules باستخدام محدد مجرد.

index.ts
ts
import { stuff } from "foo";

تم توثيق المواصفات الكاملة لهذه الخوارزمية رسميًا في توثيق Node.js؛ لن نكررها هنا. باختصار: إذا قمت بالاستيراد from "foo"، يمسح Bun نظام الملفات لأعلى للبحث عن دليل node_modules يحتوي على الحزمة foo.

NODE_PATH

يدعم Bun NODE_PATH لأدلة حل الوحدات النمطية الإضافية:

bash
NODE_PATH=./packages bun run src/index.js
ts
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";

تستخدم المسارات المتعددة فاصل المنصة (: في Unix، ; في Windows):

bash
NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows

بمجرد العثور على حزمة foo، يقرأ Bun package.json لتحديد كيفية استيراد الحزمة. لتحديد نقطة دخول الحزمة، يقرأ Bun أولاً حقل exports ويتحقق من الشروط التالية.

json
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node": "./index.js",
    "require": "./index.js", // إذا كان المستورد CommonJS
    "import": "./index.mjs", // إذا كان المستورد وحدة ES
    "default": "./index.js"
  }
}

أيًا من هذه الشروط يحدث أولاً في package.json يُستخدم لتحديد نقطة دخول الحزمة.

يحترم Bun المسارات الفرعية "exports" و "imports".

json
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}

تعمل استيرادات المسارات الفرعية والاستيرادات الشرطية معًا.

json
{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}

كما في Node.js، تحديد أي مسار فرعي في خريطة "exports" سيمنع استيراد المسارات الفرعية الأخرى؛ يمكنك فقط استيراد الملفات المصدرة صراحةً. بالنظر إلى package.json أعلاه:

ts
import stuff from "foo"; // هذا يعمل
import stuff from "foo/index.mjs"; // هذا لا يعمل

NOTE

**نشر TypeScript** — لاحظ أن Bun يدعم شرط التصدير `"bun"` الخاص. إذا كانت مكتبتك مكتوبة بـ TypeScript، يمكنك نشر ملفات TypeScript الخاصة بك (غير المحولة!) إلى `npm` مباشرةً. إذا حددت نقطة دخول `*.ts` لحزمتك في شرط `"bun"`، فسيقوم Bun باستيراد وتنفيذ ملفات مصدر TypeScript الخاصة بك مباشرةً.

إذا لم يتم تعريف exports، يعود Bun إلى "module" (استيرادات ESM فقط) ثم "main".

json
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}

شروط مخصصة

يسمح لك العلم --conditions بتحديد قائمة من الشروط لاستخدامها عند حل الحزم من package.json "exports".

هذا العلم مدعوم في كل من bun build ووقت تشغيل Bun.

sh
# استخدمه مع bun build:
bun build --conditions="react-server" --target=bun ./app/foo/route.js

# استخدمه مع وقت تشغيل bun:
bun --conditions="react-server" ./app/foo/route.js

يمكنك أيضًا استخدام conditions برمجيًا مع Bun.build:

ts
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});

إعادة تعيين المسارات

يدعم Bun إعادة تعيين مسارات الاستيراد من خلال compilerOptions.paths الخاص بـ TypeScript في tsconfig.json، والذي يعمل بشكل جيد مع المحررين. إذا لم تكن مستخدم TypeScript، يمكنك تحقيق نفس السلوك باستخدام jsconfig.json في جذر مشروعك.

json
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"], // تعيين المحدد إلى الملف
      "components/*": ["components/*"] // مطابقة البدل
    }
  }
}

يدعم Bun أيضًا استيرادات المسارات الفرعية بنمط Node.js في package.json، حيث يجب أن تبدأ المسارات المعينة بـ #. هذا النهج لا يعمل بشكل جيد مع المحررين، ولكن يمكن استخدام كلا الخيارين معًا.

json
{
  "imports": {
    "#config": "./config.ts", // تعيين المحدد إلى الملف
    "#components/*": "./components/*" // مطابقة البدل
  }
}

تفاصيل منخفضة المستوى لـ CommonJS interop في Bun">

يدعم وقت تشغيل JavaScript الخاص بـ Bun وحدات CommonJS بشكل أصلي. عندما يكتشف المحول البرمجي لـ JavaScript استخدامات module.exports، يعامل الملف كـ CommonJS. سيقوم محمّل الوحدة بعد ذلك بتغليف الوحدة المحولة في دالة بهذا الشكل:

js
(function (module, exports, require) {
  // الوحدة المحولة
})(module, exports, require);

module و exports و require تشبه إلى حد كبير module و exports و require في Node.js. يتم تعيين هذه عبر with scope في C++. تخزن Map داخلية كائن exports للتعامل مع استدعاءات require الدائرية قبل تحميل الوحدة بالكامل.

بمجرد تقييم وحدة CommonJS بنجاح، يتم إنشاء سجل وحدة اصطناعي مع تصدير وحدة ES default مضبوط على module.exports ويتم إعادة تصدير مفاتيح كائن module.exports كتصدير مسماة (إذا كان كائن module.exports كائنًا).

عند استخدام أداة تجميع Bun، يعمل هذا بشكل مختلف. ستقوم أداة التجميع بتغليف وحدة CommonJS في دالة require_${moduleName} ترجع كائن module.exports.


import.meta

كائن import.meta هو طريقة للوحدة للوصول إلى معلومات حول نفسها. هو جزء من لغة JavaScript، لكن محتوياته ليست موحدة. كل "مضيف" (متصفح، وقت تشغيل، إلخ) حر في تنفيذ أي خصائص يرغب فيها على كائن import.meta.

يطبق Bun الخصائص التالية.

ts
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"

import.meta.main; // `true` إذا تم تنفيذ هذا الملف مباشرة بواسطة `bun run`
// `false` خلاف ذلك

import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
الخاصيةالوصف
import.meta.dirالمسار المطلق للدليل الذي يحتوي على الملف الحالي، مثل /path/to/project. يعادل __dirname في وحدات CommonJS (و Node.js)
import.meta.dirnameاسم مستعار لـ import.meta.dir، لتوافق Node.js
import.meta.envاسم مستعار لـ process.env.
import.meta.fileاسم الملف الحالي، مثل index.tsx
import.meta.pathالمسار المطلق للملف الحالي، مثل /path/to/project/index.ts. يعادل __filename في وحدات CommonJS (و Node.js)
import.meta.filenameاسم مستعار لـ import.meta.path، لتوافق Node.js
import.meta.mainيشير إلى ما إذا كان الملف الحالي هو نقطة الدخول لعملية bun الحالية. هل يتم تنفيذ الملف مباشرة بواسطة bun run أم يتم استيراده؟
import.meta.resolveحل محدد وحدة (مثل "zod" أو "./file.tsx") إلى عنوان url. يعادل import.meta.resolve في المتصفحات. مثال: import.meta.resolve("zod") ترجع "file:///path/to/project/node_modules/zod/index.ts"
import.meta.urlعنوان url string للملف الحالي، مثل file:///path/to/project/index.ts. يعادل import.meta.url في المتصفحات

Bun بواسطة www.bunjs.com.cn تحرير