boa_engine/module/loader/
embedded.rs1use std::cell::RefCell;
5use std::collections::HashMap;
6use std::path::Path;
7use std::rc::Rc;
8
9use boa_engine::module::{ModuleLoader, Referrer};
10use boa_engine::{Context, JsNativeError, JsResult, JsString, Module, Source};
11
12#[macro_export]
21macro_rules! embed_module {
22 ($($x: expr),*) => {
23 $crate::module::embedded::EmbeddedModuleLoader::from_iter(
24 $crate::__embed_module_inner!($($x),*),
25 )
26 };
27}
28
29#[derive(Debug, Clone)]
30enum EmbeddedModuleEntry {
31 Source(CompressType, JsString, &'static [u8]),
32 Module(Module),
33}
34
35impl EmbeddedModuleEntry {
36 fn from_source(compress_type: CompressType, path: JsString, source: &'static [u8]) -> Self {
37 Self::Source(compress_type, path, source)
38 }
39
40 fn cache(&mut self, context: &mut Context) -> JsResult<&Module> {
41 if let Self::Source(compress, path, source) = self {
42 let mut bytes: &[u8] = match compress {
43 CompressType::None => source,
44
45 #[cfg(feature = "embedded_lz4")]
46 CompressType::Lz4 => &lz4_flex::decompress_size_prepended(source)
47 .map_err(|e| boa_engine::js_error!("Could not decompress module: {}", e))?,
48 };
49 let path = path.to_std_string_escaped();
50 let source = Source::from_reader(&mut bytes, Some(Path::new(&path)));
51 match Module::parse(source, None, context) {
52 Ok(module) => {
53 *self = Self::Module(module);
54 }
55 Err(err) => {
56 return Err(err);
57 }
58 }
59 }
60
61 match self {
62 Self::Module(module) => Ok(module),
63 EmbeddedModuleEntry::Source(_, _, _) => unreachable!(),
64 }
65 }
66
67 fn as_module(&self) -> Option<&Module> {
68 match self {
69 Self::Module(module) => Some(module),
70 Self::Source(_, _, _) => None,
71 }
72 }
73}
74
75#[derive(Debug, Copy, Clone)]
77pub enum CompressType {
78 None,
80
81 #[cfg(feature = "embedded_lz4")]
82 Lz4,
84}
85
86impl TryFrom<&str> for CompressType {
87 type Error = &'static str;
88
89 fn try_from(value: &str) -> Result<Self, Self::Error> {
90 match value {
91 "none" => Ok(Self::None),
92 #[cfg(feature = "embedded_lz4")]
93 "lz4" => Ok(Self::Lz4),
94 _ => Err("Invalid compression type"),
95 }
96 }
97}
98
99#[derive(Debug, Default, Clone)]
101#[allow(clippy::module_name_repetitions)]
102pub struct EmbeddedModuleLoader {
103 map: HashMap<JsString, RefCell<EmbeddedModuleEntry>>,
104}
105
106impl EmbeddedModuleLoader {
107 #[must_use]
110 pub fn get_module(&self, name: &JsString) -> Option<Module> {
111 self.map
112 .get(name)
113 .and_then(|module| module.borrow().as_module().cloned())
114 }
115}
116
117impl FromIterator<(&'static str, &'static str, &'static [u8])> for EmbeddedModuleLoader {
118 fn from_iter<T: IntoIterator<Item = (&'static str, &'static str, &'static [u8])>>(
119 iter: T,
120 ) -> Self {
121 Self {
122 map: iter
123 .into_iter()
124 .map(|(compress_type, path, source)| {
125 let p = JsString::from(path);
126 (
127 p.clone(),
128 RefCell::new(EmbeddedModuleEntry::from_source(
129 compress_type.try_into().expect("Invalid compress type"),
130 p,
131 source,
132 )),
133 )
134 })
135 .collect(),
136 }
137 }
138}
139
140impl ModuleLoader for EmbeddedModuleLoader {
141 fn load_imported_module(
142 self: Rc<Self>,
143 referrer: Referrer,
144 request: boa_engine::module::ModuleRequest,
145 context: &RefCell<&mut Context>,
146 ) -> impl Future<Output = JsResult<Module>> {
147 let result = (|| {
148 let specifier_path = boa_engine::module::resolve_module_specifier(
149 None,
150 request.specifier(),
151 referrer.path(),
152 &mut context.borrow_mut(),
153 )
154 .map_err(|e| {
155 JsNativeError::typ()
156 .with_message(format!(
157 "could not resolve module specifier `{}`",
158 request.specifier().display_escaped()
159 ))
160 .with_cause(e)
161 })?;
162
163 let module = self
164 .map
165 .get(&JsString::from(specifier_path.to_string_lossy().as_ref()))
166 .ok_or_else(|| {
167 JsNativeError::typ().with_message(format!(
168 "could not find module `{}`",
169 request.specifier().display_escaped()
170 ))
171 })?;
172
173 let mut embedded = module.borrow_mut();
174 embedded.cache(&mut context.borrow_mut()).cloned()
175 })();
176
177 async { result }
178 }
179}