vertigo_cli/build/
build_run.rs

1use std::path::PathBuf;
2
3use crate::commons::{ErrorCode, models::IndexModel};
4
5use super::{
6    build_opts::BuildOpts,
7    cargo_build::run_cargo_build,
8    cargo_workspace::{Workspace, get_workspace},
9    check_env::check_env,
10    find_target::{find_package_rlib_in_target, find_wasm_in_target, profile_name},
11    wasm_opt::run_wasm_opt,
12    wasm_path::WasmPath,
13};
14
15pub fn run(opts: BuildOpts) -> Result<(), ErrorCode> {
16    let ws = match get_workspace() {
17        Ok(ws) => ws,
18        Err(err) => {
19            log::error!("Can't read workspace");
20            return Err(err);
21        }
22    };
23
24    run_with_ws(opts, &ws, false)
25}
26
27pub fn run_with_ws(opts: BuildOpts, ws: &Workspace, allow_error: bool) -> Result<(), ErrorCode> {
28    let package_name = match opts.inner.package_name.as_deref() {
29        Some(name) => name.to_string(),
30        None => match ws.infer_package_name() {
31            Some(name) => {
32                log::info!("Inferred package name = {name}");
33                name
34            }
35            None => {
36                log::error!(
37                    "Can't find vertigo project in {} (no cdylib member)",
38                    ws.get_root_dir()
39                );
40                return Err(ErrorCode::CantFindCdylibMember);
41            }
42        },
43    };
44
45    check_env()?;
46
47    let release = opts.inner.release_mode.unwrap_or(true);
48    let profile = profile_name(release);
49    let external_tailwind = opts.inner.external_tailwind;
50
51    let dest_dir = WasmPath::new(PathBuf::from(&opts.common.dest_dir));
52
53    // Clean destination
54
55    dest_dir.remove_dir_all();
56    dest_dir.create_dir_all();
57
58    // Delete rlibs to re-generate static files
59
60    find_package_rlib_in_target(&package_name, profile).remove_file()?;
61
62    // Run build
63
64    let target_path = match run_cargo_build(
65        &package_name,
66        &opts.get_public_path(),
67        ws,
68        allow_error,
69        release,
70        external_tailwind,
71    )? {
72        Ok(path) => path,
73        Err(_) => return Err(ErrorCode::BuildFailed),
74    };
75
76    // Get wasm_run.js and index.template.html from vertigo build
77
78    let vertigo_statics_dir = target_path.join("static");
79
80    let mut run_script_content = match std::fs::read(vertigo_statics_dir.join("wasm_run.js")) {
81        Ok(content) => content,
82        Err(err) => {
83            log::error!("Can't read wasm_run from statics directory: {err}");
84            return Err(ErrorCode::CantReadWasmRunFromStatics);
85        }
86    };
87
88    if !opts.inner.wasm_run_source_map {
89        erase_last_two_lines(&mut run_script_content);
90    }
91
92    let run_script_hash_name = opts
93        .new_path_in_static_make(&["wasm_run.js"])
94        .save_with_hash(&run_script_content)?;
95
96    if opts.inner.wasm_run_source_map {
97        let run_script_sourcemap_content =
98            match std::fs::read_to_string(vertigo_statics_dir.join("wasm_run.js.map")) {
99                Ok(content) => {
100                    // Replace original script filename in sourcemap with the hashed one
101                    content.replace("wasm_run.js", &run_script_hash_name)
102                }
103                Err(err) => {
104                    log::error!("Can't read wasm_run sourcemap from statics directory: {err}");
105                    return Err(ErrorCode::CantReadWasmRunFromStatics);
106                }
107            };
108
109        opts.new_path_in_static_make(&["wasm_run.js.map"])
110            .save(&run_script_sourcemap_content.into_bytes())?;
111    }
112
113    // Copy .wasm to destination
114
115    let wasm_path_target = find_wasm_in_target(&package_name, profile);
116    let wasm_path = opts.new_path_in_static_from(&wasm_path_target);
117
118    // Optimize .wasm
119
120    let wasm_path_hash =
121        if opts.inner.wasm_opt.unwrap_or(true) && run_wasm_opt(&wasm_path_target, &wasm_path) {
122            // optimized
123            let wasm_path_hash = wasm_path.save_with_hash(wasm_path.read()?.as_slice())?;
124            wasm_path.remove_file()?;
125            wasm_path_hash
126        } else {
127            // copy without optimization
128            let wasm_content = wasm_path_target.read()?;
129            wasm_path.save_with_hash(wasm_content.as_slice())?
130        };
131
132    // Generate index.json in destination
133
134    let index = IndexModel {
135        run_js: opts.public_path_to(run_script_hash_name),
136        wasm: opts.public_path_to(wasm_path_hash),
137    };
138
139    let index_content = serde_json::to_string_pretty(&index).map_err(|err| {
140        log::error!("Can't serialize index.json: {err}");
141        ErrorCode::CantWriteOrRemoveFile
142    })?;
143    opts.new_path_in_static_make(&["index.json"])
144        .save(index_content.as_bytes())?;
145
146    // Copy statics generated by dom macro invocations
147
148    if let Ok(dir) = std::fs::read_dir(vertigo_statics_dir.join("included")) {
149        for entry in dir {
150            match entry {
151                Ok(entry) => {
152                    let src_file_path = WasmPath::new(entry.path());
153                    let content = src_file_path.read()?;
154                    let dest_file_path = opts.new_path_in_static_from(&src_file_path);
155                    dest_file_path.save(&content)?;
156                }
157                Err(err) => {
158                    log::warn!("Can't read entry: {err}");
159                    return Err(ErrorCode::CantReadStaticFile);
160                }
161            }
162        }
163    }
164
165    Ok(())
166}
167
168fn erase_last_two_lines(content: &mut Vec<u8>) {
169    // Find the positions of the last two newline characters
170    let mut last_newline_pos = None;
171    let mut second_last_newline_pos = None;
172
173    for (i, &byte) in content.iter().enumerate() {
174        if byte == b'\n' {
175            second_last_newline_pos = last_newline_pos; // Update second last
176            last_newline_pos = Some(i); // Update last
177        }
178    }
179
180    // Truncate the Vec<u8> to the position of the second last newline
181    if let Some(second_last) = second_last_newline_pos {
182        content.truncate(second_last);
183    }
184}