rspack_plugin_library/
amd_library_plugin.rs1use std::hash::Hash;
2
3use rspack_core::{
4 ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements, CompilationParams,
5 CompilerCompilation, ExternalModule, Filename, LibraryName, LibraryNonUmdObject, LibraryOptions,
6 LibraryType, PathData, Plugin, RuntimeCodeTemplate, RuntimeGlobals, RuntimeModule, SourceType,
7 rspack_sources::{ConcatSource, RawStringSource, SourceExt},
8};
9use rspack_error::{Result, error_bail};
10use rspack_hash::RspackHash;
11use rspack_hook::{plugin, plugin_hook};
12use rspack_plugin_javascript::{
13 JavascriptModulesChunkHash, JavascriptModulesRender, JsPlugin, RenderSource,
14};
15
16use crate::utils::{
17 COMMON_LIBRARY_NAME_MESSAGE, external_arguments, externals_dep_array, get_options_for_chunk,
18};
19
20const PLUGIN_NAME: &str = "rspack.AmdLibraryPlugin";
21
22#[derive(Debug)]
23struct AmdLibraryPluginParsed<'a> {
24 name: Option<&'a str>,
25 amd_container: Option<&'a str>,
26}
27
28#[plugin]
29#[derive(Debug)]
30pub struct AmdLibraryPlugin {
31 require_as_wrapper: bool,
32 library_type: LibraryType,
33}
34
35impl AmdLibraryPlugin {
36 pub fn new(require_as_wrapper: bool, library_type: LibraryType) -> Self {
37 Self::new_inner(require_as_wrapper, library_type)
38 }
39
40 fn parse_options<'a>(&self, library: &'a LibraryOptions) -> Result<AmdLibraryPluginParsed<'a>> {
41 if self.require_as_wrapper {
42 if library.name.is_some() {
43 error_bail!("AMD library name must be unset. {COMMON_LIBRARY_NAME_MESSAGE}")
44 }
45 } else if let Some(name) = &library.name
46 && !matches!(
47 name,
48 LibraryName::NonUmdObject(LibraryNonUmdObject::String(_))
49 )
50 {
51 error_bail!(
52 "AMD library name must be a simple string or unset. {COMMON_LIBRARY_NAME_MESSAGE}"
53 )
54 }
55 Ok(AmdLibraryPluginParsed {
56 name: library.name.as_ref().map(|name| match name {
57 LibraryName::NonUmdObject(LibraryNonUmdObject::String(s)) => s.as_str(),
58 _ => unreachable!("AMD library name must be a simple string or unset."),
59 }),
60 amd_container: library.amd_container.as_deref(),
61 })
62 }
63
64 fn get_options_for_chunk<'a>(
65 &self,
66 compilation: &'a Compilation,
67 chunk_ukey: &'a ChunkUkey,
68 ) -> Result<Option<AmdLibraryPluginParsed<'a>>> {
69 get_options_for_chunk(compilation, chunk_ukey)
70 .filter(|library| library.library_type == self.library_type)
71 .map(|library| self.parse_options(library))
72 .transpose()
73 }
74}
75
76#[plugin_hook(CompilerCompilation for AmdLibraryPlugin)]
77async fn compilation(
78 &self,
79 compilation: &mut Compilation,
80 _params: &mut CompilationParams,
81) -> Result<()> {
82 let hooks = JsPlugin::get_compilation_hooks_mut(compilation.id());
83 let mut hooks = hooks.write().await;
84 hooks.render.tap(render::new(self));
85 hooks.chunk_hash.tap(js_chunk_hash::new(self));
86 Ok(())
87}
88
89#[plugin_hook(JavascriptModulesRender for AmdLibraryPlugin)]
90async fn render(
91 &self,
92 compilation: &Compilation,
93 chunk_ukey: &ChunkUkey,
94 render_source: &mut RenderSource,
95 _runtime_template: &RuntimeCodeTemplate<'_>,
96) -> Result<()> {
97 let Some(options) = self.get_options_for_chunk(compilation, chunk_ukey)? else {
98 return Ok(());
99 };
100 let chunk = compilation
101 .build_chunk_graph_artifact
102 .chunk_by_ukey
103 .expect_get(chunk_ukey);
104 let module_graph = compilation.get_module_graph();
105 let modules = compilation
106 .build_chunk_graph_artifact
107 .chunk_graph
108 .get_chunk_modules_identifier(chunk_ukey)
109 .iter()
110 .filter_map(|identifier| {
111 module_graph
112 .module_by_identifier(identifier)
113 .and_then(|module| module.as_external_module())
114 .and_then(|m| {
115 let ty = m.get_external_type();
116 (ty == "amd" || ty == "amd-require").then_some(m)
117 })
118 })
119 .collect::<Vec<&ExternalModule>>();
120 let externals_deps_array = externals_dep_array(&modules)?;
121 let external_arguments = external_arguments(&modules, compilation);
122 let mut fn_start = format!("function({external_arguments}){{\n");
123 if compilation.options.output.iife
124 || !chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey)
125 {
126 fn_start.push_str(" return ");
127 }
128 let mut source = ConcatSource::default();
129 let amd_container_prefix = options
130 .amd_container
131 .map(|c| format!("{c}."))
132 .unwrap_or_default();
133 if self.require_as_wrapper {
134 source.add(RawStringSource::from(format!(
135 "{amd_container_prefix}require({externals_deps_array}, {fn_start}"
136 )));
137 } else if let Some(name) = options.name {
138 let normalize_name = compilation
139 .get_path(
140 &Filename::from(name),
141 PathData::default()
142 .chunk_id_optional(chunk.id().map(|id| id.as_str()))
143 .chunk_hash_optional(chunk.rendered_hash(
144 &compilation.chunk_hashes_artifact,
145 compilation.options.output.hash_digest_length,
146 ))
147 .chunk_name_optional(chunk.name_for_filename_template())
148 .content_hash_optional(chunk.rendered_content_hash_by_source_type(
149 &compilation.chunk_hashes_artifact,
150 &SourceType::JavaScript,
151 compilation.options.output.hash_digest_length,
152 )),
153 )
154 .await?;
155 source.add(RawStringSource::from(format!(
156 "{amd_container_prefix}define('{normalize_name}', {externals_deps_array}, {fn_start}"
157 )));
158 } else if modules.is_empty() {
159 source.add(RawStringSource::from(format!(
160 "{amd_container_prefix}define({fn_start}"
161 )));
162 } else {
163 source.add(RawStringSource::from(format!(
164 "{amd_container_prefix}define({externals_deps_array}, {fn_start}"
165 )));
166 }
167 source.add(render_source.source.clone());
168 source.add(RawStringSource::from_static("\n})"));
169 render_source.source = source.boxed();
170 Ok(())
171}
172
173#[plugin_hook(JavascriptModulesChunkHash for AmdLibraryPlugin)]
174async fn js_chunk_hash(
175 &self,
176 compilation: &Compilation,
177 chunk_ukey: &ChunkUkey,
178 hasher: &mut RspackHash,
179) -> Result<()> {
180 let Some(options) = self.get_options_for_chunk(compilation, chunk_ukey)? else {
181 return Ok(());
182 };
183 PLUGIN_NAME.hash(hasher);
184 if self.require_as_wrapper {
185 self.require_as_wrapper.hash(hasher);
186 } else if let Some(name) = options.name {
187 "named".hash(hasher);
188 name.hash(hasher);
189 } else if let Some(amd_container) = options.amd_container {
190 "amdContainer".hash(hasher);
191 amd_container.hash(hasher);
192 }
193 Ok(())
194}
195
196#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for AmdLibraryPlugin)]
197async fn additional_chunk_runtime_requirements(
198 &self,
199 compilation: &Compilation,
200 chunk_ukey: &ChunkUkey,
201 runtime_requirements: &mut RuntimeGlobals,
202 _runtime_modules: &mut Vec<Box<dyn RuntimeModule>>,
203) -> Result<()> {
204 if self
205 .get_options_for_chunk(compilation, chunk_ukey)?
206 .is_none()
207 {
208 return Ok(());
209 }
210 runtime_requirements.insert(RuntimeGlobals::RETURN_EXPORTS_FROM_RUNTIME);
211 Ok(())
212}
213
214impl Plugin for AmdLibraryPlugin {
215 fn name(&self) -> &'static str {
216 PLUGIN_NAME
217 }
218
219 fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
220 ctx.compiler_hooks.compilation.tap(compilation::new(self));
221 ctx
222 .compilation_hooks
223 .additional_chunk_runtime_requirements
224 .tap(additional_chunk_runtime_requirements::new(self));
225 Ok(())
226 }
227}