Files
sausage/host/src/harness/loader.rs
T

145 lines
5.0 KiB
Rust

//! Loads committed world modules and shares the bundle's package instances and status.
use deno_core::{
ModuleLoadOptions, ModuleLoadReferrer, ModuleLoadResponse, ModuleLoader, ModuleSource,
ModuleSourceCode, ModuleSpecifier, ModuleType, OpState, ResolutionKind, op2,
};
use deno_error::JsErrorBox;
use sausage_vfs::{Layer, Open};
use std::{
cell::RefCell,
collections::BTreeMap,
path::{Path, PathBuf},
rc::Rc,
sync::{Arc, Mutex, OnceLock},
};
pub(crate) type Status = Arc<Mutex<String>>;
pub(crate) fn status(path: &Path) -> Status {
static STATES: OnceLock<Mutex<BTreeMap<PathBuf, Status>>> = OnceLock::new();
STATES
.get_or_init(Default::default)
.lock()
.unwrap()
.entry(path.to_owned())
.or_insert_with(|| {
Arc::new(Mutex::new(
"{\"good\":null,\"current\":null,\"error\":null}".into(),
))
})
.clone()
}
pub(super) struct Loader {
pub root: Arc<dyn Layer>,
pub modules: RefCell<BTreeMap<String, String>>,
}
#[op2]
pub fn op_modules(state: &mut OpState, #[serde] modules: BTreeMap<String, Vec<String>>) {
let loader = state.borrow::<Rc<Loader>>();
for (name, exports) in modules {
let quoted = serde_json::to_string(&name).unwrap();
let code = exports.into_iter().enumerate().map(|(index,key)| format!("const e{index} = globalThis.__sausage.modules[{quoted}].{key}; export {{ e{index} as {key} }};\n")).collect();
loader
.modules
.borrow_mut()
.insert(format!("sausage:{name}"), code);
}
}
#[op2(fast)]
pub fn op_extension_status(state: &mut OpState, #[string] value: String) {
*state.borrow::<Status>().lock().unwrap() = value;
}
pub(super) fn transpile(name: &str, source: String) -> Result<String, JsErrorBox> {
if !name.ends_with(".ts") {
return Ok(source);
}
let parsed = deno_ast::parse_module(deno_ast::ParseParams {
specifier: ModuleSpecifier::parse(name).map_err(|e| JsErrorBox::generic(e.to_string()))?,
text: source.into(),
media_type: deno_ast::MediaType::TypeScript,
capture_tokens: false,
scope_analysis: false,
maybe_syntax: None,
})
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
Ok(parsed
.transpile(
&Default::default(),
&Default::default(),
&deno_ast::EmitOptions {
source_map: deno_ast::SourceMapOption::None,
..Default::default()
},
)
.map_err(|e| JsErrorBox::generic(e.to_string()))?
.into_source()
.text)
}
impl ModuleLoader for Loader {
fn resolve(
&self,
specifier: &str,
referrer: &str,
_: ResolutionKind,
) -> Result<ModuleSpecifier, JsErrorBox> {
if specifier == "yaml" || specifier.starts_with("@earendil-works/") {
return ModuleSpecifier::parse(&format!("sausage:{specifier}"))
.map_err(|e| JsErrorBox::generic(e.to_string()));
}
deno_core::resolve_import(specifier, referrer)
.map_err(|e| JsErrorBox::generic(e.to_string()))
}
fn load(
&self,
specifier: &ModuleSpecifier,
_: Option<&ModuleLoadReferrer>,
_: ModuleLoadOptions,
) -> ModuleLoadResponse {
if let Some(source) = self.modules.borrow().get(specifier.as_str()) {
return ModuleLoadResponse::Sync(Ok(ModuleSource::new(
ModuleType::JavaScript,
ModuleSourceCode::String(source.clone().into()),
specifier,
None,
)));
}
let root = self.root.clone();
let specifier = specifier.clone();
ModuleLoadResponse::Async(Box::pin(async move {
let work = async {
let path = specifier
.to_file_path()
.map_err(|_| JsErrorBox::generic(format!("Unknown module {specifier}")))?;
let file = root
.open(
path.to_str().unwrap(),
Open {
follow: true,
..Default::default()
},
)
.await
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
let size = file
.stat()
.await
.map_err(|e| JsErrorBox::generic(e.to_string()))?
.size;
let source = String::from_utf8(
file.read(0, size as usize)
.await
.map_err(|e| JsErrorBox::generic(e.to_string()))?,
)
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
transpile(specifier.as_str(), source)
}
.await?;
Ok(ModuleSource::new(
ModuleType::JavaScript,
ModuleSourceCode::String(work.into()),
&specifier,
None,
))
}))
}
}