turbopack_ecmascript_runtime/
browser_runtime.rs1use std::io::Write;
2
3use anyhow::Result;
4use indoc::writedoc;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{ResolvedVc, Vc};
7use turbopack_core::{
8 chunk::{AssetSuffix, ChunkLoadRetry, CrossOrigin},
9 code_builder::{Code, CodeBuilder},
10 context::AssetContext,
11 environment::ChunkLoading,
12};
13use turbopack_ecmascript::utils::StringifyJs;
14
15use crate::{RuntimeType, embed_js::embed_static_code};
16
17pub fn chunk_update_listeners_global_name(chunk_loading_global: &str) -> String {
18 format!("{chunk_loading_global}_CHUNK_UPDATE_LISTENERS")
19}
20
21#[cfg(test)]
22mod tests {
23 use super::chunk_update_listeners_global_name;
24
25 #[test]
26 fn scopes_chunk_update_listeners_to_chunk_loading_global() {
27 assert_eq!(
28 chunk_update_listeners_global_name("TURBOPACK_APP"),
29 "TURBOPACK_APP_CHUNK_UPDATE_LISTENERS"
30 );
31 }
32}
33
34#[turbo_tasks::function]
36pub async fn get_browser_runtime_code(
37 asset_context: ResolvedVc<Box<dyn AssetContext>>,
38 chunk_base_path: Vc<Option<RcStr>>,
39 asset_suffix: Vc<AssetSuffix>,
40 runtime_type: RuntimeType,
41 output_root_to_root_path: RcStr,
42 generate_source_map: bool,
43 chunk_loading_global: Vc<RcStr>,
44 cross_origin: Vc<CrossOrigin>,
45 chunk_load_retry: Vc<ChunkLoadRetry>,
46 include_async_module_runtime: bool,
47 chunk_loading: Vc<ChunkLoading>,
48 support_component_chunks: bool,
49) -> Result<Vc<Code>> {
50 let asset_context = *asset_context;
51 let environment = asset_context.compile_time_info().environment();
52
53 let shared_runtime_utils_code = embed_static_code(
54 asset_context,
55 rcstr!("shared/runtime/runtime-utils.ts"),
56 generate_source_map,
57 );
58
59 let mut runtime_base_code = vec!["browser/runtime/base/runtime-base.ts"];
60 match runtime_type {
61 RuntimeType::Production => runtime_base_code.push("browser/runtime/base/build-base.ts"),
62 RuntimeType::Development => {
63 runtime_base_code.push("shared/runtime/hmr-runtime.ts");
64 runtime_base_code.push("browser/runtime/base/dev-base.ts");
65 }
66 #[cfg(feature = "test")]
67 RuntimeType::Dummy => {
68 panic!("This configuration is not supported in the browser runtime")
69 }
70 }
71
72 let chunk_loading = &*chunk_loading.await?;
73
74 let mut runtime_backend_code = vec![];
75 match (chunk_loading, runtime_type) {
76 (ChunkLoading::Edge | ChunkLoading::SingleChunk, RuntimeType::Development) => {
79 runtime_backend_code
80 .push("browser/runtime/self-contained/runtime-backend-self-contained.ts");
81 runtime_backend_code
82 .push("browser/runtime/self-contained/dev-backend-self-contained.ts");
83 }
84 (ChunkLoading::Edge | ChunkLoading::SingleChunk, RuntimeType::Production) => {
85 runtime_backend_code
86 .push("browser/runtime/self-contained/runtime-backend-self-contained.ts");
87 }
88 (ChunkLoading::NodeJs, _) => {
90 panic!("Node.js runtime is not supported in the browser runtime!")
91 }
92 (ChunkLoading::Dom, RuntimeType::Development) => {
93 runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
94 runtime_backend_code.push("browser/runtime/dom/dev-backend-dom.ts");
95 }
96 (ChunkLoading::Dom, RuntimeType::Production) => {
97 runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
98 }
99
100 #[cfg(feature = "test")]
101 (_, RuntimeType::Dummy) => {
102 panic!("This configuration is not supported in the browser runtime")
103 }
104 };
105
106 let mut code: CodeBuilder = CodeBuilder::default();
107 let relative_root_path = output_root_to_root_path;
108 let chunk_base_path = chunk_base_path.await?;
109 let chunk_base_path = chunk_base_path.as_ref().map_or_else(|| "", |f| f.as_str());
110 let asset_suffix = asset_suffix.await?;
111 let chunk_loading_global = chunk_loading_global.await?;
112 let cross_origin = *cross_origin.await?;
113 let chunk_lists_global = format!("{}_CHUNK_LISTS", chunk_loading_global);
114 let chunk_update_listeners_global =
115 chunk_update_listeners_global_name(chunk_loading_global.as_str());
116
117 if *environment
118 .runtime_versions()
119 .supports_arrow_functions()
120 .await?
121 {
122 code += "(() => {\n";
123 } else {
124 code += "(function(){\n";
125 }
126
127 writedoc!(
131 code,
132 r#"
133 var chunksToRegister = globalThis[{}];
134 if (chunksToRegister === undefined) {{
135 chunksToRegister = [];
136 }} else if (!Array.isArray(chunksToRegister)) {{
137 return;
138 }}
139
140 var CHUNK_BASE_PATH = {};
141 var RELATIVE_ROOT_PATH = {};
142 var RUNTIME_PUBLIC_PATH = {};
143 const SUPPORT_COMPONENT_CHUNKS = {};
144 "#,
145 StringifyJs(&chunk_loading_global),
146 StringifyJs(chunk_base_path),
147 StringifyJs(relative_root_path.as_str()),
148 StringifyJs(chunk_base_path),
149 support_component_chunks,
150 )?;
151
152 if matches!(runtime_type, RuntimeType::Development) {
153 writedoc!(
154 code,
155 r#"
156 globalThis[{chunk_update_listeners_global}] ||= [];
157 var CHUNK_UPDATE_LISTENERS = {{
158 push: (registration) => globalThis[{chunk_update_listeners_global}].push(registration),
159 }};
160 "#,
161 chunk_update_listeners_global = StringifyJs(&chunk_update_listeners_global),
162 )?;
163 }
164
165 match &*asset_suffix {
166 AssetSuffix::None => {
167 writedoc!(
168 code,
169 r#"
170 var ASSET_SUFFIX = "";
171 "#
172 )?;
173 }
174 AssetSuffix::Constant(suffix) => {
175 writedoc!(
176 code,
177 r#"
178 var ASSET_SUFFIX = {};
179 "#,
180 StringifyJs(suffix.as_str())
181 )?;
182 }
183 AssetSuffix::Inferred => {
184 if matches!(
185 chunk_loading,
186 ChunkLoading::Edge | ChunkLoading::SingleChunk
187 ) {
188 panic!("AssetSuffix::Inferred is not supported in Edge or single-chunk runtimes");
189 }
190 writedoc!(
191 code,
192 r#"
193 var ASSET_SUFFIX = getAssetSuffixFromScriptSrc();
194 "#
195 )?;
196 }
197 AssetSuffix::FromGlobal(global_name) => {
198 writedoc!(
199 code,
200 r#"
201 var ASSET_SUFFIX = globalThis[{}] || "";
202 "#,
203 StringifyJs(global_name)
204 )?;
205 }
206 }
207
208 let cross_origin = cross_origin.as_str();
209 writedoc!(
210 code,
211 r#"
212 var CROSS_ORIGIN = {};
213 "#,
214 StringifyJs(&cross_origin)
215 )?;
216
217 let chunk_load_retry = *chunk_load_retry.await?;
220 writedoc!(
221 code,
222 r#"
223 var CHUNK_LOAD_RETRY_MAX_ATTEMPTS = {};
224 var CHUNK_LOAD_RETRY_BASE_DELAY_MS = {};
225 var CHUNK_LOAD_RETRY_MAX_JITTER_MS = {};
226 "#,
227 chunk_load_retry.max_retry_attempts,
228 chunk_load_retry.base_delay_ms,
229 chunk_load_retry.max_jitter_ms,
230 )?;
231
232 code.push_code(&*shared_runtime_utils_code.await?);
233 if include_async_module_runtime {
234 code.push_code(
235 &*embed_static_code(
236 asset_context,
237 rcstr!("shared/runtime/async-module.ts"),
238 generate_source_map,
239 )
240 .await?,
241 );
242 }
243 for runtime_code in runtime_base_code {
244 code.push_code(
245 &*embed_static_code(asset_context, runtime_code.into(), generate_source_map).await?,
246 );
247 }
248
249 if *environment.supports_commonjs_externals().await? {
250 code.push_code(
251 &*embed_static_code(
252 asset_context,
253 rcstr!("shared-node/base-externals-utils.ts"),
254 generate_source_map,
255 )
256 .await?,
257 );
258 }
259 if *environment.node_externals().await? {
260 code.push_code(
261 &*embed_static_code(
262 asset_context,
263 rcstr!("shared-node/node-externals-utils.ts"),
264 generate_source_map,
265 )
266 .await?,
267 );
268 }
269 for backend_code in runtime_backend_code {
270 code.push_code(
271 &*embed_static_code(asset_context, backend_code.into(), generate_source_map).await?,
272 );
273 }
274
275 writedoc!(
279 code,
280 r#"
281 globalThis[{chunk_loading_global}] = {{ push: registerChunk }};
282 chunksToRegister.forEach(registerChunk);
283 "#,
284 chunk_loading_global = StringifyJs(&chunk_loading_global),
285 )?;
286 if matches!(runtime_type, RuntimeType::Development) {
287 writedoc!(
288 code,
289 r#"
290 var chunkListsToRegister = globalThis[{chunk_lists_global}] || [];
291 globalThis[{chunk_lists_global}] = {{ push: registerChunkList }};
292 chunkListsToRegister.forEach(registerChunkList);
293 "#,
294 chunk_lists_global = StringifyJs(&chunk_lists_global),
295 )?;
296 }
297 writedoc!(
298 code,
299 r#"
300 }})();
301 "#
302 )?;
303
304 Ok(Code::cell(code.build()))
305}
306
307pub fn get_worker_runtime_code(
309 asset_context: Vc<Box<dyn AssetContext>>,
310 generate_source_map: bool,
311) -> Result<Vc<Code>> {
312 Ok(embed_static_code(
313 asset_context,
314 rcstr!("browser/runtime/base/worker-entrypoint.ts"),
315 generate_source_map,
316 ))
317}