Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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