Skip to main content

turbopack_ecmascript_runtime/
browser_runtime.rs

1use 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
17/// Returns the code for the ECMAScript runtime.
18#[turbo_tasks::function]
19pub async fn get_browser_runtime_code(
20    asset_context: ResolvedVc<Box<dyn AssetContext>>,
21    chunk_base_path: Vc<Option<RcStr>>,
22    asset_suffix: Vc<AssetSuffix>,
23    runtime_type: RuntimeType,
24    output_root_to_root_path: RcStr,
25    generate_source_map: bool,
26    chunk_loading_global: Vc<RcStr>,
27    cross_origin: Vc<CrossOrigin>,
28    chunk_load_retry: Vc<ChunkLoadRetry>,
29    has_async_modules: bool,
30    chunk_loading: Vc<ChunkLoading>,
31    support_component_chunks: bool,
32) -> Result<Vc<Code>> {
33    let asset_context = *asset_context;
34    let environment = asset_context.compile_time_info().environment();
35
36    let shared_runtime_utils_code = embed_static_code(
37        asset_context,
38        rcstr!("shared/runtime/runtime-utils.ts"),
39        generate_source_map,
40    );
41
42    let mut runtime_base_code = vec!["browser/runtime/base/runtime-base.ts"];
43    match runtime_type {
44        RuntimeType::Production => runtime_base_code.push("browser/runtime/base/build-base.ts"),
45        RuntimeType::Development => {
46            runtime_base_code.push("shared/runtime/hmr-runtime.ts");
47            runtime_base_code.push("browser/runtime/base/dev-base.ts");
48        }
49        #[cfg(feature = "test")]
50        RuntimeType::Dummy => {
51            panic!("This configuration is not supported in the browser runtime")
52        }
53    }
54
55    let chunk_loading = &*chunk_loading.await?;
56
57    let mut runtime_backend_code = vec![];
58    match (chunk_loading, runtime_type) {
59        // The self-contained backend performs no runtime chunk loading and registers chunks only
60        // via `globalThis`/`self` (no DOM).
61        (ChunkLoading::Edge | ChunkLoading::SingleChunk, RuntimeType::Development) => {
62            runtime_backend_code
63                .push("browser/runtime/self-contained/runtime-backend-self-contained.ts");
64            runtime_backend_code
65                .push("browser/runtime/self-contained/dev-backend-self-contained.ts");
66        }
67        (ChunkLoading::Edge | ChunkLoading::SingleChunk, RuntimeType::Production) => {
68            runtime_backend_code
69                .push("browser/runtime/self-contained/runtime-backend-self-contained.ts");
70        }
71        // This case should never be hit.
72        (ChunkLoading::NodeJs, _) => {
73            panic!("Node.js runtime is not supported in the browser runtime!")
74        }
75        (ChunkLoading::Dom, RuntimeType::Development) => {
76            runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
77            runtime_backend_code.push("browser/runtime/dom/dev-backend-dom.ts");
78        }
79        (ChunkLoading::Dom, RuntimeType::Production) => {
80            runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
81        }
82
83        #[cfg(feature = "test")]
84        (_, RuntimeType::Dummy) => {
85            panic!("This configuration is not supported in the browser runtime")
86        }
87    };
88
89    let mut code: CodeBuilder = CodeBuilder::default();
90    let relative_root_path = output_root_to_root_path;
91    let chunk_base_path = chunk_base_path.await?;
92    let chunk_base_path = chunk_base_path.as_ref().map_or_else(|| "", |f| f.as_str());
93    let asset_suffix = asset_suffix.await?;
94    let chunk_loading_global = chunk_loading_global.await?;
95    let cross_origin = *cross_origin.await?;
96    let chunk_lists_global = format!("{}_CHUNK_LISTS", chunk_loading_global);
97
98    if *environment
99        .runtime_versions()
100        .supports_arrow_functions()
101        .await?
102    {
103        code += "(() => {\n";
104    } else {
105        code += "(function(){\n";
106    }
107
108    writedoc!(
109        code,
110        r#"
111            if (!Array.isArray(globalThis[{}])) {{
112                return;
113            }}
114
115            var CHUNK_BASE_PATH = {};
116            var RELATIVE_ROOT_PATH = {};
117            var RUNTIME_PUBLIC_PATH = {};
118            const SUPPORT_COMPONENT_CHUNKS = {};
119        "#,
120        StringifyJs(&chunk_loading_global),
121        StringifyJs(chunk_base_path),
122        StringifyJs(relative_root_path.as_str()),
123        StringifyJs(chunk_base_path),
124        support_component_chunks,
125    )?;
126
127    match &*asset_suffix {
128        AssetSuffix::None => {
129            writedoc!(
130                code,
131                r#"
132                    var ASSET_SUFFIX = "";
133                "#
134            )?;
135        }
136        AssetSuffix::Constant(suffix) => {
137            writedoc!(
138                code,
139                r#"
140                    var ASSET_SUFFIX = {};
141                "#,
142                StringifyJs(suffix.as_str())
143            )?;
144        }
145        AssetSuffix::Inferred => {
146            if matches!(
147                chunk_loading,
148                ChunkLoading::Edge | ChunkLoading::SingleChunk
149            ) {
150                panic!("AssetSuffix::Inferred is not supported in Edge or single-chunk runtimes");
151            }
152            writedoc!(
153                code,
154                r#"
155                    var ASSET_SUFFIX = getAssetSuffixFromScriptSrc();
156                "#
157            )?;
158        }
159        AssetSuffix::FromGlobal(global_name) => {
160            writedoc!(
161                code,
162                r#"
163                    var ASSET_SUFFIX = globalThis[{}] || "";
164                "#,
165                StringifyJs(global_name)
166            )?;
167        }
168    }
169
170    let cross_origin = cross_origin.as_str();
171    writedoc!(
172        code,
173        r#"
174            var CROSS_ORIGIN = {};
175        "#,
176        StringifyJs(&cross_origin)
177    )?;
178
179    // The chunk-load retry policy is owned by the framework (e.g. Next.js) and
180    // passed in via the chunking context, so the runtime never hard-codes it.
181    let chunk_load_retry = *chunk_load_retry.await?;
182    writedoc!(
183        code,
184        r#"
185            var CHUNK_LOAD_RETRY_MAX_ATTEMPTS = {};
186            var CHUNK_LOAD_RETRY_BASE_DELAY_MS = {};
187            var CHUNK_LOAD_RETRY_MAX_JITTER_MS = {};
188        "#,
189        chunk_load_retry.max_retry_attempts,
190        chunk_load_retry.base_delay_ms,
191        chunk_load_retry.max_jitter_ms,
192    )?;
193
194    code.push_code(&*shared_runtime_utils_code.await?);
195    // Only include the async-module (top-level await) machinery when the app uses it.
196    if has_async_modules {
197        code.push_code(
198            &*embed_static_code(
199                asset_context,
200                rcstr!("shared/runtime/async-module.ts"),
201                generate_source_map,
202            )
203            .await?,
204        );
205    }
206    for runtime_code in runtime_base_code {
207        code.push_code(
208            &*embed_static_code(asset_context, runtime_code.into(), generate_source_map).await?,
209        );
210    }
211
212    if *environment.supports_commonjs_externals().await? {
213        code.push_code(
214            &*embed_static_code(
215                asset_context,
216                rcstr!("shared-node/base-externals-utils.ts"),
217                generate_source_map,
218            )
219            .await?,
220        );
221    }
222    if *environment.node_externals().await? {
223        code.push_code(
224            &*embed_static_code(
225                asset_context,
226                rcstr!("shared-node/node-externals-utils.ts"),
227                generate_source_map,
228            )
229            .await?,
230        );
231    }
232    for backend_code in runtime_backend_code {
233        code.push_code(
234            &*embed_static_code(asset_context, backend_code.into(), generate_source_map).await?,
235        );
236    }
237
238    // Registering chunks/chunk lists depends on the BACKEND variable set by the specific
239    // runtime code, so it must be appended after it. `registerChunk` handles both queued forms:
240    // chunk-registration arrays and inlined entry-only params objects.
241    writedoc!(
242        code,
243        r#"
244            var chunksToRegister = globalThis[{chunk_loading_global}];
245            globalThis[{chunk_loading_global}] = {{ push: registerChunk }};
246            chunksToRegister.forEach(registerChunk);
247        "#,
248        chunk_loading_global = StringifyJs(&chunk_loading_global),
249    )?;
250    if matches!(runtime_type, RuntimeType::Development) {
251        writedoc!(
252            code,
253            r#"
254            var chunkListsToRegister = globalThis[{chunk_lists_global}] || [];
255            globalThis[{chunk_lists_global}] = {{ push: registerChunkList }};
256            chunkListsToRegister.forEach(registerChunkList);
257        "#,
258            chunk_lists_global = StringifyJs(&chunk_lists_global),
259        )?;
260    }
261    writedoc!(
262        code,
263        r#"
264            }})();
265        "#
266    )?;
267
268    Ok(Code::cell(code.build()))
269}
270
271/// Returns the code for the ECMAScript worker entrypoint bootstrap.
272pub fn get_worker_runtime_code(
273    asset_context: Vc<Box<dyn AssetContext>>,
274    generate_source_map: bool,
275) -> Result<Vc<Code>> {
276    Ok(embed_static_code(
277        asset_context,
278        rcstr!("browser/runtime/base/worker-entrypoint.ts"),
279        generate_source_map,
280    ))
281}