Skip to main content

Library/Fn/Worker/
Bootstrap.rs

1//! Worker bootstrap code generation
2//!
3//! Generates the bootstrap code needed to run web workers.
4
5use super::{WorkerConfig, WorkerType};
6
7/// Generates bootstrap code for a web worker
8pub struct WorkerBootstrap {
9	config:WorkerConfig,
10}
11
12impl WorkerBootstrap {
13	pub fn new(config:WorkerConfig) -> Self { Self { config } }
14
15	/// Generate bootstrap code for a module worker
16	pub fn generate_module_worker(&self, entry_point:&str) -> String {
17		let mut code = String::new();
18
19		// Add shebang for ES modules in workers
20		code.push_str("// Module worker bootstrap\n");
21
22		// Add polyfills and global setup
23		code.push_str(&self.generate_polyfills());
24
25		// Add bootstrap scripts
26		for script in &self.config.bootstrap_scripts {
27			code.push_str(&format!("import '{}';\n", script));
28		}
29
30		// Add the main entry point
31		code.push_str(&format!("import '{}';\n", entry_point));
32
33		code
34	}
35
36	/// Generate bootstrap code for a classic worker
37	pub fn generate_classic_worker(&self, entry_point:&str) -> String {
38		let mut code = String::new();
39
40		// Add shebang
41		code.push_str("// Classic worker bootstrap\n");
42
43		// Add polyfills for classic workers
44		code.push_str(&self.generate_classic_polyfills());
45
46		// Add bootstrap scripts
47		for script in &self.config.bootstrap_scripts {
48			code.push_str(&format!("importScripts('{}');\n", script));
49		}
50
51		// Add the main entry point
52		code.push_str(&format!("importScripts('{}');\n", entry_point));
53
54		code
55	}
56
57	/// Generate bootstrap for a shared worker
58	pub fn generate_shared_worker(&self, entry_point:&str) -> String {
59		let mut code = String::new();
60
61		code.push_str("// Shared worker bootstrap\n");
62
63		// Shared workers need port handling
64		code.push_str(
65			r#"
66self.onconnect = function(event) {
67    const port = event.ports[0];
68
69    port.onmessage = function(event) {
70        // Handle messages from the main thread
71        self.dispatchEvent(new MessageEvent('message', event));
72    };
73
74    port.start();
75};
76
77"#,
78		);
79
80		// Add polyfills
81		code.push_str(&self.generate_classic_polyfills());
82
83		// Add the main entry point
84		code.push_str(&format!("importScripts('{}');\n", entry_point));
85
86		code
87	}
88
89	/// Generate polyfills for module workers
90	fn generate_polyfills(&self) -> String {
91		r#"
92// Polyfills for worker environment
93(function() {
94    // Ensure globalThis is available
95    if (typeof globalThis === 'undefined') {
96        self.globalThis = self;
97    }
98    
99    // Ensure MessageChannel is available
100    if (typeof MessageChannel === 'undefined') {
101        self.MessageChannel = class MessageChannel {
102            constructor() {
103                this.port1 = new MessagePort();
104
105                this.port2 = new MessagePort();
106            }
107        };
108    }
109    
110    // Ensure MessagePort is available
111    if (typeof MessagePort === 'undefined') {
112        self.MessagePort = class MessagePort {
113            constructor() {
114                this.onmessage = null;
115
116                this.onmessageerror = null;
117            }
118
119            postMessage(data) {}
120
121            start() {}
122
123            close() {}
124        };
125    }
126})();
127
128"#
129		.to_string()
130	}
131
132	/// Generate polyfills for classic workers
133	fn generate_classic_polyfills(&self) -> String {
134		r#"
135// Classic worker polyfills
136(function() {
137    // Minimal polyfills for classic workers
138    if (typeof globalThis === 'undefined') {
139        self.globalThis = self;
140    }
141})();
142
143"#
144		.to_string()
145	}
146
147	/// Generate a worker loader script that creates workers from modules
148	pub fn generate_worker_loader(&self, worker_name:&str, module_url:&str) -> String {
149		format!(
150			r#"
151(function() {{
152    const workerCode = `
153        {loader_code}
154
155    `;
156    
157    const blob = new Blob([workerCode], {{ type: 'application/javascript' }});
158
159    const url = URL.createObjectURL(blob);
160    
161    self["{worker_name}"] = new Worker(url, {{ type: 'module' }});
162    
163    // Clean up blob URL after worker is created
164    URL.revokeObjectURL(url);
165}})();
166"#,
167			loader_code = self
168				.generate_module_worker(module_url)
169				.replace("`", "\\`")
170				.replace("${", "\\${")
171		)
172	}
173}
174
175/// Generate inline worker code for small workers
176pub fn generate_inline_worker(code:&str, worker_type:WorkerType) -> String {
177	match worker_type {
178		WorkerType::Module => {
179			format!(
180				"new Worker(URL.createObjectURL(new Blob([`{}`], {{ type: 'application/javascript' }})), {{ type: \
181				 'module' }})",
182				code.replace("`", "\\`").replace("${", "\\${")
183			)
184		},
185
186		WorkerType::Classic => {
187			format!(
188				"new Worker(URL.createObjectURL(new Blob([`{}`], {{ type: 'application/javascript' }})))",
189				code.replace("`", "\\`").replace("${", "\\${")
190			)
191		},
192	}
193}
194
195/// Generate a TypeScript declaration for worker imports
196pub fn generate_worker_declaration(worker_name:&str) -> String {
197	format!(
198		r#"declare const {worker_name}: Worker;
199export {{ {worker_name} }};
200"#
201	)
202}
203
204#[cfg(test)]
205mod tests {
206
207	use super::*;
208
209	#[test]
210	fn test_module_worker_bootstrap() {
211		let config = WorkerConfig::new();
212
213		let bootstrap = WorkerBootstrap::new(config);
214
215		let code = bootstrap.generate_module_worker("./entry.js");
216
217		assert!(code.contains("Module worker bootstrap"));
218
219		assert!(code.contains("./entry.js"));
220	}
221
222	#[test]
223	fn test_classic_worker_bootstrap() {
224		let config = WorkerConfig::new();
225
226		let bootstrap = WorkerBootstrap::new(config);
227
228		let code = bootstrap.generate_classic_worker("./entry.js");
229
230		assert!(code.contains("Classic worker bootstrap"));
231
232		assert!(code.contains("./entry.js"));
233	}
234
235	#[test]
236	fn test_shared_worker_bootstrap() {
237		let config = WorkerConfig::new();
238
239		let bootstrap = WorkerBootstrap::new(config);
240
241		let code = bootstrap.generate_shared_worker("./entry.js");
242
243		assert!(code.contains("Shared worker bootstrap"));
244
245		assert!(code.contains("onconnect"));
246	}
247}