rspack_plugin_javascript/parser_plugin/
hot_module_replacement_plugin.rs

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