use super::module_map_data::ModuleMapSnapshotData;
use super::IntoModuleCodeString;
use super::IntoModuleName;
use crate::ascii_str;
use crate::error::exception_to_err_result;
use crate::error::generic_error;
use crate::error::throw_type_error;
use crate::error::to_v8_type_error;
use crate::error::AnyError;
use crate::error::JsError;
use crate::modules::get_requested_module_type_from_attributes;
use crate::modules::parse_import_attributes;
use crate::modules::recursive_load::RecursiveModuleLoad;
use crate::modules::ImportAttributesKind;
use crate::modules::ModuleCodeString;
use crate::modules::ModuleError;
use crate::modules::ModuleId;
use crate::modules::ModuleLoadId;
use crate::modules::ModuleLoader;
use crate::modules::ModuleName;
use crate::modules::ModuleRequest;
use crate::modules::ModuleType;
use crate::modules::ResolutionKind;
use crate::runtime::exception_state::ExceptionState;
use crate::runtime::JsRealm;
use crate::runtime::SnapshotLoadDataStore;
use crate::runtime::SnapshotStoreDataStore;
use crate::FastStaticString;
use crate::JsRuntime;
use crate::ModuleCodeBytes;
use crate::ModuleLoadResponse;
use crate::ModuleSource;
use crate::ModuleSourceCode;
use crate::ModuleSpecifier;
use anyhow::bail;
use anyhow::Error;
use futures::future::FutureExt;
use futures::stream::FuturesUnordered;
use futures::stream::StreamFuture;
use futures::task::AtomicWaker;
use futures::Future;
use futures::StreamExt;
use indexmap::IndexMap;
use v8::Function;
use v8::PromiseState;
use wasm_dep_analyzer::WasmDeps;
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::DerefMut;
use std::pin::Pin;
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use tokio::sync::oneshot;
use super::module_map_data::ModuleMapData;
use super::CustomModuleEvaluationKind;
use super::LazyEsmModuleLoader;
use super::RequestedModuleType;
type PrepareLoadFuture =
dyn Future<Output = (ModuleLoadId, Result<RecursiveModuleLoad, Error>)>;
type CodeCacheReadyFuture = dyn Future<Output = ()>;
use super::ImportMetaResolveCallback;
struct ModEvaluate {
module_map: Rc<ModuleMap>,
sender: Option<oneshot::Sender<Result<(), Error>>>,
module: Option<v8::Global<v8::Module>>,
notify: Vec<v8::Global<v8::Function>>,
}
impl ModEvaluate {
fn notify(&mut self, scope: &mut v8::HandleScope) {
if !self.notify.is_empty() {
let module = v8::Local::new(scope, self.module.take().unwrap());
let ns = module.get_module_namespace();
let recv = v8::undefined(scope).into();
let args = &[ns];
for notify in std::mem::take(&mut self.notify).into_iter() {
let notify = v8::Local::new(scope, notify);
notify.call(scope, recv, args);
}
}
_ = self.sender.take().unwrap().send(Ok(()));
}
}
type CodeCacheReadyCallback =
Box<dyn FnOnce(&[u8]) -> Pin<Box<dyn Future<Output = ()>>>>;
pub(crate) struct CodeCacheInfo {
data: Option<Cow<'static, [u8]>>,
ready_callback: CodeCacheReadyCallback,
}
pub const BOM_CHAR: &[u8] = &[0xef, 0xbb, 0xbf];
fn strip_bom(source_code: &[u8]) -> &[u8] {
if source_code.starts_with(BOM_CHAR) {
&source_code[BOM_CHAR.len()..]
} else {
source_code
}
}
struct DynImportModEvaluate {
load_id: ModuleLoadId,
module_id: ModuleId,
promise: v8::Global<v8::Promise>,
module: v8::Global<v8::Module>,
}
struct DynImportState {
resolver: v8::Global<v8::PromiseResolver>,
cped: v8::Global<v8::Value>,
}
pub(crate) struct ModuleMap {
pub(crate) loader: RefCell<Rc<dyn ModuleLoader>>,
pub(crate) import_meta_resolve_cb: ImportMetaResolveCallback,
exception_state: Rc<ExceptionState>,
dynamic_import_map: RefCell<HashMap<ModuleLoadId, DynImportState>>,
preparing_dynamic_imports:
RefCell<FuturesUnordered<Pin<Box<PrepareLoadFuture>>>>,
preparing_dynamic_imports_pending: Cell<bool>,
pending_dynamic_imports:
RefCell<FuturesUnordered<StreamFuture<RecursiveModuleLoad>>>,
pending_dynamic_imports_pending: Cell<bool>,
pending_dyn_mod_evaluations: RefCell<Vec<DynImportModEvaluate>>,
pending_dyn_mod_evaluations_pending: Cell<bool>,
pending_mod_evaluation: Cell<bool>,
code_cache_ready_futs:
RefCell<FuturesUnordered<Pin<Box<CodeCacheReadyFuture>>>>,
pending_code_cache_ready: Cell<bool>,
module_waker: AtomicWaker,
data: RefCell<ModuleMapData>,
will_snapshot: bool,
pub(crate) dyn_module_evaluate_idle_counter: Cell<u32>,
}
impl ModuleMap {
pub(crate) fn destroy(&self) {
self.dynamic_import_map.borrow_mut().clear();
self.preparing_dynamic_imports.borrow_mut().clear();
self.pending_dynamic_imports.borrow_mut().clear();
self.code_cache_ready_futs.borrow_mut().clear();
std::mem::take(&mut *self.data.borrow_mut());
}
pub(crate) fn next_load_id(&self) -> i32 {
let mut data = self.data.borrow_mut();
let id = data.next_load_id;
data.next_load_id += 1;
id + 1
}
#[cfg(debug_assertions)]
pub(crate) fn check_all_modules_evaluated(
&self,
scope: &mut v8::HandleScope,
) -> Result<(), Error> {
let mut not_evaluated = vec![];
let data = self.data.borrow();
for (handle, i) in data.handles_inverted.iter() {
let module = v8::Local::new(scope, handle);
match module.get_status() {
v8::ModuleStatus::Errored => {
return Err(
JsError::from_v8_exception(scope, module.get_exception()).into(),
);
}
v8::ModuleStatus::Evaluated => {}
_ => {
not_evaluated.push(data.info[*i].name.as_str().to_string());
}
}
}
if !not_evaluated.is_empty() {
let mut msg = String::new();
for m in not_evaluated {
msg.push_str(&format!(" - {}\n", m));
}
bail!("Following modules were not evaluated; make sure they are imported from other code:\n {}", msg);
}
Ok(())
}
pub(crate) fn new(
loader: Rc<dyn ModuleLoader>,
exception_state: Rc<ExceptionState>,
import_meta_resolve_cb: ImportMetaResolveCallback,
will_snapshot: bool,
) -> Self {
Self {
will_snapshot,
loader: loader.into(),
exception_state,
import_meta_resolve_cb,
dyn_module_evaluate_idle_counter: Default::default(),
dynamic_import_map: Default::default(),
preparing_dynamic_imports: Default::default(),
preparing_dynamic_imports_pending: Default::default(),
pending_dynamic_imports: Default::default(),
pending_dynamic_imports_pending: Default::default(),
pending_dyn_mod_evaluations: Default::default(),
pending_dyn_mod_evaluations_pending: Default::default(),
pending_mod_evaluation: Default::default(),
code_cache_ready_futs: Default::default(),
pending_code_cache_ready: Default::default(),
module_waker: Default::default(),
data: Default::default(),
}
}
pub(crate) fn update_with_snapshotted_data(
&self,
scope: &mut v8::HandleScope,
data_store: &mut SnapshotLoadDataStore,
data: ModuleMapSnapshotData,
) {
self
.data
.borrow_mut()
.update_with_snapshotted_data(scope, data_store, data);
}
pub(crate) fn get_id(
&self,
name: &str,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> Option<ModuleId> {
self.data.borrow().get_id(name, requested_module_type)
}
pub(crate) fn is_main_module(&self, global: &v8::Global<v8::Module>) -> bool {
self.data.borrow().is_main_module(global)
}
pub(crate) fn is_main_module_id(&self, id: ModuleId) -> bool {
self.data.borrow().main_module_id == Some(id)
}
pub(crate) fn get_name_by_module(
&self,
global: &v8::Global<v8::Module>,
) -> Option<String> {
self.data.borrow().get_name_by_module(global)
}
pub(crate) fn get_name_by_id(&self, id: ModuleId) -> Option<String> {
self.data.borrow().get_name_by_id(id)
}
pub(crate) fn get_type_by_module(
&self,
global: &v8::Global<v8::Module>,
) -> Option<ModuleType> {
self.data.borrow().get_type_by_module(global)
}
pub(crate) fn get_handle(
&self,
id: ModuleId,
) -> Option<v8::Global<v8::Module>> {
self.data.borrow().get_handle(id)
}
pub(crate) fn serialize_for_snapshotting(
&self,
data_store: &mut SnapshotStoreDataStore,
) -> ModuleMapSnapshotData {
let data = std::mem::take(&mut *self.data.borrow_mut());
data.serialize_for_snapshotting(data_store)
}
#[cfg(test)]
pub fn is_alias(
&self,
name: &str,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> bool {
self.data.borrow().is_alias(name, requested_module_type)
}
pub(crate) fn get_data(&self) -> &RefCell<ModuleMapData> {
&self.data
}
#[cfg(test)]
pub fn assert_module_map(&self, modules: &Vec<super::ModuleInfo>) {
self.data.borrow().assert_module_map(modules);
}
pub(crate) fn new_module(
&self,
scope: &mut v8::HandleScope,
main: bool,
dynamic: bool,
module_source: ModuleSource,
) -> Result<ModuleId, ModuleError> {
let ModuleSource {
code,
module_type,
module_url_found,
module_url_specified,
code_cache,
} = module_source;
let module_url_found = if let Some(module_url_found) = module_url_found {
let (module_url_found1, module_url_found2) =
module_url_found.into_cheap_copy();
self.data.borrow_mut().alias(
module_url_specified,
&module_type.clone().into(),
module_url_found1,
);
module_url_found2
} else {
module_url_specified
};
let requested_module_type = RequestedModuleType::from(module_type.clone());
let maybe_module_id = self.get_id(&module_url_found, requested_module_type);
if let Some(module_id) = maybe_module_id {
return Ok(module_id);
}
let module_id = match module_type {
ModuleType::JavaScript => {
let code = ModuleSource::get_string_source(code);
let (code_cache_info, module_url_found) =
if let Some(code_cache) = code_cache {
let (module_url_found1, module_url_found2) =
module_url_found.into_cheap_copy();
let loader = self.loader.borrow().clone();
(
Some(CodeCacheInfo {
data: code_cache.data,
ready_callback: Box::new(move |cache| {
let specifier =
ModuleSpecifier::parse(module_url_found1.as_str()).unwrap();
loader.code_cache_ready(specifier, code_cache.hash, cache)
}),
}),
module_url_found2,
)
} else {
(None, module_url_found)
};
self.new_module_from_js_source(
scope,
main,
ModuleType::JavaScript,
module_url_found,
code,
dynamic,
code_cache_info,
)?
}
ModuleType::Wasm => {
let ModuleSourceCode::Bytes(code) = code else {
return Err(ModuleError::Other(generic_error(
"Source code for Wasm module must be provided as bytes",
)));
};
self.new_wasm_module(scope, module_url_found, code, dynamic)?
}
ModuleType::Json => {
let code = ModuleSource::get_string_source(code);
self.new_json_module(scope, module_url_found, code)?
}
ModuleType::Other(module_type) => {
let state = JsRuntime::state_from(scope);
let custom_module_evaluation_cb =
state.custom_module_evaluation_cb.as_ref();
let Some(custom_evaluation_cb) = custom_module_evaluation_cb else {
return Err(ModuleError::Other(generic_error(format!(
"Importing '{}' modules is not supported",
module_type
))));
};
let module_evaluation_kind = custom_evaluation_cb(
scope,
module_type.clone(),
&module_url_found,
code,
)
.map_err(ModuleError::Other)?;
match module_evaluation_kind {
CustomModuleEvaluationKind::Synthetic(value_global) => {
let value = v8::Local::new(scope, value_global);
let exports = vec![(ascii_str!("default"), value)];
self.new_synthetic_module(
scope,
module_url_found,
ModuleType::Other(module_type.clone()),
exports,
)?
}
CustomModuleEvaluationKind::ComputedAndSynthetic(
computed_src,
synthetic_value,
synthetic_module_type,
) => {
let (url1, url2) = module_url_found.into_cheap_copy();
let value = v8::Local::new(scope, synthetic_value);
let exports = vec![(ascii_str!("default"), value)];
let _synthetic_mod_id = self.new_synthetic_module(
scope,
url1,
synthetic_module_type,
exports,
)?;
let (code_cache_info, url2) = if let Some(code_cache) = code_cache {
let (url1, url2) = url2.into_cheap_copy();
let loader = self.loader.borrow().clone();
(
Some(CodeCacheInfo {
data: code_cache.data,
ready_callback: Box::new(move |cache| {
let specifier =
ModuleSpecifier::parse(url1.as_str()).unwrap();
loader.code_cache_ready(specifier, code_cache.hash, cache)
}),
}),
url2,
)
} else {
(None, url2)
};
self.new_module_from_js_source(
scope,
main,
ModuleType::Other(module_type.clone()),
url2,
computed_src,
dynamic,
code_cache_info,
)?
}
}
}
};
Ok(module_id)
}
pub fn new_synthetic_module(
&self,
scope: &mut v8::HandleScope,
name: impl IntoModuleName,
module_type: ModuleType,
exports: Vec<(FastStaticString, v8::Local<v8::Value>)>,
) -> Result<ModuleId, ModuleError> {
let name = name.into_module_name();
let name_str = name.v8_string(scope);
let export_names = exports
.iter()
.map(|(name, _)| name.v8_string(scope))
.collect::<Vec<_>>();
let module = v8::Module::create_synthetic_module(
scope,
name_str,
&export_names,
synthetic_module_evaluation_steps,
);
let handle = v8::Global::<v8::Module>::new(scope, module);
let mut exports_global = Vec::with_capacity(exports.len());
for i in 0..exports.len() {
let export_name = export_names[i];
let (_, export_value) = exports[i];
exports_global.push((
v8::Global::new(scope, export_name),
v8::Global::new(scope, export_value),
));
}
self
.data
.borrow_mut()
.synthetic_module_exports_store
.insert(handle.clone(), exports_global);
let id = self.data.borrow_mut().create_module_info(
name,
module_type,
handle,
false,
vec![],
);
self.instantiate_module(scope, id).unwrap();
Ok(id)
}
pub(crate) fn new_es_module(
&self,
scope: &mut v8::HandleScope,
main: bool,
name: impl IntoModuleName,
source: impl IntoModuleCodeString,
is_dynamic_import: bool,
code_cache_info: Option<CodeCacheInfo>,
) -> Result<ModuleId, ModuleError> {
let name = name.into_module_name();
self.new_module_from_js_source(
scope,
main,
ModuleType::JavaScript,
name.into_module_name(),
source.into_module_code(),
is_dynamic_import,
code_cache_info,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_module_from_js_source(
&self,
scope: &mut v8::HandleScope,
main: bool,
module_type: ModuleType,
name: ModuleName,
source: ModuleCodeString,
is_dynamic_import: bool,
mut code_cache_info: Option<CodeCacheInfo>,
) -> Result<ModuleId, ModuleError> {
if main {
let data = self.data.borrow();
if let Some(main_module) = data.main_module_id {
let main_name = self.data.borrow().get_name_by_id(main_module).unwrap();
return Err(ModuleError::Other(generic_error(
format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})",
name,
main_name,
))));
}
}
let name_str = name.v8_string(scope);
let source_str = source.v8_string(scope);
let host_defined_options = self
.loader
.borrow()
.get_host_defined_options(scope, name.as_str());
let origin = script_origin(scope, name_str, true, host_defined_options);
let tc_scope = &mut v8::TryCatch::new(scope);
let (maybe_module, try_store_code_cache) = code_cache_info
.as_ref()
.and_then(|code_cache_info| {
code_cache_info.data.as_ref().map(|cache| {
let mut source = v8::script_compiler::Source::new_with_cached_data(
source_str,
Some(&origin),
v8::CachedData::new(cache),
);
let maybe_module = v8::script_compiler::compile_module2(
tc_scope,
&mut source,
v8::script_compiler::CompileOptions::ConsumeCodeCache,
v8::script_compiler::NoCacheReason::NoReason,
);
let rejected = match source.get_cached_data() {
Some(cached_data) => cached_data.rejected(),
_ => true,
};
(maybe_module, rejected)
})
})
.unwrap_or_else(|| {
let mut source =
v8::script_compiler::Source::new(source_str, Some(&origin));
(
v8::script_compiler::compile_module(tc_scope, &mut source),
true,
)
});
if tc_scope.has_caught() {
assert!(maybe_module.is_none());
let exception = tc_scope.exception().unwrap();
let exception = v8::Global::new(tc_scope, exception);
return Err(ModuleError::Exception(exception));
}
let module = maybe_module.unwrap();
if try_store_code_cache && !self.will_snapshot {
if let Some(code_cache_info) = code_cache_info.take() {
let unbound_module_script = module.get_unbound_module_script(tc_scope);
let code_cache =
unbound_module_script.create_code_cache().ok_or_else(|| {
ModuleError::Other(generic_error(
"Unable to get code cache from unbound module script",
))
})?;
let fut =
async move { (code_cache_info.ready_callback)(&code_cache).await }
.boxed_local();
self.code_cache_ready_futs.borrow_mut().push(fut);
self.pending_code_cache_ready.set(true);
}
}
let module_requests = module.get_module_requests();
let requests_len = module_requests.length();
let mut requests = Vec::with_capacity(requests_len);
for i in 0..module_requests.length() {
let module_request = v8::Local::<v8::ModuleRequest>::try_from(
module_requests.get(tc_scope, i).unwrap(),
)
.unwrap();
let import_specifier = module_request
.get_specifier()
.to_rust_string_lossy(tc_scope);
let import_attributes = module_request.get_import_attributes();
let attributes = parse_import_attributes(
tc_scope,
import_attributes,
ImportAttributesKind::StaticImport,
);
{
let state = JsRuntime::state_from(tc_scope);
if let Some(validate_import_attributes_cb) =
&state.validate_import_attributes_cb
{
(validate_import_attributes_cb)(tc_scope, &attributes);
}
}
if tc_scope.has_caught() {
let exception = tc_scope.exception().unwrap();
let exception = v8::Global::new(tc_scope, exception);
return Err(ModuleError::Exception(exception));
}
let module_specifier = match self.resolve(
&import_specifier,
name.as_ref(),
if is_dynamic_import {
ResolutionKind::DynamicImport
} else {
ResolutionKind::Import
},
) {
Ok(s) => s,
Err(e) => return Err(ModuleError::Other(e)),
};
let requested_module_type =
get_requested_module_type_from_attributes(&attributes);
let request = ModuleRequest {
specifier: module_specifier,
requested_module_type,
};
requests.push(request);
}
let handle = v8::Global::<v8::Module>::new(tc_scope, module);
let id = self.data.borrow_mut().create_module_info(
name,
module_type,
handle,
main,
requests,
);
Ok(id)
}
pub(crate) fn new_wasm_module(
&self,
scope: &mut v8::HandleScope,
name: ModuleName,
source: ModuleCodeBytes,
is_dynamic_import: bool,
) -> Result<ModuleId, ModuleError> {
let bytes = source.as_bytes();
let wasm_module_analysis = WasmDeps::parse(
bytes,
wasm_dep_analyzer::ParseOptions { skip_types: true },
)
.map_err(|e| {
let err = Error::from(e);
ModuleError::Other(err)
})?;
let Some(wasm_module) = v8::WasmModuleObject::compile(scope, bytes) else {
return Err(ModuleError::Other(generic_error(format!(
"Failed to compile Wasm module '{}'",
name.as_str()
))));
};
let wasm_module_value: v8::Local<v8::Value> = wasm_module.into();
let js_wasm_module_source =
render_js_wasm_module(name.as_str(), wasm_module_analysis);
let synthetic_module_type =
ModuleType::Other("$$deno-core-internal-wasm-module".into());
let (name1, name2) = name.into_cheap_copy();
let value = v8::Local::new(scope, wasm_module_value);
let exports = vec![(ascii_str!("default"), value)];
let _synthetic_mod_id = self.new_synthetic_module(
scope,
name1,
synthetic_module_type,
exports,
)?;
self.new_module_from_js_source(
scope,
false,
ModuleType::Wasm,
name2,
js_wasm_module_source.into(),
is_dynamic_import,
None,
)
}
pub(crate) fn new_json_module(
&self,
scope: &mut v8::HandleScope,
name: impl IntoModuleName,
code: impl IntoModuleCodeString,
) -> Result<ModuleId, ModuleError> {
let name = name.into_module_name();
let code = code.into_module_code();
let source_str = v8::String::new_from_utf8(
scope,
strip_bom(code.as_bytes()),
v8::NewStringType::Normal,
)
.unwrap();
let tc_scope = &mut v8::TryCatch::new(scope);
let parsed_json = match v8::json::parse(tc_scope, source_str) {
Some(parsed_json) => parsed_json,
None => {
assert!(tc_scope.has_caught());
let exception = tc_scope.exception().unwrap();
let exception = v8::Global::new(tc_scope, exception);
return Err(ModuleError::Exception(exception));
}
};
let exports = vec![(ascii_str!("default"), parsed_json)];
self.new_synthetic_module(tc_scope, name, ModuleType::Json, exports)
}
pub(crate) fn instantiate_module(
&self,
scope: &mut v8::HandleScope,
id: ModuleId,
) -> Result<(), v8::Global<v8::Value>> {
let tc_scope = &mut v8::TryCatch::new(scope);
let module = self
.get_handle(id)
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
if module.get_status() == v8::ModuleStatus::Errored {
return Err(v8::Global::new(tc_scope, module.get_exception()));
}
if module.get_status() != v8::ModuleStatus::Uninstantiated {
return Ok(());
}
tc_scope.set_slot(self as *const _);
let instantiate_result =
module.instantiate_module(tc_scope, Self::module_resolve_callback);
tc_scope.remove_slot::<*const Self>();
if instantiate_result.is_none() {
let exception = tc_scope.exception().unwrap();
return Err(v8::Global::new(tc_scope, exception));
}
Ok(())
}
fn module_resolve_callback<'s>(
context: v8::Local<'s, v8::Context>,
specifier: v8::Local<'s, v8::String>,
import_attributes: v8::Local<'s, v8::FixedArray>,
referrer: v8::Local<'s, v8::Module>,
) -> Option<v8::Local<'s, v8::Module>> {
let scope = &mut unsafe { v8::CallbackScope::new(context) };
let module_map =
unsafe { scope.get_slot::<*const Self>().unwrap().as_ref().unwrap() };
let referrer_global = v8::Global::new(scope, referrer);
let referrer_name = module_map
.data
.borrow()
.get_name_by_module(&referrer_global)
.expect("ModuleInfo not found");
let specifier_str = specifier.to_rust_string_lossy(scope);
let assertions = parse_import_attributes(
scope,
import_attributes,
ImportAttributesKind::StaticImport,
);
let maybe_module = module_map.resolve_callback(
scope,
&specifier_str,
&referrer_name,
assertions,
);
if let Some(module) = maybe_module {
return Some(module);
}
let msg = format!(
r#"Cannot resolve module "{specifier_str}" from "{referrer_name}""#
);
throw_type_error(scope, msg);
None
}
pub fn resolve(
&self,
specifier: &str,
referrer: &str,
kind: ResolutionKind,
) -> Result<ModuleSpecifier, AnyError> {
if specifier.starts_with("ext:")
&& !referrer.starts_with("ext:")
&& !referrer.starts_with("node:")
&& !referrer.starts_with("checkin:")
&& referrer != "."
&& kind != ResolutionKind::MainModule
{
let referrer = if referrer.is_empty() {
"(no referrer)"
} else {
referrer
};
let msg = format!("Importing ext: modules is only allowed from ext: and node: modules. Tried to import {} from {}", specifier, referrer);
return Err(generic_error(msg));
}
self.loader.borrow().resolve(specifier, referrer, kind)
}
fn resolve_callback<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
specifier: &str,
referrer: &str,
import_attributes: HashMap<String, String>,
) -> Option<v8::Local<'s, v8::Module>> {
let resolved_specifier =
match self.resolve(specifier, referrer, ResolutionKind::Import) {
Ok(s) => s,
Err(e) => {
throw_type_error(scope, e.to_string());
return None;
}
};
let module_type =
get_requested_module_type_from_attributes(&import_attributes);
if let Some(id) = self.get_id(resolved_specifier.as_str(), module_type) {
if let Some(handle) = self.get_handle(id) {
return Some(v8::Local::new(scope, handle));
}
}
None
}
pub(crate) fn get_requested_modules(
&self,
id: ModuleId,
) -> Option<Vec<ModuleRequest>> {
self.data.borrow().info.get(id).map(|i| i.requests.clone())
}
pub(crate) async fn load_main(
module_map_rc: Rc<ModuleMap>,
specifier: impl AsRef<str>,
) -> Result<RecursiveModuleLoad, Error> {
let load =
RecursiveModuleLoad::main(specifier.as_ref(), module_map_rc.clone());
load.prepare().await?;
Ok(load)
}
pub(crate) async fn load_side(
module_map_rc: Rc<ModuleMap>,
specifier: impl AsRef<str>,
) -> Result<RecursiveModuleLoad, Error> {
let load =
RecursiveModuleLoad::side(specifier.as_ref(), module_map_rc.clone());
load.prepare().await?;
Ok(load)
}
pub(crate) fn load_dynamic_import(
self: Rc<Self>,
scope: &mut v8::HandleScope,
specifier: &str,
referrer: &str,
requested_module_type: RequestedModuleType,
resolver_handle: v8::Global<v8::PromiseResolver>,
cped_handle: v8::Global<v8::Value>,
) -> bool {
let resolve_result =
self.resolve(specifier, referrer, ResolutionKind::DynamicImport);
if let Ok(module_specifier) = &resolve_result {
if let Some(id) = self
.data
.borrow()
.get_id(module_specifier.as_str(), &requested_module_type)
{
let module = self
.data
.borrow()
.get_handle(id)
.map(|handle| v8::Local::new(scope, handle))
.expect("Dyn import module info not found");
if module.get_status() == v8::ModuleStatus::Evaluated {
let resolver = resolver_handle.open(scope);
let module_namespace = module.get_module_namespace();
resolver.resolve(scope, module_namespace).unwrap();
return false;
}
}
}
let load = RecursiveModuleLoad::dynamic_import(
specifier,
referrer,
requested_module_type,
self.clone(),
);
self.dynamic_import_map.borrow_mut().insert(
load.id,
DynImportState {
resolver: resolver_handle,
cped: cped_handle,
},
);
let fut = match resolve_result {
Ok(_) => async move { (load.id, load.prepare().await.map(|()| load)) }
.boxed_local(),
Err(error) => async move { (load.id, Err(error)) }.boxed_local(),
};
self.preparing_dynamic_imports.borrow_mut().push(fut);
self.preparing_dynamic_imports_pending.set(true);
true
}
pub(crate) fn has_pending_dynamic_imports(&self) -> bool {
self.preparing_dynamic_imports_pending.get()
|| self.pending_dynamic_imports_pending.get()
}
pub(crate) fn has_pending_module_evaluation(&self) -> bool {
self.pending_mod_evaluation.get()
}
pub(crate) fn has_pending_dyn_module_evaluation(&self) -> bool {
self.pending_dyn_mod_evaluations_pending.get()
}
pub fn mod_evaluate(
self: &Rc<Self>,
scope: &mut v8::HandleScope,
id: ModuleId,
) -> impl Future<Output = Result<(), Error>> + Unpin {
let tc_scope = &mut v8::TryCatch::new(scope);
let module = self
.get_handle(id)
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
let mut status = module.get_status();
assert_eq!(
status,
v8::ModuleStatus::Instantiated,
"{} {} ({})",
if status == v8::ModuleStatus::Evaluated {
"Module already evaluated. Perhaps you've re-provided a module or extension that was already included in the snapshot?"
} else {
"Module not instantiated"
},
self.get_name_by_id(id).unwrap(),
id,
);
let (sender, receiver) = oneshot::channel();
let receiver = receiver.map(|res| {
res.unwrap_or_else(|_| {
bail!("Cannot evaluate module, because JavaScript execution has been terminated")
}
)});
let Some(value) = module.evaluate(tc_scope) else {
if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
let undefined = v8::undefined(tc_scope).into();
_ = sender
.send(exception_to_err_result(tc_scope, undefined, true, false));
} else {
debug_assert_eq!(module.get_status(), v8::ModuleStatus::Errored);
}
return receiver;
};
self.pending_mod_evaluation.set(true);
status = module.get_status();
if self.exception_state.has_dispatched_exception() {
let exception = v8::undefined(tc_scope).into();
sender
.send(exception_to_err_result(tc_scope, exception, true, false))
.expect("Failed to send module evaluation error.");
} else {
debug_assert!(
status == v8::ModuleStatus::Evaluated
|| status == v8::ModuleStatus::Errored
);
let promise = v8::Local::<v8::Promise>::try_from(value)
.expect("Expected to get promise as module evaluation result");
let (notify, module) = if self.is_main_module_id(id) {
let module = Some(v8::Global::new(tc_scope, module));
(
std::mem::take(&mut self.data.borrow_mut().main_module_callbacks),
module,
)
} else {
(vec![], None)
};
let evaluation = v8::External::new(
tc_scope,
Box::into_raw(Box::new(ModEvaluate {
module_map: self.clone(),
sender: Some(sender),
notify,
module,
})) as _,
);
fn get_sender(arg: v8::Local<v8::Value>) -> ModEvaluate {
let sender = v8::Local::<v8::External>::try_from(arg).unwrap();
*unsafe { Box::from_raw(sender.value() as _) }
}
let on_fulfilled = Function::builder(
|scope: &mut v8::HandleScope<'_>,
args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue| {
let mut sender = get_sender(args.data());
sender.module_map.pending_mod_evaluation.set(false);
sender.module_map.module_waker.wake();
sender.notify(scope);
},
)
.data(evaluation.into())
.build(tc_scope);
let on_rejected = Function::builder(
|scope: &mut v8::HandleScope<'_>,
args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue| {
let mut sender = get_sender(args.data());
sender.module_map.pending_mod_evaluation.set(false);
sender.module_map.module_waker.wake();
_ = sender.sender.take().unwrap().send(Ok(()));
scope.throw_exception(args.get(0));
},
)
.data(evaluation.into())
.build(tc_scope);
if on_fulfilled.is_none()
|| on_rejected.is_none()
|| promise
.then2(tc_scope, on_fulfilled.unwrap(), on_rejected.unwrap())
.is_none()
{
self.pending_mod_evaluation.set(false);
let mut sender = get_sender(evaluation.into());
match promise.state() {
PromiseState::Fulfilled => {
if let Some(exception) = tc_scope.exception() {
_ = sender.sender.take().unwrap().send(exception_to_err_result(
tc_scope, exception, true, false,
));
} else {
sender.notify(tc_scope);
}
}
PromiseState::Rejected => {
let err = promise.result(tc_scope);
let err = JsError::from_v8_exception(tc_scope, err);
_ = sender.sender.take().unwrap().send(Err(err.into()));
}
PromiseState::Pending => {
debug_assert!(tc_scope.is_execution_terminating());
drop(sender);
}
}
}
tc_scope.perform_microtask_checkpoint();
}
receiver
}
pub(crate) fn mod_evaluate_sync(
self: &Rc<Self>,
scope: &mut v8::HandleScope,
id: ModuleId,
) -> Result<(), Error> {
let tc_scope = &mut v8::TryCatch::new(scope);
let module = self
.get_handle(id)
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
let status = module.get_status();
assert_eq!(
status,
v8::ModuleStatus::Instantiated,
"{} {} ({})",
if status == v8::ModuleStatus::Evaluated {
"Module already evaluated. Perhaps you've re-provided a module or extension that was already included in the snapshot?"
} else {
"Module not instantiated"
},
self.get_name_by_id(id).unwrap(),
id,
);
if module.is_graph_async() {
return Err(generic_error(
"Top-level await is not allowed in synchronous evaluation",
));
}
let Some(value) = module.evaluate(tc_scope) else {
let exception = tc_scope.exception().unwrap();
return Err(JsError::from_v8_exception(tc_scope, exception).into());
};
if let Some(exception) = tc_scope.exception() {
return Err(JsError::from_v8_exception(tc_scope, exception).into());
}
let status = module.get_status();
debug_assert!(
status == v8::ModuleStatus::Evaluated
|| status == v8::ModuleStatus::Errored
);
let promise = v8::Local::<v8::Promise>::try_from(value)
.expect("Expected to get promise as module evaluation result");
match promise.state() {
PromiseState::Fulfilled => Ok(()),
PromiseState::Rejected => {
let err = promise.result(tc_scope);
Err(JsError::from_v8_exception(tc_scope, err).into())
}
PromiseState::Pending => {
unreachable!()
}
}
}
fn dynamic_import_module_evaluate(
&self,
scope: &mut v8::HandleScope,
load_id: ModuleLoadId,
id: ModuleId,
) -> Result<(), Error> {
let module_handle = self.get_handle(id).expect("ModuleInfo not found");
let status = {
let module = module_handle.open(scope);
module.get_status()
};
match status {
v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated => {}
_ => return Ok(()),
}
let tc_scope = &mut v8::TryCatch::new(scope);
{
let cped = self
.dynamic_import_map
.borrow()
.get(&load_id)
.unwrap()
.cped
.clone();
let cped = v8::Local::new(tc_scope, cped);
tc_scope.set_continuation_preserved_embedder_data(cped);
}
let module = v8::Local::new(tc_scope, &module_handle);
let maybe_value = module.evaluate(tc_scope);
let status = module.get_status();
if let Some(value) = maybe_value {
debug_assert!(
status == v8::ModuleStatus::Evaluated
|| status == v8::ModuleStatus::Errored
);
fn wake_module(
scope: &mut v8::HandleScope<'_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue,
) {
let module_map = JsRealm::module_map_from(scope);
module_map.module_waker.wake();
}
let promise = v8::Local::<v8::Promise>::try_from(value)
.expect("Expected to get promise as module evaluation result");
let wake_module_cb = Function::builder(wake_module).build(tc_scope);
if let Some(wake_module_cb) = wake_module_cb {
promise.then2(tc_scope, wake_module_cb, wake_module_cb);
} else {
}
let promise_global = v8::Global::new(tc_scope, promise);
let module_global = v8::Global::new(tc_scope, module);
let dyn_import_mod_evaluate = DynImportModEvaluate {
load_id,
module_id: id,
promise: promise_global,
module: module_global,
};
self
.pending_dyn_mod_evaluations
.borrow_mut()
.push(dyn_import_mod_evaluate);
self.pending_dyn_mod_evaluations_pending.set(true);
} else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
return Err(
generic_error("Cannot evaluate dynamically imported module, because JavaScript execution has been terminated.")
);
} else {
assert_eq!(status, v8::ModuleStatus::Errored);
}
Ok(())
}
fn evaluate_dyn_imports(&self, scope: &mut v8::HandleScope) -> bool {
if !self.pending_dyn_mod_evaluations_pending.get() {
return false;
}
let pending =
std::mem::take(self.pending_dyn_mod_evaluations.borrow_mut().deref_mut());
let mut resolved_any = false;
let mut still_pending = vec![];
for pending_dyn_evaluate in pending {
let maybe_result = {
let module_id = pending_dyn_evaluate.module_id;
let promise = pending_dyn_evaluate.promise.open(scope);
let _module = pending_dyn_evaluate.module.open(scope);
let promise_state = promise.state();
match promise_state {
v8::PromiseState::Pending => {
still_pending.push(pending_dyn_evaluate);
None
}
v8::PromiseState::Fulfilled => {
Some(Ok((pending_dyn_evaluate.load_id, module_id)))
}
v8::PromiseState::Rejected => {
let exception = promise.result(scope);
let exception = v8::Global::new(scope, exception);
Some(Err((pending_dyn_evaluate.load_id, exception)))
}
}
};
if let Some(result) = maybe_result {
resolved_any = true;
match result {
Ok((dyn_import_id, module_id)) => {
self.dynamic_import_resolve(scope, dyn_import_id, module_id);
}
Err((dyn_import_id, exception)) => {
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
}
}
}
self
.pending_dyn_mod_evaluations_pending
.set(!still_pending.is_empty());
*self.pending_dyn_mod_evaluations.borrow_mut() = still_pending;
resolved_any
}
pub(crate) fn dynamic_import_reject(
&self,
scope: &mut v8::HandleScope,
id: ModuleLoadId,
exception: v8::Global<v8::Value>,
) {
let resolver_handle = self
.dynamic_import_map
.borrow_mut()
.remove(&id)
.expect("Invalid dynamic import id")
.resolver;
let resolver = resolver_handle.open(scope);
let exception = v8::Local::new(scope, exception);
resolver.reject(scope, exception).unwrap();
scope.perform_microtask_checkpoint();
}
pub(crate) fn dynamic_import_resolve(
&self,
scope: &mut v8::HandleScope,
id: ModuleLoadId,
mod_id: ModuleId,
) {
let resolver_handle = self
.dynamic_import_map
.borrow_mut()
.remove(&id)
.expect("Invalid dynamic import id")
.resolver;
let resolver = resolver_handle.open(scope);
let module = self
.data
.borrow()
.get_handle(mod_id)
.map(|handle| v8::Local::new(scope, handle))
.expect("Dyn import module info not found");
assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated);
let module_namespace = module.get_module_namespace();
resolver.resolve(scope, module_namespace).unwrap();
self.dyn_module_evaluate_idle_counter.set(0);
scope.perform_microtask_checkpoint();
}
pub(crate) fn poll_progress(
&self,
cx: &mut Context,
scope: &mut v8::HandleScope,
) -> Result<(), Error> {
let mut has_evaluated = true;
self.module_waker.register(cx.waker());
while has_evaluated {
has_evaluated = false;
loop {
let poll_imports = self.poll_prepare_dyn_imports(cx, scope)?;
assert!(poll_imports.is_ready());
let poll_imports = self.poll_dyn_imports(cx, scope)?;
assert!(poll_imports.is_ready());
let poll_code_cache_ready = self.poll_code_cache_ready(cx)?;
assert!(poll_code_cache_ready.is_ready());
if self.evaluate_dyn_imports(scope) {
has_evaluated = true;
} else {
break;
}
}
}
Ok(())
}
fn poll_prepare_dyn_imports(
&self,
cx: &mut Context,
scope: &mut v8::HandleScope,
) -> Poll<Result<(), Error>> {
if !self.preparing_dynamic_imports_pending.get() {
return Poll::Ready(Ok(()));
}
loop {
let poll_result = self
.preparing_dynamic_imports
.borrow_mut()
.poll_next_unpin(cx);
if let Poll::Ready(Some(prepare_poll)) = poll_result {
let dyn_import_id = prepare_poll.0;
let prepare_result = prepare_poll.1;
match prepare_result {
Ok(load) => {
self
.pending_dynamic_imports
.borrow_mut()
.push(load.into_future());
self.pending_dynamic_imports_pending.set(true);
}
Err(err) => {
let exception = to_v8_type_error(scope, err);
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
}
continue;
}
self
.preparing_dynamic_imports_pending
.set(!self.preparing_dynamic_imports.borrow().is_empty());
return Poll::Ready(Ok(()));
}
}
fn poll_dyn_imports(
&self,
cx: &mut Context,
scope: &mut v8::HandleScope,
) -> Poll<Result<(), Error>> {
if !self.pending_dynamic_imports_pending.get() {
return Poll::Ready(Ok(()));
}
loop {
let poll_result = self
.pending_dynamic_imports
.borrow_mut()
.poll_next_unpin(cx);
if let Poll::Ready(Some(load_stream_poll)) = poll_result {
let maybe_result = load_stream_poll.0;
let mut load = load_stream_poll.1;
let dyn_import_id = load.id;
if let Some(load_stream_result) = maybe_result {
match load_stream_result {
Ok((request, info)) => {
let register_result =
load.register_and_recurse(scope, &request, info);
match register_result {
Ok(()) => {
self
.pending_dynamic_imports
.borrow_mut()
.push(load.into_future());
self.pending_dynamic_imports_pending.set(true);
}
Err(err) => {
let exception = match err {
ModuleError::Exception(e) => e,
ModuleError::Other(e) => to_v8_type_error(scope, e),
};
self.dynamic_import_reject(scope, dyn_import_id, exception)
}
}
}
Err(err) => {
let exception = to_v8_type_error(scope, err);
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
}
} else {
let module_id =
load.root_module_id.expect("Root module should be loaded");
let result = self.instantiate_module(scope, module_id);
if let Err(exception) = result {
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
self.dynamic_import_module_evaluate(
scope,
dyn_import_id,
module_id,
)?;
}
continue;
}
self
.pending_dynamic_imports_pending
.set(!self.pending_dynamic_imports.borrow().is_empty());
return Poll::Ready(Ok(()));
}
}
fn poll_code_cache_ready(&self, cx: &mut Context) -> Poll<Result<(), Error>> {
if !self.pending_code_cache_ready.get() {
return Poll::Ready(Ok(()));
}
loop {
let poll_result =
self.code_cache_ready_futs.borrow_mut().poll_next_unpin(cx);
if let Poll::Ready(Some(_)) = poll_result {
continue;
}
self
.pending_code_cache_ready
.set(!self.code_cache_ready_futs.borrow().is_empty());
return Poll::Ready(Ok(()));
}
}
pub(crate) fn get_module<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
module_id: ModuleId,
) -> Option<v8::Local<'s, v8::Module>> {
self
.data
.borrow()
.get_handle(module_id)
.map(|g| v8::Local::new(scope, g))
}
pub fn get_module_namespace(
&self,
scope: &mut v8::HandleScope,
module_id: ModuleId,
) -> Result<v8::Global<v8::Object>, Error> {
let module_handle = self
.data
.borrow()
.get_handle(module_id)
.expect("ModuleInfo not found");
let module = module_handle.open(scope);
if module.get_status() == v8::ModuleStatus::Errored {
let exception = module.get_exception();
return exception_to_err_result(scope, exception, false, false);
}
assert!(matches!(
module.get_status(),
v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated
));
let module_namespace: v8::Local<v8::Object> =
v8::Local::try_from(module.get_module_namespace())
.map_err(|err: v8::DataError| generic_error(err.to_string()))?;
Ok(v8::Global::new(scope, module_namespace))
}
fn get_stalled_top_level_await_message_for_module(
&self,
scope: &mut v8::HandleScope,
module_id: ModuleId,
) -> Vec<v8::Global<v8::Message>> {
let data = self.data.borrow();
let module_handle = data.handles.get(module_id).unwrap();
let module = v8::Local::new(scope, module_handle);
if module.is_synthetic_module() {
return vec![];
}
let stalled = module.get_stalled_top_level_await_message(scope);
let mut messages = vec![];
for (_, message) in stalled {
messages.push(v8::Global::new(scope, message));
}
messages
}
pub(crate) fn find_stalled_top_level_await(
&self,
scope: &mut v8::HandleScope,
) -> Vec<v8::Global<v8::Message>> {
let root_module_id = self
.data
.borrow()
.info
.iter()
.filter(|m| m.main)
.map(|m| m.id)
.next();
if let Some(root_module_id) = root_module_id {
let messages = self
.get_stalled_top_level_await_message_for_module(scope, root_module_id);
if !messages.is_empty() {
return messages;
}
}
for module_id in 0..self.data.borrow().handles.len() {
let messages =
self.get_stalled_top_level_await_message_for_module(scope, module_id);
if !messages.is_empty() {
return messages;
}
}
vec![]
}
pub(crate) fn lazy_load_es_module_with_code(
&self,
scope: &mut v8::HandleScope,
module_specifier: &str,
source_code: ModuleCodeString,
code_cache_info: Option<CodeCacheInfo>,
) -> Result<v8::Global<v8::Value>, Error> {
let specifier = ModuleSpecifier::parse(module_specifier)?;
let mod_id = self
.new_es_module(
scope,
false,
specifier.clone(),
source_code,
false,
code_cache_info,
)
.map_err(|e| e.into_any_error(scope, false, true))?;
self.instantiate_module(scope, mod_id).map_err(|e| {
let exception = v8::Local::new(scope, e);
exception_to_err_result::<()>(scope, exception, false, true).unwrap_err()
})?;
let module_handle = self.get_handle(mod_id).unwrap();
let module_local = v8::Local::<v8::Module>::new(scope, module_handle);
let status = module_local.get_status();
assert_eq!(status, v8::ModuleStatus::Instantiated);
let value = module_local.evaluate(scope).unwrap();
let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
let result = promise.result(scope);
if !result.is_undefined() {
return Err(
exception_to_err_result::<()>(scope, result, false, true).unwrap_err(),
);
}
let status = module_local.get_status();
assert_eq!(status, v8::ModuleStatus::Evaluated);
let mod_ns = module_local.get_module_namespace();
Ok(v8::Global::new(scope, mod_ns))
}
pub(crate) fn add_lazy_loaded_esm_source(
&self,
specifier: ModuleName,
code: ModuleCodeString,
) {
let data = self.data.borrow_mut();
assert!(data
.lazy_esm_sources
.borrow_mut()
.insert(specifier, code)
.is_none());
}
pub(crate) fn lazy_load_esm_module(
&self,
scope: &mut v8::HandleScope,
module_specifier: &str,
) -> Result<v8::Global<v8::Value>, Error> {
let lazy_esm_sources = self.data.borrow().lazy_esm_sources.clone();
let loader = LazyEsmModuleLoader::new(lazy_esm_sources);
{
let module_map_data = self.data.borrow();
if let Some(id) =
module_map_data.get_id(module_specifier, RequestedModuleType::None)
{
let handle = module_map_data.get_handle(id).unwrap();
let handle_local = v8::Local::new(scope, handle);
let module =
v8::Global::new(scope, handle_local.get_module_namespace());
return Ok(module);
}
}
let specifier = ModuleSpecifier::parse(module_specifier)?;
let load_response =
loader.load(&specifier, None, false, RequestedModuleType::None);
let source = match load_response {
ModuleLoadResponse::Sync(result) => result,
ModuleLoadResponse::Async(fut) => futures::executor::block_on(fut),
}?;
self.lazy_load_es_module_with_code(
scope,
module_specifier,
ModuleSource::get_string_source(source.code),
if let Some(code_cache) = source.code_cache {
let loader = self.loader.borrow().clone();
Some(CodeCacheInfo {
data: code_cache.data,
ready_callback: Box::new(move |cache| {
loader.code_cache_ready(specifier, code_cache.hash, cache)
}),
})
} else {
None
},
)
}
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn synthetic_module_evaluation_steps<'a>(
context: v8::Local<'a, v8::Context>,
module: v8::Local<v8::Module>,
) -> Option<v8::Local<'a, v8::Value>> {
let scope = &mut unsafe { v8::CallbackScope::new(context) };
let tc_scope = &mut v8::TryCatch::new(scope);
let module_map = JsRealm::module_map_from(tc_scope);
let handle = v8::Global::<v8::Module>::new(tc_scope, module);
let exports = module_map
.data
.borrow_mut()
.synthetic_module_exports_store
.remove(&handle)
.unwrap();
for (export_name, export_value) in exports {
let name = v8::Local::new(tc_scope, export_name);
let value = v8::Local::new(tc_scope, export_value);
assert!(module
.set_synthetic_module_export(tc_scope, name, value)
.unwrap());
assert!(!tc_scope.has_caught());
}
let resolver = v8::PromiseResolver::new(tc_scope).unwrap();
let undefined = v8::undefined(tc_scope);
resolver.resolve(tc_scope, undefined.into());
Some(resolver.get_promise(tc_scope).into())
}
pub fn script_origin<'a>(
s: &mut v8::HandleScope<'a>,
resource_name: v8::Local<'a, v8::String>,
is_module: bool,
host_defined_options: Option<v8::Local<'a, v8::Data>>,
) -> v8::ScriptOrigin<'a> {
v8::ScriptOrigin::new(
s,
resource_name.into(),
0,
0,
false,
0,
None,
false,
false,
is_module,
host_defined_options,
)
}
fn render_js_wasm_module(specifier: &str, wasm_deps: WasmDeps) -> String {
let mut src = Vec::with_capacity(512);
fn aggregate_wasm_module_imports(
imports: &[wasm_dep_analyzer::Import],
) -> IndexMap<String, Vec<String>> {
let mut imports_map = IndexMap::default();
for import in imports.iter().filter(|i| {
matches!(i.import_type, wasm_dep_analyzer::ImportType::Function(..))
}) {
let entry = imports_map
.entry(import.module.to_string())
.or_insert(vec![]);
entry.push(import.name.to_string());
}
imports_map
}
src.push(format!(
r#"import wasmMod from "{}" with {{ type: "$$deno-core-internal-wasm-module" }};"#,
specifier,
));
if !wasm_deps.imports.is_empty() {
let aggregated_imports = aggregate_wasm_module_imports(&wasm_deps.imports);
for (i, (key, names)) in aggregated_imports.iter().enumerate() {
src.push(format!(
r#"import {{ {} }} from "{}";"#,
names
.iter()
.enumerate()
.map(|(name_index, name)| format!(
"\"{}\" as import_{}_{}",
name, i, name_index
))
.collect::<Vec<_>>()
.join(", "),
key
));
}
src.push("const importsObject = {".to_string());
for (i, (key, names)) in aggregated_imports.iter().enumerate() {
src.push(format!(" \"{}\": {{", key).to_string());
for (name_index, name) in names.iter().enumerate() {
src.push(format!(" \"{0}\": import_{1}_{2},", name, i, name_index));
}
src.push(" },".to_string());
}
src.push("};".to_string());
src.push(
"const modInstance = new import.meta.WasmInstance(wasmMod, importsObject);".to_string(),
)
} else {
src.push(
"const modInstance = new import.meta.WasmInstance(wasmMod);".to_string(),
)
}
if !wasm_deps.exports.is_empty() {
for export_desc in wasm_deps.exports.iter().filter(|e| {
matches!(e.export_type, wasm_dep_analyzer::ExportType::Function(_))
}) {
if export_desc.name == "default" {
src.push(format!(
"export default modInstance.exports.{};",
export_desc.name
));
} else {
src.push(format!(
"export const {} = modInstance.exports.{};",
export_desc.name, export_desc.name
));
}
}
}
src.join("\n")
}
#[test]
fn test_render_js_wasm_module() {
let deps = WasmDeps {
imports: vec![],
exports: vec![],
};
let rendered = render_js_wasm_module("./foo.wasm", deps);
pretty_assertions::assert_eq!(
rendered,
r#"import wasmMod from "./foo.wasm" with { type: "$$deno-core-internal-wasm-module" };
const modInstance = new import.meta.WasmInstance(wasmMod);"#,
);
let deps = WasmDeps {
imports: vec![
wasm_dep_analyzer::Import {
name: "foo",
module: "./import.js",
import_type: wasm_dep_analyzer::ImportType::Tag(
wasm_dep_analyzer::TagType {
kind: 1,
type_index: 1,
},
),
},
wasm_dep_analyzer::Import {
name: "bar",
module: "./import.js",
import_type: wasm_dep_analyzer::ImportType::Function(1),
},
wasm_dep_analyzer::Import {
name: "fizz",
module: "./import.js",
import_type: wasm_dep_analyzer::ImportType::Function(2),
},
wasm_dep_analyzer::Import {
name: "buzz",
module: "./buzz.js",
import_type: wasm_dep_analyzer::ImportType::Function(3),
},
],
exports: vec![
wasm_dep_analyzer::Export {
name: "export1",
index: 0,
export_type: wasm_dep_analyzer::ExportType::Function(Ok(
wasm_dep_analyzer::FunctionSignature {
params: vec![],
returns: vec![],
},
)),
},
wasm_dep_analyzer::Export {
name: "export2",
index: 1,
export_type: wasm_dep_analyzer::ExportType::Table,
},
wasm_dep_analyzer::Export {
name: "export3",
index: 2,
export_type: wasm_dep_analyzer::ExportType::Memory,
},
wasm_dep_analyzer::Export {
name: "export4",
index: 3,
export_type: wasm_dep_analyzer::ExportType::Global(Ok(
wasm_dep_analyzer::GlobalType {
value_type: wasm_dep_analyzer::ValueType::F32,
mutability: false,
},
)),
},
wasm_dep_analyzer::Export {
name: "export5",
index: 4,
export_type: wasm_dep_analyzer::ExportType::Tag,
},
wasm_dep_analyzer::Export {
name: "export6",
index: 5,
export_type: wasm_dep_analyzer::ExportType::Unknown,
},
wasm_dep_analyzer::Export {
name: "default",
index: 6,
export_type: wasm_dep_analyzer::ExportType::Function(Ok(
wasm_dep_analyzer::FunctionSignature {
params: vec![],
returns: vec![],
},
)),
},
],
};
let rendered = render_js_wasm_module("./foo.wasm", deps);
pretty_assertions::assert_eq!(
rendered,
r#"import wasmMod from "./foo.wasm" with { type: "$$deno-core-internal-wasm-module" };
import { "bar" as import_0_0, "fizz" as import_0_1 } from "./import.js";
import { "buzz" as import_1_0 } from "./buzz.js";
const importsObject = {
"./import.js": {
"bar": import_0_0,
"fizz": import_0_1,
},
"./buzz.js": {
"buzz": import_1_0,
},
};
const modInstance = new import.meta.WasmInstance(wasmMod, importsObject);
export const export1 = modInstance.exports.export1;
export default modInstance.exports.default;"#,
);
}