External
External
is very similar to Object Wrap
, 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 that 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]
pub 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 that 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 a 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,
}
}
}