runtime/module/
loader.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::collections::hash_map::{Entry, HashMap};
8use std::ffi::OsStr;
9use std::fs::read_to_string;
10use std::path::{Path, PathBuf};
11
12use dunce::canonicalize;
13use ion::module::{Module, ModuleData, ModuleLoader, ModuleRequest, ModuleType};
14use ion::{Context, Error, Local, Object, Result, Value};
15use mozjs::jsapi::JSObject;
16use url::Url;
17
18use crate::cache::locate_in_cache;
19use crate::cache::map::save_sourcemap;
20use crate::config::Config;
21
22#[derive(Debug, Eq, Hash, PartialEq)]
23pub enum Specifier {
24	Path(PathBuf),
25}
26
27#[derive(Debug, Eq, Hash, PartialEq)]
28pub struct ModuleKey {
29	pub(crate) specifier: Specifier,
30	pub(crate) kind: ModuleType,
31}
32
33#[derive(Default)]
34pub struct Loader {
35	modules: HashMap<ModuleKey, *mut JSObject>,
36}
37
38impl Loader {
39	fn resolve_specifier(specifier: &str, data: Option<&ModuleData>) -> Specifier {
40		let path = if specifier.starts_with("./") || specifier.starts_with("../") {
41			Path::new(data.and_then(|d| d.path.as_ref()).unwrap()).parent().unwrap().join(specifier)
42		} else {
43			Path::new(specifier).to_path_buf()
44		};
45		Specifier::Path(path)
46	}
47}
48
49impl ModuleLoader for Loader {
50	fn resolve<'cx>(&mut self, cx: &'cx Context, private: &Value, request: &ModuleRequest) -> Result<Module<'cx>> {
51		let specifier = request.specifier(cx).to_owned(cx)?;
52		let data = ModuleData::from_private(cx, private);
53
54		let specifier = Loader::resolve_specifier(&specifier, data.as_ref());
55		let kind = request.kind(cx);
56
57		let key = ModuleKey { specifier, kind };
58		let Specifier::Path(path) = &key.specifier;
59		let name = path.to_str().unwrap();
60
61		if let Some(module) = self.modules.get(&key) {
62			return Ok(Module(Object::from(unsafe { Local::from_marked(module) })));
63		}
64
65		let script = read_to_string(path).map_err(|_| Error::new(format!("Unable to read module: {key:?}"), None))?;
66
67		let module = match kind {
68			ModuleType::JavaScript => {
69				let is_typescript = Config::global().typescript && path.extension() == Some(OsStr::new("ts"));
70				let (script, sourcemap) = is_typescript
71					.then(|| locate_in_cache(path, &script))
72					.flatten()
73					.map_or_else(|| (script, None), |(s, sm)| (s, Some(sm)));
74				if let Some(sourcemap) = sourcemap {
75					save_sourcemap(path, sourcemap);
76				}
77
78				Module::compile_and_evaluate(cx, name, Some(path.as_path()), &script).map(|(module, _)| module)
79			}
80			ModuleType::Json => {
81				Module::compile_and_evaluate(cx, name, Some(path.as_path()), &script).map(|(module, _)| module)
82			}
83		};
84
85		match module {
86			Ok(module) => {
87				let request = ModuleRequest::new(cx, name, kind);
88				self.register(cx, module.0.handle().get(), &request)?;
89				Ok(module)
90			}
91			Err(_) => Err(Error::new(format!("Unable to compile module: {key:?}"), None)),
92		}
93	}
94
95	fn register(&mut self, cx: &Context, module: *mut JSObject, request: &ModuleRequest) -> Result<()> {
96		let specifier = Specifier::Path(PathBuf::from(request.specifier(cx).to_owned(cx)?));
97		let kind = ModuleType::JavaScript;
98		let key = ModuleKey { specifier, kind };
99		match self.modules.entry(key) {
100			Entry::Vacant(v) => {
101				v.insert(module);
102				Ok(())
103			}
104			Entry::Occupied(_) => Err(Error::new("Module already exists", None)),
105		}
106	}
107
108	fn metadata(&self, cx: &Context, private: &Value, meta: &Object) -> Result<()> {
109		let data = ModuleData::from_private(cx, private);
110
111		if let Some(data) = data
112			&& let Some(path) = data.path.as_ref()
113		{
114			let url = Url::from_file_path(canonicalize(path)?).unwrap();
115			if !meta.set_as(cx, "url", url.as_str()) {
116				return Err(Error::none());
117			}
118		}
119		Ok(())
120	}
121}