1use std::{fmt, path::PathBuf, str::FromStr};
2
3use futures::future::BoxFuture;
4use rspack_core::{Compilation, PublicPath};
5use rspack_error::Result;
6use rspack_util::fx_hash::FxHashMap;
7use serde::Serialize;
8use sugar_path::SugarPath;
9
10#[derive(Serialize, Debug, Clone, Copy, Default)]
11#[serde(rename_all = "snake_case")]
12pub enum HtmlInject {
13 #[default]
14 Head,
15 Body,
16 False,
17}
18
19impl fmt::Display for HtmlInject {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.write_str(match self {
22 HtmlInject::Head => "head",
23 HtmlInject::Body => "body",
24 HtmlInject::False => "false",
25 })
26 }
27}
28
29impl FromStr for HtmlInject {
30 type Err = anyhow::Error;
31
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 if s.eq("head") {
34 Ok(HtmlInject::Head)
35 } else if s.eq("body") {
36 Ok(HtmlInject::Body)
37 } else if s.eq("false") {
38 Ok(HtmlInject::False)
39 } else {
40 Err(anyhow::Error::msg(
41 "inject in html config only support 'head', 'body', or 'false'",
42 ))
43 }
44 }
45}
46
47#[derive(Serialize, Debug, Clone, Copy)]
48#[serde(rename_all = "snake_case")]
49pub enum HtmlScriptLoading {
50 Blocking,
51 Defer,
52 Module,
53 SystemjsModule,
54}
55
56impl FromStr for HtmlScriptLoading {
57 type Err = anyhow::Error;
58
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
60 if s.eq("blocking") {
61 Ok(HtmlScriptLoading::Blocking)
62 } else if s.eq("defer") {
63 Ok(HtmlScriptLoading::Defer)
64 } else if s.eq("module") {
65 Ok(HtmlScriptLoading::Module)
66 } else if s.eq("systemjs-module") {
67 Ok(HtmlScriptLoading::SystemjsModule)
68 } else {
69 Err(anyhow::Error::msg(
70 "scriptLoading in html config only support 'blocking', 'defer' or 'module'",
71 ))
72 }
73 }
74}
75
76type TemplateParameterTsfn =
77 Box<dyn for<'a> Fn(String) -> BoxFuture<'static, Result<String>> + Sync + Send>;
78
79pub struct TemplateParameterFn {
80 pub inner: TemplateParameterTsfn,
81}
82
83impl std::fmt::Debug for TemplateParameterFn {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("TemplateParameterFn").finish()
86 }
87}
88
89#[derive(Debug)]
90pub enum TemplateParameters {
91 Map(FxHashMap<String, String>),
92 Function(TemplateParameterFn),
93 Disabled,
94}
95
96#[derive(Serialize, Debug)]
97#[serde(rename_all = "camelCase", deny_unknown_fields)]
98pub struct HtmlRspackPluginBaseOptions {
99 pub href: Option<String>,
100 pub target: Option<String>,
101}
102
103type TemplateRenderTsfn =
104 Box<dyn for<'a> Fn(String) -> BoxFuture<'static, Result<String>> + Sync + Send>;
105
106pub struct TemplateRenderFn {
107 pub inner: TemplateRenderTsfn,
108}
109
110impl std::fmt::Debug for TemplateRenderFn {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("TemplateRenderFn").finish()
113 }
114}
115
116#[derive(Serialize, Debug, Clone, Copy, Default)]
117#[serde(rename_all = "snake_case")]
118pub enum HtmlChunkSortMode {
119 #[default]
120 Auto,
121 Manual,
122 }
124
125impl FromStr for HtmlChunkSortMode {
126 type Err = anyhow::Error;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 if s.eq("auto") {
130 Ok(HtmlChunkSortMode::Auto)
131 } else if s.eq("manual") {
132 Ok(HtmlChunkSortMode::Manual)
133 } else {
134 Err(anyhow::Error::msg(
135 "chunksSortMode in html config only support 'auto' or 'manual'",
136 ))
137 }
138 }
139}
140
141#[derive(Serialize, Debug)]
142#[serde(rename_all = "camelCase", deny_unknown_fields)]
143pub struct HtmlRspackPluginOptions {
144 #[serde(default = "default_filename")]
146 pub filename: Vec<String>,
147 pub template: Option<String>,
149 #[serde(skip)]
150 pub template_fn: Option<TemplateRenderFn>,
151 pub template_content: Option<String>,
152 #[serde(skip)]
153 pub template_parameters: TemplateParameters,
154 #[serde(default = "default_inject")]
156 pub inject: HtmlInject,
157 pub public_path: Option<String>,
159 #[serde(default = "default_script_loading")]
161 pub script_loading: HtmlScriptLoading,
162
163 pub chunks: Option<Vec<String>>,
165 pub exclude_chunks: Option<Vec<String>>,
166 pub chunks_sort_mode: HtmlChunkSortMode,
167
168 #[serde(default)]
169 pub minify: Option<bool>,
170 pub title: Option<String>,
171 pub favicon: Option<String>,
172 pub meta: Option<FxHashMap<String, FxHashMap<String, String>>>,
173 pub hash: Option<bool>,
174 pub base: Option<HtmlRspackPluginBaseOptions>,
175 pub uid: Option<u32>,
177}
178
179fn default_filename() -> Vec<String> {
180 vec![String::from("index.html")]
181}
182
183fn default_script_loading() -> HtmlScriptLoading {
184 HtmlScriptLoading::Defer
185}
186
187fn default_inject() -> HtmlInject {
188 HtmlInject::Head
189}
190
191fn default_chunks_sort_mode() -> HtmlChunkSortMode {
192 HtmlChunkSortMode::Auto
193}
194
195impl Default for HtmlRspackPluginOptions {
196 fn default() -> HtmlRspackPluginOptions {
197 HtmlRspackPluginOptions {
198 filename: default_filename(),
199 template: None,
200 template_fn: None,
201 template_content: None,
202 template_parameters: TemplateParameters::Map(Default::default()),
203 inject: default_inject(),
204 public_path: None,
205 script_loading: default_script_loading(),
206 chunks: None,
207 exclude_chunks: None,
208 chunks_sort_mode: default_chunks_sort_mode(),
209 minify: None,
210 title: None,
211 favicon: None,
212 meta: None,
213 hash: None,
214 base: None,
215 uid: None,
216 }
217 }
218}
219
220impl HtmlRspackPluginOptions {
221 pub async fn get_public_path(&self, compilation: &Compilation, filename: &str) -> String {
222 match &self.public_path {
223 Some(p) => PublicPath::ensure_ends_with_slash(p.clone()),
224 None => {
225 compilation
226 .options
227 .output
228 .public_path
229 .render(compilation, filename)
230 .await
231 }
232 }
233 }
234 pub fn get_relative_path(&self, compilation: &Compilation, filename: &str) -> String {
235 let mut file_path = PathBuf::from(filename);
236
237 if file_path.is_absolute() {
238 let context_path = PathBuf::from(compilation.options.context.to_string());
239 file_path = file_path.relative(context_path);
240 }
241
242 file_path.to_string_lossy().to_string()
243 }
244}