next_custom_transforms/transforms/fonts/
mod.rs

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