Skip to main content

wasm_split_cli_support/
lib.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4};
5
6use eyre::{Result, WrapErr};
7use split_point::SplitModuleIdentifier;
8
9mod dep_graph;
10mod emit;
11mod graph_utils;
12mod js;
13mod magic_constants;
14mod read;
15mod reloc;
16mod split_point;
17mod util;
18
19#[non_exhaustive]
20pub struct Options<'a> {
21    /// The input wasm to split
22    pub input_wasm: &'a [u8],
23    /// Where to put javascript wrappers, split wasm modules.
24    ///
25    /// Default: `Path::new("wasm_split")`
26    pub output_dir: &'a Path,
27    /// Where to put the main module that has to be post-processed by wasm-bindgen.
28    /// Usually a path in `output_dir`.
29    ///
30    /// Default: `Path::new("wasm_split/main.wasm")`
31    pub main_out_path: &'a Path,
32    /// Module path of the created link file, relative to the output dir.
33    /// The wasm will use this path to import the loader functions for the split chunks.
34    ///
35    /// Default: `"./__wasm_split.js"`
36    pub link_name: &'a str,
37    /// From where will `initSync` be imported from?
38    ///
39    /// Default: `"./main.js"`
40    pub main_module: &'a str,
41    /// Verbosely output additional information about processing.
42    ///
43    /// Default: false
44    pub verbose: bool,
45    /// Enables explicit tests for assumptions we make about the input wasm file during integration testing.
46    #[doc(hidden)]
47    pub strict_tests: bool,
48}
49
50impl<'wasm> Options<'wasm> {
51    pub fn new(input_wasm: &'wasm [u8]) -> Self {
52        Self {
53            input_wasm,
54            output_dir: Path::new("wasm_split"),
55            main_out_path: Path::new("wasm_split/main.wasm"),
56            link_name: "./__wasm_split.js",
57            main_module: "./main.js",
58            verbose: false,
59            strict_tests: false,
60        }
61    }
62}
63
64#[non_exhaustive]
65pub struct SplitWasm {
66    pub split_modules: Vec<PathBuf>,
67    /// split -> dependency filestem
68    /// e.g. `{ "foo": ["chunk_0", "foo"] }`
69    pub prefetch_map: HashMap<String, Vec<String>>,
70}
71
72pub fn transform(opts: Options) -> Result<SplitWasm> {
73    let strictness = if opts.strict_tests {
74        read::Strictness::IntegrationTesting
75    } else {
76        read::Strictness::Lenient
77    };
78    // (1) parse input
79    let module = crate::read::InputModule::parse(opts.input_wasm, strictness)?;
80    if opts.verbose {
81        module.reloc_info.print_relocs();
82    }
83    // (2) dependency analysis and decide on splits
84    let (dep_graph, reloc_stub_fns) = dep_graph::get_dependencies(&module)?;
85    let split_points = split_point::get_split_points(&module)?;
86    let split_program_info = split_point::compute_split_modules(&module, &dep_graph, split_points)?;
87
88    if split_point::trace_enabled(opts.verbose) {
89        for (name, split_deps) in split_program_info.output_modules.iter() {
90            split_deps.print(format!("{:?}", name).as_str(), &module);
91        }
92    }
93    // (3) compute output modules and helper javascript
94    let link_module = opts.link_name;
95    let emit_state = emit::EmitState::new(
96        &opts,
97        &module,
98        &split_program_info,
99        link_module,
100        reloc_stub_fns,
101    )?;
102    let wasm_modules = emit::emit_modules(
103        &split_program_info,
104        &emit_state,
105        |output_module_index, identifier, data| {
106            let output_path = match identifier {
107                SplitModuleIdentifier::Main => opts.main_out_path.to_path_buf(),
108                _ => opts
109                    .output_dir
110                    .join(identifier.filename(output_module_index) + ".wasm"),
111            };
112            (identifier, output_path, data)
113        },
114    )?;
115    let js_link_module = js::link_module(opts.main_module, &split_program_info, &emit_state)?;
116    // (4) write the output
117    std::fs::create_dir_all(opts.output_dir)?;
118    let mut split_modules = vec![];
119    for (identifier, output_path, data) in wasm_modules {
120        // TODO: we could do this asynchronously
121        std::fs::write(&output_path, &data)
122            .with_context(|| format!("Error emitting {:?}", identifier))?;
123        if !matches!(identifier, SplitModuleIdentifier::Main) {
124            split_modules.push(output_path);
125        }
126    }
127    let prefetch_map = js_link_module.emit(&opts.output_dir.join(Path::new(link_module)))?;
128
129    Ok(SplitWasm {
130        split_modules,
131        prefetch_map,
132    })
133}