1#[cfg(not(unix))]
3use rquickjs::Exception;
4#[cfg(unix)]
5use rquickjs::Object;
6use rquickjs::{
7 loader::{ImportAttributes, Loader},
8 Ctx, Error, Module, Result,
9};
10use tracing::trace;
11
12const ADDON_EXT: &str = ".node";
13
14#[derive(Debug, Default)]
17pub struct NapiLoader;
18
19#[cfg(unix)]
20fn is_identifier(key: &str) -> bool {
21 let mut chars = key.chars();
22 chars
23 .next()
24 .is_some_and(|c| c == '_' || c == '$' || c.is_ascii_alphabetic())
25 && chars.all(|c| c == '_' || c == '$' || c.is_ascii_alphanumeric())
26 && key != "default"
27}
28
29#[cfg(unix)]
30fn load_module<'js>(ctx: &Ctx<'js>, name: &str) -> Result<Module<'js>> {
31 let exports = crate::api::load_addon(ctx, name)?;
32
33 let mut source = String::from("const __addon = import.meta.addon;\nexport default __addon;\n");
34 if let Some(obj) = exports.as_object().filter(|o| !o.is_function()) {
35 for key in obj.keys::<String>() {
36 let key = key?;
37 if is_identifier(&key) && key != "__addon" {
38 source.push_str(&format!("export const {key} = __addon.{key};\n"));
39 }
40 }
41 }
42
43 let module = Module::declare(ctx.clone(), name, source)?;
44 let meta: Object = module.meta()?;
45 meta.set("addon", exports)?;
46 Ok(module)
47}
48
49#[cfg(not(unix))]
50fn load_module<'js>(ctx: &Ctx<'js>, _name: &str) -> Result<Module<'js>> {
51 Err(Exception::throw_message(
52 ctx,
53 "Native addons are not supported on this platform",
54 ))
55}
56
57impl Loader for NapiLoader {
58 fn load<'js>(
59 &mut self,
60 ctx: &Ctx<'js>,
61 name: &str,
62 _attr: Option<ImportAttributes<'js>>,
63 ) -> Result<Module<'js>> {
64 if !name.ends_with(ADDON_EXT) {
65 return Err(Error::new_loading(name));
66 }
67 trace!("Loading native addon: {}", name);
68 load_module(ctx, name)
69 }
70}