this repo has no description
at wasm 219 lines 6.4 kB view raw
1#[cfg(test)] 2mod tests; 3mod wasm_filesystem; 4 5use camino::Utf8PathBuf; 6use gleam_core::{ 7 Error, 8 analyse::TargetSupport, 9 build::{ 10 Mode, NullTelemetry, PackageCompiler, StaleTracker, Target, TargetCodegenConfiguration, 11 }, 12 config::PackageConfig, 13 io::{FileSystemReader, FileSystemWriter}, 14 uid::UniqueIdGenerator, 15 warning::{VectorWarningEmitterIO, WarningEmitter}, 16}; 17use hexpm::version::Version; 18use im::HashMap; 19use std::{cell::RefCell, collections::HashSet, rc::Rc}; 20use wasm_filesystem::WasmFileSystem; 21 22use wasm_bindgen::prelude::*; 23 24#[derive(Debug, Clone, Default)] 25struct Project { 26 fs: WasmFileSystem, 27 warnings: VectorWarningEmitterIO, 28} 29 30thread_local! { 31 static PROJECTS: RefCell<HashMap<usize, Project>> = RefCell::new(HashMap::new()); 32} 33 34/// You should call this once to ensure that if the compiler crashes it gets 35/// reported in JavaScript. 36/// 37#[cfg(target_arch = "wasm32")] 38#[wasm_bindgen] 39pub fn initialise_panic_hook(debug: bool) { 40 console_error_panic_hook::set_once(); 41 42 if debug { 43 let _ = tracing_wasm::try_set_as_global_default(); 44 } 45} 46 47/// Reset the virtual file system to an empty state. 48/// 49#[wasm_bindgen] 50pub fn reset_filesystem(project_id: usize) { 51 let fs = get_filesystem(project_id); 52 fs.reset(); 53} 54 55/// Delete project, freeing any memory associated with it. 56/// 57#[wasm_bindgen] 58pub fn delete_project(project_id: usize) { 59 PROJECTS.with(|lock| { 60 _ = lock.borrow_mut().remove(&project_id); 61 }) 62} 63 64fn get_project(project_id: usize) -> Project { 65 PROJECTS.with(|lock| lock.borrow_mut().entry(project_id).or_default().clone()) 66} 67 68fn get_filesystem(project_id: usize) -> WasmFileSystem { 69 get_project(project_id).fs 70} 71 72fn get_warnings(project_id: usize) -> VectorWarningEmitterIO { 73 get_project(project_id).warnings 74} 75 76/// Write a Gleam module to the `/src` directory of the virtual file system. 77/// 78#[wasm_bindgen] 79pub fn write_module(project_id: usize, module_name: &str, code: &str) { 80 let fs = get_filesystem(project_id); 81 let path = format!("/src/{module_name}.gleam"); 82 fs.write(&Utf8PathBuf::from(path), code) 83 .expect("writing file") 84} 85 86/// Write a file to the virtual file system. 87/// 88#[wasm_bindgen] 89pub fn write_file(project_id: usize, path: &str, content: &str) { 90 let fs = get_filesystem(project_id); 91 fs.write(&Utf8PathBuf::from(path), content) 92 .expect("writing file") 93} 94 95/// Write a non-text file to the virtual file system. 96/// 97#[wasm_bindgen] 98pub fn write_file_bytes(project_id: usize, path: &str, content: &[u8]) { 99 let fs = get_filesystem(project_id); 100 fs.write_bytes(&Utf8PathBuf::from(path), content) 101 .expect("writing file") 102} 103 104/// Read a file from the virtual file system. 105/// 106#[wasm_bindgen] 107pub fn read_file_bytes(project_id: usize, path: &str) -> Option<Vec<u8>> { 108 let fs = get_filesystem(project_id); 109 fs.read_bytes(&Utf8PathBuf::from(path)).ok() 110} 111 112/// Run the package compiler. If this succeeds you can use 113/// 114#[wasm_bindgen] 115pub fn compile_package(project_id: usize, target: &str) -> Result<(), String> { 116 let target = match target.to_lowercase().as_str() { 117 "erl" | "erlang" => Target::Erlang, 118 "js" | "javascript" => Target::JavaScript, 119 _ => { 120 let msg = format!("Unknown target `{target}`, expected `erlang` or `javascript`"); 121 return Err(msg); 122 } 123 }; 124 125 do_compile_package(get_project(project_id), target).map_err(|e| e.pretty_string()) 126} 127 128/// Get the compiled JavaScript output for a given module. 129/// 130/// You need to call `compile_package` before calling this function. 131/// 132#[wasm_bindgen] 133pub fn read_compiled_javascript(project_id: usize, module_name: &str) -> Option<String> { 134 let fs = get_filesystem(project_id); 135 let path = format!("/build/{module_name}.mjs"); 136 fs.read(&Utf8PathBuf::from(path)).ok() 137} 138 139/// Get the compiled Erlang output for a given module. 140/// 141/// You need to call `compile_package` before calling this function. 142/// 143#[wasm_bindgen] 144pub fn read_compiled_erlang(project_id: usize, module_name: &str) -> Option<String> { 145 let fs = get_filesystem(project_id); 146 let path = format!( 147 "/build/_gleam_artefacts/{}.erl", 148 module_name.replace('/', "@") 149 ); 150 fs.read(&Utf8PathBuf::from(path)).ok() 151} 152 153/// Clear any stored warnings. This is performed automatically when before compilation. 154/// 155#[wasm_bindgen] 156pub fn reset_warnings(project_id: usize) { 157 get_warnings(project_id).reset(); 158} 159 160/// Pop the latest warning from the compiler. 161/// 162#[wasm_bindgen] 163pub fn pop_warning(project_id: usize) -> Option<String> { 164 get_warnings(project_id).pop().map(|w| w.to_pretty_string()) 165} 166 167fn do_compile_package(project: Project, target: Target) -> Result<(), Error> { 168 let ids = UniqueIdGenerator::new(); 169 let mut type_manifests = im::HashMap::new(); 170 let mut defined_modules = im::HashMap::new(); 171 #[allow(clippy::arc_with_non_send_sync)] 172 let warning_emitter = WarningEmitter::new(Rc::new(project.warnings)); 173 let config = PackageConfig { 174 name: "library".into(), 175 version: Version::new(1, 0, 0), 176 target, 177 ..Default::default() 178 }; 179 180 let target = match target { 181 Target::Erlang => TargetCodegenConfiguration::Erlang { app_file: None }, 182 Target::JavaScript => TargetCodegenConfiguration::JavaScript { 183 emit_typescript_definitions: false, 184 prelude_location: Utf8PathBuf::from("./gleam_prelude.mjs"), 185 }, 186 Target::Wasm => TargetCodegenConfiguration::Wasm {}, 187 }; 188 189 tracing::info!("Compiling package"); 190 191 let lib = Utf8PathBuf::from("/lib"); 192 let out = Utf8PathBuf::from("/build"); 193 let package = Utf8PathBuf::from("/"); 194 let mut compiler = PackageCompiler::new( 195 &config, 196 Mode::Dev, 197 &package, 198 &out, 199 &lib, 200 &target, 201 ids, 202 project.fs, 203 ); 204 compiler.write_entrypoint = false; 205 compiler.write_metadata = false; 206 compiler.compile_beam_bytecode = true; 207 compiler.target_support = TargetSupport::Enforced; 208 compiler 209 .compile( 210 &warning_emitter, 211 &mut type_manifests, 212 &mut defined_modules, 213 &mut StaleTracker::default(), 214 &mut HashSet::new(), 215 &NullTelemetry, 216 ) 217 .into_result() 218 .map(|_| ()) 219}