Skip to main content

componentize_qjs/
lib.rs

1pub mod codegen;
2pub mod stubwasi;
3
4use std::path::Path;
5
6use anyhow::{anyhow, Context, Result};
7use bytes::Bytes;
8use stubwasi::stub_wasi_imports;
9use wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
10use wasmtime::component::{Component, Linker, ResourceTable};
11use wasmtime::{Config, Engine, Store};
12use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
13use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
14use wasmtime_wizer::{WasmtimeWizerComponent, Wizer};
15use wit_parser::Resolve;
16
17include!(concat!(env!("OUT_DIR"), "/output.rs"));
18
19wasmtime::component::bindgen!({
20    path: "wit/init.wit",
21    world: "init",
22    exports: { default: async },
23});
24
25struct Ctx {
26    wasi: WasiCtx,
27    table: ResourceTable,
28}
29
30impl WasiView for Ctx {
31    fn ctx(&mut self) -> WasiCtxView<'_> {
32        WasiCtxView {
33            ctx: &mut self.wasi,
34            table: &mut self.table,
35        }
36    }
37}
38
39/// Options for componentizing a JavaScript source file.
40pub struct ComponentizeOpts<'a> {
41    /// Path to the WIT file or directory
42    pub wit_path: &'a Path,
43    /// JavaScript source code
44    pub js_source: &'a str,
45    /// World name to use from the WIT (None = default world)
46    pub world_name: Option<&'a str>,
47    /// Stub all WASI imports with traps
48    pub stub_wasi: bool,
49    /// Disable automatic garbage collection in the QuickJS runtime
50    pub disable_gc: bool,
51}
52
53/// Convert JavaScript source code into a WebAssembly component.
54pub async fn componentize(opts: &ComponentizeOpts<'_>) -> Result<Vec<u8>> {
55    let mut resolve = Resolve::default();
56    let (pkg_id, _) = resolve.push_path(opts.wit_path)?;
57    let world_id = resolve.select_world(&[pkg_id], opts.world_name)?;
58
59    let shim = codegen::generate_shim(&resolve, world_id);
60    let mut wit_dylib = wit_dylib::create(&resolve, world_id, None);
61
62    wit_component::embed_component_metadata(
63        &mut wit_dylib,
64        &resolve,
65        world_id,
66        wit_component::StringEncoding::UTF8,
67    )?;
68
69    let pre_wizer_component = wit_component::Linker::default()
70        .validate(true)
71        .library("componentize_qjs_runtime.wasm", RUNTIME_WASM, false)?
72        .library("wit-dylib.wasm", &wit_dylib, false)?
73        .adapter(
74            "wasi_snapshot_preview1",
75            WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER,
76        )?
77        .encode()
78        .context("failed to link and encode component")?;
79
80    let mut component =
81        wizer_init(&pre_wizer_component, &shim, opts.js_source, opts.disable_gc).await?;
82
83    if opts.stub_wasi {
84        component = stub_wasi_imports(&component).context("failed to stub WASI imports")?;
85    }
86
87    Ok(component)
88}
89
90async fn wizer_init(component: &[u8], shim: &str, js: &str, disable_gc: bool) -> Result<Vec<u8>> {
91    let stdout = MemoryOutputPipe::new(10000);
92    let stderr = MemoryOutputPipe::new(10000);
93
94    let mut wasi = WasiCtxBuilder::new();
95    let wasi = wasi
96        .stdin(MemoryInputPipe::new(Bytes::new()))
97        .stdout(stdout.clone())
98        .stderr(stderr.clone())
99        .build();
100
101    let table = ResourceTable::new();
102    let mut config = Config::new();
103    config.wasm_component_model(true);
104    config.wasm_component_model_async(true);
105
106    let engine = Engine::new(&config)?;
107    let mut store = Store::new(&engine, Ctx { wasi, table });
108
109    let wizer = Wizer::new();
110    let (cx, instrumented) = wizer.instrument_component(component)?;
111    let comp = Component::new(&engine, &instrumented)?;
112
113    let mut linker = Linker::new(&engine);
114    wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
115    let instance = linker.instantiate_async(&mut store, &comp).await?;
116
117    let init = Init::new(&mut store, &instance)?;
118    init.call_init(&mut store, shim, js, disable_gc)
119        .await?
120        .map_err(|e| anyhow!("{e}"))
121        .with_context(move || {
122            format!(
123                "{}{}",
124                String::from_utf8_lossy(&stdout.contents()),
125                String::from_utf8_lossy(&stderr.contents())
126            )
127        })?;
128
129    let component = wizer
130        .snapshot_component(
131            cx,
132            &mut WasmtimeWizerComponent {
133                store: &mut store,
134                instance,
135            },
136        )
137        .await?;
138
139    Ok(component)
140}