rspack_plugin_javascript/parser_plugin/
hot_module_replacement_plugin.rs

1use 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}