next_custom_transforms/transforms/fonts/
mod.rs

1use rustc_hash::{FxHashMap, FxHashSet};
2use serde::Deserialize;
3use swc_core::{
4    atoms::Wtf8Atom,
5    common::{BytePos, Spanned},
6    ecma::{
7        ast::{Id, ModuleItem, Pass},
8        atoms::Atom,
9        visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitWith},
10    },
11};
12
13mod find_functions_outside_module_scope;
14mod font_functions_collector;
15mod font_imports_generator;
16
17#[derive(Clone, Debug, Deserialize)]
18#[serde(deny_unknown_fields, rename_all = "camelCase")]
19pub struct Config {
20    pub font_loaders: Vec<Wtf8Atom>,
21    pub relative_file_path_from_root: Atom,
22}
23
24pub fn next_font_loaders(config: Config) -> impl Pass + VisitMut {
25    visit_mut_pass(NextFontLoaders {
26        config,
27        state: State {
28            ..Default::default()
29        },
30    })
31}
32
33#[derive(Debug)]
34pub struct FontFunction {
35    loader: Wtf8Atom,
36    function_name: Option<Atom>,
37}
38#[derive(Debug, Default)]
39pub struct State {
40    font_functions: FxHashMap<Id, FontFunction>,
41    removable_module_items: FxHashSet<BytePos>,
42    font_imports: Vec<ModuleItem>,
43    font_exports: Vec<ModuleItem>,
44    font_functions_in_allowed_scope: FxHashSet<BytePos>,
45}
46
47struct NextFontLoaders {
48    config: Config,
49    state: State,
50}
51
52impl VisitMut for NextFontLoaders {
53    noop_visit_mut_type!();
54
55    fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
56        // Find imported functions from font loaders
57        let mut functions_collector = font_functions_collector::FontFunctionsCollector {
58            font_loaders: &self.config.font_loaders,
59            state: &mut self.state,
60        };
61        items.visit_with(&mut functions_collector);
62
63        if !self.state.removable_module_items.is_empty() {
64            // Generate imports from font function calls
65            let mut import_generator = font_imports_generator::FontImportsGenerator {
66                state: &mut self.state,
67                relative_path: &self.config.relative_file_path_from_root,
68            };
69            items.visit_with(&mut import_generator);
70
71            // Find font function refs in wrong scope
72            let mut wrong_scope =
73                find_functions_outside_module_scope::FindFunctionsOutsideModuleScope {
74                    state: &self.state,
75                };
76            items.visit_with(&mut wrong_scope);
77
78            fn is_removable(ctx: &NextFontLoaders, item: &ModuleItem) -> bool {
79                ctx.state.removable_module_items.contains(&item.span_lo())
80            }
81
82            let first_removable_index = items
83                .iter()
84                .position(|item| is_removable(self, item))
85                .unwrap();
86
87            // Remove marked module items
88            items.retain(|item| !is_removable(self, item));
89
90            // Add font imports and exports
91            items.splice(
92                first_removable_index..first_removable_index,
93                std::mem::take(&mut self.state.font_imports),
94            );
95            items.append(&mut self.state.font_exports);
96        }
97    }
98}