Skip to main content

rquickjs_napi/
loader.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2#[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/// Loads `.node` files as modules whose default export is the addon's `module.exports`.
15/// Enumerable string keys that are valid identifiers are also exposed as named exports.
16#[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}