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