diff --git a/core/extensions.rs b/core/extensions.rs
index e38d11fc98..54d6480711 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -1,7 +1,8 @@
 use crate::error::AnyError;
 use crate::{OpFn, OpState};
 
-pub type SourcePair = (&'static str, &'static str);
+pub type SourcePair = (&'static str, Box<SourceLoadFn>);
+pub type SourceLoadFn = dyn Fn() -> Result<String, AnyError>;
 pub type OpPair = (&'static str, Box<OpFn>);
 pub type OpMiddlewareFn = dyn Fn(&'static str, Box<OpFn>) -> Box<OpFn>;
 pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), AnyError>;
@@ -24,10 +25,10 @@ impl Extension {
 
   /// returns JS source code to be loaded into the isolate (either at snapshotting,
   /// or at startup).  as a vector of a tuple of the file name, and the source code.
-  pub fn init_js(&self) -> Vec<SourcePair> {
+  pub fn init_js(&self) -> &[SourcePair] {
     match &self.js_files {
-      Some(files) => files.clone(),
-      None => vec![],
+      Some(files) => files,
+      None => &[],
     }
   }
 
@@ -104,8 +105,9 @@ impl ExtensionBuilder {
     }
   }
 }
-/// Helps embed JS files in an extension. Returns Vec<(&'static str, &'static str)>
-/// representing the filename and source code.
+/// Helps embed JS files in an extension. Returns Vec<(&'static str, Box<SourceLoadFn>)>
+/// representing the filename and source code. This is only meant for extensions
+/// that will be snapshotted, as code will be loaded at runtime.
 ///
 /// Example:
 /// ```ignore
@@ -121,7 +123,13 @@ macro_rules! include_js_files {
     vec![
       $((
         concat!($prefix, "/", $file),
-        include_str!($file),
+        Box::new(|| {
+          let c = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+          let path = c.join($file);
+          println!("cargo:rerun-if-changed={}", path.display());
+          let src = std::fs::read_to_string(path)?;
+          Ok(src)
+        }),
       ),)+
     ]
   };
diff --git a/core/runtime.rs b/core/runtime.rs
index 428add8e35..71aad8e0bb 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -385,8 +385,9 @@ impl JsRuntime {
     for m in extensions.iter_mut() {
       let js_files = m.init_js();
       for (filename, source) in js_files {
+        let source = source()?;
         // TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap
-        self.execute(filename, source)?;
+        self.execute(filename, &source)?;
       }
     }
     // Restore extensions
diff --git a/extensions/timers/benches/timers_ops.rs b/extensions/timers/benches/timers_ops.rs
index 18ef103365..e31fe31ace 100644
--- a/extensions/timers/benches/timers_ops.rs
+++ b/extensions/timers/benches/timers_ops.rs
@@ -9,10 +9,12 @@ fn setup() -> Vec<Extension> {
     deno_timers::init::<deno_timers::NoTimersPermission>(),
     Extension::builder()
     .js(vec![
-      ("setup", r#"
-      const { opNow, setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers;
-      Deno.core.setMacrotaskCallback(handleTimerMacrotask);
-      "#),
+      ("setup",
+        Box::new(|| Ok(r#"
+        const { opNow, setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers;
+        Deno.core.setMacrotaskCallback(handleTimerMacrotask);
+        "#.to_owned())),
+      ),
     ])
     .state(|state| {
       state.put(deno_timers::NoTimersPermission{});
diff --git a/extensions/url/benches/url_ops.rs b/extensions/url/benches/url_ops.rs
index 252c1bfd62..c390af0d87 100644
--- a/extensions/url/benches/url_ops.rs
+++ b/extensions/url/benches/url_ops.rs
@@ -10,7 +10,9 @@ fn setup() -> Vec<Extension> {
     Extension::builder()
       .js(vec![(
         "setup",
-        "const { URL } = globalThis.__bootstrap.url;",
+        Box::new(|| {
+          Ok(r#"const { URL } = globalThis.__bootstrap.url;"#.to_owned())
+        }),
       )])
       .build(),
   ]