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    code_builder::{Code, CodeBuilder},
9    context::AssetContext,
10    environment::{ChunkLoading, Environment},
11};
12use turbopack_ecmascript::utils::StringifyJs;
13
14use crate::{
15    ChunkSuffix, RuntimeType, asset_context::get_runtime_asset_context, embed_js::embed_static_code,
16};
17
18/// Returns the code for the ECMAScript runtime.
19#[turbo_tasks::function]
20pub async fn get_browser_runtime_code(
21    environment: ResolvedVc<Environment>,
22    chunk_base_path: Vc<Option<RcStr>>,
23    chunk_suffix: Vc<ChunkSuffix>,
24    runtime_type: RuntimeType,
25    output_root_to_root_path: RcStr,
26    generate_source_map: bool,
27) -> Result<Vc<Code>> {
28    let asset_context = get_runtime_asset_context(*environment).resolve().await?;
29
30    let shared_runtime_utils_code = embed_static_code(
31        asset_context,
32        rcstr!("shared/runtime-utils.ts"),
33        generate_source_map,
34    );
35
36    let mut runtime_base_code = vec!["browser/runtime/base/runtime-base.ts"];
37    match runtime_type {
38        RuntimeType::Production => runtime_base_code.push("browser/runtime/base/build-base.ts"),
39        RuntimeType::Development => {
40            runtime_base_code.push("browser/runtime/base/dev-base.ts");
41        }
42        #[cfg(feature = "test")]
43        RuntimeType::Dummy => {
44            panic!("This configuration is not supported in the browser runtime")
45        }
46    }
47
48    let chunk_loading = &*asset_context
49        .compile_time_info()
50        .environment()
51        .chunk_loading()
52        .await?;
53
54    let mut runtime_backend_code = vec![];
55    match (chunk_loading, runtime_type) {
56        (ChunkLoading::Edge, RuntimeType::Development) => {
57            runtime_backend_code.push("browser/runtime/edge/runtime-backend-edge.ts");
58            runtime_backend_code.push("browser/runtime/edge/dev-backend-edge.ts");
59        }
60        (ChunkLoading::Edge, RuntimeType::Production) => {
61            runtime_backend_code.push("browser/runtime/edge/runtime-backend-edge.ts");
62        }
63        // This case should never be hit.
64        (ChunkLoading::NodeJs, _) => {
65            panic!("Node.js runtime is not supported in the browser runtime!")
66        }
67        (ChunkLoading::Dom, RuntimeType::Development) => {
68            runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
69            runtime_backend_code.push("browser/runtime/dom/dev-backend-dom.ts");
70        }
71        (ChunkLoading::Dom, RuntimeType::Production) => {
72            runtime_backend_code.push("browser/runtime/dom/runtime-backend-dom.ts");
73        }
74
75        #[cfg(feature = "test")]
76        (_, RuntimeType::Dummy) => {
77            panic!("This configuration is not supported in the browser runtime")
78        }
79    };
80
81    let mut code: CodeBuilder = CodeBuilder::default();
82    let relative_root_path = output_root_to_root_path;
83    let chunk_base_path = chunk_base_path.await?;
84    let chunk_base_path = chunk_base_path.as_ref().map_or_else(|| "", |f| f.as_str());
85    let chunk_suffix = chunk_suffix.await?;
86
87    writedoc!(
88        code,
89        r#"
90            (() => {{
91            if (!Array.isArray(globalThis.TURBOPACK)) {{
92                return;
93            }}
94
95            const CHUNK_BASE_PATH = {};
96            const RELATIVE_ROOT_PATH = {};
97            const RUNTIME_PUBLIC_PATH = {};
98        "#,
99        StringifyJs(chunk_base_path),
100        StringifyJs(relative_root_path.as_str()),
101        StringifyJs(chunk_base_path),
102    )?;
103
104    match &*chunk_suffix {
105        ChunkSuffix::None => {
106            writedoc!(
107                code,
108                r#"
109                    const CHUNK_SUFFIX = "";
110                "#
111            )?;
112        }
113        ChunkSuffix::Constant(suffix) => {
114            writedoc!(
115                code,
116                r#"
117                    const CHUNK_SUFFIX = {};
118                "#,
119                StringifyJs(suffix.as_str())
120            )?;
121        }
122        ChunkSuffix::FromScriptSrc => {
123            writedoc!(
124                code,
125                r#"
126                    const CHUNK_SUFFIX = (self.TURBOPACK_CHUNK_SUFFIX ?? document?.currentScript?.getAttribute?.('src')?.replace(/^(.*(?=\?)|^.*$)/, "")) || "";
127                "#
128            )?;
129        }
130    }
131
132    code.push_code(&*shared_runtime_utils_code.await?);
133    for runtime_code in runtime_base_code {
134        code.push_code(
135            &*embed_static_code(asset_context, runtime_code.into(), generate_source_map).await?,
136        );
137    }
138
139    if *environment.supports_commonjs_externals().await? {
140        code.push_code(
141            &*embed_static_code(
142                asset_context,
143                rcstr!("shared-node/base-externals-utils.ts"),
144                generate_source_map,
145            )
146            .await?,
147        );
148    }
149    if *environment.node_externals().await? {
150        code.push_code(
151            &*embed_static_code(
152                asset_context,
153                rcstr!("shared-node/node-externals-utils.ts"),
154                generate_source_map,
155            )
156            .await?,
157        );
158    }
159    if *environment.supports_wasm().await? {
160        code.push_code(
161            &*embed_static_code(
162                asset_context,
163                rcstr!("shared-node/node-wasm-utils.ts"),
164                generate_source_map,
165            )
166            .await?,
167        );
168    }
169
170    for backend_code in runtime_backend_code {
171        code.push_code(
172            &*embed_static_code(asset_context, backend_code.into(), generate_source_map).await?,
173        );
174    }
175
176    // Registering chunks and chunk lists depends on the BACKEND variable, which is set by the
177    // specific runtime code, hence it must be appended after it.
178    writedoc!(
179        code,
180        r#"
181            const chunksToRegister = globalThis.TURBOPACK;
182            globalThis.TURBOPACK = {{ push: registerChunk }};
183            chunksToRegister.forEach(registerChunk);
184        "#
185    )?;
186    if matches!(runtime_type, RuntimeType::Development) {
187        writedoc!(
188            code,
189            r#"
190            const chunkListsToRegister = globalThis.TURBOPACK_CHUNK_LISTS || [];
191            globalThis.TURBOPACK_CHUNK_LISTS = {{ push: registerChunkList }};
192            chunkListsToRegister.forEach(registerChunkList);
193        "#
194        )?;
195    }
196    writedoc!(
197        code,
198        r#"
199            }})();
200        "#
201    )?;
202
203    Ok(Code::cell(code.build()))
204}