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, 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    worker_asset_prefix: Vc<Option<RcStr>>,
23    asset_suffix: Vc<AssetSuffix>,
24    worker_forwarded_globals: Vc<Vec<RcStr>>,
25    runtime_type: RuntimeType,
26    output_root_to_root_path: RcStr,
27    generate_source_map: bool,
28    chunk_loading_global: Vc<RcStr>,
29    cross_origin: Vc<CrossOrigin>,
30) -> Result<Vc<Code>> {
31    let asset_context = *asset_context;
32    let environment = asset_context.compile_time_info().environment();
33
34    let shared_runtime_utils_code = embed_static_code(
35        asset_context,
36        rcstr!("shared/runtime/runtime-utils.ts"),
37        generate_source_map,
38    );
39
40    let mut runtime_base_code = vec!["browser/runtime/base/runtime-base.ts"];
41    match runtime_type {
42        RuntimeType::Production => runtime_base_code.push("browser/runtime/base/build-base.ts"),
43        RuntimeType::Development => {
44            runtime_base_code.push("shared/runtime/hmr-runtime.ts");
45            runtime_base_code.push("browser/runtime/base/dev-base.ts");
46        }
47        #[cfg(feature = "test")]
48        RuntimeType::Dummy => {
49            panic!("This configuration is not supported in the browser runtime")
50        }
51    }
52
53    let chunk_loading = &*asset_context
54        .compile_time_info()
55        .environment()
56        .chunk_loading()
57        .await?;
58
59    let mut runtime_backend_code = vec![];
60    match (chunk_loading, runtime_type) {
61        (ChunkLoading::Edge, RuntimeType::Development) => {
62            runtime_backend_code.push("browser/runtime/edge/runtime-backend-edge.ts");
63            runtime_backend_code.push("browser/runtime/edge/dev-backend-edge.ts");
64        }
65        (ChunkLoading::Edge, RuntimeType::Production) => {
66            runtime_backend_code.push("browser/runtime/edge/runtime-backend-edge.ts");
67        }
68        // This case should never be hit.
69        (ChunkLoading::NodeJs, _) => {
70            panic!("Node.js runtime is not supported in the browser runtime!")
71        }
72        (ChunkLoading::Dom, RuntimeType::Development) => {
73            runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
74            runtime_backend_code.push("browser/runtime/dom/dev-backend-dom.ts");
75        }
76        (ChunkLoading::Dom, RuntimeType::Production) => {
77            runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
78        }
79
80        #[cfg(feature = "test")]
81        (_, RuntimeType::Dummy) => {
82            panic!("This configuration is not supported in the browser runtime")
83        }
84    };
85
86    let mut code: CodeBuilder = CodeBuilder::default();
87    let relative_root_path = output_root_to_root_path;
88    let chunk_base_path = chunk_base_path.await?;
89    let chunk_base_path = chunk_base_path.as_ref().map_or_else(|| "", |f| f.as_str());
90    // `null` (no override) and `Some("")` (empty-string prefix) are distinct
91    // states, so inject as a JS literal — `null` for None and a quoted string
92    // for Some — instead of collapsing both to "".
93    let worker_asset_prefix = worker_asset_prefix.await?;
94    let worker_asset_prefix_js: String = worker_asset_prefix.as_ref().map_or_else(
95        || "null".to_string(),
96        |f| format!("{}", StringifyJs(f.as_str())),
97    );
98    let asset_suffix = asset_suffix.await?;
99    let chunk_loading_global = chunk_loading_global.await?;
100    let cross_origin = *cross_origin.await?;
101    let chunk_lists_global = format!("{}_CHUNK_LISTS", chunk_loading_global);
102
103    if *environment
104        .runtime_versions()
105        .supports_arrow_functions()
106        .await?
107    {
108        code += "(() => {\n";
109    } else {
110        code += "(function(){\n";
111    }
112
113    writedoc!(
114        code,
115        r#"
116            if (!Array.isArray(globalThis[{}])) {{
117                return;
118            }}
119
120            var CHUNK_BASE_PATH = {};
121            var WORKER_BASE_PATH = {};
122            var RELATIVE_ROOT_PATH = {};
123            var RUNTIME_PUBLIC_PATH = {};
124        "#,
125        StringifyJs(&chunk_loading_global),
126        StringifyJs(chunk_base_path),
127        worker_asset_prefix_js,
128        StringifyJs(relative_root_path.as_str()),
129        StringifyJs(chunk_base_path),
130    )?;
131
132    match &*asset_suffix {
133        AssetSuffix::None => {
134            writedoc!(
135                code,
136                r#"
137                    var ASSET_SUFFIX = "";
138                "#
139            )?;
140        }
141        AssetSuffix::Constant(suffix) => {
142            writedoc!(
143                code,
144                r#"
145                    var ASSET_SUFFIX = {};
146                "#,
147                StringifyJs(suffix.as_str())
148            )?;
149        }
150        AssetSuffix::Inferred => {
151            if chunk_loading == &ChunkLoading::Edge {
152                panic!("AssetSuffix::Inferred is not supported in Edge runtimes");
153            }
154            writedoc!(
155                code,
156                r#"
157                    var ASSET_SUFFIX = getAssetSuffixFromScriptSrc();
158                "#
159            )?;
160        }
161        AssetSuffix::FromGlobal(global_name) => {
162            writedoc!(
163                code,
164                r#"
165                    var ASSET_SUFFIX = globalThis[{}] || "";
166                "#,
167                StringifyJs(global_name)
168            )?;
169        }
170    }
171
172    let cross_origin = cross_origin.as_str();
173    writedoc!(
174        code,
175        r#"
176            var CROSS_ORIGIN = {};
177        "#,
178        StringifyJs(&cross_origin)
179    )?;
180
181    // Output the list of global variable names to forward to workers
182    let worker_forwarded_globals = worker_forwarded_globals.await?;
183    writedoc!(
184        code,
185        r#"
186            var WORKER_FORWARDED_GLOBALS = {};
187        "#,
188        StringifyJs(&*worker_forwarded_globals)
189    )?;
190
191    code.push_code(&*shared_runtime_utils_code.await?);
192    for runtime_code in runtime_base_code {
193        code.push_code(
194            &*embed_static_code(asset_context, runtime_code.into(), generate_source_map).await?,
195        );
196    }
197
198    if *environment.supports_commonjs_externals().await? {
199        code.push_code(
200            &*embed_static_code(
201                asset_context,
202                rcstr!("shared-node/base-externals-utils.ts"),
203                generate_source_map,
204            )
205            .await?,
206        );
207    }
208    if *environment.node_externals().await? {
209        code.push_code(
210            &*embed_static_code(
211                asset_context,
212                rcstr!("shared-node/node-externals-utils.ts"),
213                generate_source_map,
214            )
215            .await?,
216        );
217    }
218    if *environment.supports_wasm().await? {
219        code.push_code(
220            &*embed_static_code(
221                asset_context,
222                rcstr!("shared-node/node-wasm-utils.ts"),
223                generate_source_map,
224            )
225            .await?,
226        );
227    }
228
229    for backend_code in runtime_backend_code {
230        code.push_code(
231            &*embed_static_code(asset_context, backend_code.into(), generate_source_map).await?,
232        );
233    }
234
235    // Registering chunks and chunk lists depends on the BACKEND variable, which is set by the
236    // specific runtime code, hence it must be appended after it.
237    writedoc!(
238        code,
239        r#"
240            var chunksToRegister = globalThis[{chunk_loading_global}];
241            globalThis[{chunk_loading_global}] = {{ push: registerChunk }};
242            chunksToRegister.forEach(registerChunk);
243        "#,
244        chunk_loading_global = StringifyJs(&chunk_loading_global),
245    )?;
246    if matches!(runtime_type, RuntimeType::Development) {
247        writedoc!(
248            code,
249            r#"
250            var chunkListsToRegister = globalThis[{chunk_lists_global}] || [];
251            globalThis[{chunk_lists_global}] = {{ push: registerChunkList }};
252            chunkListsToRegister.forEach(registerChunkList);
253        "#,
254            chunk_lists_global = StringifyJs(&chunk_lists_global),
255        )?;
256    }
257    writedoc!(
258        code,
259        r#"
260            }})();
261        "#
262    )?;
263
264    Ok(Code::cell(code.build()))
265}
266
267/// Returns the code for the ECMAScript worker entrypoint bootstrap.
268pub fn get_worker_runtime_code(
269    asset_context: Vc<Box<dyn AssetContext>>,
270    generate_source_map: bool,
271) -> Result<Vc<Code>> {
272    Ok(embed_static_code(
273        asset_context,
274        rcstr!("browser/runtime/base/worker-entrypoint.ts"),
275        generate_source_map,
276    ))
277}