Repository: denoland/deno_core
Pull request #442 (2024-01-14 00:42:30)

feat: Change CustomModuleEvaluationCb to return an enum
This commit changes "CustomModuleEvaluationCb" to return an enum
that tells how to interpret the return value.

In addition to evaluating the returned value as a synthetic module like before
(using "CustomModuleEvaluationKind::Synthetic"), now users can evaluate a
synthetic module and a "computed" module that uses the synthetic module
(using "CustomModuleEvaluationKind::ComputedAndSynthetic").

Ref https://github.com/denoland/deno_core/pull/402


Patch:
diff --git a/core/lib.rs b/core/lib.rs
index f51d4f7..1aced19 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -90,6 +90,7 @@ pub use crate::module_specifier::resolve_url;
 pub use crate::module_specifier::resolve_url_or_path;
 pub use crate::module_specifier::ModuleResolutionError;
 pub use crate::module_specifier::ModuleSpecifier;
+pub use crate::modules::CustomModuleEvaluationKind;
 pub use crate::modules::ExtModuleLoaderCb;
 pub use crate::modules::FsModuleLoader;
 pub use crate::modules::ModuleCodeBytes;
diff --git a/core/modules/map.rs b/core/modules/map.rs
index c8b2879..edf685b 100644
--- a/core/modules/map.rs
+++ b/core/modules/map.rs
@@ -48,6 +48,7 @@ use tokio::sync::oneshot;
 
 use super::module_map_data::ModuleMapData;
 use super::module_map_data::ModuleMapSnapshottedData;
+use super::CustomModuleEvaluationKind;
 use super::LazyEsmModuleLoader;
 use super::RequestedModuleType;
 
@@ -245,6 +246,11 @@ impl ModuleMap {
     self.data.borrow().is_alias(name, requested_module_type)
   }
 
+  #[cfg(test)]
+  pub 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);
@@ -296,7 +302,7 @@ impl ModuleMap {
       return Ok(module_id);
     }
 
-    let module_id = match module_type.clone() {
+    let module_id = match module_type {
       ModuleType::JavaScript => {
         let code =
           ModuleSource::get_string_source(module_url_found.as_str(), code)
@@ -328,20 +334,56 @@ impl ModuleMap {
           ))));
         };
 
-        let value_global = custom_evaluation_cb(
+        // TODO(bartlomieju): creating a global just to create a local from it
+        // seems superfluous. However, changing `CustomModuleEvaluationCb` to have
+        // a lifetime will have a viral effect and required `JsRuntimeOptions`
+        // to have a callback as well as `JsRuntime`.
+        let module_evaluation_kind = custom_evaluation_cb(
           scope,
           module_type.clone(),
           &module_url_found,
           code,
         )
         .map_err(ModuleError::Other)?;
-        let value = v8::Local::new(scope, value_global);
-        self.new_synthetic_module(
-          scope,
-          module_url_found,
-          ModuleType::Other(module_type.clone()),
-          value,
-        )?
+
+        match module_evaluation_kind {
+          // Simple case, we just got a single value so we create a regular
+          // synthetic module.
+          CustomModuleEvaluationKind::Synthetic(value_global) => {
+            let value = v8::Local::new(scope, value_global);
+            self.new_synthetic_module(
+              scope,
+              module_url_found,
+              ModuleType::Other(module_type.clone()),
+              value,
+            )?
+          }
+
+          // Complex case - besides a synthetic module, we will create a new
+          // module from JS code.
+          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 _synthetic_mod_id = self.new_synthetic_module(
+              scope,
+              url1,
+              synthetic_module_type,
+              value,
+            )?;
+            self.new_module_from_js_source(
+              scope,
+              main,
+              ModuleType::Other(module_type.clone()),
+              url2,
+              computed_src,
+              dynamic,
+            )?
+          }
+        }
       }
     };
     Ok(module_id)
diff --git a/core/modules/mod.rs b/core/modules/mod.rs
index 3390962..49e93c9 100644
--- a/core/modules/mod.rs
+++ b/core/modules/mod.rs
@@ -130,9 +130,29 @@ pub type CustomModuleEvaluationCb = Box<
     Cow<'_, str>,
     &FastString,
     ModuleSourceCode,
-  ) -> Result<v8::Global<v8::Value>, AnyError>,
+  ) -> Result<CustomModuleEvaluationKind, AnyError>,
 >;
 
+pub enum CustomModuleEvaluationKind {
+  /// This evaluation results in a single, "synthetic" module.
+  Synthetic(v8::Global<v8::Value>),
+
+  /// This evaluation results in creation of two modules:
+  ///  - a "computed" module - some JavaScript that most likely is rendered and
+  ///    uses the "synthetic" module - this module's ID is returned from
+  ///    [`new_module`] call.
+  ///  - a "synthetic" module - a kind of a helper module that abstracts
+  ///    the source of JS objects - this module is set up first.
+  ComputedAndSynthetic(
+    // Source code of computed module,
+    FastString,
+    // Synthetic module value
+    v8::Global<v8::Value>,
+    // Synthetic module type
+    ModuleType,
+  ),
+}
+
 #[derive(Debug)]
 pub(crate) enum ImportAttributesKind {
   StaticImport,


Test patch:
diff --git a/core/modules/tests.rs b/core/modules/tests.rs
index 6d65b5f..8c532c0 100644
--- a/core/modules/tests.rs
+++ b/core/modules/tests.rs
@@ -5,8 +5,10 @@ use crate::error::generic_error;
 use crate::modules::loaders::ModuleLoadEventCounts;
 use crate::modules::loaders::TestingModuleLoader;
 use crate::modules::loaders::*;
+use crate::modules::CustomModuleEvaluationKind;
 use crate::modules::ModuleCodeBytes;
 use crate::modules::ModuleError;
+use crate::modules::ModuleInfo;
 use crate::modules::ModuleRequest;
 use crate::modules::ModuleSourceCode;
 use crate::modules::RequestedModuleType;
@@ -748,13 +750,13 @@ fn test_custom_module_type_default() {
 }
 
 #[test]
-fn test_custom_module_type_callback() {
+fn test_custom_module_type_callback_synthetic() {
   fn custom_eval_cb(
     scope: &mut v8::HandleScope,
     module_type: Cow<'_, str>,
     _module_name: &FastString,
     module_code: ModuleSourceCode,
-  ) -> Result<v8::Global<v8::Value>, Error> {
+  ) -> Result<CustomModuleEvaluationKind, Error> {
     if module_type != "bytes" {
       return Err(generic_error(format!(
         "Can't load '{}' module",
@@ -772,7 +774,8 @@ fn test_custom_module_type_callback() {
     let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
     let uint8_array = v8::Uint8Array::new(scope, ab, 0, buf_len).unwrap();
     let value: v8::Local<v8::Value> = uint8_array.into();
-    Ok(v8::Global::new(scope, value))
+    let value_global = v8::Global::new(scope, value);
+    Ok(CustomModuleEvaluationKind::Synthetic(value_global))
   }
 
   let loader = Rc::new(TestingModuleLoader::new(StaticModuleLoader::new([])));
@@ -828,6 +831,106 @@ fn test_custom_module_type_callback() {
   };
 }
 
+#[test]
+fn test_custom_module_type_callback_computed() {
+  fn custom_eval_cb(
+    scope: &mut v8::HandleScope,
+    module_type: Cow<'_, str>,
+    module_name: &FastString,
+    module_code: ModuleSourceCode,
+  ) -> Result<CustomModuleEvaluationKind, Error> {
+    if module_type != "foobar" {
+      return Err(generic_error(format!(
+        "Can't load '{}' module",
+        module_type
+      )));
+    }
+
+    let buf = match module_code {
+      ModuleSourceCode::Bytes(buf) => buf.to_vec(),
+      ModuleSourceCode::String(str_) => str_.as_bytes().to_vec(),
+    };
+    let buf_len: usize = buf.len();
+    let backing_store = v8::ArrayBuffer::new_backing_store_from_vec(buf);
+    let backing_store_shared = backing_store.make_shared();
+    let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
+    let uint8_array = v8::Uint8Array::new(scope, ab, 0, buf_len).unwrap();
+    let value: v8::Local<v8::Value> = uint8_array.into();
+    let value_global = v8::Global::new(scope, value);
+
+    let computed_src = format!(
+      r#"
+import bytes from "{}" with {{ type: "foobar-synth" }};
+
+export const foo = bytes;
+    "#,
+      module_name.as_str()
+    );
+    Ok(CustomModuleEvaluationKind::ComputedAndSynthetic(
+      computed_src.into(),
+      value_global,
+      ModuleType::Other("foobar-synth".into()),
+    ))
+  }
+
+  let loader = Rc::new(TestingModuleLoader::new(StaticModuleLoader::new([])));
+  let mut runtime = JsRuntime::new(RuntimeOptions {
+    module_loader: Some(loader.clone()),
+    custom_module_evaluation_cb: Some(Box::new(custom_eval_cb)),
+    ..Default::default()
+  });
+
+  let module_map = runtime.module_map().clone();
+
+  let mod_id = {
+    let scope = &mut runtime.handle_scope();
+    let specifier_a = ascii_str!("file:///b.png");
+    module_map
+      .new_module(
+        scope,
+        true,
+        false,
+        ModuleSource {
+          code: ModuleSourceCode::Bytes(ModuleCodeBytes::Static(&[])),
+          module_url_found: None,
+          module_url_specified: specifier_a,
+          module_type: ModuleType::Other("foobar".into()),
+        },
+      )
+      .unwrap()
+  };
+
+  let data = module_map.get_data();
+  let data = data.borrow();
+  let info = data.info.get(mod_id).unwrap();
+  assert_eq!(
+    info,
+    &ModuleInfo {
+      id: mod_id,
+      main: true,
+      name: ascii_str!("file:///b.png"),
+      requests: vec![ModuleRequest {
+        specifier: "file:///b.png".to_string(),
+        requested_module_type: RequestedModuleType::Other(
+          "foobar-synth".into()
+        )
+      }],
+      module_type: RequestedModuleType::Other("foobar".into()),
+    }
+  );
+  let info = data.info.get(mod_id - 1).unwrap();
+  assert_eq!(
+    info,
+    &ModuleInfo {
+      id: mod_id - 1,
+      main: false,
+      name: ascii_str!("file:///b.png"),
+      requests: vec![],
+      module_type: RequestedModuleType::Other("foobar-synth".into()),
+    }
+  );
+}
+
 #[tokio::test]
 async fn dyn_import_err() {
   let loader = Rc::new(TestingModuleLoader::new(StaticModuleLoader::new([])));