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