xtask_wasm_run_example/
lib.rs1use quote::{quote, quote_spanned};
2use syn::spanned::Spanned;
3use syn::{parse, parse_macro_input};
4
5#[proc_macro_attribute]
48pub fn run_example(
49 attr: proc_macro::TokenStream,
50 item: proc_macro::TokenStream,
51) -> proc_macro::TokenStream {
52 let item = parse_macro_input!(item as syn::ItemFn);
53 let attr = parse_macro_input!(attr with RunExample::parse);
54
55 attr.generate(item)
56 .unwrap_or_else(|err| err.to_compile_error())
57 .into()
58}
59
60struct RunExample {
61 index: Option<syn::Expr>,
62 static_dir: Option<syn::Expr>,
63 app_name: Option<syn::Expr>,
64}
65
66impl RunExample {
67 fn parse(input: parse::ParseStream) -> parse::Result<Self> {
68 let mut index = None;
69 let mut static_dir = None;
70 let mut app_name = None;
71
72 while !input.is_empty() {
73 let ident: syn::Ident = input.parse()?;
74 let _eq_token: syn::Token![=] = input.parse()?;
75 let expr: syn::Expr = input.parse()?;
76
77 match ident.to_string().as_str() {
78 "index" => index = Some(expr),
79 "static_dir" => static_dir = Some(expr),
80 "app_name" => app_name = Some(expr),
81 _ => return Err(parse::Error::new(ident.span(), "unrecognized argument")),
82 }
83
84 let _comma_token: syn::Token![,] = match input.parse() {
85 Ok(x) => x,
86 Err(_) if input.is_empty() => break,
87 Err(err) => return Err(err),
88 };
89 }
90
91 Ok(RunExample {
92 index,
93 static_dir,
94 app_name,
95 })
96 }
97
98 fn generate(self, item: syn::ItemFn) -> syn::Result<proc_macro2::TokenStream> {
99 let fn_block = item.block;
100
101 let index = if let Some(expr) = &self.index {
102 quote_spanned! { expr.span()=> std::fs::write(dist_dir.join("index.html"), #expr)?; }
103 } else if self.static_dir.is_some() {
104 quote! {}
105 } else {
106 quote! {
107 std::fs::write(
108 dist_dir.join("index.html"),
109 r#"<!DOCTYPE html><html><head><meta charset="utf-8"/><script type="module">import init from "/app.js";init(new URL('app_bg.wasm', import.meta.url));</script></head><body></body></html>"#,
110 )?;
111 }
112 };
113
114 let app_name = if let Some(expr) = &self.app_name {
115 quote_spanned! { expr.span()=> .app_name(#expr) }
116 } else {
117 quote! {}
118 };
119
120 let static_dir = if let Some(expr) = self.static_dir {
121 quote_spanned! { expr.span()=> .static_dir_path(#expr) }
122 } else {
123 quote! {}
124 };
125
126 Ok(quote! {
127 #[cfg(target_arch = "wasm32")]
128 pub mod xtask_wasm_run_example {
129 use super::*;
130 use xtask_wasm::wasm_bindgen;
131
132 #[xtask_wasm::wasm_bindgen::prelude::wasm_bindgen(start)]
133 pub fn run_app() -> Result<(), xtask_wasm::wasm_bindgen::JsValue> {
134 xtask_wasm::console_error_panic_hook::set_once();
135
136 #fn_block
137
138 Ok(())
139 }
140 }
141
142 #[cfg(not(target_arch = "wasm32"))]
143 fn main() -> xtask_wasm::anyhow::Result<()> {
144 use xtask_wasm::{env_logger, log, clap};
145
146 #[derive(clap::Parser)]
147 struct Cli {
148 #[clap(subcommand)]
149 command: Option<Command>,
150 }
151
152 #[derive(clap::Parser)]
153 enum Command {
154 Dist(xtask_wasm::Dist),
155 Start(xtask_wasm::DevServer),
156 }
157
158 env_logger::builder()
159 .filter(Some(module_path!()), log::LevelFilter::Info)
160 .filter(Some("xtask"), log::LevelFilter::Info)
161 .init();
162
163 let cli: Cli = clap::Parser::parse();
164 let mut dist_command = xtask_wasm::xtask_command();
165 dist_command.arg("dist");
166
167 match cli.command {
168 Some(Command::Dist(mut dist)) => {
169 let dist_dir = dist
170 .example(module_path!())
171 #app_name
172 #static_dir
173 .run(env!("CARGO_PKG_NAME"))?;
174
175 #index
176
177 Ok(())
178 }
179 Some(Command::Start(dev_server)) => {
180 let served_path = xtask_wasm::default_dist_dir(false);
181 dev_server.command(dist_command).start(served_path)
182 }
183 None => {
184 let dev_server: xtask_wasm::DevServer = clap::Parser::parse();
185 let served_path = xtask_wasm::default_dist_dir(false);
186 dev_server.command(dist_command).start(served_path)
187 }
188 }
189 }
190
191 #[cfg(target_arch = "wasm32")]
192 fn main() {}
193 })
194 }
195}