1use crate::error::exception_to_err_result;
3use crate::error::AnyError;
4use crate::fast_string::FastString;
5use crate::module_specifier::ModuleSpecifier;
6use crate::FastStaticString;
7use anyhow::bail;
8use anyhow::Context;
9use anyhow::Error;
10use serde::Deserialize;
11use serde::Serialize;
12use std::borrow::Cow;
13use std::collections::HashMap;
14use std::future::Future;
15use std::sync::Arc;
16use url::Url;
17
18mod loaders;
19mod map;
20mod module_map_data;
21mod recursive_load;
22
23#[cfg(all(test, not(miri)))]
24mod tests;
25
26pub(crate) use loaders::ExtModuleLoader;
27pub use loaders::ExtModuleLoaderCb;
28pub use loaders::FsModuleLoader;
29pub(crate) use loaders::LazyEsmModuleLoader;
30pub use loaders::ModuleLoadResponse;
31pub use loaders::ModuleLoader;
32pub use loaders::NoopModuleLoader;
33pub use loaders::StaticModuleLoader;
34pub(crate) use map::synthetic_module_evaluation_steps;
35pub(crate) use map::ModuleMap;
36pub(crate) use module_map_data::ModuleMapSnapshotData;
37
38pub type ModuleId = usize;
39pub(crate) type ModuleLoadId = i32;
40
41#[derive(Debug)]
45pub enum ModuleSourceCode {
46 String(ModuleCodeString),
47 Bytes(ModuleCodeBytes),
48}
49
50pub type ModuleCodeString = FastString;
51pub type ModuleName = FastString;
52
53pub trait IntoModuleName {
55 fn into_module_name(self) -> ModuleName;
56}
57
58impl IntoModuleName for ModuleName {
59 fn into_module_name(self) -> ModuleName {
60 self
61 }
62}
63
64impl IntoModuleName for &'static str {
65 fn into_module_name(self) -> ModuleName {
66 ModuleName::from_static(self)
67 }
68}
69
70impl IntoModuleName for String {
71 fn into_module_name(self) -> ModuleName {
72 ModuleName::from(self)
73 }
74}
75
76impl IntoModuleName for Url {
77 fn into_module_name(self) -> ModuleName {
78 ModuleName::from(self)
79 }
80}
81
82impl IntoModuleName for FastStaticString {
83 fn into_module_name(self) -> ModuleName {
84 ModuleName::from(self)
85 }
86}
87
88pub trait IntoModuleCodeString {
90 fn into_module_code(self) -> ModuleCodeString;
91}
92
93impl IntoModuleCodeString for ModuleCodeString {
94 fn into_module_code(self) -> ModuleCodeString {
95 self
96 }
97}
98
99impl IntoModuleCodeString for &'static str {
100 fn into_module_code(self) -> ModuleCodeString {
101 ModuleCodeString::from_static(self)
102 }
103}
104
105impl IntoModuleCodeString for String {
106 fn into_module_code(self) -> ModuleCodeString {
107 ModuleCodeString::from(self)
108 }
109}
110
111impl IntoModuleCodeString for FastStaticString {
112 fn into_module_code(self) -> ModuleCodeString {
113 ModuleCodeString::from(self)
114 }
115}
116
117impl IntoModuleCodeString for Arc<str> {
118 fn into_module_code(self) -> ModuleCodeString {
119 ModuleCodeString::from(self)
120 }
121}
122
123#[derive(Debug)]
124pub enum ModuleCodeBytes {
125 Static(&'static [u8]),
127
128 Boxed(Box<[u8]>),
131
132 Arc(Arc<[u8]>),
134}
135
136impl ModuleCodeBytes {
137 pub fn as_bytes(&self) -> &[u8] {
138 match self {
139 ModuleCodeBytes::Static(s) => s,
140 ModuleCodeBytes::Boxed(s) => s,
141 ModuleCodeBytes::Arc(s) => s,
142 }
143 }
144
145 pub fn to_vec(&self) -> Vec<u8> {
146 match self {
147 ModuleCodeBytes::Static(s) => s.to_vec(),
148 ModuleCodeBytes::Boxed(s) => s.to_vec(),
149 ModuleCodeBytes::Arc(s) => s.to_vec(),
150 }
151 }
152}
153
154impl From<Arc<[u8]>> for ModuleCodeBytes {
155 fn from(value: Arc<[u8]>) -> Self {
156 Self::Arc(value)
157 }
158}
159
160impl From<Box<[u8]>> for ModuleCodeBytes {
161 fn from(value: Box<[u8]>) -> Self {
162 Self::Boxed(value)
163 }
164}
165
166impl From<&'static [u8]> for ModuleCodeBytes {
167 fn from(value: &'static [u8]) -> Self {
168 Self::Static(value)
169 }
170}
171
172pub type ImportMetaResolveCallback = Box<
174 dyn Fn(&dyn ModuleLoader, String, String) -> Result<ModuleSpecifier, Error>,
175>;
176
177pub(crate) fn default_import_meta_resolve_cb(
178 loader: &dyn ModuleLoader,
179 specifier: String,
180 referrer: String,
181) -> Result<ModuleSpecifier, Error> {
182 if specifier.starts_with("npm:") {
183 bail!("\"npm:\" specifiers are currently not supported in import.meta.resolve()");
184 }
185
186 loader.resolve(&specifier, &referrer, ResolutionKind::DynamicImport)
187}
188
189pub type ValidateImportAttributesCb =
192 Box<dyn Fn(&mut v8::HandleScope, &HashMap<String, String>)>;
193
194pub type CustomModuleEvaluationCb = Box<
197 dyn Fn(
198 &mut v8::HandleScope,
199 Cow<'_, str>,
200 &FastString,
201 ModuleSourceCode,
202 ) -> Result<CustomModuleEvaluationKind, AnyError>,
203>;
204
205pub type EvalContextGetCodeCacheCb =
206 Box<dyn Fn(&str) -> Result<Option<Cow<'static, [u8]>>, AnyError>>;
207
208pub type EvalContextCodeCacheReadyCb = Box<dyn Fn(&str, &[u8])>;
209
210pub enum CustomModuleEvaluationKind {
211 Synthetic(v8::Global<v8::Value>),
213
214 ComputedAndSynthetic(
221 FastString,
223 v8::Global<v8::Value>,
225 ModuleType,
227 ),
228}
229
230#[derive(Debug)]
231pub(crate) enum ImportAttributesKind {
232 StaticImport,
233 DynamicImport,
234}
235
236pub(crate) fn parse_import_attributes(
237 scope: &mut v8::HandleScope,
238 attributes: v8::Local<v8::FixedArray>,
239 kind: ImportAttributesKind,
240) -> HashMap<String, String> {
241 let mut assertions: HashMap<String, String> = HashMap::default();
242
243 let assertions_per_line = match kind {
244 ImportAttributesKind::StaticImport => 3,
247 ImportAttributesKind::DynamicImport => 2,
249 };
250 assert_eq!(attributes.length() % assertions_per_line, 0);
251 let no_of_assertions = attributes.length() / assertions_per_line;
252
253 for i in 0..no_of_assertions {
254 let assert_key = attributes.get(scope, assertions_per_line * i).unwrap();
255 let assert_key_val = v8::Local::<v8::Value>::try_from(assert_key).unwrap();
256 let assert_value = attributes
257 .get(scope, (assertions_per_line * i) + 1)
258 .unwrap();
259 let assert_value_val =
260 v8::Local::<v8::Value>::try_from(assert_value).unwrap();
261 assertions.insert(
262 assert_key_val.to_rust_string_lossy(scope),
263 assert_value_val.to_rust_string_lossy(scope),
264 );
265 }
266
267 assertions
268}
269
270pub(crate) fn get_requested_module_type_from_attributes(
271 attributes: &HashMap<String, String>,
272) -> RequestedModuleType {
273 let Some(ty) = attributes.get("type") else {
274 return RequestedModuleType::None;
275 };
276
277 if ty == "json" {
278 RequestedModuleType::Json
279 } else {
280 RequestedModuleType::Other(Cow::Owned(ty.to_string()))
281 }
282}
283
284#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
290#[repr(u32)]
291pub enum ModuleType {
292 JavaScript,
293 Wasm,
294 Json,
295 Other(Cow<'static, str>),
296}
297
298impl std::fmt::Display for ModuleType {
299 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
300 match self {
301 Self::JavaScript => write!(f, "JavaScript"),
302 Self::Wasm => write!(f, "Wasm"),
303 Self::Json => write!(f, "JSON"),
304 Self::Other(ty) => write!(f, "{}", ty),
305 }
306 }
307}
308
309impl ModuleType {
310 pub fn to_v8<'s>(
311 &self,
312 scope: &mut v8::HandleScope<'s>,
313 ) -> v8::Local<'s, v8::Value> {
314 match self {
315 ModuleType::JavaScript => v8::Integer::new(scope, 0).into(),
316 ModuleType::Wasm => v8::Integer::new(scope, 1).into(),
317 ModuleType::Json => v8::Integer::new(scope, 2).into(),
318 ModuleType::Other(ty) => v8::String::new(scope, ty).unwrap().into(),
319 }
320 }
321
322 pub fn try_from_v8(
323 scope: &mut v8::HandleScope,
324 value: v8::Local<v8::Value>,
325 ) -> Option<Self> {
326 Some(if let Some(int) = value.to_integer(scope) {
327 match int.int32_value(scope).unwrap_or_default() {
328 0 => ModuleType::JavaScript,
329 1 => ModuleType::Wasm,
330 2 => ModuleType::Json,
331 _ => return None,
332 }
333 } else if let Ok(str) = v8::Local::<v8::String>::try_from(value) {
334 ModuleType::Other(Cow::Owned(str.to_rust_string_lossy(scope)))
335 } else {
336 return None;
337 })
338 }
339}
340
341#[derive(Debug)]
358pub struct ModuleSource {
359 pub code: ModuleSourceCode,
360 pub module_type: ModuleType,
361 pub code_cache: Option<Cow<'static, [u8]>>,
362 module_url_specified: ModuleName,
363 module_url_found: Option<ModuleName>,
365}
366
367impl ModuleSource {
368 pub fn new(
370 module_type: impl Into<ModuleType>,
371 code: ModuleSourceCode,
372 specifier: &ModuleSpecifier,
373 code_cache: Option<Cow<'static, [u8]>>,
374 ) -> Self {
375 let module_url_specified = specifier.as_ref().to_owned().into();
376 Self {
377 code,
378 module_type: module_type.into(),
379 code_cache,
380 module_url_specified,
381 module_url_found: None,
382 }
383 }
384
385 pub fn new_with_redirect(
388 module_type: impl Into<ModuleType>,
389 code: ModuleSourceCode,
390 specifier: &ModuleSpecifier,
391 specifier_found: &ModuleSpecifier,
392 code_cache: Option<Cow<'static, [u8]>>,
393 ) -> Self {
394 let module_url_found = if specifier == specifier_found {
395 None
396 } else {
397 Some(specifier_found.as_ref().to_owned().into())
398 };
399 let module_url_specified = specifier.as_ref().to_owned().into();
400 Self {
401 code,
402 module_type: module_type.into(),
403 code_cache,
404 module_url_specified,
405 module_url_found,
406 }
407 }
408
409 #[cfg(test)]
410 pub fn for_test(code: &'static str, file: impl AsRef<str>) -> Self {
411 Self {
412 code: ModuleSourceCode::String(code.into_module_code()),
413 module_type: ModuleType::JavaScript,
414 code_cache: None,
415 module_url_specified: file.as_ref().to_owned().into(),
416 module_url_found: None,
417 }
418 }
419
420 #[cfg(test)]
422 pub fn for_test_with_redirect(
423 code: &'static str,
424 specified: impl AsRef<str>,
425 found: impl AsRef<str>,
426 code_cache: Option<Cow<'static, [u8]>>,
427 ) -> Self {
428 let specified = specified.as_ref().to_string();
429 let found = found.as_ref().to_string();
430 let found = if found == specified {
431 None
432 } else {
433 Some(found.into())
434 };
435 Self {
436 code: ModuleSourceCode::String(code.into_module_code()),
437 module_type: ModuleType::JavaScript,
438 code_cache,
439 module_url_specified: specified.into(),
440 module_url_found: found,
441 }
442 }
443
444 pub fn get_string_source(
445 specifier: &str,
446 code: ModuleSourceCode,
447 ) -> Result<ModuleCodeString, AnyError> {
448 match code {
449 ModuleSourceCode::String(code) => Ok(code),
450 ModuleSourceCode::Bytes(bytes) => {
451 let str_ = String::from_utf8(bytes.to_vec()).with_context(|| {
452 format!("Can't convert source code to string for {}", specifier)
453 })?;
454 Ok(ModuleCodeString::from(str_))
455 }
456 }
457 }
458}
459
460pub type ModuleSourceFuture = dyn Future<Output = Result<ModuleSource, Error>>;
461
462#[derive(Debug, PartialEq, Eq)]
463pub enum ResolutionKind {
464 MainModule,
468 Import,
471 DynamicImport,
475}
476
477#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
478#[repr(u8)]
479pub enum RequestedModuleType {
480 None,
489
490 Json,
503
504 Other(Cow<'static, str>),
515}
516
517impl RequestedModuleType {
518 pub fn to_v8<'s>(
519 &self,
520 scope: &mut v8::HandleScope<'s>,
521 ) -> v8::Local<'s, v8::Value> {
522 match self {
523 RequestedModuleType::None => v8::Integer::new(scope, 0).into(),
524 RequestedModuleType::Json => v8::Integer::new(scope, 1).into(),
525 RequestedModuleType::Other(ty) => {
526 v8::String::new(scope, ty).unwrap().into()
527 }
528 }
529 }
530
531 pub fn try_from_v8(
532 scope: &mut v8::HandleScope,
533 value: v8::Local<v8::Value>,
534 ) -> Option<Self> {
535 Some(if let Some(int) = value.to_integer(scope) {
536 match int.int32_value(scope).unwrap_or_default() {
537 0 => RequestedModuleType::None,
538 1 => RequestedModuleType::Json,
539 _ => return None,
540 }
541 } else if let Ok(str) = v8::Local::<v8::String>::try_from(value) {
542 RequestedModuleType::Other(Cow::Owned(str.to_rust_string_lossy(scope)))
543 } else {
544 return None;
545 })
546 }
547}
548
549impl AsRef<RequestedModuleType> for RequestedModuleType {
550 fn as_ref(&self) -> &RequestedModuleType {
551 self
552 }
553}
554
555impl PartialEq<ModuleType> for RequestedModuleType {
557 fn eq(&self, other: &ModuleType) -> bool {
558 match other {
559 ModuleType::JavaScript => self == &RequestedModuleType::None,
560 ModuleType::Wasm => self == &RequestedModuleType::None,
561 ModuleType::Json => self == &RequestedModuleType::Json,
562 ModuleType::Other(ty) => self == &RequestedModuleType::Other(ty.clone()),
563 }
564 }
565}
566
567impl From<ModuleType> for RequestedModuleType {
568 fn from(module_type: ModuleType) -> RequestedModuleType {
569 match module_type {
570 ModuleType::JavaScript => RequestedModuleType::None,
571 ModuleType::Wasm => RequestedModuleType::None,
572 ModuleType::Json => RequestedModuleType::Json,
573 ModuleType::Other(ty) => RequestedModuleType::Other(ty.clone()),
574 }
575 }
576}
577
578impl std::fmt::Display for RequestedModuleType {
579 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
580 match self {
581 Self::None => write!(f, "None"),
582 Self::Json => write!(f, "JSON"),
583 Self::Other(ty) => write!(f, "Other({ty})"),
584 }
585 }
586}
587
588#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
593pub(crate) struct ModuleRequest {
594 pub specifier: String,
595 pub requested_module_type: RequestedModuleType,
596}
597
598#[derive(Debug, PartialEq, Serialize, Deserialize)]
599pub(crate) struct ModuleInfo {
600 #[allow(unused)]
601 pub id: ModuleId,
602 pub main: bool,
603 pub name: ModuleName,
604 pub requests: Vec<ModuleRequest>,
605 pub module_type: ModuleType,
606}
607
608#[derive(Debug)]
609pub(crate) enum ModuleError {
610 Exception(v8::Global<v8::Value>),
611 Other(Error),
612}
613
614impl ModuleError {
615 pub fn into_any_error(
616 self,
617 scope: &mut v8::HandleScope,
618 in_promise: bool,
619 clear_error: bool,
620 ) -> AnyError {
621 match self {
622 ModuleError::Exception(exception) => {
623 let exception = v8::Local::new(scope, exception);
624 exception_to_err_result::<()>(scope, exception, in_promise, clear_error)
625 .unwrap_err()
626 }
627 ModuleError::Other(error) => error,
628 }
629 }
630}