mirror of
https://github.com/denoland/deno.git
synced 2025-01-18 03:44:05 -05:00
feat: ES module snapshotting (#17460)
This commit adds support for snapshotting ES modules. This is done by adding an ability to serialize and deserialize a "ModuleMap" and attach it to the snapshot, using "add_context_data" API. This has been tested with 400 modules and seems to not have a limit on the number of modules that might be snapshotted. Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
18e8ce4ca5
commit
8dbf7d7866
2 changed files with 379 additions and 23 deletions
145
core/modules.rs
145
core/modules.rs
|
@ -14,6 +14,8 @@ use futures::stream::Stream;
|
|||
use futures::stream::StreamFuture;
|
||||
use futures::stream::TryStreamExt;
|
||||
use log::debug;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
@ -157,7 +159,7 @@ fn json_module_evaluation_steps<'a>(
|
|||
/// how to interpret the module; it is only used to validate
|
||||
/// the module against an import assertion (if one is present
|
||||
/// in the import statement).
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ModuleType {
|
||||
JavaScript,
|
||||
Json,
|
||||
|
@ -720,7 +722,7 @@ impl Stream for RecursiveModuleLoad {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) enum AssertedModuleType {
|
||||
JavaScriptOrWasm,
|
||||
Json,
|
||||
|
@ -748,12 +750,13 @@ impl std::fmt::Display for AssertedModuleType {
|
|||
/// Usually executable (`JavaScriptOrWasm`) is used, except when an
|
||||
/// import assertions explicitly constrains an import to JSON, in
|
||||
/// which case this will have a `AssertedModuleType::Json`.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct ModuleRequest {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub asserted_module_type: AssertedModuleType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct ModuleInfo {
|
||||
#[allow(unused)]
|
||||
pub id: ModuleId,
|
||||
|
@ -765,7 +768,8 @@ pub(crate) struct ModuleInfo {
|
|||
}
|
||||
|
||||
/// A symbolic module entity.
|
||||
enum SymbolicModule {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub(crate) enum SymbolicModule {
|
||||
/// This module is an alias to another module.
|
||||
/// This is useful such that multiple names could point to
|
||||
/// the same underlying module (particularly due to redirects).
|
||||
|
@ -783,12 +787,12 @@ pub(crate) enum ModuleError {
|
|||
/// A collection of JS modules.
|
||||
pub(crate) struct ModuleMap {
|
||||
// Handling of specifiers and v8 objects
|
||||
ids_by_handle: HashMap<v8::Global<v8::Module>, ModuleId>,
|
||||
pub(crate) ids_by_handle: HashMap<v8::Global<v8::Module>, ModuleId>,
|
||||
pub handles_by_id: HashMap<ModuleId, v8::Global<v8::Module>>,
|
||||
pub info: HashMap<ModuleId, ModuleInfo>,
|
||||
by_name: HashMap<(String, AssertedModuleType), SymbolicModule>,
|
||||
next_module_id: ModuleId,
|
||||
next_load_id: ModuleLoadId,
|
||||
pub(crate) by_name: HashMap<(String, AssertedModuleType), SymbolicModule>,
|
||||
pub(crate) next_module_id: ModuleId,
|
||||
pub(crate) next_load_id: ModuleLoadId,
|
||||
|
||||
// Handling of futures for loading module sources
|
||||
pub loader: Rc<dyn ModuleLoader>,
|
||||
|
@ -806,6 +810,131 @@ pub(crate) struct ModuleMap {
|
|||
}
|
||||
|
||||
impl ModuleMap {
|
||||
pub fn serialize_for_snapshotting(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope,
|
||||
) -> (v8::Global<v8::Object>, Vec<v8::Global<v8::Module>>) {
|
||||
let obj = v8::Object::new(scope);
|
||||
|
||||
let next_module_id_str = v8::String::new(scope, "next_module_id").unwrap();
|
||||
let next_module_id = v8::Integer::new(scope, self.next_module_id);
|
||||
obj.set(scope, next_module_id_str.into(), next_module_id.into());
|
||||
|
||||
let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap();
|
||||
let next_load_id = v8::Integer::new(scope, self.next_load_id);
|
||||
obj.set(scope, next_load_id_str.into(), next_load_id.into());
|
||||
|
||||
let info_obj = v8::Object::new(scope);
|
||||
for (key, value) in self.info.clone().into_iter() {
|
||||
let key_val = v8::Integer::new(scope, key);
|
||||
let module_info = serde_v8::to_v8(scope, value).unwrap();
|
||||
info_obj.set(scope, key_val.into(), module_info);
|
||||
}
|
||||
let info_str = v8::String::new(scope, "info").unwrap();
|
||||
obj.set(scope, info_str.into(), info_obj.into());
|
||||
|
||||
let by_name_triples: Vec<(String, AssertedModuleType, SymbolicModule)> =
|
||||
self
|
||||
.by_name
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|el| (el.0 .0, el.0 .1, el.1))
|
||||
.collect();
|
||||
let by_name_array = serde_v8::to_v8(scope, by_name_triples).unwrap();
|
||||
let by_name_str = v8::String::new(scope, "by_name").unwrap();
|
||||
obj.set(scope, by_name_str.into(), by_name_array);
|
||||
|
||||
let obj_global = v8::Global::new(scope, obj);
|
||||
|
||||
let mut handles_and_ids: Vec<(ModuleId, v8::Global<v8::Module>)> =
|
||||
self.handles_by_id.clone().into_iter().collect();
|
||||
handles_and_ids.sort_by_key(|(id, _)| *id);
|
||||
let handles: Vec<v8::Global<v8::Module>> = handles_and_ids
|
||||
.into_iter()
|
||||
.map(|(_, handle)| handle)
|
||||
.collect();
|
||||
(obj_global, handles)
|
||||
}
|
||||
|
||||
pub fn update_with_snapshot_data(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope,
|
||||
data: v8::Global<v8::Object>,
|
||||
module_handles: Vec<v8::Global<v8::Module>>,
|
||||
) {
|
||||
let local_data: v8::Local<v8::Object> = v8::Local::new(scope, data);
|
||||
|
||||
{
|
||||
let next_module_id_str =
|
||||
v8::String::new(scope, "next_module_id").unwrap();
|
||||
let next_module_id =
|
||||
local_data.get(scope, next_module_id_str.into()).unwrap();
|
||||
assert!(next_module_id.is_int32());
|
||||
let integer = next_module_id.to_integer(scope).unwrap();
|
||||
let val = integer.int32_value(scope).unwrap();
|
||||
self.next_module_id = val;
|
||||
}
|
||||
|
||||
{
|
||||
let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap();
|
||||
let next_load_id =
|
||||
local_data.get(scope, next_load_id_str.into()).unwrap();
|
||||
assert!(next_load_id.is_int32());
|
||||
let integer = next_load_id.to_integer(scope).unwrap();
|
||||
let val = integer.int32_value(scope).unwrap();
|
||||
self.next_load_id = val;
|
||||
}
|
||||
|
||||
{
|
||||
let mut info = HashMap::new();
|
||||
let info_str = v8::String::new(scope, "info").unwrap();
|
||||
let info_data: v8::Local<v8::Object> = local_data
|
||||
.get(scope, info_str.into())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let keys = info_data
|
||||
.get_own_property_names(scope, v8::GetPropertyNamesArgs::default())
|
||||
.unwrap();
|
||||
let keys_len = keys.length();
|
||||
|
||||
for i in 0..keys_len {
|
||||
let key = keys.get_index(scope, i).unwrap();
|
||||
let key_val = key.to_integer(scope).unwrap();
|
||||
let key_int = key_val.int32_value(scope).unwrap();
|
||||
let value = info_data.get(scope, key).unwrap();
|
||||
let module_info = serde_v8::from_v8(scope, value).unwrap();
|
||||
info.insert(key_int, module_info);
|
||||
}
|
||||
self.info = info;
|
||||
}
|
||||
|
||||
{
|
||||
let by_name_str = v8::String::new(scope, "by_name").unwrap();
|
||||
let by_name_data = local_data.get(scope, by_name_str.into()).unwrap();
|
||||
let by_name_deser: Vec<(String, AssertedModuleType, SymbolicModule)> =
|
||||
serde_v8::from_v8(scope, by_name_data).unwrap();
|
||||
self.by_name = by_name_deser
|
||||
.into_iter()
|
||||
.map(|(name, module_type, symbolic_module)| {
|
||||
((name, module_type), symbolic_module)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
self.ids_by_handle = module_handles
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, handle)| (handle.clone(), (index + 1) as i32))
|
||||
.collect();
|
||||
|
||||
self.handles_by_id = module_handles
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, handle)| ((index + 1) as i32, handle.clone()))
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
loader: Rc<dyn ModuleLoader>,
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
|
|
257
core/runtime.rs
257
core/runtime.rs
|
@ -351,7 +351,8 @@ impl JsRuntime {
|
|||
DENO_INIT.call_once(move || v8_init(v8_platform, options.will_snapshot));
|
||||
|
||||
// Add builtins extension
|
||||
if options.startup_snapshot.is_none() {
|
||||
let has_startup_snapshot = options.startup_snapshot.is_some();
|
||||
if !has_startup_snapshot {
|
||||
options
|
||||
.extensions_with_js
|
||||
.insert(0, crate::ops_builtin::init_builtins());
|
||||
|
@ -423,6 +424,62 @@ impl JsRuntime {
|
|||
// V8 takes ownership of external_references.
|
||||
let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs));
|
||||
let global_context;
|
||||
let mut module_map_data = None;
|
||||
let mut module_handles = vec![];
|
||||
|
||||
fn get_context_data(
|
||||
scope: &mut v8::HandleScope<()>,
|
||||
context: v8::Local<v8::Context>,
|
||||
) -> (Vec<v8::Global<v8::Module>>, v8::Global<v8::Object>) {
|
||||
fn data_error_to_panic(err: v8::DataError) -> ! {
|
||||
match err {
|
||||
v8::DataError::BadType { actual, expected } => {
|
||||
panic!(
|
||||
"Invalid type for snapshot data: expected {}, got {}",
|
||||
expected, actual
|
||||
);
|
||||
}
|
||||
v8::DataError::NoData { expected } => {
|
||||
panic!("No data for snapshot data: expected {}", expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut module_handles = vec![];
|
||||
let mut scope = v8::ContextScope::new(scope, context);
|
||||
// The 0th element is the module map itself, followed by X number of module
|
||||
// handles. We need to deserialize the "next_module_id" field from the
|
||||
// map to see how many module handles we expect.
|
||||
match scope.get_context_data_from_snapshot_once::<v8::Object>(0) {
|
||||
Ok(val) => {
|
||||
let next_module_id = {
|
||||
let next_module_id_str =
|
||||
v8::String::new(&mut scope, "next_module_id").unwrap();
|
||||
let next_module_id =
|
||||
val.get(&mut scope, next_module_id_str.into()).unwrap();
|
||||
assert!(next_module_id.is_int32());
|
||||
let integer = next_module_id.to_integer(&mut scope).unwrap();
|
||||
integer.int32_value(&mut scope).unwrap()
|
||||
};
|
||||
let no_of_modules = next_module_id - 1;
|
||||
|
||||
for i in 1..=no_of_modules {
|
||||
match scope
|
||||
.get_context_data_from_snapshot_once::<v8::Module>(i as usize)
|
||||
{
|
||||
Ok(val) => {
|
||||
let module_global = v8::Global::new(&mut scope, val);
|
||||
module_handles.push(module_global);
|
||||
}
|
||||
Err(err) => data_error_to_panic(err),
|
||||
}
|
||||
}
|
||||
|
||||
(module_handles, v8::Global::new(&mut scope, val))
|
||||
}
|
||||
Err(err) => data_error_to_panic(err),
|
||||
}
|
||||
}
|
||||
|
||||
let (mut isolate, snapshot_options) = if options.will_snapshot {
|
||||
let (snapshot_creator, snapshot_loaded) =
|
||||
|
@ -468,6 +525,14 @@ impl JsRuntime {
|
|||
let scope = &mut v8::HandleScope::new(&mut isolate);
|
||||
let context =
|
||||
bindings::initialize_context(scope, &op_ctxs, snapshot_options);
|
||||
|
||||
// Get module map data from the snapshot
|
||||
if has_startup_snapshot {
|
||||
let context_data = get_context_data(scope, context);
|
||||
module_handles = context_data.0;
|
||||
module_map_data = Some(context_data.1);
|
||||
}
|
||||
|
||||
global_context = v8::Global::new(scope, context);
|
||||
scope.set_default_context(context);
|
||||
}
|
||||
|
@ -510,6 +575,13 @@ impl JsRuntime {
|
|||
let context =
|
||||
bindings::initialize_context(scope, &op_ctxs, snapshot_options);
|
||||
|
||||
// Get module map data from the snapshot
|
||||
if has_startup_snapshot {
|
||||
let context_data = get_context_data(scope, context);
|
||||
module_handles = context_data.0;
|
||||
module_map_data = Some(context_data.1);
|
||||
}
|
||||
|
||||
global_context = v8::Global::new(scope, context);
|
||||
}
|
||||
|
||||
|
@ -544,7 +616,7 @@ impl JsRuntime {
|
|||
state.inspector = inspector;
|
||||
state
|
||||
.known_realms
|
||||
.push(v8::Weak::new(&mut isolate, global_context));
|
||||
.push(v8::Weak::new(&mut isolate, &global_context));
|
||||
}
|
||||
isolate.set_data(
|
||||
Self::STATE_DATA_OFFSET,
|
||||
|
@ -552,6 +624,16 @@ impl JsRuntime {
|
|||
);
|
||||
|
||||
let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state)));
|
||||
if let Some(module_map_data) = module_map_data {
|
||||
let scope =
|
||||
&mut v8::HandleScope::with_context(&mut isolate, global_context);
|
||||
let mut module_map = module_map_rc.borrow_mut();
|
||||
module_map.update_with_snapshot_data(
|
||||
scope,
|
||||
module_map_data,
|
||||
module_handles,
|
||||
);
|
||||
}
|
||||
isolate.set_data(
|
||||
Self::MODULE_MAP_DATA_OFFSET,
|
||||
Rc::into_raw(module_map_rc.clone()) as *mut c_void,
|
||||
|
@ -911,15 +993,37 @@ impl JsRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
self.state.borrow_mut().global_realm.take();
|
||||
self.state.borrow_mut().inspector.take();
|
||||
|
||||
// Serialize the module map and store its data in the snapshot.
|
||||
{
|
||||
let module_map_rc = self.module_map.take().unwrap();
|
||||
let module_map = module_map_rc.borrow();
|
||||
let (module_map_data, module_handles) =
|
||||
module_map.serialize_for_snapshotting(&mut self.handle_scope());
|
||||
|
||||
let context = self.global_context();
|
||||
let mut scope = self.handle_scope();
|
||||
let local_context = v8::Local::new(&mut scope, context);
|
||||
let local_data = v8::Local::new(&mut scope, module_map_data);
|
||||
let offset = scope.add_context_data(local_context, local_data);
|
||||
assert_eq!(offset, 0);
|
||||
|
||||
for (index, handle) in module_handles.into_iter().enumerate() {
|
||||
let module_handle = v8::Local::new(&mut scope, handle);
|
||||
let offset = scope.add_context_data(local_context, module_handle);
|
||||
assert_eq!(offset, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop existing ModuleMap to drop v8::Global handles
|
||||
{
|
||||
self.module_map.take();
|
||||
let v8_isolate = self.v8_isolate();
|
||||
Self::drop_state_and_module_map(v8_isolate);
|
||||
}
|
||||
|
||||
self.state.borrow_mut().global_realm.take();
|
||||
|
||||
// Drop other v8::Global handles before snapshotting
|
||||
{
|
||||
for weak_context in &self.state.clone().borrow().known_realms {
|
||||
|
@ -2605,10 +2709,13 @@ pub mod tests {
|
|||
use super::*;
|
||||
use crate::error::custom_error;
|
||||
use crate::error::AnyError;
|
||||
use crate::modules::AssertedModuleType;
|
||||
use crate::modules::ModuleInfo;
|
||||
use crate::modules::ModuleSource;
|
||||
use crate::modules::ModuleSourceFuture;
|
||||
use crate::modules::ModuleType;
|
||||
use crate::modules::ResolutionKind;
|
||||
use crate::modules::SymbolicModule;
|
||||
use crate::ZeroCopyBuf;
|
||||
use deno_ops::op;
|
||||
use futures::future::lazy;
|
||||
|
@ -2618,6 +2725,7 @@ pub mod tests {
|
|||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
// deno_ops macros generate code assuming deno_core in scope.
|
||||
mod deno_core {
|
||||
pub use crate::*;
|
||||
|
@ -3422,8 +3530,6 @@ pub mod tests {
|
|||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
assert_eq!(specifier, "file:///main.js");
|
||||
assert_eq!(referrer, ".");
|
||||
let s = crate::resolve_import(specifier, referrer).unwrap();
|
||||
Ok(s)
|
||||
}
|
||||
|
@ -3434,29 +3540,150 @@ pub mod tests {
|
|||
_maybe_referrer: Option<ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
eprintln!("load() should not be called");
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
let loader = std::rc::Rc::new(ModsLoader::default());
|
||||
fn create_module(
|
||||
runtime: &mut JsRuntime,
|
||||
i: usize,
|
||||
main: bool,
|
||||
) -> ModuleInfo {
|
||||
let specifier = crate::resolve_url(&format!("file:///{i}.js")).unwrap();
|
||||
let prev = i - 1;
|
||||
let source_code = format!(
|
||||
r#"
|
||||
import {{ f{prev} }} from "file:///{prev}.js";
|
||||
export function f{i}() {{ return f{prev}() }}
|
||||
"#
|
||||
);
|
||||
|
||||
let id = if main {
|
||||
futures::executor::block_on(
|
||||
runtime.load_main_module(&specifier, Some(source_code)),
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
futures::executor::block_on(
|
||||
runtime.load_side_module(&specifier, Some(source_code)),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(i + 1, id as usize);
|
||||
|
||||
let _ = runtime.mod_evaluate(id);
|
||||
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
|
||||
|
||||
ModuleInfo {
|
||||
id,
|
||||
main,
|
||||
name: specifier.to_string(),
|
||||
requests: vec![crate::modules::ModuleRequest {
|
||||
specifier: crate::resolve_url(&format!("file:///{prev}.js")).unwrap(),
|
||||
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
|
||||
}],
|
||||
module_type: ModuleType::JavaScript,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_module_map(runtime: &mut JsRuntime, modules: &Vec<ModuleInfo>) {
|
||||
let module_map_rc = runtime.get_module_map();
|
||||
let module_map = module_map_rc.borrow();
|
||||
assert_eq!(module_map.ids_by_handle.len(), modules.len());
|
||||
assert_eq!(module_map.handles_by_id.len(), modules.len());
|
||||
assert_eq!(module_map.info.len(), modules.len());
|
||||
assert_eq!(module_map.by_name.len(), modules.len());
|
||||
|
||||
assert_eq!(module_map.next_module_id, (modules.len() + 1) as ModuleId);
|
||||
assert_eq!(module_map.next_load_id, (modules.len() + 1) as ModuleId);
|
||||
|
||||
let ids_by_handle = module_map.ids_by_handle.values().collect::<Vec<_>>();
|
||||
|
||||
for info in modules {
|
||||
assert!(ids_by_handle.contains(&&info.id));
|
||||
assert!(module_map.handles_by_id.contains_key(&info.id));
|
||||
assert_eq!(module_map.info.get(&info.id).unwrap(), info);
|
||||
assert_eq!(
|
||||
module_map
|
||||
.by_name
|
||||
.get(&(info.name.clone(), AssertedModuleType::JavaScriptOrWasm))
|
||||
.unwrap(),
|
||||
&SymbolicModule::Mod(info.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let loader = Rc::new(ModsLoader::default());
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(loader),
|
||||
module_loader: Some(loader.clone()),
|
||||
will_snapshot: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let specifier = crate::resolve_url("file:///main.js").unwrap();
|
||||
let source_code = "Deno.core.print('hello\\n')".to_string();
|
||||
|
||||
let module_id = futures::executor::block_on(
|
||||
runtime.load_main_module(&specifier, Some(source_code)),
|
||||
let specifier = crate::resolve_url("file:///0.js").unwrap();
|
||||
let source_code =
|
||||
r#"export function f0() { return "hello world" }"#.to_string();
|
||||
let id = futures::executor::block_on(
|
||||
runtime.load_side_module(&specifier, Some(source_code)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = runtime.mod_evaluate(module_id);
|
||||
let _ = runtime.mod_evaluate(id);
|
||||
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
|
||||
|
||||
let _snapshot = runtime.snapshot();
|
||||
let mut modules = vec![];
|
||||
modules.push(ModuleInfo {
|
||||
id,
|
||||
main: false,
|
||||
name: specifier.to_string(),
|
||||
requests: vec![],
|
||||
module_type: ModuleType::JavaScript,
|
||||
});
|
||||
|
||||
modules.extend((1..200).map(|i| create_module(&mut runtime, i, false)));
|
||||
|
||||
assert_module_map(&mut runtime, &modules);
|
||||
|
||||
let snapshot = runtime.snapshot();
|
||||
|
||||
let mut runtime2 = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(loader.clone()),
|
||||
will_snapshot: true,
|
||||
startup_snapshot: Some(Snapshot::JustCreated(snapshot)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_module_map(&mut runtime2, &modules);
|
||||
|
||||
modules.extend((200..400).map(|i| create_module(&mut runtime2, i, false)));
|
||||
modules.push(create_module(&mut runtime2, 400, true));
|
||||
|
||||
assert_module_map(&mut runtime2, &modules);
|
||||
|
||||
let snapshot2 = runtime2.snapshot();
|
||||
|
||||
let mut runtime3 = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(loader),
|
||||
startup_snapshot: Some(Snapshot::JustCreated(snapshot2)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_module_map(&mut runtime3, &modules);
|
||||
|
||||
let source_code = r#"(async () => {
|
||||
const mod = await import("file:///400.js");
|
||||
return mod.f400();
|
||||
})();"#
|
||||
.to_string();
|
||||
let val = runtime3.execute_script(".", &source_code).unwrap();
|
||||
let val = futures::executor::block_on(runtime3.resolve_value(val)).unwrap();
|
||||
{
|
||||
let scope = &mut runtime3.handle_scope();
|
||||
let value = v8::Local::new(scope, val);
|
||||
let str_ = value.to_string(scope).unwrap().to_rust_string_lossy(scope);
|
||||
assert_eq!(str_, "hello world");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Reference in a new issue