1use crate::cmd::{cfg_spinner, run_stage};
2use crate::install::Tools;
3use crate::parse::{ExportOpts, Opts};
4use crate::thread::{spawn_thread, ThreadHandle};
5use crate::{errors::*, get_user_crate_name};
6use console::{style, Emoji};
7use indicatif::{MultiProgress, ProgressBar};
8use std::fs;
9use std::path::{Path, PathBuf};
10
11static EXPORTING: Emoji<'_, '_> = Emoji("📦", "");
13static BUILDING: Emoji<'_, '_> = Emoji("🏗️ ", ""); macro_rules! handle_exit_code {
17 ($code:expr) => {
18 let (_, _, code) = $code;
19 if code != 0 {
20 return ::std::result::Result::Ok(code);
21 }
22 };
23}
24
25macro_rules! copy_file {
34 ($from:expr, $to:expr, $target:expr) => {
35 #[cfg(unix)]
37 if std::os::unix::fs::symlink($target.join($from), $target.join($to)).is_err() {
38 if let Err(err) = fs::copy($target.join($from), $target.join($to)) {
40 return Err(ExportError::MoveAssetFailed {
41 to: $to.to_string(),
42 from: $from.to_string(),
43 source: err,
44 });
45 }
46 }
47 #[cfg(windows)]
48 if std::os::windows::fs::symlink_file($target.join($from), $target.join($to)).is_err() {
49 if let Err(err) = fs::copy($target.join($from), $target.join($to)) {
51 return Err(ExportError::MoveAssetFailed {
52 to: $to.to_string(),
53 from: $from.to_string(),
54 source: err,
55 });
56 }
57 }
58 };
59}
60
61macro_rules! copy_directory {
70 ($from:expr, $to:expr, $to_symlink:expr, $target:expr) => {
71 #[cfg(unix)]
73 if std::os::unix::fs::symlink($target.join($from), $target.join($to_symlink)).is_err() {
74 if let Err(err) = fs_extra::dir::copy(
76 $target.join($from),
77 $target.join($to),
78 &fs_extra::dir::CopyOptions::new(),
79 ) {
80 return Err(ExportError::MoveDirFailed {
81 to: $to.to_string(),
82 from: $from.to_string(),
83 source: err,
84 });
85 }
86 }
87 #[cfg(windows)]
88 if std::os::windows::fs::symlink_dir($target.join($from), $target.join($to_symlink))
89 .is_err()
90 {
91 if let Err(err) = fs_extra::dir::copy(
93 $target.join($from),
94 $target.join($to),
95 &fs_extra::dir::CopyOptions::new(),
96 ) {
97 return Err(ExportError::MoveDirFailed {
98 to: $to.to_string(),
99 from: $from.to_string(),
100 source: err,
101 });
102 }
103 }
104 };
105}
106
107pub fn finalize_export(target: &Path) -> Result<(), ExportError> {
110 copy_file!(
113 "dist/pkg/perseus_engine.js",
114 "dist/exported/.perseus/bundle.js",
115 target
116 );
117 copy_file!(
118 "dist/pkg/perseus_engine_bg.wasm",
119 "dist/exported/.perseus/bundle.wasm",
120 target
121 );
122 if fs::metadata(target.join("dist/pkg/snippets")).is_ok() {
125 copy_directory!(
126 "dist/pkg/snippets",
127 "dist/exported/.perseus", "dist/exported/.perseus/snippets", target
130 );
131 }
132
133 Ok(())
134}
135
136#[allow(clippy::type_complexity)]
142pub fn export_internal(
143 dir: PathBuf,
144 spinners: &MultiProgress,
145 num_steps: u8,
146 is_release: bool,
147 tools: &Tools,
148 global_opts: &Opts,
149) -> Result<
150 (
151 ThreadHandle<impl FnOnce() -> Result<i32, ExportError>, Result<i32, ExportError>>,
152 ThreadHandle<impl FnOnce() -> Result<i32, ExportError>, Result<i32, ExportError>>,
153 ),
154 ExportError,
155> {
156 let tools = tools.clone();
157 let Opts {
158 cargo_browser_args,
159 cargo_engine_args,
160 wasm_bindgen_args,
161 wasm_opt_args,
162 verbose,
163 mut wasm_release_rustflags,
164 ..
165 } = global_opts.clone();
166 let crate_name = get_user_crate_name(&dir)?;
167 wasm_release_rustflags.push_str(" --cfg=client");
168
169 let ep_msg = format!(
171 "{} {} Exporting your app's pages",
172 style(format!("[1/{}]", num_steps)).bold().dim(),
173 EXPORTING
174 );
175 let wb_msg = format!(
177 "{} {} Building your app to Wasm",
178 style(format!("[2/{}]", num_steps)).bold().dim(),
179 BUILDING
180 );
181
182 let ep_spinner = spinners.insert(0, ProgressBar::new_spinner());
186 let ep_spinner = cfg_spinner(ep_spinner, &ep_msg);
187 let ep_target = dir.clone();
188 let wb_spinner = spinners.insert(1, ProgressBar::new_spinner());
189 let wb_spinner = cfg_spinner(wb_spinner, &wb_msg);
190 let wb_target = dir;
191 let cargo_engine_exec = tools.cargo_engine.clone();
192 let ep_thread = spawn_thread(
193 move || {
194 handle_exit_code!(run_stage(
195 vec![&format!(
196 "{} run {} {}",
197 cargo_engine_exec,
198 if is_release { "--release" } else { "" },
199 cargo_engine_args
200 )],
201 &ep_target,
202 &ep_spinner,
203 &ep_msg,
204 vec![
205 ("PERSEUS_ENGINE_OPERATION", "export"),
206 ("CARGO_TARGET_DIR", "dist/target_engine"),
207 ("RUSTFLAGS", "--cfg=engine"),
208 ("CARGO_TERM_COLOR", "always")
209 ],
210 verbose,
211 )?);
212
213 Ok(0)
214 },
215 global_opts.sequential,
216 );
217 let wb_thread = spawn_thread(
218 move || {
219 let mut cmds = vec![
220 format!(
222 "{} build --target wasm32-unknown-unknown {} {}",
223 tools.cargo_browser,
224 if is_release { "--release" } else { "" },
225 cargo_browser_args
226 ),
227 format!(
229 "{cmd} ./dist/target_wasm/wasm32-unknown-unknown/{profile}/{crate_name}.wasm --out-dir dist/pkg --out-name perseus_engine --target web {args}",
230 cmd=tools.wasm_bindgen,
231 profile={ if is_release { "release" } else { "debug" } },
232 args=wasm_bindgen_args,
233 crate_name=crate_name
234 )
235 ];
236 if is_release {
238 cmds.push(format!(
239 "{cmd} -Oz ./dist/pkg/perseus_engine_bg.wasm -o ./dist/pkg/perseus_engine_bg.wasm {args}",
240 cmd=tools.wasm_opt,
241 args=wasm_opt_args
242 ));
243 }
244 let cmds = cmds.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
245 handle_exit_code!(run_stage(
246 cmds,
247 &wb_target,
248 &wb_spinner,
249 &wb_msg,
250 if is_release {
251 vec![
252 ("CARGO_TARGET_DIR", "dist/target_wasm"),
253 ("RUSTFLAGS", &wasm_release_rustflags),
254 ("CARGO_TERM_COLOR", "always"),
255 ]
256 } else {
257 vec![
258 ("CARGO_TARGET_DIR", "dist/target_wasm"),
259 ("RUSTFLAGS", "--cfg=client"),
260 ("CARGO_TERM_COLOR", "always"),
261 ]
262 },
263 verbose,
264 )?);
265
266 Ok(0)
267 },
268 global_opts.sequential,
269 );
270
271 Ok((ep_thread, wb_thread))
272}
273
274pub fn export(
277 dir: PathBuf,
278 opts: &ExportOpts,
279 tools: &Tools,
280 global_opts: &Opts,
281) -> Result<i32, ExportError> {
282 let spinners = MultiProgress::new();
283 let num_spinners = if opts.serve { 3 } else { 2 };
285
286 let (ep_thread, wb_thread) = export_internal(
287 dir.clone(),
288 &spinners,
289 num_spinners,
290 opts.release,
291 tools,
292 global_opts,
293 )?;
294 let ep_res = ep_thread
295 .join()
296 .map_err(|_| ExecutionError::ThreadWaitFailed)??;
297 if ep_res != 0 {
298 return Ok(ep_res);
299 }
300 let wb_res = wb_thread
301 .join()
302 .map_err(|_| ExecutionError::ThreadWaitFailed)??;
303 if wb_res != 0 {
304 return Ok(wb_res);
305 }
306
307 finalize_export(&dir)?;
309
310 Ok(0)
312}