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