js_component_bindgen/
lib.rs

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