componentize_qjs_cli/
cli.rs1use componentize_qjs::{ComponentizeOpts, Runtime, componentize};
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 #[arg(long, conflicts_with = "runtime")]
50 pub opt_size: bool,
51
52 #[arg(long, conflicts_with = "runtime")]
55 pub sync: bool,
56
57 #[arg(long, value_name = "PATH")]
59 pub runtime: Option<std::path::PathBuf>,
60}
61
62pub async fn run(args: Vec<String>) -> Result<()> {
64 let args =
65 CliArgs::try_parse_from(std::iter::once("componentize-qjs".to_string()).chain(args))?;
66
67 if !args.wit.exists() {
68 anyhow::bail!("WIT file/directory not found: {}", args.wit.display());
69 }
70 if !args.js.exists() {
71 anyhow::bail!("JavaScript file not found: {}", args.js.display());
72 }
73 if let Some(runtime_file) = &args.runtime
74 && !runtime_file.exists()
75 {
76 anyhow::bail!("Runtime file not found: {}", runtime_file.display());
77 }
78
79 let js_source = fs::read_to_string(&args.js)
80 .with_context(|| format!("failed to read JS file: {}", args.js.display()))?;
81
82 let js_source = if args.minify {
83 let allocator = Allocator::default();
84 let source_type = SourceType::mjs();
85 let ret = OxcParser::new(&allocator, &js_source, source_type).parse();
86 let mut program = ret.program;
87
88 let options = MinifierOptions {
89 mangle: Some(MangleOptions {
90 top_level: Some(false),
91 ..Default::default()
92 }),
93 compress: Some(CompressOptions {
94 unused: CompressOptionsUnused::Keep,
95 keep_names: CompressOptionsKeepNames::all_false(),
96 ..CompressOptions::default()
97 }),
98 };
99 let ret = Minifier::new(options).minify(&allocator, &mut program);
100 Codegen::new()
101 .with_scoping(ret.scoping)
102 .build(&program)
103 .code
104 } else {
105 js_source
106 };
107
108 println!("componentize-qjs");
109 println!(" WIT: {}", args.wit.display());
110 println!(" JS: {}", args.js.display());
111 println!(" Output: {}", args.output.display());
112
113 let runtime = match &args.runtime {
114 Some(file) => Runtime::Custom(&fs::read(file)?),
115 None => match (args.sync, args.opt_size) {
116 (true, true) => Runtime::OptSizeSync,
117 (true, false) => Runtime::DefaultSync,
118 (false, true) => Runtime::OptSize,
119 (false, false) => Runtime::default(),
120 },
121 };
122
123 if args.stub_wasi {
124 println!("Stubbing WASI imports...");
125 }
126
127 let component = componentize(&ComponentizeOpts {
128 wit_path: &args.wit,
129 js_source: &js_source,
130 world_name: args.world.as_deref(),
131 stub_wasi: args.stub_wasi,
132 disable_gc: args.disable_gc,
133 runtime,
134 })
135 .await?;
136
137 fs::write(&args.output, &component)
138 .with_context(|| format!("failed to write output to {}", args.output.display()))?;
139
140 println!("Component written to {}", args.output.display());
141 println!(" Size: {} bytes", component.len());
142
143 Ok(())
144}