Repository: denoland/deno_core
Pull request #633 (2024-03-04 23:19:50)

feat(core): eagerly load extension sources

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) Report
Attention: Patch coverage is `81.67539%` with `35 lines` in your changes are missing coverage. Please review.
> Project coverage is 80.34%. Comparing base [(`c43ac7e`)](https://app.codecov.io/gh/denoland/deno_core/commit/c43ac7ea6c13c11c780bb54ea9fad30d7d6c8ca7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) to head [(`75e59b1`)](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).

| [Files](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) | Patch % | Lines |
|---|---|---|
| [core/extension\_set.rs](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS9leHRlbnNpb25fc2V0LnJz) | 65.38% | [27 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |
| [core/fast\_string.rs](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS9mYXN0X3N0cmluZy5ycw==) | 50.00% | [3 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |
| [core/runtime/jsruntime.rs](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS9ydW50aW1lL2pzcnVudGltZS5ycw==) | 96.07% | [2 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |
| [testing/checkin/runner/ts\_module\_loader.rs](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-dGVzdGluZy9jaGVja2luL3J1bm5lci90c19tb2R1bGVfbG9hZGVyLnJz) | 85.71% | [2 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |
| [core/extensions.rs](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS9leHRlbnNpb25zLnJz) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #633      +/-   ##
==========================================
- Coverage   80.44%   80.34%   -0.10%     
==========================================
  Files          97       97              
  Lines       23763    23774      +11     
==========================================
- Hits        19116    19102      -14     
- Misses       4647     4672      +25     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/denoland/deno_core/pull/633?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).


Patch:
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 7dc94b4..ded86fe 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -14,9 +14,7 @@ description = "A modern JavaScript/TypeScript runtime built with V8, Rust, and T
 path = "lib.rs"
 
 [features]
-default = ["include_icu_data", "v8_use_custom_libcxx", "snapshot_data_json"]
-snapshot_data_json = []
-snapshot_data_bincode = ["bincode"]
+default = ["include_icu_data", "v8_use_custom_libcxx"]
 include_icu_data = ["deno_core_icudata"]
 v8_use_custom_libcxx = ["v8/use_custom_libcxx"]
 include_js_files_for_snapshotting = []
@@ -25,7 +23,7 @@ unsafe_use_unprotected_platform = []
 
 [dependencies]
 anyhow.workspace = true
-bincode = { workspace = true, optional = true }
+bincode.workspace = true
 bit-set.workspace = true
 bit-vec.workspace = true
 bytes.workspace = true
diff --git a/core/benches/snapshot/snapshot.rs b/core/benches/snapshot/snapshot.rs
index 10ec0de..7c01711 100644
--- a/core/benches/snapshot/snapshot.rs
+++ b/core/benches/snapshot/snapshot.rs
@@ -1,11 +1,17 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 use criterion::*;
+use deno_ast::MediaType;
 use deno_ast::ParseParams;
+use deno_ast::SourceTextInfo;
+use deno_core::error::AnyError;
 use deno_core::Extension;
-use deno_core::ExtensionFileSourceCode;
 use deno_core::JsRuntime;
 use deno_core::JsRuntimeForSnapshot;
+use deno_core::ModuleCodeString;
+use deno_core::ModuleName;
 use deno_core::RuntimeOptions;
+use deno_core::SourceMapData;
+use std::rc::Rc;
 use std::time::Duration;
 use std::time::Instant;
 
@@ -42,32 +48,32 @@ fn make_extensions_ops() -> Vec<Extension> {
   fake_extensions!(init_ops, a, b, c, d, e, f, g, h, i, j, k, l, m, n)
 }
 
-fn transpile_extensions(extensions: &mut Vec<Extension>) {
-  for extension in extensions {
-    for source in extension.esm_files.to_mut() {
-      let code = source.load().unwrap().as_str().to_owned();
-      let transpiled = deno_ast::parse_module(ParseParams {
-        specifier: source.specifier.to_string(),
-        text_info: deno_ast::SourceTextInfo::from_string(code),
-        media_type: deno_ast::MediaType::TypeScript,
-        capture_tokens: false,
-        scope_analysis: false,
-        maybe_syntax: None,
-      })
-      .unwrap()
-      .transpile(&deno_ast::EmitOptions {
-        imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove,
-        inline_source_map: false,
-        source_map: true,
-        inline_sources: true,
-        ..Default::default()
-      })
-      .unwrap();
-
-      source.code = ExtensionFileSourceCode::Computed(transpiled.text.into());
-      source.source_map = Some(transpiled.source_map.unwrap().into_bytes());
-    }
-  }
+pub fn maybe_transpile_source(
+  specifier: ModuleName,
+  source: ModuleCodeString,
+) -> Result<(ModuleCodeString, Option<SourceMapData>), AnyError> {
+  let media_type = MediaType::TypeScript;
+
+  let parsed = deno_ast::parse_module(ParseParams {
+    specifier: specifier.to_string(),
+    text_info: SourceTextInfo::from_string(source.as_str().to_owned()),
+    media_type,
+    capture_tokens: false,
+    scope_analysis: false,
+    maybe_syntax: None,
+  })?;
+  let transpiled_source = parsed.transpile(&deno_ast::EmitOptions {
+    imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove,
+    inline_source_map: false,
+    source_map: true,
+    inline_sources: true,
+    ..Default::default()
+  })?;
+
+  Ok((
+    transpiled_source.text.into(),
+    transpiled_source.source_map.map(|s| s.into_bytes().into()),
+  ))
 }
 
 fn bench_take_snapshot_empty(c: &mut Criterion) {
@@ -93,12 +99,16 @@ fn bench_take_snapshot(c: &mut Criterion) {
     b.iter_custom(|iters| {
       let mut total = 0;
       for _ in 0..iters {
-        let mut extensions = make_extensions();
-        if transpile {
-          transpile_extensions(&mut extensions);
-        }
+        let extensions = make_extensions();
         let runtime = JsRuntimeForSnapshot::new(RuntimeOptions {
           startup_snapshot: None,
+          extension_transpiler: if transpile {
+            Some(Rc::new(|specifier, source| {
+              maybe_transpile_source(specifier, source)
+            }))
+          } else {
+            None
+          },
           extensions,
           ..Default::default()
         });
@@ -118,12 +128,16 @@ fn bench_take_snapshot(c: &mut Criterion) {
 
 fn bench_load_snapshot(c: &mut Criterion) {
   fn inner(b: &mut Bencher, transpile: bool) {
-    let mut extensions = make_extensions();
-    if transpile {
-      transpile_extensions(&mut extensions);
-    }
+    let extensions = make_extensions();
     let runtime = JsRuntimeForSnapshot::new(RuntimeOptions {
       extensions,
+      extension_transpiler: if transpile {
+        Some(Rc::new(|specifier, source| {
+          maybe_transpile_source(specifier, source)
+        }))
+      } else {
+        None
+      },
       startup_snapshot: None,
       ..Default::default()
     });
diff --git a/core/extension_set.rs b/core/extension_set.rs
index 757778e..4be2485 100644
--- a/core/extension_set.rs
+++ b/core/extension_set.rs
@@ -1,19 +1,28 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
 use std::cell::RefCell;
+use std::iter::Chain;
 use std::rc::Rc;
 
+use crate::error::AnyError;
 use crate::extensions::Extension;
+use crate::extensions::ExtensionSourceType;
 use crate::extensions::GlobalObjectMiddlewareFn;
 use crate::extensions::GlobalTemplateMiddlewareFn;
 use crate::extensions::OpMiddlewareFn;
+use crate::modules::ModuleName;
 use crate::ops::OpCtx;
+use crate::runtime::ExtensionTranspiler;
 use crate::runtime::JsRuntimeState;
 use crate::runtime::OpDriverImpl;
+use crate::source_map::SourceMapper;
+use crate::ExtensionFileSource;
+use crate::FastString;
 use crate::GetErrorClassFn;
+use crate::ModuleCodeString;
 use crate::OpDecl;
 use crate::OpMetricsFactoryFn;
 use crate::OpState;
+use crate::SourceMapGetter;
 
 /// Contribute to the `OpState` from each extension.
 pub fn setup_op_state(op_state: &mut OpState, extensions: &mut [Extension]) {
@@ -162,7 +171,6 @@ pub fn get_middlewares_and_external_refs(
     if let Some(middleware) = extension.get_global_object_middleware() {
       global_object_middlewares.push(middleware);
     }
-
     additional_references
       .extend_from_slice(extension.get_external_references());
   }
@@ -173,3 +181,120 @@ pub fn get_middlewares_and_external_refs(
     additional_references,
   )
 }
+
+#[derive(Debug)]
+pub struct LoadedSource {
+  pub source_type: ExtensionSourceType,
+  pub specifier: ModuleName,
+  pub code: ModuleCodeString,
+}
+
+#[derive(Debug, Default)]
+pub struct LoadedSources {
+  pub js: Vec<LoadedSource>,
+  pub esm: Vec<LoadedSource>,
+  pub lazy_esm: Vec<LoadedSource>,
+  pub esm_entry_points: Vec<FastString>,
+}
+
+impl LoadedSources {
+  pub fn len(&self) -> usize {
+    self.js.len() + self.esm.len() + self.lazy_esm.len()
+  }
+
+  pub fn is_empty(&self) -> bool {
+    self.js.is_empty() && self.esm.is_empty() && self.lazy_esm.is_empty()
+  }
+}
+
+type VecIntoIter<'a> = <&'a Vec<LoadedSource> as IntoIterator>::IntoIter;
+type VecIntoIterMut<'a> = <&'a mut Vec<LoadedSource> as IntoIterator>::IntoIter;
+
+impl<'a> IntoIterator for &'a LoadedSources {
+  type Item = &'a LoadedSource;
+  type IntoIter =
+    Chain<Chain<VecIntoIter<'a>, VecIntoIter<'a>>, VecIntoIter<'a>>;
+  fn into_iter(self) -> Self::IntoIter {
+    self
+      .js
+      .iter()
+      .chain(self.esm.iter())
+      .chain(self.lazy_esm.iter())
+  }
+}
+
+impl<'a> IntoIterator for &'a mut LoadedSources {
+  type Item = &'a mut LoadedSource;
+  type IntoIter =
+    Chain<Chain<VecIntoIterMut<'a>, VecIntoIterMut<'a>>, VecIntoIterMut<'a>>;
+  fn into_iter(self) -> Self::IntoIter {
+    self
+      .js
+      .iter_mut()
+      .chain(self.esm.iter_mut())
+      .chain(self.lazy_esm.iter_mut())
+  }
+}
+
+fn load(
+  transpiler: Option<&ExtensionTranspiler>,
+  source: &ExtensionFileSource,
+  source_mapper: &mut SourceMapper<Rc<dyn SourceMapGetter>>,
+  load_callback: &mut impl FnMut(&ExtensionFileSource),
+) -> Result<ModuleCodeString, AnyError> {
+  load_callback(source);
+  let mut source_code = source.load()?;
+  let mut source_map = None;
+  if let Some(transpiler) = transpiler {
+    (source_code, source_map) =
+      transpiler(ModuleName::from_static(source.specifier), source_code)?;
+  }
+  if let Some(source_map) = source_map {
+    source_mapper
+      .ext_source_maps
+      .insert(source.specifier.to_owned(), source_map);
+  }
+  Ok(source_code)
+}
+
+pub fn into_sources(
+  transpiler: Option<&ExtensionTranspiler>,
+  extensions: &[Extension],
+  source_mapper: &mut SourceMapper<Rc<dyn SourceMapGetter>>,
+  mut load_callback: impl FnMut(&ExtensionFileSource),
+) -> Result<LoadedSources, AnyError> {
+  let mut sources = LoadedSources::default();
+
+  for extension in extensions {
+    if let Some(esm_entry_point) = extension.esm_entry_point {
+      sources
+        .esm_entry_points
+        .push(FastString::from_static(esm_entry_point));
+    }
+    for file in &*extension.lazy_loaded_esm_files {
+      let code = load(transpiler, file, source_mapper, &mut load_callback)?;
+      sources.lazy_esm.push(LoadedSource {
+        source_type: ExtensionSourceType::LazyEsm,
+        specifier: ModuleName::from_static(file.specifier),
+        code,
+      });
+    }
+    for file in &*extension.js_files {
+      let code = load(transpiler, file, source_mapper, &mut load_callback)?;
+      sources.js.push(LoadedSource {
+        source_type: ExtensionSourceType::Js,
+        specifier: ModuleName::from_static(file.specifier),
+        code,
+      });
+    }
+    for file in &*extension.esm_files {
+      let code = load(transpiler, file, source_mapper, &mut load_callback)?;
+      sources.esm.push(LoadedSource {
+        source_type: ExtensionSourceType::Esm,
+        specifier: ModuleName::from_static(file.specifier),
+        code,
+      });
+    }
+  }
+  Ok(sources)
+}
diff --git a/core/extensions.rs b/core/extensions.rs
index 644f0ad..b5d618d 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -51,11 +51,17 @@ impl std::fmt::Debug for ExtensionFileSourceCode {
   }
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ExtensionSourceType {
+  LazyEsm,
+  Js,
+  Esm,
+}
+
 #[derive(Clone, Debug)]
 pub struct ExtensionFileSource {
   pub specifier: &'static str,
   pub code: ExtensionFileSourceCode,
-  pub source_map: Option<Vec<u8>>,
   _unconstructable_use_new: PhantomData<()>,
 }
 
@@ -65,7 +71,6 @@ impl ExtensionFileSource {
     Self {
       specifier,
       code: ExtensionFileSourceCode::IncludedInBinary(code),
-      source_map: None,
       _unconstructable_use_new: PhantomData,
     }
   }
@@ -75,7 +80,6 @@ impl ExtensionFileSource {
     Self {
       specifier,
       code: ExtensionFileSourceCode::Computed(code),
-      source_map: None,
       _unconstructable_use_new: PhantomData,
     }
   }
@@ -88,7 +92,6 @@ impl ExtensionFileSource {
     Self {
       specifier,
       code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path),
-      source_map: None,
       _unconstructable_use_new: PhantomData,
     }
   }
@@ -101,7 +104,6 @@ impl ExtensionFileSource {
     Self {
       specifier,
       code: ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code),
-      source_map: None,
       _unconstructable_use_new: PhantomData,
     }
   }
diff --git a/core/fast_string.rs b/core/fast_string.rs
index 3198a70..f2ea372 100644
--- a/core/fast_string.rs
+++ b/core/fast_string.rs
@@ -3,6 +3,7 @@
 use serde::Deserializer;
 use serde::Serializer;
 use std::borrow::Borrow;
+use std::ffi::OsStr;
 use std::fmt::Debug;
 use std::fmt::Display;
 use std::fmt::Formatter;
@@ -311,6 +312,18 @@ impl AsRef<str> for FastString {
   }
 }
 
+impl AsRef<[u8]> for FastString {
+  fn as_ref(&self) -> &[u8] {
+    self.as_str().as_ref()
+  }
+}
+
+impl AsRef<OsStr> for FastString {
+  fn as_ref(&self) -> &OsStr {
+    self.as_str().as_ref()
+  }
+}
+
 impl Deref for FastString {
   type Target = str;
   fn deref(&self) -> &Self::Target {
diff --git a/core/lib.rs b/core/lib.rs
index 43a4931..26260bb 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -106,6 +106,7 @@ pub use crate::modules::ModuleCodeString;
 pub use crate::modules::ModuleId;
 pub use crate::modules::ModuleLoadResponse;
 pub use crate::modules::ModuleLoader;
+pub use crate::modules::ModuleName;
 pub use crate::modules::ModuleSource;
 pub use crate::modules::ModuleSourceCode;
 pub use crate::modules::ModuleSourceFuture;
@@ -143,6 +144,7 @@ pub use crate::runtime::RuntimeOptions;
 pub use crate::runtime::SharedArrayBufferStore;
 pub use crate::runtime::V8_WRAPPER_OBJECT_INDEX;
 pub use crate::runtime::V8_WRAPPER_TYPE_INDEX;
+pub use crate::source_map::SourceMapData;
 pub use crate::source_map::SourceMapGetter;
 pub use crate::tasks::V8CrossThreadTaskSpawner;
 pub use crate::tasks::V8TaskSpawner;
diff --git a/core/modules/loaders.rs b/core/modules/loaders.rs
index 0be6ee1..fa3313d 100644
--- a/core/modules/loaders.rs
+++ b/core/modules/loaders.rs
@@ -4,13 +4,13 @@ use crate::extensions::ExtensionFileSource;
 use crate::module_specifier::ModuleSpecifier;
 use crate::modules::IntoModuleCodeString;
 use crate::modules::ModuleCodeString;
+use crate::modules::ModuleName;
 use crate::modules::ModuleSource;
 use crate::modules::ModuleSourceFuture;
 use crate::modules::ModuleType;
 use crate::modules::RequestedModuleType;
 use crate::modules::ResolutionKind;
 use crate::resolve_import;
-use crate::Extension;
 use crate::ModuleSourceCode;
 
 use anyhow::anyhow;
@@ -123,18 +123,17 @@ pub type ExtModuleLoaderCb =
   Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCodeString, Error>>;
 
 pub(crate) struct ExtModuleLoader {
-  sources: RefCell<HashMap<&'static str, ModuleCodeString>>,
+  sources: RefCell<HashMap<ModuleName, ModuleCodeString>>,
 }
 
 impl ExtModuleLoader {
-  pub fn new(extensions: &'_ [Extension]) -> Result<Self, Error> {
+  pub fn new(
+    loaded_sources: Vec<(ModuleName, ModuleCodeString)>,
+  ) -> Result<Self, Error> {
     // Guesstimate a length
-    let mut sources = HashMap::with_capacity(extensions.len() * 3);
-    for extension in extensions {
-      for esm in extension.get_esm_sources() {
-        let source = esm.load()?;
-        sources.insert(esm.specifier, source);
-      }
+    let mut sources = HashMap::with_capacity(loaded_sources.len());
+    for source in loaded_sources {
+      sources.insert(source.0, source.1);
     }
     Ok(ExtModuleLoader {
       sources: RefCell::new(sources),
@@ -204,12 +203,12 @@ impl ModuleLoader for ExtModuleLoader {
 /// ES modules that were embedded in the binary using `lazy_loaded_esm`
 /// option in `extension!` macro.
 pub(crate) struct LazyEsmModuleLoader {
-  sources: Rc<RefCell<HashMap<&'static str, ExtensionFileSource>>>,
+  sources: Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
 }
 
 impl LazyEsmModuleLoader {
   pub fn new(
-    sources: Rc<RefCell<HashMap<&'static str, ExtensionFileSource>>>,
+    sources: Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
   ) -> Self {
     LazyEsmModuleLoader { sources }
   }
@@ -232,19 +231,16 @@ impl ModuleLoader for LazyEsmModuleLoader {
     _is_dyn_import: bool,
     _requested_module_type: RequestedModuleType,
   ) -> ModuleLoadResponse {
-    let sources = self.sources.borrow();
-    let source = match sources.get(specifier.as_str()) {
+    let mut sources = self.sources.borrow_mut();
+    let source = match sources.remove(specifier.as_str()) {
       Some(source) => source,
       None => return ModuleLoadResponse::Sync(Err(anyhow!("Specifier \"{}\" cannot be lazy-loaded as it was not included in the binary.", specifier))),
     };
-    let result = source.load().map(|code| {
-      ModuleSource::new(
-        ModuleType::JavaScript,
-        ModuleSourceCode::String(code),
-        specifier,
-      )
-    });
-    ModuleLoadResponse::Sync(result)
+    ModuleLoadResponse::Sync(Ok(ModuleSource::new(
+      ModuleType::JavaScript,
+      ModuleSourceCode::String(source),
+      specifier,
+    )))
   }
 
   fn prepare_load(
diff --git a/core/modules/map.rs b/core/modules/map.rs
index 7bd9ef1..3dab5aa 100644
--- a/core/modules/map.rs
+++ b/core/modules/map.rs
@@ -26,7 +26,6 @@ use crate::runtime::exception_state::ExceptionState;
 use crate::runtime::JsRealm;
 use crate::runtime::SnapshotLoadDataStore;
 use crate::runtime::SnapshotStoreDataStore;
-use crate::ExtensionFileSource;
 use crate::FastStaticString;
 use crate::JsRuntime;
 use crate::ModuleLoadResponse;
@@ -504,7 +503,7 @@ impl ModuleMap {
         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.as_ref(),
+          name,
           main_name,
         ))));
       }
@@ -1505,21 +1504,17 @@ impl ModuleMap {
     Ok(v8::Global::new(scope, mod_ns))
   }
 
-  pub(crate) fn add_lazy_loaded_esm_sources(
+  pub(crate) fn add_lazy_loaded_esm_source(
     &self,
-    sources: &[ExtensionFileSource],
+    specifier: ModuleName,
+    code: ModuleCodeString,
   ) {
-    if sources.is_empty() {
-      return;
-    }
-
     let data = self.data.borrow_mut();
-    data.lazy_esm_sources.borrow_mut().extend(
-      sources
-        .iter()
-        .cloned()
-        .map(|source| (source.specifier, source)),
-    );
+    assert!(data
+      .lazy_esm_sources
+      .borrow_mut()
+      .insert(specifier, code)
+      .is_none());
   }
 
   /// Lazy load and evaluate an ES module. Only modules that have been added
diff --git a/core/modules/module_map_data.rs b/core/modules/module_map_data.rs
index 6e2c0cf..8b55d44 100644
--- a/core/modules/module_map_data.rs
+++ b/core/modules/module_map_data.rs
@@ -10,7 +10,7 @@ use crate::modules::ModuleType;
 use crate::runtime::SnapshotDataId;
 use crate::runtime::SnapshotLoadDataStore;
 use crate::runtime::SnapshotStoreDataStore;
-use crate::ExtensionFileSource;
+use crate::ModuleCodeString;
 use serde::Deserialize;
 use serde::Serialize;
 use std::cell::RefCell;
@@ -142,7 +142,7 @@ pub(crate) struct ModuleMapData {
   /// to evaluate a "synthetic module".
   pub(crate) synthetic_module_exports_store: SyntheticModuleExportsStore,
   pub(crate) lazy_esm_sources:
-    Rc<RefCell<HashMap<&'static str, ExtensionFileSource>>>,
+    Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
 }
 
 /// Snapshot-compatible representation of this data.
@@ -204,7 +204,7 @@ impl ModuleMapData {
     };
     loop {
       let symbolic_module =
-        map.get(requested_module_type.as_ref(), mod_name.as_ref())?;
+        map.get(requested_module_type.as_ref(), mod_name)?;
       match symbolic_module {
         SymbolicModule::Alias(target) => {
           debug_assert!(mod_name != target);
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
index e68f472..98a2a59 100644
--- a/core/runtime/jsrealm.rs
+++ b/core/runtime/jsrealm.rs
@@ -6,6 +6,7 @@ use super::op_driver::OpDriver;
 use crate::error::exception_to_err_result;
 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;
@@ -266,15 +267,13 @@ impl JsRealm {
   pub fn execute_script(
     &self,
     isolate: &mut v8::Isolate,
-    name: &'static str,
+    name: impl IntoModuleName,
     source_code: impl IntoModuleCodeString,
   ) -> Result<v8::Global<v8::Value>, Error> {
     let scope = &mut self.0.handle_scope(isolate);
 
     let source = source_code.into_module_code().v8_string(scope);
-    debug_assert!(name.is_ascii());
-    let name =
-      v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
+    let name = name.into_module_name().v8_string(scope);
     let origin = bindings::script_origin(scope, name);
 
     let tc_scope = &mut v8::TryCatch::new(scope);
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
index d25a2c8..2406db2 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -18,6 +18,7 @@ use crate::error::AnyError;
 use crate::error::GetErrorClassFn;
 use crate::error::JsError;
 use crate::extension_set;
+use crate::extension_set::LoadedSources;
 use crate::extensions::GlobalObjectMiddlewareFn;
 use crate::extensions::GlobalTemplateMiddlewareFn;
 use crate::include_js_files;
@@ -32,6 +33,7 @@ use crate::modules::IntoModuleName;
 use crate::modules::ModuleId;
 use crate::modules::ModuleLoader;
 use crate::modules::ModuleMap;
+use crate::modules::ModuleName;
 use crate::modules::RequestedModuleType;
 use crate::modules::ValidateImportAttributesCb;
 use crate::ops_metrics::dispatch_metrics_async;
@@ -39,6 +41,7 @@ use crate::ops_metrics::OpMetricsFactoryFn;
 use crate::runtime::ContextState;
 use crate::runtime::JsRealm;
 use crate::runtime::OpDriverImpl;
+use crate::source_map::SourceMapData;
 use crate::source_map::SourceMapGetter;
 use crate::source_map::SourceMapper;
 use crate::stats::RuntimeActivityType;
@@ -46,7 +49,9 @@ use crate::Extension;
 use crate::ExtensionFileSource;
 use crate::ExtensionFileSourceCode;
 use crate::FastStaticString;
+use crate::FastString;
 use crate::FeatureChecker;
+use crate::ModuleCodeString;
 use crate::NoopModuleLoader;
 use crate::OpMetricsEvent;
 use crate::OpState;
@@ -60,6 +65,7 @@ use futures::Future;
 use futures::FutureExt;
 use smallvec::SmallVec;
 use std::any::Any;
+
 use std::cell::Cell;
 use std::cell::RefCell;
 use std::collections::HashMap;
@@ -80,6 +86,12 @@ use v8::Isolate;
 pub type WaitForInspectorDisconnectCallback = Box<dyn Fn()>;
 const STATE_DATA_OFFSET: u32 = 0;
 
+pub type ExtensionTranspiler =
+  dyn Fn(
+    ModuleName,
+    ModuleCodeString,
+  ) -> Result<(ModuleCodeString, Option<SourceMapData>), AnyError>;
+
 /// Objects that need to live as long as the isolate
 #[derive(Default)]
 pub(crate) struct IsolateAllocations {
@@ -415,6 +427,9 @@ pub struct RuntimeOptions {
   /// executed tries to load modules.
   pub module_loader: Option<Rc<dyn ModuleLoader>>,
 
+  /// If specified, transpiles extensions before loading.
+  pub extension_transpiler: Option<Rc<ExtensionTranspiler>>,
+
   /// Provide a function that may optionally provide a metrics collector
   /// for a given op.
   pub op_metrics_factory_fn: Option<OpMetricsFactoryFn>,
@@ -610,36 +625,24 @@ impl JsRuntime {
   ) -> Result<JsRuntime, Error> {
     let init_mode = InitMode::from_options(&options);
     let mut extensions = std::mem::take(&mut options.extensions);
+    let isolate_allocations = IsolateAllocations::default();
 
     // First let's create an `OpState` and contribute to it from extensions...
     let mut op_state = OpState::new(options.feature_checker.take());
     extension_set::setup_op_state(&mut op_state, &mut extensions);
 
-    let mut source_mapper = SourceMapper::new(options.source_map_getter);
-    for extension in &extensions {
-      for esm in extension.get_esm_sources() {
-        if let Some(source_map) = &esm.source_map {
-          source_mapper
-            .ext_source_maps
-            .insert(esm.specifier.to_owned(), source_map.to_owned());
-        }
-      }
-      for js in extension.get_js_sources() {
-        if let Some(source_map) = &js.source_map {
-          source_mapper
-            .ext_source_maps
-            .insert(js.specifier.to_owned(), source_map.to_owned());
-        }
-      }
-      for lazy_loaded_esm in extension.get_lazy_loaded_esm_sources() {
-        if let Some(source_map) = &lazy_loaded_esm.source_map {
-          source_mapper.ext_source_maps.insert(
-            lazy_loaded_esm.specifier.to_owned(),
-            source_map.to_owned(),
-          );
-        }
-      }
-    }
+    // Load the sources and source maps
+    let mut files_loaded = Vec::with_capacity(128);
+    let mut source_mapper: SourceMapper<Rc<dyn SourceMapGetter>> =
+      SourceMapper::new(options.source_map_getter);
+    let sources = extension_set::into_sources(
+      options.extension_transpiler.as_deref(),
+      &extensions,
+      &mut source_mapper,
+      |source| {
+        mark_as_loaded_from_fs_during_snapshot(&mut files_loaded, &source.code)
+      },
+    )?;
 
     // ...now let's set up ` JsRuntimeState`, we'll need to set some fields
     // later, after `JsRuntime` is all set up...
@@ -684,19 +687,21 @@ impl JsRuntime {
       global_object_middlewares,
       additional_references,
     ) = extension_set::get_middlewares_and_external_refs(&mut extensions);
-    let external_refs =
-      bindings::create_external_references(&op_ctxs, &additional_references);
 
     let (maybe_startup_snapshot, sidecar_data) = options
       .startup_snapshot
       .take()
       .map(snapshot::deconstruct)
       .unzip();
+
+    let external_refs_static =
+      bindings::create_external_references(&op_ctxs, &additional_references);
+
     let mut isolate = setup::create_isolate(
       will_snapshot,
       options.create_params.take(),
       maybe_startup_snapshot,
-      external_refs,
+      external_refs_static,
     );
 
     let cpp_heap = setup::init_cppgc(&mut isolate);
@@ -848,13 +853,11 @@ impl JsRuntime {
         v8_isolate: ManuallyDrop::new(isolate),
         cpp_heap: ManuallyDrop::new(cpp_heap),
       },
-      allocations: IsolateAllocations::default(),
+      allocations: isolate_allocations,
       files_loaded_from_fs_during_snapshot: vec![],
       is_main_runtime: options.is_main,
     };
 
-    let mut files_loaded = Vec::with_capacity(128);
-
     // ...we're almost done with the setup, all that's left is to execute
     // internal JS and then execute code provided by extensions...
     {
@@ -886,12 +889,7 @@ impl JsRuntime {
 
       js_runtime.store_js_callbacks(&realm, will_snapshot);
 
-      js_runtime.init_extension_js(
-        &realm,
-        &module_map,
-        extensions,
-        &mut files_loaded,
-      )?;
+      js_runtime.init_extension_js(&realm, &module_map, sources)?;
     }
 
     if will_snapshot {
@@ -1053,56 +1051,44 @@ impl JsRuntime {
     &mut self,
     realm: &JsRealm,
     module_map: &Rc<ModuleMap>,
-    extensions: Vec<Extension>,
-    files_loaded: &mut Vec<&'static str>,
+    loaded_sources: LoadedSources,
   ) -> Result<(), Error> {
-    // Initialization of JS happens in phases:
-    // 1. Iterate through all extensions:
-    //  a. Execute all extension "script" JS files
-    //  b. Load all extension "module" JS files (but do not execute them yet)
-    //  c. If extension has an "entrypoint" collect it
-    // 2. Execute all collected entrypoints.
-    // 3. Verify that all extension files have been executed.
-
-    let mut esm_entrypoints = vec![];
-
-    for extension in extensions {
-      // If the extension provides "lazy loaded ES modules" then store them
-      // on the ModuleMap.
-      module_map
-        .add_lazy_loaded_esm_sources(extension.get_lazy_loaded_esm_sources());
-
-      let maybe_esm_entry_point = extension.get_esm_entry_point();
-
-      for file_source in extension.get_esm_sources() {
-        mark_as_loaded_from_fs_during_snapshot(files_loaded, &file_source.code);
-        realm
-          .load_side_es_module_from_code(
-            self.v8_isolate(),
-            &ModuleSpecifier::parse(file_source.specifier)?,
-            None,
-          )
-          .await?;
-      }
+    // First, add all the lazy ESM
+    for source in loaded_sources.lazy_esm {
+      module_map.add_lazy_loaded_esm_source(source.specifier, source.code);
+    }
 
-      if let Some(entry_point) = maybe_esm_entry_point {
-        esm_entrypoints.push(entry_point);
-      }
+    // Temporarily override the loader of the `ModuleMap` so we can load
+    // extension code.
 
-      for file_source in extension.get_js_sources() {
-        mark_as_loaded_from_fs_during_snapshot(files_loaded, &file_source.code);
-        realm.execute_script(
-          self.v8_isolate(),
-          file_source.specifier,
-          file_source.load()?,
-        )?;
-      }
+    // TODO(bartlomieju): maybe this should be a method on the `ModuleMap`,
+    // instead of explicitly changing the `.loader` field?
+    let loader = module_map.loader.borrow().clone();
+    let mut modules = Vec::with_capacity(loaded_sources.esm.len());
+    let mut sources = Vec::with_capacity(loaded_sources.esm.len());
+    for esm in loaded_sources.esm {
+      modules.push(ModuleSpecifier::parse(&esm.specifier).unwrap());
+      sources.push((esm.specifier, esm.code));
+    }
+    let ext_loader = Rc::new(ExtModuleLoader::new(sources)?);
+    *module_map.loader.borrow_mut() = ext_loader.clone();
+
+    // Next, load the extension modules as side modules (but do not execute them)
+    for module in modules {
+      realm
+        .load_side_es_module_from_code(self.v8_isolate(), &module, None)
+        .await?;
+    }
+
+    // Execute extension scripts
+    for source in loaded_sources.js {
+      realm.execute_script(self.v8_isolate(), source.specifier, source.code)?;
     }
 
     // ...then execute all entry points
-    for specifier in esm_entrypoints {
+    for specifier in loaded_sources.esm_entry_points {
       let Some(mod_id) =
-        module_map.get_id(specifier, RequestedModuleType::None)
+        module_map.get_id(&specifier, RequestedModuleType::None)
       else {
         bail!("{} not present in the module map", specifier);
       };
@@ -1112,6 +1098,19 @@ impl JsRuntime {
       module_map.mod_evaluate_sync(scope, mod_id)?;
     }
 
+    #[cfg(debug_assertions)]
+    {
+      let mut scope = realm.handle_scope(self.v8_isolate());
+      module_map.check_all_modules_evaluated(&mut scope)?;
+    }
+
+    let module_map = realm.0.module_map();
+    *module_map.loader.borrow_mut() = loader;
+    Rc::try_unwrap(ext_loader)
+      .map_err(drop)
+      .unwrap()
+      .finalize()?;
+
     Ok(())
   }
 
@@ -1120,38 +1119,14 @@ impl JsRuntime {
     &mut self,
     realm: &JsRealm,
     module_map: &Rc<ModuleMap>,
-    extensions: Vec<Extension>,
-    files_loaded: &mut Vec<&'static str>,
+    loaded_sources: LoadedSources,
   ) -> Result<(), Error> {
-    // Temporarily override the loader of the `ModuleMap` so we can load
-    // extension code.
-
-    // TODO(bartlomieju): maybe this should be a method on the `ModuleMap`,
-    // instead of explicitly changing the `.loader` field?
-    let loader = module_map.loader.borrow().clone();
-    let ext_loader = Rc::new(ExtModuleLoader::new(&extensions)?);
-    *module_map.loader.borrow_mut() = ext_loader.clone();
-
     futures::executor::block_on(self.init_extension_js_inner(
       realm,
       module_map,
-      extensions,
-      files_loaded,
+      loaded_sources,
     ))?;
 
-    #[cfg(debug_assertions)]
-    {
-      let mut scope = realm.handle_scope(self.v8_isolate());
-      module_map.check_all_modules_evaluated(&mut scope)?;
-    }
-
-    let module_map = realm.0.module_map();
-    *module_map.loader.borrow_mut() = loader;
-    Rc::try_unwrap(ext_loader)
-      .map_err(drop)
-      .unwrap()
-      .finalize()?;
-
     Ok(())
   }
 
@@ -1273,7 +1248,7 @@ impl JsRuntime {
     let isolate = &mut self.inner.v8_isolate;
     self.inner.main_realm.execute_script(
       isolate,
-      name,
+      FastString::from_static(name),
       source_code.into_module_code(),
     )
   }
@@ -1841,7 +1816,6 @@ impl JsRuntimeForSnapshot {
   pub fn snapshot(mut self) -> Box<[u8]> {
     // Ensure there are no live inspectors to prevent crashes.
     self.inner.prepare_for_cleanup();
-
     let realm = JsRealm::clone(&self.inner.main_realm);
 
     // Set the context to be snapshot's default context
@@ -1876,7 +1850,7 @@ impl JsRuntimeForSnapshot {
         .source_mapper
         .borrow()
         .ext_source_maps
-        .to_owned();
+        .clone();
 
       let snapshotted_data = SnapshottedData {
         module_map_data,
diff --git a/core/runtime/mod.rs b/core/runtime/mod.rs
index 0779ebb..4ef8412 100644
--- a/core/runtime/mod.rs
+++ b/core/runtime/mod.rs
@@ -24,6 +24,7 @@ pub(crate) use jsrealm::OpDriverImpl;
 pub use jsruntime::CompiledWasmModuleStore;
 pub use jsruntime::CreateRealmOptions;
 pub use jsruntime::CrossIsolateStore;
+pub use jsruntime::ExtensionTranspiler;
 pub(crate) use jsruntime::InitMode;
 pub use jsruntime::JsRuntime;
 pub use jsruntime::JsRuntimeForSnapshot;
diff --git a/core/runtime/snapshot.rs b/core/runtime/snapshot.rs
index 97ff65e..14f6662 100644
--- a/core/runtime/snapshot.rs
+++ b/core/runtime/snapshot.rs
@@ -3,9 +3,11 @@
 use anyhow::Error;
 use serde::Deserialize;
 use serde::Serialize;
+use std::borrow::Cow;
 use std::collections::HashMap;
 use std::path::Path;
 use std::path::PathBuf;
+use std::rc::Rc;
 use std::time::Instant;
 
 use crate::modules::ModuleMapSnapshotData;
@@ -13,6 +15,8 @@ use crate::Extension;
 use crate::JsRuntimeForSnapshot;
 use crate::RuntimeOptions;
 
+use super::ExtensionTranspiler;
+
 pub type WithRuntimeCb = dyn Fn(&mut JsRuntimeForSnapshot);
 
 pub type SnapshotDataId = u32;
@@ -110,6 +114,7 @@ pub struct CreateSnapshotOptions {
   pub startup_snapshot: Option<&'static [u8]>,
   pub skip_op_registration: bool,
   pub extensions: Vec<Extension>,
+  pub extension_transpiler: Option<Rc<ExtensionTranspiler>>,
   pub with_runtime_cb: Option<Box<WithRuntimeCb>>,
 }
 
@@ -140,6 +145,7 @@ pub fn create_snapshot(
   let mut js_runtime = JsRuntimeForSnapshot::new(RuntimeOptions {
     startup_snapshot: create_snapshot_options.startup_snapshot,
     extensions: create_snapshot_options.extensions,
+    extension_transpiler: create_snapshot_options.extension_transpiler,
     skip_op_registration: create_snapshot_options.skip_op_registration,
     ..Default::default()
   });
@@ -224,58 +230,38 @@ pub fn get_js_files(
   js_files
 }
 
-pub(crate) struct SnapshottedData {
+pub(crate) struct SnapshottedData<'snapshot> {
   pub module_map_data: ModuleMapSnapshotData,
   pub js_handled_promise_rejection_cb: Option<v8::Global<v8::Function>>,
-  pub ext_source_maps: HashMap<String, Vec<u8>>,
+  pub ext_source_maps: HashMap<String, Cow<'snapshot, [u8]>>,
 }
 
 /// Snapshot sidecar data that is safe to serialize via serde.
 #[derive(Serialize, Deserialize)]
-pub(crate) struct SerializableSnapshotSidecarData {
+pub(crate) struct SerializableSnapshotSidecarData<'snapshot> {
   data_count: u32,
   module_map_data: ModuleMapSnapshotData,
   js_handled_promise_rejection_cb: Option<SnapshotDataId>,
-  ext_source_maps: HashMap<String, Vec<u8>>,
+  ext_source_maps: HashMap<String, Cow<'snapshot, [u8]>>,
 }
 
-impl SerializableSnapshotSidecarData {
-  fn from_slice(slice: &[u8]) -> Self {
-    #[cfg(all(
-      feature = "snapshot_data_json",
-      not(feature = "snapshot_data_bincode")
-    ))]
-    let raw_data: SerializableSnapshotSidecarData =
-      serde_json::from_slice(slice)
-        .expect("Failed to deserialize snapshot data");
-
-    #[cfg(feature = "snapshot_data_bincode")]
-    let raw_data: SerializableSnapshotSidecarData =
-      bincode::deserialize(slice).expect("Failed to deserialize snapshot data");
-
-    raw_data
+impl<'snapshot> SerializableSnapshotSidecarData<'snapshot> {
+  fn from_slice(slice: &'snapshot [u8]) -> Self {
+    bincode::deserialize(slice).expect("Failed to deserialize snapshot data")
   }
 
   fn into_bytes(self) -> Box<[u8]> {
-    #[cfg(all(
-      feature = "snapshot_data_json",
-      not(feature = "snapshot_data_bincode")
-    ))]
-    let local_data = serde_json::to_vec(&self).unwrap();
-    #[cfg(feature = "snapshot_data_bincode")]
-    let local_data = bincode::serialize(&self).unwrap();
-
-    local_data.into_boxed_slice()
+    bincode::serialize(&self).unwrap().into_boxed_slice()
   }
 }
 
 /// Given the sidecar data and a scope to extract data from, reconstructs the
 /// `SnapshottedData` and `SnapshotLoadDataStore`.
-pub(crate) fn load_snapshotted_data_from_snapshot(
+pub(crate) fn load_snapshotted_data_from_snapshot<'snapshot>(
   scope: &mut v8::HandleScope<()>,
   context: v8::Local<v8::Context>,
-  raw_data: SerializableSnapshotSidecarData,
-) -> (SnapshottedData, SnapshotLoadDataStore) {
+  raw_data: SerializableSnapshotSidecarData<'snapshot>,
+) -> (SnapshottedData<'snapshot>, SnapshotLoadDataStore) {
   let scope = &mut v8::ContextScope::new(scope, context);
   let mut data = SnapshotLoadDataStore::default();
   for i in 0..raw_data.data_count {
@@ -300,12 +286,12 @@ pub(crate) fn load_snapshotted_data_from_snapshot(
 
 /// Given a `SnapshottedData` and `SnapshotStoreDataStore`, attaches the data to the
 /// context and returns the serialized sidecar data.
-pub(crate) fn store_snapshotted_data_for_snapshot(
+pub(crate) fn store_snapshotted_data_for_snapshot<'snapshot>(
   scope: &mut v8::HandleScope,
   context: v8::Global<v8::Context>,
-  snapshotted_data: SnapshottedData,
+  snapshotted_data: SnapshottedData<'snapshot>,
   mut data_store: SnapshotStoreDataStore,
-) -> SerializableSnapshotSidecarData {
+) -> SerializableSnapshotSidecarData<'snapshot> {
   let context = v8::Local::new(scope, context);
   let js_handled_promise_rejection_cb = snapshotted_data
     .js_handled_promise_rejection_cb
@@ -317,7 +303,7 @@ pub(crate) fn store_snapshotted_data_for_snapshot(
     ext_source_maps: snapshotted_data.ext_source_maps,
   };
 
-  for data in data_store.data.drain(..) {
+  for data in data_store.data {
     let data = v8::Local::new(scope, data);
     scope.add_context_data(context, data);
   }
diff --git a/core/source_map.rs b/core/source_map.rs
index a15022a..396e213 100644
--- a/core/source_map.rs
+++ b/core/source_map.rs
@@ -4,6 +4,7 @@
 
 use crate::resolve_url;
 pub use sourcemap::SourceMap;
+use std::borrow::Cow;
 use std::collections::HashMap;
 use std::rc::Rc;
 use std::str;
@@ -51,11 +52,13 @@ pub enum SourceMapApplication {
   },
 }
 
+pub type SourceMapData = Cow<'static, [u8]>;
+
 pub struct SourceMapper<G: SourceMapGetter> {
   maps: HashMap<String, Option<SourceMap>>,
   source_lines: HashMap<(String, i64), Option<String>>,
   getter: Option<G>,
-  pub(crate) ext_source_maps: HashMap<String, Vec<u8>>,
+  pub(crate) ext_source_maps: HashMap<String, SourceMapData>,
   // This is not the right place for this, but it's the easiest way to make
   // op_apply_source_map a fast op. This stashing should happen in #[op2].
   pub(crate) stashed_file_name: Option<String>,


Test patch:
diff --git a/core/runtime/tests/snapshot.rs b/core/runtime/tests/snapshot.rs
index fda36a8..cfc618a 100644
--- a/core/runtime/tests/snapshot.rs
+++ b/core/runtime/tests/snapshot.rs
@@ -127,6 +127,7 @@ fn test_snapshot_creator() {
       cargo_manifest_dir: "",
       startup_snapshot: None,
       skip_op_registration: false,
+      extension_transpiler: None,
       extensions: vec![],
       with_runtime_cb: Some(Box::new(|runtime| {
         runtime.execute_script("a.js", "a = 1 + 2").unwrap();
@@ -158,6 +159,7 @@ fn test_snapshot_creator_warmup() {
       startup_snapshot: None,
       skip_op_registration: false,
       extensions: vec![],
+      extension_transpiler: None,
       with_runtime_cb: Some(Box::new(move |runtime| {
         c.replace_with(|&mut c| c + 1);
 
diff --git a/testing/checkin/runner/mod.rs b/testing/checkin/runner/mod.rs
index 4072eb1..580957a 100644
--- a/testing/checkin/runner/mod.rs
+++ b/testing/checkin/runner/mod.rs
@@ -25,6 +25,7 @@ use self::ops_worker::worker_create;
 use self::ops_worker::WorkerCloseWatcher;
 use self::ops_worker::WorkerHostSide;
 use self::testing::TestFunctions;
+use self::ts_module_loader::maybe_transpile_source;
 
 mod ops;
 mod ops_async;
@@ -89,20 +90,13 @@ fn create_runtime(
   parent: Option<WorkerCloseWatcher>,
 ) -> (JsRuntime, WorkerHostSide) {
   let (worker, worker_host_side) = worker_create(parent);
-  let mut extensions_for_snapshot = vec![checkin_runtime::init_ops_and_esm()];
-
-  for extension in &mut extensions_for_snapshot {
-    use ts_module_loader::maybe_transpile_source;
-    for source in extension.esm_files.to_mut() {
-      maybe_transpile_source(source).unwrap();
-    }
-    for source in extension.js_files.to_mut() {
-      maybe_transpile_source(source).unwrap();
-    }
-  }
+  let extensions_for_snapshot = vec![checkin_runtime::init_ops_and_esm()];
 
   let runtime_for_snapshot = JsRuntimeForSnapshot::new(RuntimeOptions {
     extensions: extensions_for_snapshot,
+    extension_transpiler: Some(Rc::new(|specifier, source| {
+      maybe_transpile_source(specifier, source)
+    })),
     ..Default::default()
   });
 
diff --git a/testing/checkin/runner/ts_module_loader.rs b/testing/checkin/runner/ts_module_loader.rs
index b4e2a57..b67c7d2 100644
--- a/testing/checkin/runner/ts_module_loader.rs
+++ b/testing/checkin/runner/ts_module_loader.rs
@@ -12,16 +12,17 @@ use deno_ast::ParseParams;
 use deno_ast::SourceTextInfo;
 use deno_core::error::AnyError;
 use deno_core::resolve_import;
-use deno_core::ExtensionFileSource;
-use deno_core::ExtensionFileSourceCode;
+use deno_core::ModuleCodeString;
 use deno_core::ModuleLoadResponse;
 use deno_core::ModuleLoader;
+use deno_core::ModuleName;
 use deno_core::ModuleSource;
 use deno_core::ModuleSourceCode;
 use deno_core::ModuleSpecifier;
 use deno_core::ModuleType;
 use deno_core::RequestedModuleType;
 use deno_core::ResolutionKind;
+use deno_core::SourceMapData;
 use deno_core::SourceMapGetter;
 
 #[derive(Clone, Default)]
@@ -131,29 +132,29 @@ impl ModuleLoader for TypescriptModuleLoader {
 }
 
 pub fn maybe_transpile_source(
-  source: &mut ExtensionFileSource,
-) -> Result<(), AnyError> {
+  specifier: ModuleName,
+  source: ModuleCodeString,
+) -> Result<(ModuleCodeString, Option<SourceMapData>), AnyError> {
   // Always transpile `checkin:` built-in modules, since they might be TypeScript.
-  let media_type = if source.specifier.starts_with("checkin:") {
+  let media_type = if specifier.starts_with("checkin:") {
     MediaType::TypeScript
   } else {
-    MediaType::from_path(Path::new(&source.specifier))
+    MediaType::from_path(Path::new(&specifier))
   };
 
   match media_type {
     MediaType::TypeScript => {}
-    MediaType::JavaScript => return Ok(()),
-    MediaType::Mjs => return Ok(()),
+    MediaType::JavaScript => return Ok((source, None)),
+    MediaType::Mjs => return Ok((source, None)),
     _ => panic!(
       "Unsupported media type for snapshotting {media_type:?} for file {}",
-      source.specifier
+      specifier
     ),
   }
-  let code = source.load()?;
 
   let parsed = deno_ast::parse_module(ParseParams {
-    specifier: source.specifier.to_string(),
-    text_info: SourceTextInfo::from_string(code.as_str().to_owned()),
+    specifier: specifier.to_string(),
+    text_info: SourceTextInfo::from_string(source.as_str().to_owned()),
     media_type,
     capture_tokens: false,
     scope_analysis: false,
@@ -167,8 +168,8 @@ pub fn maybe_transpile_source(
     ..Default::default()
   })?;
 
-  source.code =
-    ExtensionFileSourceCode::Computed(transpiled_source.text.into());
-  source.source_map = Some(transpiled_source.source_map.unwrap().into_bytes());
-  Ok(())
+  Ok((
+    transpiled_source.text.into(),
+    transpiled_source.source_map.map(|s| s.into_bytes().into()),
+  ))
 }