Skip to main content

Rest/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    port.onmessage = function(event) {
69        // Handle messages from the main thread
70        self.dispatchEvent(new MessageEvent('message', event));
71    };
72    port.start();
73};
74
75"#,
76		);
77
78		// Add polyfills
79		code.push_str(&self.generate_classic_polyfills());
80
81		// Add the main entry point
82		code.push_str(&format!("importScripts('{}');\n", entry_point));
83
84		code
85	}
86
87	/// Generate polyfills for module workers
88	fn generate_polyfills(&self) -> String {
89		r#"
90// Polyfills for worker environment
91(function() {
92    // Ensure globalThis is available
93    if (typeof globalThis === 'undefined') {
94        self.globalThis = self;
95    }
96    
97    // Ensure MessageChannel is available
98    if (typeof MessageChannel === 'undefined') {
99        self.MessageChannel = class MessageChannel {
100            constructor() {
101                this.port1 = new MessagePort();
102                this.port2 = new MessagePort();
103            }
104        };
105    }
106    
107    // Ensure MessagePort is available
108    if (typeof MessagePort === 'undefined') {
109        self.MessagePort = class MessagePort {
110            constructor() {
111                this.onmessage = null;
112                this.onmessageerror = null;
113            }
114            postMessage(data) {}
115            start() {}
116            close() {}
117        };
118    }
119})();
120
121"#
122		.to_string()
123	}
124
125	/// Generate polyfills for classic workers
126	fn generate_classic_polyfills(&self) -> String {
127		r#"
128// Classic worker polyfills
129(function() {
130    // Minimal polyfills for classic workers
131    if (typeof globalThis === 'undefined') {
132        self.globalThis = self;
133    }
134})();
135
136"#
137		.to_string()
138	}
139
140	/// Generate a worker loader script that creates workers from modules
141	pub fn generate_worker_loader(&self, worker_name:&str, module_url:&str) -> String {
142		format!(
143			r#"
144(function() {{
145    const workerCode = `
146        {loader_code}
147    `;
148    
149    const blob = new Blob([workerCode], {{ type: 'application/javascript' }});
150    const url = URL.createObjectURL(blob);
151    
152    self["{worker_name}"] = new Worker(url, {{ type: 'module' }});
153    
154    // Clean up blob URL after worker is created
155    URL.revokeObjectURL(url);
156}})();
157"#,
158			loader_code = self
159				.generate_module_worker(module_url)
160				.replace("`", "\\`")
161				.replace("${", "\\${")
162		)
163	}
164}
165
166/// Generate inline worker code for small workers
167pub fn generate_inline_worker(code:&str, worker_type:WorkerType) -> String {
168	match worker_type {
169		WorkerType::Module => {
170			format!(
171				"new Worker(URL.createObjectURL(new Blob([`{}`], {{ type: 'application/javascript' }})), {{ type: \
172				 'module' }})",
173				code.replace("`", "\\`").replace("${", "\\${")
174			)
175		},
176		WorkerType::Classic => {
177			format!(
178				"new Worker(URL.createObjectURL(new Blob([`{}`], {{ type: 'application/javascript' }})))",
179				code.replace("`", "\\`").replace("${", "\\${")
180			)
181		},
182	}
183}
184
185/// Generate a TypeScript declaration for worker imports
186pub fn generate_worker_declaration(worker_name:&str) -> String {
187	format!(
188		r#"declare const {worker_name}: Worker;
189export {{ {worker_name} }};
190"#
191	)
192}
193
194#[cfg(test)]
195mod tests {
196	use super::*;
197
198	#[test]
199	fn test_module_worker_bootstrap() {
200		let config = WorkerConfig::new();
201		let bootstrap = WorkerBootstrap::new(config);
202
203		let code = bootstrap.generate_module_worker("./entry.js");
204		assert!(code.contains("Module worker bootstrap"));
205		assert!(code.contains("./entry.js"));
206	}
207
208	#[test]
209	fn test_classic_worker_bootstrap() {
210		let config = WorkerConfig::new();
211		let bootstrap = WorkerBootstrap::new(config);
212
213		let code = bootstrap.generate_classic_worker("./entry.js");
214		assert!(code.contains("Classic worker bootstrap"));
215		assert!(code.contains("./entry.js"));
216	}
217
218	#[test]
219	fn test_shared_worker_bootstrap() {
220		let config = WorkerConfig::new();
221		let bootstrap = WorkerBootstrap::new(config);
222
223		let code = bootstrap.generate_shared_worker("./entry.js");
224		assert!(code.contains("Shared worker bootstrap"));
225		assert!(code.contains("onconnect"));
226	}
227}