componentize_qjs_cli/
cli.rs1use componentize_qjs::{componentize, ComponentizeOpts};
2
3use anyhow::{Context, Result};
4use clap::Parser;
5use oxc_allocator::Allocator;
6use oxc_codegen::Codegen;
7use oxc_minifier::{
8 CompressOptions, CompressOptionsKeepNames, CompressOptionsUnused, MangleOptions, Minifier,
9 MinifierOptions,
10};
11use oxc_parser::Parser as OxcParser;
12use oxc_span::SourceType;
13
14use std::fs;
15
16#[derive(Parser)]
17#[command(name = "componentize-qjs")]
18#[command(about = "Convert JavaScript to WebAssembly components using QuickJS")]
19pub struct CliArgs {
20 #[arg(short, long)]
22 pub wit: std::path::PathBuf,
23
24 #[arg(short, long)]
26 pub js: std::path::PathBuf,
27
28 #[arg(short, long, default_value = "output.wasm")]
30 pub output: std::path::PathBuf,
31
32 #[arg(short = 'n', long)]
34 pub world: Option<String>,
35
36 #[arg(long)]
38 pub stub_wasi: bool,
39
40 #[arg(short = 'm', long)]
42 pub minify: bool,
43
44 #[arg(long)]
46 pub disable_gc: bool,
47}
48
49pub async fn run(args: Vec<String>) -> Result<()> {
51 let args =
52 CliArgs::try_parse_from(std::iter::once("componentize-qjs".to_string()).chain(args))?;
53
54 if !args.wit.exists() {
55 anyhow::bail!("WIT file/directory not found: {}", args.wit.display());
56 }
57 if !args.js.exists() {
58 anyhow::bail!("JavaScript file not found: {}", args.js.display());
59 }
60
61 let js_source = fs::read_to_string(&args.js)
62 .with_context(|| format!("failed to read JS file: {}", args.js.display()))?;
63
64 let js_source = if args.minify {
65 let allocator = Allocator::default();
66 let source_type = SourceType::mjs();
67 let ret = OxcParser::new(&allocator, &js_source, source_type).parse();
68 let mut program = ret.program;
69
70 let options = MinifierOptions {
71 mangle: Some(MangleOptions {
72 top_level: Some(false),
73 ..Default::default()
74 }),
75 compress: Some(CompressOptions {
76 unused: CompressOptionsUnused::Keep,
77 keep_names: CompressOptionsKeepNames::all_false(),
78 ..CompressOptions::default()
79 }),
80 };
81 let ret = Minifier::new(options).minify(&allocator, &mut program);
82 Codegen::new()
83 .with_scoping(ret.scoping)
84 .build(&program)
85 .code
86 } else {
87 js_source
88 };
89
90 println!("componentize-qjs");
91 println!(" WIT: {}", args.wit.display());
92 println!(" JS: {}", args.js.display());
93 println!(" Output: {}", args.output.display());
94
95 if args.stub_wasi {
96 println!("Stubbing WASI imports...");
97 }
98
99 let component = componentize(&ComponentizeOpts {
100 wit_path: &args.wit,
101 js_source: &js_source,
102 world_name: args.world.as_deref(),
103 stub_wasi: args.stub_wasi,
104 disable_gc: args.disable_gc,
105 })
106 .await?;
107
108 fs::write(&args.output, &component)
109 .with_context(|| format!("failed to write output to {}", args.output.display()))?;
110
111 println!("Component written to {}", args.output.display());
112 println!(" Size: {} bytes", component.len());
113
114 Ok(())
115}