js_component_bindgen/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2mod core;
3mod files;
4mod transpile_bindgen;
5mod ts_bindgen;
6
7pub mod esm_bindgen;
8pub mod function_bindgen;
9pub mod intrinsics;
10pub mod names;
11pub mod source;
12pub use transpile_bindgen::{AsyncMode, BindingsMode, InstantiationMode, TranspileOpts};
13
14use anyhow::Result;
15use transpile_bindgen::transpile_bindgen;
16
17use anyhow::{bail, ensure, Context};
18use wasmtime_environ::component::{ComponentTypesBuilder, Export, StaticModuleIndex};
19use wasmtime_environ::{PrimaryMap, ScopeVec, Tunables};
20use wit_component::DecodedWasm;
21
22use ts_bindgen::ts_bindgen;
23use wit_parser::{Package, Resolve, Stability, Type, TypeDefKind, TypeId, WorldId};
24
25/// Calls [`write!`] with the passed arguments and unwraps the result.
26///
27/// Useful for writing to things with infallible `Write` implementations like
28/// `Source` and `String`.
29///
30/// [`write!`]: std::write
31#[macro_export]
32macro_rules! uwrite {
33    ($dst:expr, $($arg:tt)*) => {
34        write!($dst, $($arg)*).unwrap()
35    };
36}
37
38/// Calls [`writeln!`] with the passed arguments and unwraps the result.
39///
40/// Useful for writing to things with infallible `Write` implementations like
41/// `Source` and `String`.
42///
43/// [`writeln!`]: std::writeln
44#[macro_export]
45macro_rules! uwriteln {
46    ($dst:expr, $($arg:tt)*) => {
47        writeln!($dst, $($arg)*).unwrap()
48    };
49}
50
51pub struct Transpiled {
52    pub files: Vec<(String, Vec<u8>)>,
53    pub imports: Vec<String>,
54    pub exports: Vec<(String, Export)>,
55}
56
57pub struct ComponentInfo {
58    pub imports: Vec<String>,
59    pub exports: Vec<(String, wasmtime_environ::component::Export)>,
60}
61
62pub fn generate_types(
63    name: &str,
64    resolve: Resolve,
65    world_id: WorldId,
66    opts: TranspileOpts,
67) -> Result<Vec<(String, Vec<u8>)>, anyhow::Error> {
68    let mut files = files::Files::default();
69
70    ts_bindgen(&name, &resolve, world_id, &opts, &mut files)
71        .context("failed to generate Typescript bindings")?;
72
73    let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
74    for (name, source) in files.iter() {
75        files_out.push((name.to_string(), source.to_vec()));
76    }
77    Ok(files_out)
78}
79
80/// Generate the JS transpilation bindgen for a given Wasm component binary
81/// Outputs the file map and import and export metadata for the Transpilation
82#[cfg(feature = "transpile-bindgen")]
83pub fn transpile(component: &[u8], opts: TranspileOpts) -> Result<Transpiled, anyhow::Error> {
84    use wasmtime_environ::component::{Component, Translator};
85
86    let name = opts.name.clone();
87    let mut files = files::Files::default();
88
89    // Use the `wit-component` crate here to parse `binary` and discover
90    // the type-level descriptions and `Resolve` corresponding to the
91    // component binary. This will synthesize a `Resolve` which has a top-level
92    // package which has a single document and `world` within it which describes
93    // the state of the component. This is then further used afterwards for
94    // bindings Transpilation as-if a `*.wit` file was input.
95    let decoded = wit_component::decode(component)
96        .context("failed to extract interface information from component")?;
97
98    let (resolve, world_id) = match decoded {
99        DecodedWasm::WitPackage(_, _) => bail!("unexpected wit package as input"),
100        DecodedWasm::Component(resolve, world_id) => (resolve, world_id),
101    };
102
103    // Components are complicated, there's no real way around that. To
104    // handle all the work of parsing a component and figuring out how to
105    // instantiate core wasm modules and such all the work is offloaded to
106    // Wasmtime itself. This crate generator is based on Wasmtime's
107    // low-level `wasmtime-environ` crate which is technically not a public
108    // dependency but the same author who worked on that in Wasmtime wrote
109    // this as well so... "seems fine".
110    //
111    // Note that we're not pulling in the entire Wasmtime engine here,
112    // moreso just the "spine" of validating a component. This enables using
113    // Wasmtime's internal `Component` representation as a much easier to
114    // process version of a component that has decompiled everything
115    // internal to a component to a straight linear list of initializers
116    // that need to be executed to instantiate a component.
117    let scope = ScopeVec::new();
118    let tunables = Tunables::default_u32();
119    let mut validator = wasmtime_environ::wasmparser::Validator::default();
120    let mut types = ComponentTypesBuilder::new(&validator);
121
122    let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope)
123        .translate(component)
124        .context("failed to parse the input component")?;
125
126    let modules: PrimaryMap<StaticModuleIndex, core::Translation<'_>> = modules
127        .into_iter()
128        .map(|(_i, module)| core::Translation::new(module, opts.multi_memory))
129        .collect::<Result<_>>()?;
130
131    let wasmtime_component = Component::default();
132    let types = types.finish(&wasmtime_component);
133
134    // Insert all core wasm modules into the generated `Files` which will
135    // end up getting used in the `generate_instantiate` method.
136    for (i, module) in modules.iter() {
137        files.push(&core_file_name(&name, i.as_u32()), module.wasm());
138    }
139
140    if !opts.no_typescript {
141        ts_bindgen(&name, &resolve, world_id, &opts, &mut files)
142            .context("failed to generate Typescript bindings")?;
143    }
144
145    let (imports, exports) = transpile_bindgen(
146        &name, &component, &modules, &types.0, &resolve, world_id, opts, &mut files,
147    );
148
149    let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
150    for (name, source) in files.iter() {
151        files_out.push((name.to_string(), source.to_vec()));
152    }
153    Ok(Transpiled {
154        files: files_out,
155        imports,
156        exports,
157    })
158}
159
160fn core_file_name(name: &str, idx: u32) -> String {
161    let i_str = if idx == 0 {
162        String::from("")
163    } else {
164        (idx + 1).to_string()
165    };
166    format!("{}.core{i_str}.wasm", name)
167}
168
169pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId {
170    loop {
171        match &resolve.types[id].kind {
172            TypeDefKind::Type(Type::Id(that_id)) => id = *that_id,
173            _ => break id,
174        }
175    }
176}
177
178/// Check if an item (usually some form of [`WorldItem`]) should be allowed through the feature gate
179/// of a given package.
180fn feature_gate_allowed(
181    resolve: &Resolve,
182    package: &Package,
183    stability: &Stability,
184    item_name: &str,
185) -> Result<bool> {
186    Ok(match stability {
187        Stability::Unknown => true,
188        Stability::Stable { since, .. } => {
189            let Some(package_version) = package.name.version.as_ref() else {
190                // If the package version is missing (we're likely dealing with an unresolved package)
191                // and we can't really check much.
192                return Ok(true);
193            };
194
195            ensure!(
196                package_version >= since,
197                "feature gate on [{item_name}] refers to an unreleased (future) package version [{since}] (current package version is [{package_version}])"
198            );
199
200            // Stabilization (@since annotation) overrides features and deprecation
201            true
202        }
203        Stability::Unstable {
204            feature,
205            deprecated: _,
206        } => {
207            // If a @unstable feature is present but the related feature was not enabled
208            // or all features was not selected, exclude
209            resolve.all_features || resolve.features.contains(feature)
210        }
211    })
212}
213
214/// Utility function for deducing whether a type can throw
215pub fn get_thrown_type<'a>(
216    resolve: &'a Resolve,
217    return_type: Option<Type>,
218) -> Option<(Option<&'a Type>, Option<&'a Type>)> {
219    match return_type {
220        None => None,
221        Some(ty) => match ty {
222            Type::Id(id) => match &resolve.types[id].kind {
223                TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())),
224                _ => None,
225            },
226            _ => None,
227        },
228    }
229}