intlayer_swc_plugin/
imports.rs1use crate::{
5 ast::{make_ident, make_str},
6 dictionary_imports::InjectedImports,
7 packages::NESTED_DICTIONARIES_SUBDIR,
8 paths::relative_import_path,
9};
10use std::{collections::HashSet, path::Path};
11use swc_core::{common::DUMMY_SP, ecma::ast::*};
12
13pub struct DictionaryDirs<'a> {
15 pub dictionaries_dir: &'a str,
17 pub dynamic_dictionaries_dir: &'a str,
19 pub fetch_dictionaries_dir: &'a str,
21}
22
23fn json_import_attributes() -> Box<ObjectLit> {
25 Box::new(ObjectLit {
26 span: DUMMY_SP,
27 props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
28 key: PropName::Ident(make_ident("type").into()),
29 value: Box::new(Expr::Lit(Lit::Str(make_str("json")))),
30 })))],
31 })
32}
33
34fn default_import(local: Ident, specifier: String, with_json_attribute: bool) -> ModuleItem {
37 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
38 span: DUMMY_SP,
39 specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
40 span: DUMMY_SP,
41 local,
42 })],
43 src: Box::new(Str::from(specifier)),
44 type_only: false,
45 with: with_json_attribute.then(json_import_attributes),
46 phase: ImportPhase::Evaluation,
47 }))
48}
49
50fn import_insert_position(body: &[ModuleItem]) -> usize {
58 let mut insert_position = 0;
59
60 for item in body {
61 let ModuleItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = item else {
62 break;
63 };
64 let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr else {
65 break;
66 };
67 let Some(directive) = value.as_str() else {
68 break;
69 };
70 if directive.starts_with("import") || directive.starts_with("require") {
71 break;
72 }
73
74 insert_position += 1;
75 }
76
77 insert_position
78}
79
80pub fn inject_dictionary_imports(
89 program: &mut Program,
90 injected_imports: &InjectedImports,
91 dirs: &DictionaryDirs<'_>,
92 nesting_dictionary_keys: &HashSet<&str>,
93 source_file_path: &str,
94) {
95 let Program::Module(Module { body, .. }) = program else {
96 return;
97 };
98
99 let file_dir_abs = Path::new(source_file_path)
100 .parent()
101 .unwrap_or_else(|| Path::new("/"));
102
103 let mut insert_position = import_insert_position(body);
104
105 for (key, ident) in injected_imports.static_imports.iter().rev() {
106 let has_nested_dictionaries = nesting_dictionary_keys.contains(key.as_str());
107 let dictionary_file_abs = if has_nested_dictionaries {
108 Path::new(dirs.dictionaries_dir)
109 .join(NESTED_DICTIONARIES_SUBDIR)
110 .join(format!("{}.mjs", key))
111 } else {
112 Path::new(dirs.dictionaries_dir).join(format!("{}.json", key))
113 };
114
115 body.insert(
116 insert_position,
117 default_import(
118 ident.clone(),
119 relative_import_path(&dictionary_file_abs, file_dir_abs),
120 !has_nested_dictionaries,
121 ),
122 );
123
124 insert_position += 1;
125 }
126
127 for (key, ident) in injected_imports.dynamic_imports.iter().rev() {
128 let is_fetch_loader = ident.sym.as_ref().ends_with("_fetch");
129 let target_dir = if is_fetch_loader {
130 dirs.fetch_dictionaries_dir
131 } else {
132 dirs.dynamic_dictionaries_dir
133 };
134 let dictionary_file_abs = Path::new(target_dir).join(format!("{}.mjs", key));
135
136 body.insert(
137 insert_position,
138 default_import(
139 ident.clone(),
140 relative_import_path(&dictionary_file_abs, file_dir_abs),
141 false,
142 ),
143 );
144
145 insert_position += 1;
146 }
147}