Skip to content

NOTE

هذه الوثيقة مخصصة للمشرفين والمساهمين في Bun، وتصف تفاصيل التنفيذ الداخلية.

مولد الروابط الجديد، الذي تم تقديمه إلى قاعدة الكود في ديسمبر 2024، يفحص ملفات *.bind.ts للعثور على تعريفات الدوال والفئات، وينشئ كود الربط للتفاعل بين JavaScript والكود الأصلي.

يوجد حاليًا مولدات كود وأنظمة أخرى تحقق أغراضًا مماثلة. سيتم استبدال جميع ما يلي تمامًا لصالح هذا النظام في النهاية:

  • "مولد الفئات"، لتحويل *.classes.ts للفئات المخصصة.
  • "JS2Native"، للسماح باستدعاءات مخصصة من src/js إلى الكود الأصلي.

إنشاء دوال JS في Zig

بالنظر إلى ملف يطبق دالة بسيطة، مثل add:

zig
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
    return std.math.add(i32, a, b) catch {
        // يمكن لدوال الربط إرجاع `error.OutOfMemory` و `error.JSError`.
        // يجب تحويل الأخطاء الأخرى مثل `error.Overflow` من `std.math.add`.
        // تذكر أن تكون وصفيًا.
        return global.throwPretty("Integer overflow while adding", .{});
    };
}

const gen = bun.gen.math; // "math" هو الاسم الأساسي لهذا الملف

const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;

ثم صف مخطط API باستخدام دالة .bind.ts. يذهب ملف الربط بجوار ملف Zig.

ts
import { t, fn } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    a: t.i32,
    b: t.i32.default(1),
  },
  ret: t.i32,
});

تعريف هذه الدالة يعادل:

ts
/**
 * يرمي خطأ إذا لم يتم تقديم أي وسيطات.
 * يغلف الأرقام خارج النطاق باستخدام modulo.
 */
declare function add(a: number, b: number = 1): number;

سيقوم مولد الكود بتوفير bun.gen.math.jsAdd، وهو تنفيذ الدالة الأصلية. لتمريرها إلى JavaScript، استخدم bun.gen.math.createAddCallback(global). ملفات JS في src/js/ قد تستخدم $bindgenFn("math.bind.ts", "add") للحصول على مقبض للتنفيذ.

السلاسل النصية

نوع استقبال السلاسل النصية هو أحد t.DOMString، t.ByteString، و t.USVString. هذه تتطابق مباشرة مع نظيراتها في WebIDL، ولديها منطق تحويل مختلف قليلاً. سيقوم Bindgen بتمرير BunString إلى الكود الأصلي في جميع الحالات.

عند الشك، استخدم DOMString.

يمكن استخدام t.UTF8String بدلاً من t.DOMString، لكنه سيستدعي bun.String.toUTF8. يتم تمرير []const u8 (بيانات WTF-8) إلى استدعاء الكود الأصلي، ويتم تحريرها بعد إرجاع الدالة.

ملخصات من مواصفات WebIDL:

  • ByteString يمكن أن يحتوي فقط على أحرف latin1 صالحة. ليس من الآمن افتراض أن bun.String بالفعل بتنسيق 8 بت، لكنه مرجح للغاية.
  • USVString لن يحتوي على أزواج بديلة غير صالحة، أي نص يمكن تمثيله بشكل صحيح في UTF-8.
  • DOMString هي الاستراتيجية الأكثر مرونة ولكن أيضًا الأكثر توصية.

متغيرات الدالة

يمكن لـ variants تحديد متغيرات متعددة (تُعرف أيضًا باسم overloads).

ts
import { t, fn } from "bindgen";

export const action = fn({
  variants: [
    {
      args: {
        a: t.i32,
      },
      ret: t.i32,
    },
    {
      args: {
        a: t.DOMString,
      },
      ret: t.DOMString,
    },
  ],
});

في Zig، يحصل كل متغير على رقم، بناءً على الترتيب الذي يحدده المخطط.

zig
fn action1(a: i32) i32 {
  return a;
}

fn action2(a: bun.String) bun.String {
  return a;
}

t.dictionary

الـ dictionary هو تعريف لكائن JavaScript، عادةً كمدخلات دالة. لمخرجات الدالة، عادةً ما تكون فكرة أكثر ذكاءً إعلان نوع فئة لإضافة دوال وإلغاء البنية.

التعدادات

لاستخدام نوع تعداد WebIDL، استخدم إما:

  • t.stringEnum: إنشاء وتوليد كود لنوع تعداد جديد.
  • t.zigEnum: اشتقاق نوع bindgen من تعداد موجود في قاعدة الكود.

مثال على stringEnum كما هو مستخدم في fmt.zig / bun:internal-for-testing:

ts
export const Formatter = t.stringEnum("highlight-javascript", "escape-powershell");

export const fmtString = fn({
  args: {
    global: t.globalObject,
    code: t.UTF8String,
    formatter: Formatter,
  },
  ret: t.DOMString,
});

تشجع WebIDL بشدة استخدام حالة kebab لقيم التعداد، لتكون متسقة مع واجهات برمجة التطبيقات للويب الموجودة.

اشتقاق التعدادات من كود Zig

TODO: zigEnum

t.oneOf

الـ oneOf هو اتحاد بين نوعين أو أكثر. يتم تمثيله بواسطة union(enum) في Zig.

TODO:

السمات

هناك مجموعة من السمات التي يمكن ربطها بأنواع t.*. على جميع الأنواع هناك:

  • .required، في معلمات dictionary فقط
  • .optional، في وسيطات الدالة فقط
  • .default(T)

عندما تكون القيمة اختيارية، يتم تخفيضها إلى اختيارية Zig.

اعتمادًا على النوع، هناك سمات أكثر متاحة. راجع تعريفات الأنواع في الإكمال التلقائي لمزيد من التفاصيل. لاحظ أنه يمكن تطبيق واحد فقط من الثلاثة أعلاه، ويجب تطبيقها في النهاية.

سمات الأعداد الصحيحة

تسمح أنواع الأعداد الصحيحة بتخصيص سلوك التجاوز مع clamp أو enforceRange:

ts
import { t, fn } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // فرض في نطاق i32
    a: t.i32.enforceRange(),
    // تقييد إلى نطاق u16
    b: t.u16,
    // فرض في نطاق تعسفي، مع قيمة افتراضية إذا لم يتم تقديمها
    c: t.i32.enforceRange(0, 1000).default(5),
    // تقييد إلى نطاق تعسفي، أو null
    d: t.u16.clamp(0, 10).optional,
  },
  ret: t.i32,
});

تتوفر دوال تحقق مختلفة لـ Node.js مثل validateInteger و validateNumber والمزيد. استخدم هذه عند تنفيذ واجهات برمجة تطبيقات Node.js، حتى تتطابق رسائل الخطأ 1:1 مع ما يفعله Node.

على عكس enforceRange، المأخوذ من WebIDL، دوال validate* أكثر صرامة على المدخلات التي تقبلها. على سبيل المثال، محقق الأرقام في Node يتحقق من typeof value === 'number'، بينما تستخدم WebIDL ToNumber للتحويل الخاسر.

ts
import { t, fn } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // رمي خطأ إذا لم يتم تقديم رقم
    a: t.f64.validateNumber(),
    // صالح في نطاق i32
    a: t.i32.validateInt32(),
    // f64 ضمن نطاق الأعداد الصحيحة الآمنة
    b: t.f64.validateInteger(),
    // f64 في النطاق المعطى
    c: t.f64.validateNumber(-10000, 10000),
  },
  ret: t.i32,
});

الاستدعاءات الخلفية

TODO

الفئات

TODO

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