rspack_plugin_javascript/parser_plugin/
hot_module_replacement_plugin.rs1use rspack_core::{BoxDependency, DependencyRange, SpanExt};
2use swc_core::{
3 common::{Span, Spanned},
4 ecma::{ast::CallExpr, atoms::Atom},
5};
6
7use crate::{
8 dependency::{
9 ESMAcceptDependency, ImportMetaHotAcceptDependency, ImportMetaHotDeclineDependency,
10 ModuleArgumentDependency, ModuleHotAcceptDependency, ModuleHotDeclineDependency,
11 import_emitted_runtime,
12 },
13 parser_plugin::JavascriptParserPlugin,
14 utils::eval,
15 visitors::{JavascriptParser, expr_name},
16};
17
18type CreateDependency = fn(Atom, DependencyRange) -> BoxDependency;
19
20fn extract_deps(
21 parser: &mut JavascriptParser,
22 call_expr: &CallExpr,
23 create_dependency: CreateDependency,
24) -> Vec<BoxDependency> {
25 let mut dependencies: Vec<BoxDependency> = vec![];
26
27 if let Some(first_arg) = call_expr.args.first() {
28 let expr = parser.evaluate_expression(&first_arg.expr);
29 if expr.is_string() {
30 dependencies.push(create_dependency(
31 expr.string().as_str().into(),
32 (expr.range().0, expr.range().1 - 1).into(),
33 ));
34 } else if expr.is_array() {
35 expr
36 .items()
37 .iter()
38 .filter(|item| item.is_string())
39 .for_each(|expr| {
40 dependencies.push(create_dependency(
41 expr.string().as_str().into(),
42 (expr.range().0, expr.range().1 - 1).into(),
43 ));
44 });
45 }
46 }
47
48 dependencies
49}
50
51impl JavascriptParser<'_> {
52 fn create_hmr_expression_handler(&mut self, span: Span) {
53 self.build_info.module_concatenation_bailout = Some(String::from("Hot Module Replacement"));
54 self
55 .presentational_dependencies
56 .push(Box::new(ModuleArgumentDependency::new(
57 Some("hot".into()),
58 span.into(),
59 Some(self.source_map.clone()),
60 )));
61 }
62
63 fn create_accept_handler(
64 &mut self,
65 call_expr: &CallExpr,
66 create_dependency: CreateDependency,
67 ) -> Option<bool> {
68 self.build_info.module_concatenation_bailout = Some(String::from("Hot Module Replacement"));
69 self
70 .presentational_dependencies
71 .push(Box::new(ModuleArgumentDependency::new(
72 Some("hot.accept".into()),
73 call_expr.callee.span().into(),
74 Some(self.source_map.clone()),
75 )));
76 let dependencies = extract_deps(self, call_expr, create_dependency);
77 if !dependencies.is_empty() {
78 let dependency_ids = dependencies.iter().map(|dep| *dep.id()).collect::<Vec<_>>();
79 let callback_arg = call_expr.args.get(1);
80 let range = if let Some(callback) = callback_arg {
81 Into::<DependencyRange>::into(callback.span())
82 } else {
83 DependencyRange::new(call_expr.span().real_hi() - 1, 0)
84 };
85 self
86 .presentational_dependencies
87 .push(Box::new(ESMAcceptDependency::new(
88 range,
89 callback_arg.is_some(),
90 dependency_ids,
91 Some(self.source_map.clone()),
92 )));
93 self.dependencies.extend(dependencies);
94 for arg in call_expr.args.iter().skip(1) {
95 self.walk_expression(&arg.expr);
96 }
97 return Some(true);
98 }
99 self.walk_expr_or_spread(&call_expr.args);
100 Some(true)
101 }
102
103 fn create_decline_handler(
104 &mut self,
105 call_expr: &CallExpr,
106 create_dependency: CreateDependency,
107 ) -> Option<bool> {
108 self.build_info.module_concatenation_bailout = Some(String::from("Hot Module Replacement"));
109 self
110 .presentational_dependencies
111 .push(Box::new(ModuleArgumentDependency::new(
112 Some("hot.decline".into()),
113 call_expr.callee.span().into(),
114 Some(self.source_map.clone()),
115 )));
116 let dependencies = extract_deps(self, call_expr, create_dependency);
117 self.dependencies.extend(dependencies);
118 Some(true)
119 }
120}
121
122pub struct ModuleHotReplacementParserPlugin {
123 _private: (),
124}
125
126impl ModuleHotReplacementParserPlugin {
127 #[allow(clippy::new_without_default)]
128 pub fn new() -> Self {
129 import_emitted_runtime::init_map();
130 Self { _private: () }
131 }
132}
133
134impl JavascriptParserPlugin for ModuleHotReplacementParserPlugin {
135 fn evaluate_identifier(
136 &self,
137 _parser: &mut JavascriptParser,
138 for_name: &str,
139 start: u32,
140 end: u32,
141 ) -> Option<crate::utils::eval::BasicEvaluatedExpression<'static>> {
142 if for_name == expr_name::MODULE_HOT {
143 Some(eval::evaluate_to_identifier(
144 expr_name::MODULE_HOT.into(),
145 expr_name::MODULE.into(),
146 Some(true),
147 start,
148 end,
149 ))
150 } else {
151 None
152 }
153 }
154
155 fn member(
156 &self,
157 parser: &mut JavascriptParser,
158 expr: &swc_core::ecma::ast::MemberExpr,
159 for_name: &str,
160 ) -> Option<bool> {
161 if for_name == expr_name::MODULE_HOT {
162 parser.create_hmr_expression_handler(expr.span());
163 Some(true)
164 } else {
165 None
166 }
167 }
168
169 fn call(
170 &self,
171 parser: &mut JavascriptParser,
172 call_expr: &swc_core::ecma::ast::CallExpr,
173 for_name: &str,
174 ) -> Option<bool> {
175 if for_name == expr_name::MODULE_HOT_ACCEPT {
176 parser.create_accept_handler(call_expr, |request, range| {
177 Box::new(ModuleHotAcceptDependency::new(request, range))
178 })
179 } else if for_name == expr_name::MODULE_HOT_DECLINE {
180 parser.create_decline_handler(call_expr, |request, range| {
181 Box::new(ModuleHotDeclineDependency::new(request, range))
182 })
183 } else {
184 None
185 }
186 }
187}
188
189pub struct ImportMetaHotReplacementParserPlugin {
190 _private: (),
191}
192
193impl ImportMetaHotReplacementParserPlugin {
194 #[allow(clippy::new_without_default)]
195 pub fn new() -> Self {
196 import_emitted_runtime::init_map();
197 Self { _private: () }
198 }
199}
200
201impl JavascriptParserPlugin for ImportMetaHotReplacementParserPlugin {
202 fn evaluate_identifier(
203 &self,
204 _parser: &mut JavascriptParser,
205 for_name: &str,
206 start: u32,
207 end: u32,
208 ) -> Option<crate::utils::eval::BasicEvaluatedExpression<'static>> {
209 if for_name == expr_name::IMPORT_META_WEBPACK_HOT {
210 Some(eval::evaluate_to_identifier(
211 expr_name::IMPORT_META_WEBPACK_HOT.into(),
212 expr_name::IMPORT_META.into(),
213 Some(true),
214 start,
215 end,
216 ))
217 } else {
218 None
219 }
220 }
221
222 fn member(
223 &self,
224 parser: &mut JavascriptParser,
225 expr: &swc_core::ecma::ast::MemberExpr,
226 for_name: &str,
227 ) -> Option<bool> {
228 if for_name == expr_name::IMPORT_META_WEBPACK_HOT {
229 parser.create_hmr_expression_handler(expr.span());
230 Some(true)
231 } else {
232 None
233 }
234 }
235
236 fn call(
237 &self,
238 parser: &mut JavascriptParser,
239 call_expr: &swc_core::ecma::ast::CallExpr,
240 for_name: &str,
241 ) -> Option<bool> {
242 if for_name == expr_name::IMPORT_META_WEBPACK_HOT_ACCEPT {
243 parser.create_accept_handler(call_expr, |request, range| {
244 Box::new(ImportMetaHotAcceptDependency::new(request, range))
245 })
246 } else if for_name == expr_name::IMPORT_META_WEBPACK_HOT_DECLINE {
247 parser.create_decline_handler(call_expr, |request, range| {
248 Box::new(ImportMetaHotDeclineDependency::new(request, range))
249 })
250 } else {
251 None
252 }
253 }
254}