use crate::error::exception_to_err_result;
use crate::error::AnyError;
use crate::fast_string::FastString;
use crate::module_specifier::ModuleSpecifier;
use crate::FastStaticString;
use anyhow::bail;
use anyhow::Error;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use url::Url;
mod loaders;
mod map;
mod module_map_data;
mod recursive_load;
#[cfg(all(test, not(miri)))]
mod tests;
pub(crate) use loaders::ExtModuleLoader;
pub use loaders::ExtModuleLoaderCb;
pub use loaders::FsModuleLoader;
pub(crate) use loaders::LazyEsmModuleLoader;
pub use loaders::ModuleLoadResponse;
pub use loaders::ModuleLoader;
pub use loaders::NoopModuleLoader;
pub use loaders::StaticModuleLoader;
pub(crate) use map::script_origin;
pub(crate) use map::synthetic_module_evaluation_steps;
pub(crate) use map::ModuleMap;
pub(crate) use module_map_data::ModuleMapSnapshotData;
pub type ModuleId = usize;
pub(crate) type ModuleLoadId = i32;
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum ModuleSourceCode {
String(ModuleCodeString),
Bytes(ModuleCodeBytes),
}
impl ModuleSourceCode {
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::String(s) => s.as_bytes(),
Self::Bytes(b) => b.as_bytes(),
}
}
}
pub type ModuleCodeString = FastString;
pub type ModuleName = FastString;
pub trait IntoModuleName {
fn into_module_name(self) -> ModuleName;
}
impl IntoModuleName for ModuleName {
fn into_module_name(self) -> ModuleName {
self
}
}
impl IntoModuleName for &'static str {
fn into_module_name(self) -> ModuleName {
ModuleName::from_static(self)
}
}
impl IntoModuleName for String {
fn into_module_name(self) -> ModuleName {
ModuleName::from(self)
}
}
impl IntoModuleName for Url {
fn into_module_name(self) -> ModuleName {
ModuleName::from(self)
}
}
impl IntoModuleName for FastStaticString {
fn into_module_name(self) -> ModuleName {
ModuleName::from(self)
}
}
pub trait IntoModuleCodeString {
fn into_module_code(self) -> ModuleCodeString;
}
impl IntoModuleCodeString for ModuleCodeString {
fn into_module_code(self) -> ModuleCodeString {
self
}
}
impl IntoModuleCodeString for &'static str {
fn into_module_code(self) -> ModuleCodeString {
ModuleCodeString::from_static(self)
}
}
impl IntoModuleCodeString for String {
fn into_module_code(self) -> ModuleCodeString {
ModuleCodeString::from(self)
}
}
impl IntoModuleCodeString for FastStaticString {
fn into_module_code(self) -> ModuleCodeString {
ModuleCodeString::from(self)
}
}
impl IntoModuleCodeString for Arc<str> {
fn into_module_code(self) -> ModuleCodeString {
ModuleCodeString::from(self)
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum ModuleCodeBytes {
Static(&'static [u8]),
Boxed(Box<[u8]>),
Arc(Arc<[u8]>),
}
impl ModuleCodeBytes {
pub fn as_bytes(&self) -> &[u8] {
match self {
ModuleCodeBytes::Static(s) => s,
ModuleCodeBytes::Boxed(s) => s,
ModuleCodeBytes::Arc(s) => s,
}
}
pub fn to_vec(&self) -> Vec<u8> {
match self {
ModuleCodeBytes::Static(s) => s.to_vec(),
ModuleCodeBytes::Boxed(s) => s.to_vec(),
ModuleCodeBytes::Arc(s) => s.to_vec(),
}
}
}
impl From<Arc<[u8]>> for ModuleCodeBytes {
fn from(value: Arc<[u8]>) -> Self {
Self::Arc(value)
}
}
impl From<Box<[u8]>> for ModuleCodeBytes {
fn from(value: Box<[u8]>) -> Self {
Self::Boxed(value)
}
}
impl From<&'static [u8]> for ModuleCodeBytes {
fn from(value: &'static [u8]) -> Self {
Self::Static(value)
}
}
pub type ImportMetaResolveCallback = Box<
dyn Fn(&dyn ModuleLoader, String, String) -> Result<ModuleSpecifier, Error>,
>;
pub(crate) fn default_import_meta_resolve_cb(
loader: &dyn ModuleLoader,
specifier: String,
referrer: String,
) -> Result<ModuleSpecifier, Error> {
if specifier.starts_with("npm:") {
bail!("\"npm:\" specifiers are currently not supported in import.meta.resolve()");
}
loader.resolve(&specifier, &referrer, ResolutionKind::DynamicImport)
}
pub type ValidateImportAttributesCb =
Box<dyn Fn(&mut v8::HandleScope, &HashMap<String, String>)>;
pub type CustomModuleEvaluationCb = Box<
dyn Fn(
&mut v8::HandleScope,
Cow<'_, str>,
&FastString,
ModuleSourceCode,
) -> Result<CustomModuleEvaluationKind, AnyError>,
>;
pub type EvalContextGetCodeCacheCb =
Box<dyn Fn(&Url, &v8::String) -> Result<SourceCodeCacheInfo, AnyError>>;
pub type EvalContextCodeCacheReadyCb = Box<dyn Fn(Url, u64, &[u8])>;
pub enum CustomModuleEvaluationKind {
Synthetic(v8::Global<v8::Value>),
ComputedAndSynthetic(
FastString,
v8::Global<v8::Value>,
ModuleType,
),
}
#[derive(Debug)]
pub(crate) enum ImportAttributesKind {
StaticImport,
DynamicImport,
}
pub(crate) fn parse_import_attributes(
scope: &mut v8::HandleScope,
attributes: v8::Local<v8::FixedArray>,
kind: ImportAttributesKind,
) -> HashMap<String, String> {
let mut assertions: HashMap<String, String> = HashMap::default();
let assertions_per_line = match kind {
ImportAttributesKind::StaticImport => 3,
ImportAttributesKind::DynamicImport => 2,
};
assert_eq!(attributes.length() % assertions_per_line, 0);
let no_of_assertions = attributes.length() / assertions_per_line;
for i in 0..no_of_assertions {
let assert_key = attributes.get(scope, assertions_per_line * i).unwrap();
let assert_key_val = v8::Local::<v8::Value>::try_from(assert_key).unwrap();
let assert_value = attributes
.get(scope, (assertions_per_line * i) + 1)
.unwrap();
let assert_value_val =
v8::Local::<v8::Value>::try_from(assert_value).unwrap();
assertions.insert(
assert_key_val.to_rust_string_lossy(scope),
assert_value_val.to_rust_string_lossy(scope),
);
}
assertions
}
pub(crate) fn get_requested_module_type_from_attributes(
attributes: &HashMap<String, String>,
) -> RequestedModuleType {
let Some(ty) = attributes.get("type") else {
return RequestedModuleType::None;
};
if ty == "json" {
RequestedModuleType::Json
} else {
RequestedModuleType::Other(Cow::Owned(ty.to_string()))
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum ModuleType {
JavaScript,
Wasm,
Json,
Other(Cow<'static, str>),
}
impl std::fmt::Display for ModuleType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::JavaScript => write!(f, "JavaScript"),
Self::Wasm => write!(f, "Wasm"),
Self::Json => write!(f, "JSON"),
Self::Other(ty) => write!(f, "{}", ty),
}
}
}
impl ModuleType {
pub fn to_v8<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
) -> v8::Local<'s, v8::Value> {
match self {
ModuleType::JavaScript => v8::Integer::new(scope, 0).into(),
ModuleType::Wasm => v8::Integer::new(scope, 1).into(),
ModuleType::Json => v8::Integer::new(scope, 2).into(),
ModuleType::Other(ty) => v8::String::new(scope, ty).unwrap().into(),
}
}
pub fn try_from_v8(
scope: &mut v8::HandleScope,
value: v8::Local<v8::Value>,
) -> Option<Self> {
Some(if let Some(int) = value.to_integer(scope) {
match int.int32_value(scope).unwrap_or_default() {
0 => ModuleType::JavaScript,
1 => ModuleType::Wasm,
2 => ModuleType::Json,
_ => return None,
}
} else if let Ok(str) = v8::Local::<v8::String>::try_from(value) {
ModuleType::Other(Cow::Owned(str.to_rust_string_lossy(scope)))
} else {
return None;
})
}
}
#[derive(Debug)]
pub struct SourceCodeCacheInfo {
pub hash: u64,
pub data: Option<Cow<'static, [u8]>>,
}
#[derive(Debug)]
pub struct ModuleSource {
pub code: ModuleSourceCode,
pub module_type: ModuleType,
pub code_cache: Option<SourceCodeCacheInfo>,
module_url_specified: ModuleName,
module_url_found: Option<ModuleName>,
}
impl ModuleSource {
pub fn new(
module_type: impl Into<ModuleType>,
code: ModuleSourceCode,
specifier: &ModuleSpecifier,
code_cache: Option<SourceCodeCacheInfo>,
) -> Self {
let module_url_specified = specifier.as_ref().to_owned().into();
Self {
code,
module_type: module_type.into(),
code_cache,
module_url_specified,
module_url_found: None,
}
}
pub fn new_with_redirect(
module_type: impl Into<ModuleType>,
code: ModuleSourceCode,
specifier: &ModuleSpecifier,
specifier_found: &ModuleSpecifier,
code_cache: Option<SourceCodeCacheInfo>,
) -> Self {
let module_url_found = if specifier == specifier_found {
None
} else {
Some(specifier_found.as_ref().to_owned().into())
};
let module_url_specified = specifier.as_ref().to_owned().into();
Self {
code,
module_type: module_type.into(),
code_cache,
module_url_specified,
module_url_found,
}
}
#[cfg(test)]
pub fn for_test(code: &'static str, file: impl AsRef<str>) -> Self {
Self {
code: ModuleSourceCode::String(code.into_module_code()),
module_type: ModuleType::JavaScript,
code_cache: None,
module_url_specified: file.as_ref().to_owned().into(),
module_url_found: None,
}
}
#[cfg(test)]
pub fn for_test_with_redirect(
code: &'static str,
specified: impl AsRef<str>,
found: impl AsRef<str>,
code_cache: Option<SourceCodeCacheInfo>,
) -> Self {
let specified = specified.as_ref().to_string();
let found = found.as_ref().to_string();
let found = if found == specified {
None
} else {
Some(found.into())
};
Self {
code: ModuleSourceCode::String(code.into_module_code()),
module_type: ModuleType::JavaScript,
code_cache,
module_url_specified: specified.into(),
module_url_found: found,
}
}
pub fn get_string_source(code: ModuleSourceCode) -> ModuleCodeString {
match code {
ModuleSourceCode::String(code) => code,
ModuleSourceCode::Bytes(bytes) => {
match String::from_utf8_lossy(bytes.as_bytes()) {
Cow::Borrowed(s) => ModuleCodeString::from(s.to_owned()),
Cow::Owned(s) => ModuleCodeString::from(s),
}
}
}
}
}
pub type ModuleSourceFuture = dyn Future<Output = Result<ModuleSource, Error>>;
#[derive(Debug, PartialEq, Eq)]
pub enum ResolutionKind {
MainModule,
Import,
DynamicImport,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum RequestedModuleType {
None,
Json,
Other(Cow<'static, str>),
}
impl RequestedModuleType {
pub fn to_v8<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
) -> v8::Local<'s, v8::Value> {
match self {
RequestedModuleType::None => v8::Integer::new(scope, 0).into(),
RequestedModuleType::Json => v8::Integer::new(scope, 1).into(),
RequestedModuleType::Other(ty) => {
v8::String::new(scope, ty).unwrap().into()
}
}
}
pub fn try_from_v8(
scope: &mut v8::HandleScope,
value: v8::Local<v8::Value>,
) -> Option<Self> {
Some(if let Some(int) = value.to_integer(scope) {
match int.int32_value(scope).unwrap_or_default() {
0 => RequestedModuleType::None,
1 => RequestedModuleType::Json,
_ => return None,
}
} else if let Ok(str) = v8::Local::<v8::String>::try_from(value) {
RequestedModuleType::Other(Cow::Owned(str.to_rust_string_lossy(scope)))
} else {
return None;
})
}
}
impl AsRef<RequestedModuleType> for RequestedModuleType {
fn as_ref(&self) -> &RequestedModuleType {
self
}
}
impl PartialEq<ModuleType> for RequestedModuleType {
fn eq(&self, other: &ModuleType) -> bool {
match other {
ModuleType::JavaScript => self == &RequestedModuleType::None,
ModuleType::Wasm => self == &RequestedModuleType::None,
ModuleType::Json => self == &RequestedModuleType::Json,
ModuleType::Other(ty) => self == &RequestedModuleType::Other(ty.clone()),
}
}
}
impl From<ModuleType> for RequestedModuleType {
fn from(module_type: ModuleType) -> RequestedModuleType {
match module_type {
ModuleType::JavaScript => RequestedModuleType::None,
ModuleType::Wasm => RequestedModuleType::None,
ModuleType::Json => RequestedModuleType::Json,
ModuleType::Other(ty) => RequestedModuleType::Other(ty.clone()),
}
}
}
impl std::fmt::Display for RequestedModuleType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::Json => write!(f, "JSON"),
Self::Other(ty) => write!(f, "Other({ty})"),
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub(crate) struct ModuleRequest {
pub specifier: ModuleSpecifier,
pub requested_module_type: RequestedModuleType,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct ModuleInfo {
#[allow(unused)]
pub id: ModuleId,
pub main: bool,
pub name: ModuleName,
pub requests: Vec<ModuleRequest>,
pub module_type: ModuleType,
}
#[derive(Debug)]
pub(crate) enum ModuleError {
Exception(v8::Global<v8::Value>),
Other(Error),
}
impl ModuleError {
pub fn into_any_error(
self,
scope: &mut v8::HandleScope,
in_promise: bool,
clear_error: bool,
) -> AnyError {
match self {
ModuleError::Exception(exception) => {
let exception = v8::Local::new(scope, exception);
exception_to_err_result::<()>(scope, exception, in_promise, clear_error)
.unwrap_err()
}
ModuleError::Other(error) => error,
}
}
}