use deno_core::error::CoreError;
use deno_error::JsErrorBox;
use super::CodeCacheInfo;
use super::ModuleMap;
use super::is_internal_scheme;
use crate::ModuleLoadResponse;
use crate::ModuleSource;
use crate::ModuleSpecifier;
use crate::error::CoreErrorKind;
use crate::error::JsError;
use crate::error::exception_to_err;
use crate::modules::LazyEsmModuleLoader;
use crate::modules::ModuleCodeString;
use crate::modules::ModuleId;
use crate::modules::ModuleLoader;
use crate::modules::ModuleName;
use crate::modules::RequestedModuleType;
use crate::modules::loaders::ModuleLoadOptions;
impl ModuleMap {
pub(crate) fn lazy_load_es_module_with_code(
&self,
scope: &mut v8::PinScope,
module_specifier: &str,
source_code: ModuleCodeString,
code_cache_info: Option<CodeCacheInfo>,
) -> Result<v8::Global<v8::Value>, CoreError> {
let specifier = ModuleSpecifier::parse(module_specifier)?;
let previous_loading_internal_modules =
is_internal_scheme(specifier.scheme())
.then(|| self.loading_internal_modules.replace(true));
let result = self.lazy_load_es_module_with_code_inner(
scope,
specifier,
source_code,
code_cache_info,
);
if let Some(previous) = previous_loading_internal_modules {
self.loading_internal_modules.set(previous);
}
result
}
fn lazy_load_es_module_with_code_inner(
&self,
scope: &mut v8::PinScope,
specifier: ModuleSpecifier,
source_code: ModuleCodeString,
code_cache_info: Option<CodeCacheInfo>,
) -> Result<v8::Global<v8::Value>, CoreError> {
let mod_id = self
.new_es_module(
scope,
false,
specifier.into(),
source_code,
false,
code_cache_info,
)
.map_err(|e| e.into_error(scope, false, true))?;
self.instantiate_module(scope, mod_id).map_err(|e| {
let exception = v8::Local::new(scope, e);
exception_to_err(scope, exception, false, true)
})?;
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();
if !self.evaluating_top_level.get() {
scope.perform_microtask_checkpoint();
}
let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
let result = promise.result(scope);
if !result.is_undefined() {
return Err(
CoreErrorKind::Js(exception_to_err(scope, result, false, true))
.into_box(),
);
}
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 has_lazy_esm_source(&self, specifier: &str) -> bool {
let data = self.data.borrow();
data.known_lazy_esm.borrow().contains(specifier)
}
pub(crate) fn take_lazy_esm_source(
&self,
specifier: &str,
) -> Option<ModuleCodeString> {
let data = self.data.borrow();
let mut sources = data.lazy_esm_sources.borrow_mut();
let entry = sources.get_mut(specifier)?;
let placeholder = ModuleCodeString::from_static("");
let owned = std::mem::replace(entry, placeholder);
let (keep, give) = owned.into_cheap_copy();
*entry = keep;
data
.consumed_lazy_specifiers
.borrow_mut()
.insert(specifier.to_string());
Some(give)
}
pub(crate) fn add_lazy_loaded_esm_source(
&self,
specifier: ModuleName,
code: ModuleCodeString,
) {
let data = self.data.borrow_mut();
data
.known_lazy_esm
.borrow_mut()
.insert(std::borrow::Cow::Owned(specifier.as_str().to_string()));
assert!(
data
.lazy_esm_sources
.borrow_mut()
.insert(specifier, code)
.is_none()
);
}
pub(crate) fn lazy_load_synthetic_esm_module(
&self,
scope: &mut v8::PinScope,
module_specifier: &str,
) -> Result<v8::Global<v8::Value>, CoreError> {
let cached_handle = {
let data = self.data.borrow();
data
.get_id(module_specifier, RequestedModuleType::None)
.and_then(|id| data.get_handle(id))
};
if let Some(handle) = cached_handle {
let handle_local = v8::Local::new(scope, handle);
if handle_local.get_status() == v8::ModuleStatus::Instantiated {
let value = handle_local.evaluate(scope).unwrap();
if !self.evaluating_top_level.get() {
scope.perform_microtask_checkpoint();
}
let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
let result = promise.result(scope);
if !result.is_undefined() {
return Err(
CoreErrorKind::Js(exception_to_err(scope, result, false, true))
.into_box(),
);
}
}
return Ok(v8::Global::new(scope, handle_local.get_module_namespace()));
}
let module_id = self.build_synthetic_esm_module(scope, module_specifier)?;
let handle = self.get_handle(module_id).unwrap();
let handle_local = v8::Local::new(scope, handle);
let value = handle_local.evaluate(scope).unwrap();
if !self.evaluating_top_level.get() {
scope.perform_microtask_checkpoint();
}
let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
let result = promise.result(scope);
if !result.is_undefined() {
return Err(
CoreErrorKind::Js(exception_to_err(scope, result, false, true))
.into_box(),
);
}
Ok(v8::Global::new(scope, handle_local.get_module_namespace()))
}
pub(crate) fn lazy_load_esm_module(
&self,
scope: &mut v8::PinScope,
module_specifier: &str,
) -> Result<v8::Global<v8::Value>, CoreError> {
if !self.has_lazy_esm_source(module_specifier) {
return Err(
JsErrorBox::generic(format!(
"Specifier \"{module_specifier}\" cannot be lazy-loaded as it was not included in the binary."
))
.into(),
);
}
let lazy_esm_sources = self.data.borrow().lazy_esm_sources.clone();
let loader = LazyEsmModuleLoader::new(lazy_esm_sources);
let cached_id_and_handle = {
let module_map_data = self.data.borrow();
module_map_data
.get_id(module_specifier, RequestedModuleType::None)
.and_then(|id| module_map_data.get_handle(id).map(|h| (id, h)))
};
if let Some((cached_id, handle)) = cached_id_and_handle {
crate::modules::import_graph::record_lazy_esm_cached(
scope,
module_specifier,
);
let handle_local = v8::Local::new(scope, handle);
if handle_local.get_status() == v8::ModuleStatus::Uninstantiated {
self.instantiate_module(scope, cached_id).map_err(|e| {
let exception = v8::Local::new(scope, e);
exception_to_err(scope, exception, false, true)
})?;
}
if handle_local.get_status() == v8::ModuleStatus::Instantiated {
let value = handle_local.evaluate(scope).unwrap();
if !self.evaluating_top_level.get() {
scope.perform_microtask_checkpoint();
}
let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
let result = promise.result(scope);
if !result.is_undefined() {
return Err(
CoreErrorKind::Js(exception_to_err(scope, result, false, true))
.into_box(),
);
}
}
let module = v8::Global::new(scope, handle_local.get_module_namespace());
return Ok(module);
}
crate::modules::import_graph::record_lazy_esm(scope, module_specifier);
let specifier = ModuleSpecifier::parse(module_specifier)?;
let load_response = loader.load(
&specifier,
None,
ModuleLoadOptions {
is_dynamic_import: false,
is_synchronous: false,
requested_module_type: RequestedModuleType::None,
},
);
let source = match load_response {
ModuleLoadResponse::Sync(result) => result,
ModuleLoadResponse::Async(fut) => futures::executor::block_on(fut),
}?;
let source_code = ModuleSource::get_string_source(source.code);
let code_cache_info = self
.loader
.borrow()
.get_code_cache(&specifier, AsRef::<str>::as_ref(&source_code))
.map(|info| {
let loader = self.loader.borrow().clone();
CodeCacheInfo {
data: info.data,
ready_callback: Box::new(move |cache| {
loader.code_cache_ready(specifier, info.hash, cache)
}),
}
});
self.lazy_load_es_module_with_code(
scope,
module_specifier,
source_code,
code_cache_info,
)
}
pub(crate) fn add_lazy_loaded_script_source(
&self,
specifier: ModuleName,
code: ModuleCodeString,
) {
let data = self.data.borrow_mut();
assert!(
data
.lazy_script_sources
.borrow_mut()
.insert(specifier, code)
.is_none(),
"Duplicate lazy script source"
);
}
pub(crate) fn has_synthetic_esm_module(&self, specifier: &str) -> bool {
let data = self.data.borrow();
let modules = data.synthetic_esm_modules.borrow();
let key: ModuleName = String::from(specifier).into();
modules.contains_key(&key)
}
pub(crate) fn add_synthetic_esm_module(
&self,
module_specifier: ModuleName,
backing_specifier: ModuleName,
) {
let data = self.data.borrow();
assert!(
data
.synthetic_esm_modules
.borrow_mut()
.insert(module_specifier, backing_specifier)
.is_none(),
"Duplicate synthetic_esm module mapping"
);
}
pub(crate) fn build_synthetic_esm_module(
&self,
scope: &mut v8::PinScope,
specifier: &str,
) -> Result<ModuleId, CoreError> {
let backing_specifier = {
let data = self.data.borrow();
let modules = data.synthetic_esm_modules.borrow();
let key: ModuleName = String::from(specifier).into();
modules.get(&key).map(|v| v.as_str().to_string())
}
.ok_or_else(|| {
CoreError::from(JsErrorBox::generic(format!(
"Specifier {specifier} is not a synthetic_esm module"
)))
})?;
let exports_global = self.load_ext_script(scope, &backing_specifier)?;
let exports_local = v8::Local::new(scope, exports_global);
let exports_obj = v8::Local::<v8::Object>::try_from(exports_local)
.map_err(|_| {
CoreError::from(JsErrorBox::type_error(format!(
"synthetic_esm backing script {backing_specifier} did not return an object"
)))
})?;
Ok(self.new_synthetic_module_from_exports_object(
scope,
String::from(specifier),
exports_obj,
))
}
pub(super) fn try_resolve_synthetic_esm<'s, 'i>(
&self,
scope: &mut v8::PinScope<'s, 'i>,
specifier: &str,
) -> Option<v8::Local<'s, v8::Module>> {
if !self.has_synthetic_esm_module(specifier) {
return None;
}
match self.build_synthetic_esm_module(scope, specifier) {
Ok(module_id) => {
let handle = self.get_handle(module_id)?;
Some(v8::Local::new(scope, handle))
}
Err(e) => {
crate::error::throw_js_error_class(scope, &e);
None
}
}
}
pub(crate) fn load_ext_script(
&self,
scope: &mut v8::PinScope,
specifier: &str,
) -> Result<v8::Global<v8::Value>, CoreError> {
crate::modules::import_graph::record_lazy_script(scope, specifier);
let specifier_str = String::from(specifier);
let data = self.data.borrow();
{
let specifier_key: ModuleName = specifier_str.clone().into();
if let Some(cached) =
data.loaded_script_results.borrow().get(&specifier_key)
{
return Ok(cached.clone());
}
}
{
let specifier_key: ModuleName = specifier_str.clone().into();
let loading = data.lazy_script_loading.borrow();
if loading.contains(&specifier_key) {
return Err(
JsErrorBox::generic(format!(
"Circular dependency detected when loading script \"{specifier}\""
))
.into(),
);
}
}
let source = {
let specifier_key: ModuleName = specifier_str.clone().into();
let mut sources = data.lazy_script_sources.borrow_mut();
sources.remove(&specifier_key)
};
let source = match source {
Some(s) => s,
None => {
return Err(
JsErrorBox::generic(format!(
"Script \"{specifier}\" cannot be lazy-loaded as it was not included in the binary."
))
.into(),
);
}
};
data
.consumed_lazy_specifiers
.borrow_mut()
.insert(specifier_str.clone());
data
.lazy_script_loading
.borrow_mut()
.insert(specifier_str.clone().into());
drop(data);
let name = v8::String::new(scope, specifier).unwrap();
let origin = v8::ScriptOrigin::new(
scope,
name.into(),
0,
0,
false,
-1,
None,
false,
false,
false,
None,
);
v8::tc_scope!(let tc_scope, scope);
let v8_source = if AsRef::<str>::as_ref(&source)
.starts_with("\"use strict\"; return (")
{
source.v8_string(tc_scope).unwrap()
} else {
let wrapped_source = wrap_lazy_ext_script(AsRef::<str>::as_ref(&source));
v8::String::new(tc_scope, &wrapped_source).unwrap()
};
let bootstrap_param = v8::String::new(tc_scope, "__bootstrap").unwrap();
let cache_specifier = crate::ModuleSpecifier::parse(&specifier_str).ok();
let code_cache_info = cache_specifier.as_ref().and_then(|spec| {
self
.loader
.borrow()
.get_code_cache(spec, AsRef::<str>::as_ref(&source))
});
let (mut compile_source, compile_options) =
match code_cache_info.as_ref().and_then(|i| i.data.as_ref()) {
Some(data) => (
v8::script_compiler::Source::new_with_cached_data(
v8_source,
Some(&origin),
v8::CachedData::new(data),
),
v8::script_compiler::CompileOptions::ConsumeCodeCache,
),
None => (
v8::script_compiler::Source::new(v8_source, Some(&origin)),
v8::script_compiler::CompileOptions::NoCompileOptions,
),
};
let function = match v8::script_compiler::compile_function(
tc_scope,
&mut compile_source,
&[bootstrap_param],
&[],
compile_options,
v8::script_compiler::NoCacheReason::NoReason,
) {
Some(f) => f,
None => {
let exception = tc_scope.exception().unwrap();
let err = JsError::from_v8_exception(tc_scope, exception);
self
.data
.borrow()
.lazy_script_loading
.borrow_mut()
.remove(&ModuleName::from(specifier_str.clone()));
return Err(CoreErrorKind::Js(err).into_box());
}
};
let rejected = compile_source
.get_cached_data()
.map(|d| d.rejected())
.unwrap_or(true);
let had_data = code_cache_info
.as_ref()
.map(|i| i.data.is_some())
.unwrap_or(false);
if (!had_data || rejected)
&& let (Some(spec), Some(info)) =
(cache_specifier.as_ref(), code_cache_info.as_ref())
&& let Some(cache) = function.create_code_cache()
{
let fut = self.loader.borrow().code_cache_ready(
spec.clone(),
info.hash,
&cache[..],
);
self.code_cache_ready_futs.push(fut);
}
let captured = self.data.borrow().captured_bootstrap.borrow().clone();
let bootstrap_arg = match &captured {
Some(global) => v8::Local::new(tc_scope, global),
None => v8::undefined(tc_scope).into(),
};
let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into();
let result = match function.call(tc_scope, undefined, &[bootstrap_arg]) {
Some(value) => v8::Global::new(tc_scope, value),
None => {
assert!(tc_scope.has_caught());
let exception = tc_scope.exception().unwrap();
let err = JsError::from_v8_exception(tc_scope, exception);
self
.data
.borrow()
.lazy_script_loading
.borrow_mut()
.remove(&ModuleName::from(specifier_str.clone()));
return Err(CoreErrorKind::Js(err).into_box());
}
};
{
let data = self.data.borrow();
data
.lazy_script_loading
.borrow_mut()
.remove(&ModuleName::from(specifier_str.clone()));
data
.loaded_script_results
.borrow_mut()
.insert(ModuleName::from(specifier_str), result.clone());
}
Ok(result)
}
pub(crate) fn set_captured_bootstrap(&self, value: v8::Global<v8::Value>) {
*self.data.borrow().captured_bootstrap.borrow_mut() = Some(value);
}
pub(crate) fn captured_bootstrap(&self) -> Option<v8::Global<v8::Value>> {
self.data.borrow().captured_bootstrap.borrow().clone()
}
}
fn strip_script_prologue(source: &str) -> Option<&str> {
let mut rest = source;
loop {
let trimmed = rest.trim_start();
if let Some(after) = trimmed.strip_prefix("//") {
let nl = after.find('\n').map(|i| i + 1).unwrap_or(after.len());
rest = &after[nl..];
continue;
}
if let Some(after) = trimmed.strip_prefix("/*") {
let end = after.find("*/").map(|i| i + 2).unwrap_or(after.len());
rest = &after[end..];
continue;
}
if trimmed.starts_with('"') || trimmed.starts_with('\'') {
let quote = trimmed.as_bytes()[0];
let after_quote = &trimmed[1..];
if let Some(close) = after_quote.find(quote as char) {
let after_str = after_quote[close + 1..].trim_start();
if let Some(after_semi) = after_str.strip_prefix(';') {
rest = after_semi;
continue;
}
}
}
if trimmed.starts_with("(function") {
return Some(trimmed);
}
return None;
}
}
pub fn wrap_lazy_ext_script(source: &str) -> String {
let expr_start = strip_script_prologue(source).unwrap_or(source);
let trimmed =
expr_start.trim_end_matches(|c: char| c.is_whitespace() || c == ';');
format!("\"use strict\"; return ({trimmed});")
}