this repo has no description
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}