Skip to content

NOTE

이 문서는 Bun 의 유지관리자와 기여자를 위한 것으로, 내부 구현 세부사항을 설명합니다.

2024 년 12 월에 코드베이스에 도입된 새로운 바인딩 생성기는 *.bind.ts 파일을 스캔하여 함수와 클래스 정의를 찾고, JavaScript 와 네이티브 코드 간 상호 운용을 위한 글루 코드를 생성합니다.

현재 유사한 목적을 달성하는 다른 코드 생성기와 시스템이 있습니다. 다음 항목들은 결국 이 하나로 완전히 대체될 것입니다.

  • "클래스 생성기", 커스텀 클래스를 위해 *.classes.ts 변환
  • "JS2Native", src/js 에서 네이티브 코드로 임시 호출 허용

Zig 에서 JS 함수 생성

간단한 함수 (예: 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` 를 반환할 수 있습니다.
        // `std.math.add` 의 `error.Overflow` 와 같은 다른 오류는 변환해야 합니다.
        // 설명을 구체적으로 작성하세요.
        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;

그런 다음 .bind.ts 함수를 사용하여 API 스키마를 설명합니다. 바인딩 파일은 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
/**
 * 인수가 0 개이면 예외를 발생시킵니다.
 * 범위를 벗어난 숫자는 모듈로를 사용하여 래핑합니다.
 */
declare function add(a: number, b: number = 1): number;

코드 생성기는 네이티브 함수 구현인 bun.gen.math.jsAdd 를 제공합니다. JavaScript 에 전달하려면 bun.gen.math.createAddCallback(global) 를 사용합니다. src/js/ 의 JS 파일은 $bindgenFn("math.bind.ts", "add") 를 사용하여 구현에 대한 핸들을 얻을 수 있습니다.

문자열

문자열을 수신하기 위한 타입은 t.DOMString, t.ByteString, t.USVString 중 하나입니다. 이들은 WebIDL 대응 항목에 직접 매핑되며 약간 다른 변환 로직을 가집니다. Bindgen 은 모든 경우에 BunString 을 네이티브 코드에 전달합니다.

잘 모르겠으면 DOMString 을 사용하세요.

t.UTF8Stringt.DOMString 대신 사용할 수 있지만 bun.String.toUTF8 을 호출합니다. 네이티브 콜백은 []const u8(WTF-8 데이터) 를 네이티브 코드에 전달하고 함수 반환 후 해제합니다.

WebIDL 스펙의 요약:

  • ByteString 은 유효한 latin1 문자만 포함할 수 있습니다. bun.String 이 이미 8 비트 형식이라고 가정하는 것은 안전하지 않지만 매우 가능성이 높습니다.
  • USVString 은 유효하지 않은 서로게이트 쌍, 즉 UTF-8 으로 올바르게 표현할 수 있는 텍스트를 포함하지 않습니다.
  • DOMString 은 가장 느슨하지만 가장 권장되는 전략입니다.

함수 변형

variants 는 여러 변형 (오버로드라고도 함) 을 지정할 수 있습니다.

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: 새로운 enum 타입을 생성하고 코드 생성
  • t.zigEnum: 코드베이스의 기존 enum 에서 bindgen 타입 파생

fmt.zig / bun:internal-for-testing 에서 사용되는 stringEnum 의 예

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 은 기존 Web API 와 일관성을 유지하기 위해 열거형 값에 케밥 케이스를 사용하는 것을 강력히 권장합니다.

Zig 코드에서 enum 파생

TODO: zigEnum

t.oneOf

oneOf 는 두 개 이상의 타입 간 유니온입니다. Zig 에서 union(enum) 으로 표현됩니다.

TODO:

속성

t.* 타입에 체이닝할 수 있는 속성 집합이 있습니다. 모든 타입에서 사용 가능한 속성은 다음과 같습니다.

  • .required, 딕셔너리 파라미터에서만 사용
  • .optional, 함수 인수에서만 사용
  • .default(T)

값이 선택적일 경우 Zig optional 로 낮아집니다.

타입에 따라 사용 가능한 속성이 더 있습니다. 자세한 내용은 자동 완성의 타입 정의를 참조하세요. 위 세 가지 중 하나만 적용할 수 있으며 마지막에 적용해야 합니다.

정수 속성

정수 타입은 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,
});

validateInteger, validateNumber 등 다양한 Node.js 검증 함수를 사용할 수 있습니다. Node.js API 를 구현할 때 오류 메시지가 Node 의 동작과 1:1 로 일치하도록 이러한 함수를 사용하세요.

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 by www.bunjs.com.cn 편집