/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/core/runtime/jsrealm.rs
691 строка
25 KB
Bartek Iwańczuk
perf(core): run the common-path tick drain in Rust (#36157)
27 июл 2026, 15:59
Не верифицирован
27 июл 2026, 15:59
0c965f5
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::cell::Cell; use std::cell::RefCell; use std::collections::HashSet; use std::hash::BuildHasherDefault; use std::hash::Hasher; use std::rc::Rc; use std::sync::Arc; use super::exception_state::ExceptionState; #[cfg(test)] use super::op_driver::OpDriver; use crate::_ops::OpMethodDecl; use crate::ModuleSourceCode; use crate::SourceCodeCacheInfo; use crate::cppgc::FunctionTemplateData; use crate::error::CoreError; use crate::error::CreateCodeCacheError; use crate::error::JsError; use crate::error::exception_to_err; use crate::error::exception_to_err_result; use crate::event_loop::EventLoopPhases; use crate::module_specifier::ModuleSpecifier; use crate::modules::IntoModuleCodeString; use crate::modules::IntoModuleName; use crate::modules::ModuleCodeString; use crate::modules::ModuleId; use crate::modules::ModuleMap; use crate::modules::ModuleName; use crate::modules::recursive_load::RecursiveModuleLoad; use crate::modules::script_origin; use crate::ops::ExternalOpsTracker; use crate::ops::OpCtx; use crate::reactor::DefaultReactor; use crate::stats::RuntimeActivityTraces; use crate::tasks::V8TaskSpawnerFactory; use crate::uv_compat::UvLoopInner; use crate::web_timeout::UserTimer; pub const CONTEXT_STATE_SLOT_INDEX: i32 = 1; pub const MODULE_MAP_SLOT_INDEX: i32 = 2; // Hasher used for `unrefed_ops`. Since these are rolling i32, there's no // need to actually hash them. #[derive(Default)] pub(crate) struct IdentityHasher(u64); impl Hasher for IdentityHasher { fn write_i32(&mut self, i: i32) { self.0 = i as u64; } fn finish(&self) -> u64 { self.0 } fn write(&mut self, _bytes: &[u8]) { unreachable!() } } /// We may wish to experiment with alternative drivers in the future. pub(crate) type OpDriverImpl = super::op_driver::FuturesUnorderedDriver; pub(crate) type UnrefedOps = Rc<RefCell<HashSet<i32, BuildHasherDefault<IdentityHasher>>>>; /// Indices into the shared immediate_info buffer (Uint32Array). pub(crate) const IMM_IDX_COUNT: usize = 0; pub(crate) const IMM_IDX_REF_COUNT: usize = 1; pub(crate) const IMM_IDX_HAS_OUTSTANDING: usize = 2; pub struct ContextState { pub(crate) task_spawner_factory: Arc<V8TaskSpawnerFactory>, pub(crate) user_timer: UserTimer<DefaultReactor>, // Per-phase JS callbacks for the event loop. // js_event_loop_tick_cb: resolves completed async ops in one // Rust-to-JS call. Tick draining is handled separately by // js_drain_next_tick_and_macrotasks_cb. pub(crate) js_event_loop_tick_cb: RefCell<Option<v8::Global<v8::Function>>>, pub(crate) js_process_timers_cb: RefCell<Option<v8::Global<v8::Function>>>, // js_drain_next_tick_and_macrotasks_cb: drains nextTick/microtask queues // only (used in the I/O tight loop where timers/ops are not involved). pub(crate) js_drain_next_tick_and_macrotasks_cb: RefCell<Option<v8::Global<v8::Function>>>, pub(crate) js_handle_rejections_cb: RefCell<Option<v8::Global<v8::Function>>>, pub(crate) run_immediate_callbacks_cb: RefCell<Option<v8::Global<v8::Function>>>, pub(crate) js_wasm_streaming_cb: RefCell<Option<v8::Global<v8::Function>>>, pub(crate) wasm_instance_fn: RefCell<Option<v8::Global<v8::Function>>>, // WeakMap<ModuleNamespace, WebAssembly.Instance.exports> shared by the // synthetic modules rendered for `.wasm` files, so that a Wasm module // importing a global from another Wasm module links against the original // `WebAssembly.Global` object instead of the unwrapped snapshot value // exposed to JS. Stands in for the [[Instance]] slot of the Wasm module // record from the ESM integration proposal (same trick as Node.js). pub(crate) wasm_instances_map: RefCell<Option<v8::Global<v8::Object>>>, pub(crate) unrefed_ops: UnrefedOps, pub(crate) activity_traces: RuntimeActivityTraces, pub(crate) pending_ops: Rc<OpDriverImpl>, // We don't explicitly re-read this prop but need the slice to live alongside // the context pub(crate) op_ctxs: Box<[OpCtx]>, pub(crate) op_method_decls: Vec<OpMethodDecl>, pub(crate) methods_ctx_offset: usize, /// Snapshots built against V8 14.9+ bake the *slow* version of each op /// (see `op_ctx_template`); fast-call overloads are re-attached at runtime /// by `upgrade_snapshotted_ops_with_fast_calls`. That pass creates ~1.6k V8 /// functions (~0.9ms). It only benefits ops accessed at runtime (baked /// modules captured their slow refs at snapshot eval), so we DEFER it until /// the first residual ext-module load — a program that never loads a /// residual module (e.g. `deno run empty.js`) never pays for it. pub(crate) fast_ops_upgraded: Cell<bool>, /// `(Deno.core.ops, Deno.core.setUpAsyncStub)` captured at `new_inner` time, /// because `Deno.core` is scrubbed from the public `Deno` after bootstrap and /// the deferred upgrade (which runs post-bootstrap) can no longer read them /// from the global. `None` when the upgrade isn't deferred. pub(crate) deferred_fast_ops: RefCell<Option<(v8::Global<v8::Object>, v8::Global<v8::Function>)>>, pub(crate) isolate: Option<v8::UnsafeRawIsolatePtr>, pub(crate) exception_state: Rc<ExceptionState>, /// Shared tick info buffer exposed to JS as a Uint8Array. /// Index 0: hasTickScheduled (1 = true, 0 = false) /// Index 1: hasRejectionToWarn (set by Rust in promise_reject_callback) pub(crate) tick_info: Box<[u8; 2]>, /// Shared immediate info buffer exposed to JS as a Uint32Array. /// Indices: IMM_IDX_COUNT, IMM_IDX_REF_COUNT, IMM_IDX_HAS_OUTSTANDING pub(crate) immediate_info: Box<[u32; 3]>, /// Shared timer info buffer exposed to JS as an Int32Array. /// Index 0: refed timer count (managed by JS) pub(crate) timer_info: Box<[i32; 1]>, /// Active JS-managed timers tracked for the leak sanitizer. /// Maps timer ID → (is_repeat, is_system). System timers (e.g. /// AbortSignal.timeout) are excluded from sanitizer stats. pub(crate) active_timers: RefCell<std::collections::HashMap<usize, (bool, bool)>>, pub(crate) external_ops_tracker: ExternalOpsTracker, pub(crate) ext_import_meta_proto: RefCell<Option<v8::Global<v8::Object>>>, /// Lazily-cached "next"/"done"/"value" iterator keys used by the WebIDL /// sequence converter. Cached per-context (not in a `thread_local`) because /// `v8::String` handles are isolate-bound. See /// [`crate::webidl::WebIdlSequenceKeys`]. pub(crate) webidl_sequence_keys: RefCell<Option<crate::webidl::WebIdlSequenceKeys>>, /// Phase-specific state for the libuv-style event loop. pub event_loop_phases: RefCell<EventLoopPhases>, /// Pointer to the `UvLoopInner` for the libuv compat layer. /// Set via [`JsRuntime::register_uv_loop`] when a `uv_loop_t` is /// associated with this context. /// /// # Safety /// The pointee is heap-allocated by `uv_loop_init` (boxed) and lives until /// `uv_loop_close` destroys it. The caller of `register_uv_loop` must /// guarantee the `uv_loop_t` outlives this `ContextState`. Both /// `UvLoopInner` and `ContextState` are `!Send` -- all access is on the /// event loop thread. pub(crate) uv_loop_inner: Cell<Option<*const UvLoopInner>>, /// Raw pointer to the `uv_loop_t` handle. At registration time, /// `loop_.data` is set to a `Global::into_raw()` pointer so that /// libuv-style C callbacks can retrieve the context. /// /// # Safety /// Same lifetime requirements as `uv_loop_inner` above. pub(crate) uv_loop_ptr: Cell<Option<*mut crate::uv_compat::uv_loop_t>>, /// Two-handle setImmediate mechanism (matching Node.js): a check handle /// (always running, always unref'd) and an idle handle that controls /// event loop liveness for refed immediates. `None` when no uv loop is /// registered (e.g. during snapshotting). pub(crate) immediate_check_handle: RefCell<Option<crate::uv_compat::ImmediateCheckHandle>>, } impl ContextState { pub(crate) fn has_tick_scheduled(&self) -> bool { self.tick_info[0] != 0 } /// Mirrors JS `hasRejectionToWarn()`. `tick_info[1]` is written from Rust in /// the promise reject callback and cleared from JS in /// `processTicksAndRejections`; both run on the event loop thread so no /// synchronization is needed for this read. pub(crate) fn has_rejection_to_warn(&self) -> bool { self.tick_info[1] != 0 } pub(crate) fn new( op_driver: Rc<OpDriverImpl>, isolate_ptr: v8::UnsafeRawIsolatePtr, op_ctxs: Box<[OpCtx]>, op_method_decls: Vec<OpMethodDecl>, methods_ctx_offset: usize, external_ops_tracker: ExternalOpsTracker, unrefed_ops: UnrefedOps, ) -> Self { Self { isolate: Some(isolate_ptr), exception_state: Default::default(), tick_info: Box::new([0u8; 2]), immediate_info: Box::new([0u32; 3]), js_event_loop_tick_cb: Default::default(), js_process_timers_cb: Default::default(), js_drain_next_tick_and_macrotasks_cb: Default::default(), js_handle_rejections_cb: Default::default(), run_immediate_callbacks_cb: Default::default(), js_wasm_streaming_cb: Default::default(), wasm_instance_fn: Default::default(), wasm_instances_map: Default::default(), activity_traces: Default::default(), op_ctxs, op_method_decls, methods_ctx_offset, fast_ops_upgraded: Cell::new(false), deferred_fast_ops: RefCell::new(None), pending_ops: op_driver, task_spawner_factory: Default::default(), user_timer: Default::default(), timer_info: Box::new([0i32; 1]), active_timers: Default::default(), unrefed_ops, external_ops_tracker, ext_import_meta_proto: Default::default(), webidl_sequence_keys: Default::default(), event_loop_phases: Default::default(), uv_loop_inner: Cell::new(None), uv_loop_ptr: Cell::new(None), immediate_check_handle: RefCell::new(None), } } } /// A representation of a JavaScript realm tied to a [`JsRuntime`], that allows /// execution in the realm's context. /// /// A [`JsRealm`] instance is a reference to an already existing realm, which /// does not hold ownership of it, so instances can be created and dropped as /// needed. As such, calling [`JsRealm::new`] doesn't create a new realm, and /// cloning a [`JsRealm`] only creates a new reference. See /// [`JsRuntime::create_realm`] to create new realms instead. /// /// Despite [`JsRealm`] instances being references, multiple instances that /// point to the same realm won't overlap because every operation requires /// passing a mutable reference to the [`v8::Isolate`]. Therefore, no operation /// on two [`JsRealm`] instances tied to the same isolate can be run at the same /// time, regardless of whether they point to the same realm. /// /// # Panics /// /// Every method of [`JsRealm`] will panic if you call it with a reference to a /// [`v8::Isolate`] other than the one that corresponds to the current context. /// /// In other words, the [`v8::Isolate`] parameter for all the related [`JsRealm`] methods /// must be extracted from the pre-existing [`JsRuntime`]. /// /// # Lifetime of the realm /// /// As long as the corresponding isolate is alive, a [`JsRealm`] instance will /// keep the underlying V8 context alive even if it would have otherwise been /// garbage collected. #[derive(Clone)] #[repr(transparent)] pub(crate) struct JsRealm(pub(crate) JsRealmInner); #[derive(Clone)] pub(crate) struct JsRealmInner { pub(crate) context_state: Rc<ContextState>, context: v8::Global<v8::Context>, pub(crate) module_map: Rc<ModuleMap>, pub(crate) function_templates: Rc<RefCell<FunctionTemplateData>>, } impl JsRealmInner { pub(crate) fn new( context_state: Rc<ContextState>, context: v8::Global<v8::Context>, module_map: Rc<ModuleMap>, function_templates: Rc<RefCell<FunctionTemplateData>>, ) -> Self { Self { context_state, context: context.clone(), module_map, function_templates, } } #[inline(always)] pub fn context(&self) -> &v8::Global<v8::Context> { &self.context } #[inline(always)] pub(crate) fn state(&self) -> Rc<ContextState> { self.context_state.clone() } #[inline(always)] pub(crate) fn module_map(&self) -> Rc<ModuleMap> { self.module_map.clone() } #[inline(always)] pub(crate) fn function_templates(&self) -> Rc<RefCell<FunctionTemplateData>> { self.function_templates.clone() } pub fn destroy(self) { let state = self.state(); let raw_ptr = self.state().isolate.unwrap(); // SAFETY: We know the isolate outlives the realm let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(raw_ptr) }; // Close the immediate check/idle handles before the uv loop is dropped. // SAFETY: The uv loop is still alive (op_state hasn't been cleared yet). if let Some(handle) = state.immediate_check_handle.borrow_mut().take() { unsafe { handle.close() }; } if let Some(loop_ptr) = state.uv_loop_ptr.get() { // SAFETY: `loop_ptr` is valid for the lifetime of the runtime // (guaranteed by `register_uv_loop`). `data` was set to a // `Global::into_raw()` pointer during registration. We // reconstruct the Global via `from_raw` so it is properly // dropped. `NonNull::new_unchecked` is safe because we checked // `!is_null()`. `isolate` is valid because we just obtained it // from the raw isolate pointer above. unsafe { let data = (*loop_ptr).data; if !data.is_null() { let raw = std::ptr::NonNull::new_unchecked(data as *mut v8::Context); let _global = v8::Global::from_raw(&mut isolate, raw); (*loop_ptr).data = std::ptr::null_mut(); } } } v8::scope!(let scope, &mut isolate); // These globals will prevent snapshots from completing, take them state.exception_state.prepare_to_destroy(); std::mem::take(&mut *state.js_event_loop_tick_cb.borrow_mut()); std::mem::take(&mut *state.js_process_timers_cb.borrow_mut()); std::mem::take( &mut *state.js_drain_next_tick_and_macrotasks_cb.borrow_mut(), ); std::mem::take(&mut *state.js_handle_rejections_cb.borrow_mut()); std::mem::take(&mut *state.run_immediate_callbacks_cb.borrow_mut()); std::mem::take(&mut *state.js_wasm_streaming_cb.borrow_mut()); { let ctx = self.context().open(scope); // SAFETY: Clear all embedder data unsafe { let ctx_state = ctx.get_aligned_pointer_from_embedder_data(CONTEXT_STATE_SLOT_INDEX); let _ = Rc::from_raw(ctx_state as *mut ContextState); let module_map = ctx.get_aligned_pointer_from_embedder_data(MODULE_MAP_SLOT_INDEX); // Explcitly destroy data in the module map, as there might be some pending // futures there and we want them dropped. let map = Rc::from_raw(module_map as *mut ModuleMap); map.destroy(); ctx.set_aligned_pointer_in_embedder_data( CONTEXT_STATE_SLOT_INDEX, std::ptr::null_mut(), ); ctx.set_aligned_pointer_in_embedder_data( MODULE_MAP_SLOT_INDEX, std::ptr::null_mut(), ); } ctx.clear_all_slots(); // Expect that this context is dead (we only check this in debug mode) // TODO(bartlomieju): This check fails for some tests, will need to fix this // debug_assert_eq!(Rc::strong_count(&module_map), 1, "ModuleMap still in use."); } // Expect that this context is dead (we only check this in debug mode) // TODO(mmastrac): This check fails for some tests, will need to fix this // debug_assert_eq!(Rc::strong_count(&self.context), 1, "Realm was still alive when we wanted to destroy it. Not dropped?"); } } unsafe fn clone_rc_raw<T>(raw: *const T) -> Rc<T> { unsafe { Rc::increment_strong_count(raw); Rc::from_raw(raw) } } macro_rules! context_scope { ($scope: ident, $self: expr, $isolate: expr) => { v8::scope!($scope, $isolate); let context = v8::Local::new($scope, $self.context()); let $scope = &mut v8::ContextScope::new($scope, context); }; } pub(crate) use context_scope; impl JsRealm { pub(crate) fn new(inner: JsRealmInner) -> Self { Self(inner) } #[inline(always)] pub(crate) fn state_from_scope(scope: &mut v8::PinScope) -> Rc<ContextState> { let context = scope.get_current_context(); // SAFETY: slot is valid and set during realm creation unsafe { let rc = context .get_aligned_pointer_from_embedder_data(CONTEXT_STATE_SLOT_INDEX); clone_rc_raw(rc as *const ContextState) } } #[inline(always)] pub(crate) fn module_map_from(scope: &mut v8::PinScope) -> Rc<ModuleMap> { let context = scope.get_current_context(); // SAFETY: slot is valid and set during realm creation unsafe { let rc = context.get_aligned_pointer_from_embedder_data(MODULE_MAP_SLOT_INDEX); clone_rc_raw(rc as *const ModuleMap) } } #[inline(always)] pub(crate) fn exception_state_from_scope( scope: &mut v8::PinScope, ) -> Rc<ExceptionState> { Self::state_from_scope(scope).exception_state.clone() } #[cfg(test)] #[inline(always)] pub fn num_pending_ops(&self) -> usize { self.0.context_state.pending_ops.len() } #[cfg(test)] #[inline(always)] pub fn num_unrefed_ops(&self) -> usize { self.0.context_state.unrefed_ops.borrow().len() } #[inline(always)] pub fn context(&self) -> &v8::Global<v8::Context> { self.0.context() } /// Executes traditional JavaScript code (traditional = not ES modules) in the /// realm's context. /// /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`]. /// /// The `name` parameter can be a filepath or any other string. E.g.: /// /// - "/some/file/path.js" /// - "<anon>" /// - "[native code]" /// /// The same `name` value can be used for multiple executions. pub fn execute_script( &self, isolate: &mut v8::Isolate, name: impl IntoModuleName, source_code: impl IntoModuleCodeString, ) -> Result<v8::Global<v8::Value>, Box<JsError>> { context_scope!(scope, self, isolate); let source = source_code.into_module_code().v8_string(scope).unwrap(); let name = name.into_module_name().v8_string(scope).unwrap(); let origin = script_origin(scope, name, false, None); v8::tc_scope!(let tc_scope, scope); let script = match v8::Script::compile(tc_scope, source, Some(&origin)) { Some(script) => script, None => { let exception = tc_scope.exception().unwrap(); return exception_to_err_result(tc_scope, exception, false, false); } }; match script.run(tc_scope) { Some(value) => { let value_handle = v8::Global::new(tc_scope, value); Ok(value_handle) } None => { assert!(tc_scope.has_caught()); let exception = tc_scope.exception().unwrap(); exception_to_err_result(tc_scope, exception, false, false) } } } // TODO(nathanwhit): reduce duplication between this and `execute_script`, and // try to factor out the code cache logic to share with `op_eval_context` pub fn execute_script_with_cache( &self, isolate: &mut v8::Isolate, name: ModuleSpecifier, source_code: impl IntoModuleCodeString, get_cache: &dyn Fn( &ModuleSpecifier, &ModuleSourceCode, ) -> SourceCodeCacheInfo, cache_ready: &dyn Fn(ModuleSpecifier, u64, &[u8]), ) -> Result<v8::Global<v8::Value>, CoreError> { context_scope!(scope, self, isolate); let specifier = name.clone(); let code = source_code.into_module_code(); let source = ModuleSourceCode::String(code); let code_cache = get_cache(&name, &source); let ModuleSourceCode::String(source) = source else { unreachable!() }; let name = name.into_module_name().v8_string(scope).unwrap(); let source = source.v8_string(scope).unwrap(); let origin = script_origin(scope, name, false, None); v8::tc_scope!(let tc_scope, scope); let (maybe_script, maybe_code_cache_hash) = if let Some(data) = &code_cache.data { let mut source = v8::script_compiler::Source::new_with_cached_data( source, Some(&origin), v8::CachedData::new(data), ); let script = v8::script_compiler::compile( tc_scope, &mut source, v8::script_compiler::CompileOptions::ConsumeCodeCache, v8::script_compiler::NoCacheReason::NoReason, ); // Check if the provided code cache is rejected by V8. let rejected = match source.get_cached_data() { Some(cached_data) => cached_data.rejected(), _ => true, }; let maybe_code_cache_hash = if rejected { Some(code_cache.hash) // recreate the cache } else { None }; (Some(script), maybe_code_cache_hash) } else { (None, Some(code_cache.hash)) }; let script = maybe_script .unwrap_or_else(|| v8::Script::compile(tc_scope, source, Some(&origin))); let script = match script { Some(script) => script, None => { let exception = tc_scope.exception().unwrap(); return Ok(exception_to_err_result(tc_scope, exception, false, false)?); } }; if let Some(code_cache_hash) = maybe_code_cache_hash { let unbound_script = script.get_unbound_script(tc_scope); let code_cache = unbound_script .create_code_cache() .ok_or_else(|| CreateCodeCacheError(specifier.clone()))?; cache_ready(specifier, code_cache_hash, &code_cache); } match script.run(tc_scope) { Some(value) => { let value_handle = v8::Global::new(tc_scope, value); Ok(value_handle) } None => { assert!(tc_scope.has_caught()); let exception = tc_scope.exception().unwrap(); Ok(exception_to_err_result(tc_scope, exception, false, false)?) } } } /// Returns the namespace object of a module. /// /// This is only available after module evaluation has completed. /// This function panics if module has not been instantiated. pub fn get_module_namespace( &self, isolate: &mut v8::Isolate, module_id: ModuleId, ) -> Result<v8::Global<v8::Object>, CoreError> { context_scope!(scope, self, isolate); self.0.module_map().get_module_namespace(scope, module_id) } pub(crate) fn instantiate_module( &self, scope: &mut v8::PinScope, id: ModuleId, ) -> Result<(), v8::Global<v8::Value>> { self.0.module_map().instantiate_module(scope, id) } pub(crate) fn modules_idle(&self) -> bool { self.0.module_map.dyn_module_evaluate_idle_counter.get() > 1 } pub(crate) fn increment_modules_idle(&self) { let count = &self.0.module_map.dyn_module_evaluate_idle_counter; count.set(count.get() + 1) } /// Asynchronously load specified ES module and all of its dependencies. /// /// This method is meant to be used when loading some utility code that /// might be later imported by the main module (ie. an entry point module). /// /// User must call [`ModuleMap::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. // TODO(bartlomieju): create a separate method to execute code synchronously // from a loader? Would simplify JsRuntime code and not require running in // a `block_on`. pub(crate) async fn load_side_es_module_from_code( &self, isolate: &mut v8::Isolate, specifier: String, code: Option<ModuleCodeString>, ) -> Result<ModuleId, CoreError> { let module_map_rc = self.0.module_map(); if let Some(code) = code { let specifier = specifier.to_owned(); context_scope!(scope, self, isolate); // false for side module (not main module) module_map_rc .new_es_module(scope, false, specifier.into(), code, false, None) .map_err(|e| e.into_error(scope, false, false))?; } let root_id = RecursiveModuleLoad::side( specifier, module_map_rc, crate::modules::SideModuleKind::Async, None, ) .await? .run_to_completion(|load, step| { context_scope!(scope, self, isolate); match step { crate::modules::recursive_load::RegisterStep::Register { request, source, } => load .register_and_recurse(scope, request, source) .map_err(|e| e.into_error(scope, false, false)), } }) .await?; context_scope!(scope, self, isolate); self.instantiate_module(scope, root_id).map_err(|e| { let exception = v8::Local::new(scope, e); exception_to_err(scope, exception, false, false) })?; Ok(root_id) } /// Load and evaluate an ES module provided the specifier and source code. /// /// The module should not have Top-Level Await (that is, it should be /// possible to evaluate it synchronously). /// /// It is caller's responsibility to ensure that not duplicate specifiers are /// passed to this method. pub(crate) fn lazy_load_es_module_with_code( &self, isolate: &mut v8::Isolate, module_specifier: ModuleName, code: ModuleCodeString, ) -> Result<v8::Global<v8::Value>, CoreError> { let module_map_rc = self.0.module_map(); context_scope!(scope, self, isolate); module_map_rc.lazy_load_es_module_with_code( scope, module_specifier.as_str(), code, None, ) } }