use clap::Parser;
use std::path::PathBuf;
use wasm_split_cli::SplitModule;
fn main() {
tracing_subscriber::fmt()
.without_time()
.compact()
.with_env_filter("debug,walrus=info")
.init();
match Commands::parse() {
Commands::Split(split_args) => split(split_args),
Commands::Validate(validate_args) => validate(validate_args),
}
}
#[derive(Parser)]
enum Commands {
#[clap(name = "split")]
Split(SplitArgs),
#[clap(name = "validate")]
Validate(ValidateArgs),
}
#[derive(Parser)]
struct SplitArgs {
original: PathBuf,
bindgened: PathBuf,
out_dir: PathBuf,
}
fn split(args: SplitArgs) {
let original = std::fs::read(&args.original).expect("failed to read input file");
let bindgened = std::fs::read(&args.bindgened).expect("failed to read input file");
_ = std::fs::remove_dir_all(&args.out_dir);
std::fs::create_dir_all(&args.out_dir).expect("failed to create output dir");
tracing::info!("Building split module");
let module = wasm_split_cli::Splitter::new(&original, &bindgened).unwrap();
let mut chunks = module.emit().unwrap();
tracing::info!(
"Writing main module to {}",
args.out_dir.join("main.wasm").display()
);
std::fs::write(args.out_dir.join("main.wasm"), &chunks.main.bytes).unwrap();
std::fs::write(
args.out_dir.join("__wasm_split.js"),
emit_js(&chunks.chunks, &chunks.modules),
)
.expect("failed to write js module");
for (idx, chunk) in chunks.chunks.iter().enumerate() {
tracing::info!(
"Writing chunk {} to {}",
idx,
args.out_dir
.join(format!("chunk_{}_{}.wasm", idx, chunk.module_name))
.display()
);
std::fs::write(
args.out_dir
.join(format!("chunk_{}_{}.wasm", idx, chunk.module_name)),
&chunk.bytes,
)
.expect("failed to write chunk");
}
for (idx, module) in chunks.modules.iter_mut().enumerate() {
tracing::info!(
"Writing module {} to {}",
idx,
args.out_dir
.join(format!(
"module_{}_{}.wasm",
idx,
module.component_name.as_ref().unwrap()
))
.display()
);
std::fs::write(
args.out_dir.join(format!(
"module_{}_{}.wasm",
idx,
module.component_name.as_ref().unwrap()
)),
&module.bytes,
)
.expect("failed to write chunk");
}
}
fn emit_js(chunks: &[SplitModule], modules: &[SplitModule]) -> String {
use std::fmt::Write;
let mut glue = format!(
r#"import {{ initSync }} from "./main.js";
{}"#,
include_str!("./__wasm_split.js")
);
for (idx, chunk) in chunks.iter().enumerate() {
tracing::debug!("emitting chunk: {:?}", chunk.module_name);
writeln!(
glue,
"export const __wasm_split_load_chunk_{idx} = makeLoad(\"/harness/split/chunk_{idx}_{module}.wasm\", [], fusedImports, initSync);",
module = chunk.module_name
).expect("failed to write to string");
}
for (idx, module) in modules.iter().enumerate() {
let deps = module
.relies_on_chunks
.iter()
.map(|idx| format!("__wasm_split_load_chunk_{idx}"))
.collect::<Vec<_>>()
.join(", ");
let hash_id = module.hash_id.as_ref().unwrap();
writeln!(
glue,
"export const __wasm_split_load_{module}_{hash_id}_{cname} = makeLoad(\"/harness/split/module_{idx}_{cname}.wasm\", [{deps}], fusedImports, initSync);",
module = module.module_name,
idx = idx,
cname = module.component_name.as_ref().unwrap(),
deps = deps
)
.expect("failed to write to string");
}
glue
}
#[derive(Parser)]
struct ValidateArgs {
main: PathBuf,
chunks: Vec<PathBuf>,
}
fn validate(args: ValidateArgs) {
let bytes = std::fs::read(&args.main).expect("failed to read input file");
let main_module = walrus::Module::from_buffer(&bytes).unwrap();
for chunk in args.chunks {
let bytes = std::fs::read(chunk).expect("failed to read input file");
let chunk_module = walrus::Module::from_buffer(&bytes).unwrap();
assert!(chunk_module.tables.iter().count() == 1);
for import in chunk_module.imports.iter() {
let matching = main_module.exports.iter().find(|e| e.name == import.name);
let Some(matching) = matching else {
tracing::error!("Could not find matching export for import {import:#?}");
continue;
};
tracing::debug!("import: {:?}", matching.name);
}
}
}