turbopack_browser/ecmascript/
worker.rs1use std::io::Write;
2
3use anyhow::Result;
4use indoc::writedoc;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{ResolvedVc, ValueToString, Vc};
7use turbo_tasks_fs::{File, FileContent, FileSystemPath};
8use turbo_tasks_hash::hash_xxh3_hash64;
9use turbopack_core::{
10 asset::{Asset, AssetContent},
11 chunk::{ChunkingContext, MinifyType},
12 code_builder::{Code, CodeBuilder},
13 ident::AssetIdent,
14 output::{OutputAsset, OutputAssetsReference, OutputAssetsWithReferenced},
15 source_map::{GenerateSourceMap, SourceMapAsset},
16};
17use turbopack_ecmascript::minify::minify;
18
19#[turbo_tasks::value(shared)]
24#[derive(ValueToString)]
25#[value_to_string("Ecmascript Browser Worker Entrypoint")]
26pub struct EcmascriptBrowserWorkerEntrypoint {
27 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
28 forwarded_globals: ResolvedVc<Vec<RcStr>>,
32}
33
34#[turbo_tasks::value_impl]
35impl EcmascriptBrowserWorkerEntrypoint {
36 #[turbo_tasks::function]
37 pub async fn new(
38 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
39 forwarded_globals: Vc<Vec<RcStr>>,
40 ) -> Result<Vc<Self>> {
41 Ok(EcmascriptBrowserWorkerEntrypoint {
42 chunking_context,
43 forwarded_globals: forwarded_globals.to_resolved().await?,
44 }
45 .cell())
46 }
47
48 #[turbo_tasks::function]
49 async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
50 let this = self.await?;
51
52 let source_maps = *this
53 .chunking_context
54 .reference_chunk_source_maps(Vc::upcast(self))
55 .await?;
56
57 let forwarded_globals = this.forwarded_globals.await?;
58 let mut code = generate_worker_bootstrap_code(&forwarded_globals)?;
59
60 if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? {
61 code = minify(code, source_maps, mangle)?;
62 }
63
64 Ok(code.cell())
65 }
66
67 #[turbo_tasks::function]
68 async fn ident_for_path(&self) -> Result<Vc<AssetIdent>> {
69 let chunk_root_path = self.chunking_context.chunk_root_path().owned().await?;
70 let forwarded_globals = self.forwarded_globals.await?;
71 let globals_hash = hash_xxh3_hash64(&*forwarded_globals);
72 let ident = AssetIdent::from_path(chunk_root_path)
73 .with_modifier(rcstr!("turbopack worker entrypoint"))
74 .with_modifier(format!("{globals_hash:08x}").into());
75 Ok(ident)
76 }
77
78 #[turbo_tasks::function]
79 async fn source_map(self: Vc<Self>) -> Result<Vc<SourceMapAsset>> {
80 let this = self.await?;
81 Ok(SourceMapAsset::new(
82 *this.chunking_context,
83 self.ident_for_path(),
84 Vc::upcast(self),
85 ))
86 }
87}
88
89#[turbo_tasks::value_impl]
90impl OutputAssetsReference for EcmascriptBrowserWorkerEntrypoint {
91 #[turbo_tasks::function]
92 async fn references(self: Vc<Self>) -> Result<Vc<OutputAssetsWithReferenced>> {
93 Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(vec![
94 ResolvedVc::upcast(self.source_map().to_resolved().await?),
95 ])))
96 }
97}
98
99#[turbo_tasks::value_impl]
100impl OutputAsset for EcmascriptBrowserWorkerEntrypoint {
101 #[turbo_tasks::function]
102 async fn path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
103 let this = self.await?;
104 let ident = self.ident_for_path();
105 Ok(this.chunking_context.chunk_path(
106 Some(Vc::upcast(self)),
107 ident,
108 Some(rcstr!("turbopack-worker")),
109 rcstr!(".js"),
110 ))
111 }
112}
113
114#[turbo_tasks::value_impl]
115impl Asset for EcmascriptBrowserWorkerEntrypoint {
116 #[turbo_tasks::function]
117 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
118 Ok(AssetContent::file(
119 FileContent::Content(File::from(
120 self.code()
121 .to_rope_with_magic_comments(|| self.source_map())
122 .await?,
123 ))
124 .cell(),
125 ))
126 }
127}
128
129#[turbo_tasks::value_impl]
130impl GenerateSourceMap for EcmascriptBrowserWorkerEntrypoint {
131 #[turbo_tasks::function]
132 fn generate_source_map(self: Vc<Self>) -> Vc<FileContent> {
133 self.code().generate_source_map()
134 }
135}
136
137fn generate_worker_bootstrap_code(forwarded_globals: &[RcStr]) -> Result<Code> {
142 let mut code: CodeBuilder = CodeBuilder::default();
143
144 let mut global_assignments = vec![
147 "TURBOPACK_NEXT_CHUNK_URLS: chunkUrls".to_string(),
148 "TURBOPACK_ASSET_SUFFIX: param(1)".to_string(),
149 ];
150 for (i, name) in forwarded_globals.iter().enumerate() {
151 global_assignments.push(format!("{name}: param({n})", n = i + 2));
153 }
154 let globals_js = global_assignments.join(",\n ");
155
156 writedoc!(
170 code,
171 r##"
172 (function() {{
173 function abort(message) {{
174 console.error(message);
175 throw new Error(message);
176 }}
177 if (
178 typeof self["WorkerGlobalScope"] === "undefined" ||
179 !(self instanceof self["WorkerGlobalScope"])
180 ) {{
181 abort("Worker entrypoint must be loaded in a worker context");
182 }}
183
184 // Try querystring first (SharedWorker), then hash (regular Worker)
185 var url = new URL(location.href);
186 var paramsString = url.searchParams.get("params");
187 if (!paramsString && url.hash.startsWith("#params=")) {{
188 paramsString = decodeURIComponent(url.hash.slice("#params=".length));
189 }}
190
191 if (!paramsString) abort("Missing worker bootstrap config");
192
193 var params = JSON.parse(paramsString);
194 var param = (n) => typeof params[n] === 'string' ? params[n] : '';
195 var chunkUrls = Array.isArray(params[0]) ? params[0] : [];
196
197 Object.assign(self, {{
198 {0}
199 }});
200
201 if (chunkUrls.length > 0) {{
202 var scriptsToLoad = [];
203 for (var i = 0; i < chunkUrls.length; i++) {{
204 var chunk = chunkUrls[i];
205 // Chunks are relative to the origin.
206 var chunkUrl = new URL(chunk, location.origin);
207 if (chunkUrl.origin !== location.origin) {{
208 abort("Refusing to load script from foreign origin: " + chunkUrl.origin);
209 }}
210 scriptsToLoad.push(chunkUrl.toString());
211 }}
212
213 // As scripts are loaded, allow them to pop from the array
214 chunkUrls.reverse();
215 importScripts.apply(self, scriptsToLoad);
216 }}
217 }})();
218 "##,
219 globals_js
220 )?;
221
222 Ok(code.build())
223}