use std::ptr::NonNull;
use rspack_core::{
ChunkUkey, Compilation, RuntimeGlobals, RuntimeModule, RuntimeModuleGenerateContext,
RuntimeTemplate, impl_runtime_module,
};
use crate::{
CreateScriptData, RuntimeModuleChunkWrapper, RuntimePlugin, get_chunk_runtime_requirements,
};
#[impl_runtime_module]
#[derive(Debug)]
pub struct LoadScriptRuntimeModule {
unique_name: String,
with_create_script_url: bool,
chunk_ukey: ChunkUkey,
}
impl LoadScriptRuntimeModule {
pub fn new(
runtime_template: &RuntimeTemplate,
unique_name: String,
with_create_script_url: bool,
chunk_ukey: ChunkUkey,
) -> Self {
Self::with_default(
runtime_template,
unique_name,
with_create_script_url,
chunk_ukey,
)
}
}
enum TemplateId {
Raw,
CreateScript,
}
#[async_trait::async_trait]
impl RuntimeModule for LoadScriptRuntimeModule {
fn template(&self) -> Vec<(String, String)> {
vec![
(
self.template_id(TemplateId::Raw),
include_str!("runtime/load_script.ejs").to_string(),
),
(
self.template_id(TemplateId::CreateScript),
include_str!("runtime/load_script_create_script.ejs").to_string(),
),
]
}
async fn generate(
&self,
context: &RuntimeModuleGenerateContext<'_>,
) -> rspack_error::Result<String> {
let compilation = context.compilation;
let runtime_template = context.runtime_template;
let runtime_requirements = get_chunk_runtime_requirements(compilation, &self.chunk_ukey);
let with_fetch_priority = runtime_requirements.contains(RuntimeGlobals::HAS_FETCH_PRIORITY);
let unique_prefix = if self.unique_name.is_empty() {
None
} else {
Some(format!(
r#"var uniqueName = {};"#,
rspack_util::json_stringify_str(&format!("{}:", self.unique_name))
))
};
let create_script_code = runtime_template.render(
&self.template_id(TemplateId::CreateScript),
Some(serde_json::json!({
"_script_type": &compilation.options.output.script_type,
"_unique_prefix": unique_prefix.is_some(),
"_with_fetch_priority": with_fetch_priority,
"_with_create_script_url": self.with_create_script_url,
"_cross_origin": compilation.options.output.cross_origin_loading.to_string(),
"_chunk_load_timeout": compilation.options.output.chunk_load_timeout.saturating_div(1000).to_string(),
})),
)?;
let hooks = RuntimePlugin::get_compilation_hooks(compilation.id());
let chunk_ukey = self.chunk_ukey;
let res = hooks
.borrow()
.create_script
.call(CreateScriptData {
code: create_script_code,
chunk: RuntimeModuleChunkWrapper {
chunk_ukey,
compilation_id: compilation.id(),
compilation: NonNull::from(compilation),
},
})
.await?;
let render_source = runtime_template.render(
&self.template_id(TemplateId::Raw),
Some(serde_json::json!({
"_unique_prefix": unique_prefix.unwrap_or_default(),
"_create_script": res.code,
"_chunk_load_timeout": compilation.options.output.chunk_load_timeout.to_string(),
"_fetch_priority": if with_fetch_priority { ", fetchPriority" } else { "" },
})),
)?;
Ok(render_source)
}
fn additional_runtime_requirements(&self, compilation: &Compilation) -> RuntimeGlobals {
if compilation.options.output.trusted_types.is_some() {
RuntimeGlobals::CREATE_SCRIPT_URL
} else {
RuntimeGlobals::default()
}
}
}
impl LoadScriptRuntimeModule {
fn template_id(&self, id: TemplateId) -> String {
let base_id = self.id.to_string();
match id {
TemplateId::Raw => base_id,
TemplateId::CreateScript => format!("{base_id}_create_script"),
}
}
}