Library/Fn/Worker/
Detect.rs1use std::path::Path;
6
7use walkdir::WalkDir;
8
9use super::{WorkerConfig, WorkerInfo, WorkerType};
10
11pub struct WorkerDetector {
13 config:WorkerConfig,
14}
15
16impl WorkerDetector {
17 pub fn new(config:WorkerConfig) -> Self { Self { config } }
18
19 pub fn detect_workers(&self, root_dir:&Path) -> Vec<WorkerInfo> {
21 let mut workers = Vec::new();
22
23 for entry in WalkDir::new(root_dir).follow_links(true).into_iter().filter_map(|e| e.ok()) {
24 let path = entry.path();
25
26 if self.is_worker_file(path) {
27 if let Some(worker_info) = self.create_worker_info(path) {
28 workers.push(worker_info);
29 }
30 }
31 }
32
33 workers
34 }
35
36 pub fn is_worker_file(&self, path:&Path) -> bool {
38 if !path.is_file() {
39 return false;
40 }
41
42 let file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
43
44 let worker_patterns = [
46 ".worker.ts",
47 ".worker.js",
48 ".worker.tsx",
49 ".worker.jsx",
50 "-worker.ts",
51 "-worker.js",
52 "worker.ts",
53 "worker.js",
54 "SharedWorker.ts",
55 "SharedWorker.js",
56 ];
57
58 for pattern in worker_patterns {
59 if file_name.ends_with(pattern) {
60 return true;
61 }
62 }
63
64 if let Ok(content) = std::fs::read_to_string(path) {
66 return self.contains_worker_marker(&content);
67 }
68
69 false
70 }
71
72 fn create_worker_info(&self, path:&Path) -> Option<WorkerInfo> {
74 let file_name = path.file_name()?.to_str()?;
75
76 let worker_type = if file_name.contains("SharedWorker") || file_name.contains("shared") {
77 WorkerType::Classic
79 } else {
80 self.config.worker_type
81 };
82
83 let is_shared = file_name.contains("SharedWorker") || file_name.contains("shared");
84
85 let source_path = path.to_string_lossy().to_string();
86
87 let name = path.file_stem()?.to_str()?.replace(".worker", "").replace("-worker", "");
88
89 let output_path = Path::new(&self.config.output_dir)
90 .join(format!("{}.js", name))
91 .to_string_lossy()
92 .to_string();
93
94 Some(WorkerInfo { source_path, output_path, name, worker_type, dependencies:Vec::new(), is_shared })
95 }
96
97 fn contains_worker_marker(&self, content:&str) -> bool {
99 let markers = [
100 "new Worker(",
101 "new SharedWorker(",
102 "self.onmessage",
103 "self.postMessage",
104 "importScripts(",
105 "// @worker",
106 "//worker",
107 ];
108
109 for marker in markers {
110 if content.contains(marker) {
111 return true;
112 }
113 }
114
115 false
116 }
117
118 pub fn extract_dependencies(&self, path:&Path) -> Vec<String> {
120 let mut deps = Vec::new();
121
122 if let Ok(content) = std::fs::read_to_string(path) {
123 for line in content.lines() {
125 let trimmed = line.trim();
126
127 if trimmed.starts_with("import ") {
129 if let Some(from_start) = trimmed.find("from") {
130 let import_part = &trimmed[from_start + 4..];
131
132 if let Some(path_start) = import_part.find('"') {
133 let path_end = import_part[path_start + 1..].find('"');
134
135 if let Some(end) = path_end {
136 let import_path = &import_part[path_start + 1..path_start + 1 + end];
137
138 deps.push(import_path.to_string());
139 }
140 }
141 }
142 }
143
144 if trimmed.starts_with("importScripts(") {
146 if let Some(paren_start) = trimmed.find('(') {
147 let paren_content = &trimmed[paren_start + 1..];
148
149 if let Some(paren_end) = paren_content.find(')') {
150 let scripts = &paren_content[..paren_end];
151
152 for script in scripts.split(',') {
153 let script = script.trim().trim_matches('"').trim_matches('\'');
154
155 if !script.is_empty() {
156 deps.push(script.to_string());
157 }
158 }
159 }
160 }
161 }
162 }
163 }
164
165 deps
166 }
167}
168
169#[cfg(test)]
170mod tests {
171
172 use super::*;
173
174 #[test]
175 fn test_worker_detection_by_name() {
176 let config = WorkerConfig::new();
177
178 let detector = WorkerDetector::new(config);
179
180 assert!(detector.is_worker_file(Path::new("test.worker.ts")));
181
182 assert!(detector.is_worker_file(Path::new("my-worker.js")));
183
184 assert!(detector.is_worker_file(Path::new("SharedWorker.ts")));
185
186 assert!(!detector.is_worker_file(Path::new("regular.ts")));
187 }
188
189 #[test]
190 fn test_worker_detection_by_content() {
191 let config = WorkerConfig::new();
192
193 let detector = WorkerDetector::new(config);
194
195 let content = r#"
196 self.onmessage = function(e) {
197
198 self.postMessage(e.data);
199 };
200
201 "#;
202
203 assert!(detector.contains_worker_marker(content));
204 }
205
206 #[test]
207 fn test_dependency_extraction() {
208 let config = WorkerConfig::new();
209
210 let detector = WorkerDetector::new(config);
211
212 let path = Path::new("test.worker.ts");
213
214 let _deps = detector.extract_dependencies(path);
216 }
217}