External
External
(opens in a new tab) is very similar to Object Wrap
(opens in a new tab), which is used in Class under the hood.
Object Wrap
attaches a native value to a JavaScript Object and can notify you when the attached JavaScript Object is recycled by GC. External
creates an empty, blank JavaScript Object which holds the native value under the hood. It only works by passing the object back to Rust:
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
fn create_source_map(length: u32) -> External<Buffer> {
External::new(vec![0; length as usize].into())
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export class ExternalObject<T> {
readonly '': {
readonly '': unique symbol
[K: symbol]: T
}
}
export function createSourceMap(length: number): ExternalObject<Buffer>
External
is very useful when you want to return a JavaScript Object with some methods on it to interact with the native Rust code.
Here is a real-world example:
https://github.com/h-a-n-a/magic-string-rs/blob/v0.3.0/node/src/lib.rs#L96-L103 (opens in a new tab)
https://github.com/h-a-n-a/magic-string-rs/blob/v0.3.0/node/index.js#L7-L23 (opens in a new tab)
impl MagicString {
#[napi(ts_return_type = "{ toString: () => string, toUrl: () => string }")]
pub fn generate_map(
&mut self,
options: Option<magic_string::GenerateDecodedMapOptions>,
) -> Result<External<SourceMap>> {
let external = create_external(self.0.generate_map(options.unwrap_or_default())?);
Ok(external)
}
/// @internal
#[napi]
pub fn to_sourcemap_string(&mut self, sourcemap: External<SourceMap>) -> Result<String> {
Ok((*sourcemap.as_ref()).to_string()?)
}
/// @internal
#[napi]
pub fn to_sourcemap_url(&mut self, sourcemap: External<SourceMap>) -> Result<String> {
Ok((*sourcemap.as_ref()).to_url()?)
}
}
First the generate_map
method returns an External
object, and then the JavaScript function holds the External
object in closure:
module.exports.MagicString = class MagicString extends MagicStringNative {
generateMap(options) {
const sourcemap = super.generateMap({
file: null,
source: null,
sourceRoot: null,
includeContent: false,
...options,
})
const toString = () => super.toSourcemapString(sourcemap)
const toUrl = () => super.toSourcemapUrl(sourcemap)
return {
toString,
toUrl,
}
}
}