Skip to main content

rspack_core/exports/
target.rs

1use std::sync::Arc;
2
3use rspack_util::atom::Atom;
4use rustc_hash::FxHashSet as HashSet;
5
6use crate::{
7  DependencyId, ExportInfo, ExportInfoData, ExportsInfo, ExportsInfoArtifact, ModuleGraph,
8  ModuleIdentifier,
9};
10
11#[derive(Debug, Hash, Clone, PartialEq, Eq)]
12pub enum TerminalBinding {
13  ExportInfo(ExportInfo),
14  ExportsInfo(ExportsInfo),
15}
16
17#[derive(Debug, Clone)]
18pub struct UnResolvedExportInfoTarget {
19  pub dependency: Option<DependencyId>,
20  pub export: Option<Vec<Atom>>,
21}
22
23pub type ResolveFilterFnTy<'a> = dyn Fn(&ResolvedExportInfoTarget) -> bool + 'a;
24
25#[derive(Debug)]
26pub enum GetTargetResult {
27  Target(ResolvedExportInfoTarget),
28  Circular,
29}
30
31#[derive(Clone, Debug, Eq)]
32pub struct ResolvedExportInfoTarget {
33  pub module: ModuleIdentifier,
34  pub export: Option<Vec<Atom>>,
35  /// using dependency id to retrieve Connection
36  pub dependency: DependencyId,
37}
38
39impl PartialEq for ResolvedExportInfoTarget {
40  fn eq(&self, other: &Self) -> bool {
41    self.module == other.module && self.export == other.export
42  }
43}
44
45#[derive(Clone, Debug)]
46pub enum FindTargetResult {
47  NoTarget,
48  InvalidTarget(FindTargetResultItem),
49  ValidTarget(FindTargetResultItem),
50}
51
52#[derive(Clone, Debug)]
53pub struct FindTargetResultItem {
54  pub module: ModuleIdentifier,
55  pub export: Option<Vec<Atom>>,
56  pub defer: bool,
57}
58
59pub fn get_terminal_binding(
60  export_info: &ExportInfoData,
61  mg: &ModuleGraph,
62  exports_info_artifact: &ExportsInfoArtifact,
63) -> Option<TerminalBinding> {
64  if export_info.terminal_binding() {
65    return Some(TerminalBinding::ExportInfo(export_info.id()));
66  }
67  let Some(GetTargetResult::Target(target)) = get_target(
68    export_info,
69    mg,
70    exports_info_artifact,
71    &|_| true,
72    &mut Default::default(),
73  ) else {
74    return None;
75  };
76  let exports_info = exports_info_artifact.get_exports_info(&target.module);
77  let Some(export) = target.export else {
78    return Some(TerminalBinding::ExportsInfo(exports_info));
79  };
80  exports_info
81    .as_data(exports_info_artifact)
82    .get_read_only_export_info_recursive(exports_info_artifact, &export)
83    .map(|data| TerminalBinding::ExportInfo(data.id()))
84}
85
86pub fn find_target(
87  export_info: &ExportInfoData,
88  mg: &ModuleGraph,
89  exports_info_artifact: &ExportsInfoArtifact,
90  valid_target_module_filter: Arc<impl Fn(&ModuleIdentifier) -> bool>,
91  visited: &mut HashSet<ExportInfo>,
92) -> FindTargetResult {
93  if !export_info.target_is_set() || export_info.target().is_empty() {
94    return FindTargetResult::NoTarget;
95  }
96  let max_target = export_info.get_max_target();
97  let Some(raw_target) = max_target.values().next() else {
98    return FindTargetResult::NoTarget;
99  };
100  let mut target = FindTargetResultItem {
101    module: *raw_target
102      .dependency
103      .and_then(|dep_id| mg.connection_by_dependency_id(&dep_id))
104      .expect("should have connection")
105      .module_identifier(),
106    export: raw_target.export.clone(),
107    defer: raw_target
108      .dependency
109      .as_ref()
110      .map(|dep| {
111        let dependency = mg.dependency_by_id(dep);
112        dependency.get_phase().is_defer()
113      })
114      .unwrap_or_default(),
115  };
116  loop {
117    if valid_target_module_filter(&target.module) {
118      return FindTargetResult::ValidTarget(target);
119    }
120    let name = &target.export.as_ref().expect("should have export")[0];
121    let exports_info = exports_info_artifact.get_exports_info_data(&target.module);
122    let export_info = exports_info.get_export_info_without_mut_module_graph(name);
123    let export_info_id = export_info.id();
124    if !visited.insert(export_info_id) {
125      return FindTargetResult::NoTarget;
126    }
127    let new_target = find_target(
128      &export_info,
129      mg,
130      exports_info_artifact,
131      valid_target_module_filter.clone(),
132      visited,
133    );
134    let new_target = match new_target {
135      FindTargetResult::NoTarget => return FindTargetResult::InvalidTarget(target),
136      FindTargetResult::InvalidTarget(module) => return FindTargetResult::InvalidTarget(module),
137      FindTargetResult::ValidTarget(target) => target,
138    };
139    if target.export.as_ref().map(|item| item.len()) == Some(1) {
140      target = new_target;
141    } else {
142      target = FindTargetResultItem {
143        module: new_target.module,
144        export: if let Some(export) = new_target.export {
145          Some(
146            [
147              export,
148              target
149                .export
150                .as_ref()
151                .and_then(|export| export.get(1..).map(|slice| slice.to_vec()))
152                .unwrap_or_default(),
153            ]
154            .concat(),
155          )
156        } else {
157          target
158            .export
159            .and_then(|export| export.get(1..).map(|slice| slice.to_vec()))
160        },
161        defer: new_target.defer,
162      }
163    }
164  }
165}
166
167pub fn get_target(
168  export_info: &ExportInfoData,
169  mg: &ModuleGraph,
170  exports_info_artifact: &ExportsInfoArtifact,
171  resolve_filter: &ResolveFilterFnTy<'_>,
172  already_visited: &mut HashSet<ExportInfo>,
173) -> Option<GetTargetResult> {
174  if !export_info.target_is_set() || export_info.target().is_empty() {
175    return None;
176  }
177  if !already_visited.insert(export_info.id()) {
178    return Some(GetTargetResult::Circular);
179  }
180
181  let max_target = export_info.get_max_target();
182  let mut values = max_target.values().map(|item| UnResolvedExportInfoTarget {
183    dependency: item.dependency,
184    export: item.export.clone(),
185  });
186  let target = resolve_target(
187    values.next()?,
188    already_visited,
189    resolve_filter,
190    mg,
191    exports_info_artifact,
192  );
193
194  if let Some(GetTargetResult::Target(target)) = &target {
195    for val in values {
196      let resolved_target = resolve_target(
197        val,
198        already_visited,
199        resolve_filter,
200        mg,
201        exports_info_artifact,
202      );
203      let Some(GetTargetResult::Target(resolved_target)) = &resolved_target else {
204        return resolved_target;
205      };
206      if resolved_target != target {
207        return None;
208      }
209    }
210  }
211
212  target
213}
214
215fn resolve_target(
216  input_target: UnResolvedExportInfoTarget,
217  already_visited: &mut HashSet<ExportInfo>,
218  resolve_filter: &ResolveFilterFnTy<'_>,
219  mg: &ModuleGraph,
220  exports_info_artifact: &ExportsInfoArtifact,
221) -> Option<GetTargetResult> {
222  let mut target = ResolvedExportInfoTarget {
223    module: *input_target
224      .dependency
225      .and_then(|dep_id| mg.connection_by_dependency_id(&dep_id))
226      .expect("should have connection")
227      .module_identifier(),
228    export: input_target.export,
229    dependency: input_target.dependency.expect("should have dependency"),
230  };
231  if target.export.is_none() {
232    return Some(GetTargetResult::Target(target));
233  }
234  if !resolve_filter(&target) {
235    return Some(GetTargetResult::Target(target));
236  }
237  loop {
238    let Some(name) = target.export.as_ref().and_then(|exports| exports.first()) else {
239      return Some(GetTargetResult::Target(target));
240    };
241
242    let exports_info = exports_info_artifact.get_exports_info_data(&target.module);
243    let maybe_export_info = exports_info.get_export_info_without_mut_module_graph(name);
244    let maybe_export_info_id = maybe_export_info.id();
245    if already_visited.contains(&maybe_export_info_id) {
246      return Some(GetTargetResult::Circular);
247    }
248    let new_target = get_target(
249      &maybe_export_info,
250      mg,
251      exports_info_artifact,
252      resolve_filter,
253      already_visited,
254    );
255
256    match new_target {
257      Some(GetTargetResult::Circular) => {
258        return Some(GetTargetResult::Circular);
259      }
260      None => return Some(GetTargetResult::Target(target)),
261      Some(GetTargetResult::Target(t)) => {
262        // SAFETY: if the target.exports is None, program will not reach here
263        let target_exports = target.export.as_ref().expect("should have exports");
264        if target_exports.len() == 1 {
265          target = t;
266          if target.export.is_none() {
267            return Some(GetTargetResult::Target(target));
268          }
269        } else {
270          target.module = t.module;
271          target.dependency = t.dependency;
272          target.export = if let Some(mut exports) = t.export {
273            exports.extend_from_slice(&target_exports[1..]);
274            Some(exports)
275          } else {
276            Some(target_exports[1..].to_vec())
277          }
278        }
279      }
280    }
281    if !resolve_filter(&target) {
282      return Some(GetTargetResult::Target(target));
283    }
284    already_visited.insert(maybe_export_info_id);
285  }
286}
287
288pub fn can_move_target(
289  export_info: &ExportInfoData,
290  mg: &ModuleGraph,
291  exports_info_artifact: &ExportsInfoArtifact,
292  resolve_filter: &ResolveFilterFnTy<'_>,
293) -> Option<ResolvedExportInfoTarget> {
294  let Some(GetTargetResult::Target(target)) = get_target(
295    export_info,
296    mg,
297    exports_info_artifact,
298    resolve_filter,
299    &mut Default::default(),
300  ) else {
301    return None;
302  };
303  let max_target = export_info.get_max_target();
304  let original_target = max_target
305    .values()
306    .next()
307    .expect("should have export info target"); // refer https://github.com/webpack/webpack/blob/ac7e531436b0d47cd88451f497cdfd0dad41535d/lib/ExportsInfo.js#L1388-L1394
308  if original_target.dependency.as_ref() == Some(&target.dependency)
309    && original_target.export == target.export
310  {
311    return None;
312  }
313  Some(target)
314}