feat(napi): support export rust mod as ts namespace

This commit is contained in:
LongYinan 2021-11-23 19:00:31 +08:00
parent e4ca46f32b
commit 1fe39ff66d
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
29 changed files with 639 additions and 336 deletions
crates/napi/src/bindgen_runtime

View file

@ -21,6 +21,7 @@ pub struct i64n(pub i64);
/// <https://nodejs.org/api/n-api.html#napi_create_bigint_words>
/// The resulting BigInt is calculated as: (1)^sign_bit (words\[0\] × (2^64)^0 + words\[1\] × (2^64)^1 + …)
#[derive(Debug, Clone)]
pub struct BigInt {
/// true for negative numbers
pub sign_bit: bool,

View file

@ -9,9 +9,15 @@ pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_v
pub type ModuleExportsCallback =
unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
type ModuleRegisterCallback =
RefCell<Vec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>>;
type ModuleClassProperty =
RefCell<HashMap<&'static str, HashMap<Option<&'static str>, (&'static str, Vec<Property>)>>>;
thread_local! {
static MODULE_REGISTER_CALLBACK: RefCell<Vec<(&'static str, ExportRegisterCallback)>> = Default::default();
static MODULE_CLASS_PROPERTIES: RefCell<HashMap<&'static str, (&'static str, Vec<Property>)>> = Default::default();
static MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default();
static MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default();
static REGISTERED_CLASSES: RefCell<HashMap<
/* export name */ &'static str,
/* constructor */ sys::napi_ref,
@ -34,17 +40,27 @@ pub fn register_module_exports(callback: ModuleExportsCallback) {
MODULE_EXPORTS.with(|cell| cell.set(vec![callback]));
}
pub fn register_module_export(name: &'static str, cb: ExportRegisterCallback) {
pub fn register_module_export(
js_mod: Option<&'static str>,
name: &'static str,
cb: ExportRegisterCallback,
) {
MODULE_REGISTER_CALLBACK.with(|exports| {
let mut list = exports.borrow_mut();
list.push((name, cb));
list.push((js_mod, (name, cb)));
});
}
pub fn register_class(rust_name: &'static str, js_name: &'static str, props: Vec<Property>) {
pub fn register_class(
rust_name: &'static str,
js_mod: Option<&'static str>,
js_name: &'static str,
props: Vec<Property>,
) {
MODULE_CLASS_PROPERTIES.with(|map| {
let mut map = map.borrow_mut();
let val = map.entry(rust_name).or_default();
let val = val.entry(js_mod).or_default();
val.0 = js_name;
val.1.extend(props.into_iter());
@ -56,73 +72,158 @@ unsafe extern "C" fn napi_register_module_v1(
env: sys::napi_env,
exports: sys::napi_value,
) -> sys::napi_value {
let mut exports_objects: HashMap<Option<&'static str>, sys::napi_value> = HashMap::default();
MODULE_REGISTER_CALLBACK.with(|to_register_exports| {
to_register_exports
.take()
.iter_mut()
.fold(
HashMap::<Option<&'static str>, Vec<(&'static str, ExportRegisterCallback)>>::new(),
|mut acc, (js_mod, item)| {
if let Some(k) = acc.get_mut(js_mod) {
k.push(*item);
} else {
acc.insert(*js_mod, vec![*item]);
}
acc
},
)
.iter()
.for_each(|(name, callback)| {
let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes());
unsafe {
if let Err(e) = callback(env).and_then(|v| {
check_status!(
sys::napi_set_named_property(env, exports, js_name.as_ptr(), v),
"Failed to register export `{}`",
name,
)
}) {
JsError::from(e).throw_into(env)
.for_each(|(js_mod, items)| {
let mut exports_js_mod = ptr::null_mut();
if let Some(js_mod_str) = js_mod {
if let Some(exports_object) = exports_objects.get(js_mod) {
exports_js_mod = *exports_object;
} else {
check_status_or_throw!(
env,
sys::napi_create_object(env, &mut exports_js_mod),
"Create export JavaScript Object [{}] failed",
js_mod_str
);
check_status_or_throw!(
env,
sys::napi_set_named_property(
env,
exports,
js_mod_str.as_ptr() as *const _,
exports_js_mod
),
"Set exports Object [{}] into exports object failed",
js_mod_str
);
exports_objects.insert(*js_mod, exports_js_mod);
}
}
for (name, callback) in items {
let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes());
unsafe {
if let Err(e) = callback(env).and_then(|v| {
check_status!(
sys::napi_set_named_property(
env,
if exports_js_mod.is_null() {
exports
} else {
exports_js_mod
},
js_name.as_ptr(),
v
),
"Failed to register export `{}`",
name,
)
}) {
JsError::from(e).throw_into(env)
}
}
}
})
});
MODULE_CLASS_PROPERTIES.with(|to_register_classes| {
for (rust_name, (js_name, props)) in to_register_classes.take().into_iter() {
unsafe {
let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor);
// one or more or zero?
// zero is for `#[napi(task)]`
if ctor.is_empty() && props.is_empty() {
continue;
}
let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop);
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
for (rust_name, js_mods) in to_register_classes.take().iter() {
for (js_mod, (js_name, props)) in js_mods {
let mut exports_js_mod = ptr::null_mut();
unsafe {
if let Some(js_mod_str) = js_mod {
if let Some(exports_object) = exports_objects.get(js_mod) {
exports_js_mod = *exports_object;
} else {
check_status_or_throw!(
env,
sys::napi_create_object(env, &mut exports_js_mod),
"Create export JavaScript Object [{}] failed",
js_mod_str
);
check_status_or_throw!(
env,
sys::napi_set_named_property(
env,
exports,
js_mod_str.as_ptr() as *const _,
exports_js_mod
),
"Set exports Object [{}] into exports object failed",
js_mod_str
);
exports_objects.insert(*js_mod, exports_js_mod);
}
}
let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor);
// one or more or zero?
// zero is for `#[napi(task)]`
if ctor.is_empty() && props.is_empty() {
continue;
}
let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop);
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes());
let mut class_ptr = ptr::null_mut();
let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes());
let mut class_ptr = ptr::null_mut();
check_status_or_throw!(
env,
sys::napi_define_class(
check_status_or_throw!(
env,
js_class_name.as_ptr(),
js_name.len(),
Some(ctor),
ptr::null_mut(),
raw_props.len(),
raw_props.as_ptr(),
&mut class_ptr,
),
"Failed to register class `{}` generate by struct `{}`",
&js_name,
&rust_name
);
sys::napi_define_class(
env,
js_class_name.as_ptr(),
js_name.len(),
Some(ctor),
ptr::null_mut(),
raw_props.len(),
raw_props.as_ptr(),
&mut class_ptr,
),
"Failed to register class `{}` generate by struct `{}`",
&js_name,
&rust_name
);
let mut ctor_ref = ptr::null_mut();
sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
let mut ctor_ref = ptr::null_mut();
sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
REGISTERED_CLASSES.with(|registered_classes| {
let mut registered_class = registered_classes.borrow_mut();
registered_class.insert(js_name, ctor_ref);
});
REGISTERED_CLASSES.with(|registered_classes| {
let mut registered_class = registered_classes.borrow_mut();
registered_class.insert(js_name, ctor_ref);
});
check_status_or_throw!(
env,
sys::napi_set_named_property(env, exports, js_class_name.as_ptr(), class_ptr),
"Failed to register class `{}` generate by struct `{}`",
&js_name,
&rust_name
);
check_status_or_throw!(
env,
sys::napi_set_named_property(
env,
if exports_js_mod.is_null() {
exports
} else {
exports_js_mod
},
js_class_name.as_ptr(),
class_ptr
),
"Failed to register class `{}` generate by struct `{}`",
&js_name,
&rust_name
);
}
}
}
});