turbopack_browser/ecmascript/
content.rs1use std::io::Write;
2
3use anyhow::Result;
4use either::Either;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{ResolvedVc, Vc, turbobail};
7use turbo_tasks_fs::{File, FileContent};
8use turbopack_core::{
9 asset::AssetContent,
10 chunk::{ChunkingContext, MinifyType, ModuleId},
11 code_builder::{Code, CodeBuilder},
12 output::OutputAsset,
13 source_map::{GenerateSourceMap, SourceMapAsset},
14 version::{MergeableVersionedContent, Version, VersionedContent, VersionedContentMerger},
15};
16use turbopack_ecmascript::{
17 chunk::{
18 EcmascriptChunkContent, EcmascriptChunkContentEntries, strict_chunk_wrapper,
19 strict_factory_mode, write_module_factories,
20 },
21 hmr::{
22 EcmascriptHmrChunkContent, merger::EcmascriptChunkContentMerger,
23 version::EcmascriptChunkVersion,
24 },
25 minify::minify,
26 utils::StringifyJs,
27};
28
29use super::chunk::EcmascriptBrowserChunk;
30use crate::{
31 BrowserChunkingContext,
32 chunking_context::{CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR, CurrentChunkMethod},
33};
34
35#[turbo_tasks::value(serialization = "skip")]
36pub struct EcmascriptBrowserChunkContent {
37 pub(super) chunking_context: ResolvedVc<BrowserChunkingContext>,
38 pub(super) chunk: ResolvedVc<EcmascriptBrowserChunk>,
39 pub(super) content: ResolvedVc<EcmascriptChunkContent>,
40 pub(super) source_map: ResolvedVc<SourceMapAsset>,
41}
42
43#[turbo_tasks::value_impl]
44impl EcmascriptBrowserChunkContent {
45 #[turbo_tasks::function]
46 pub(crate) fn new(
47 chunking_context: ResolvedVc<BrowserChunkingContext>,
48 chunk: ResolvedVc<EcmascriptBrowserChunk>,
49 content: ResolvedVc<EcmascriptChunkContent>,
50 source_map: ResolvedVc<SourceMapAsset>,
51 ) -> Result<Vc<Self>> {
52 Ok(EcmascriptBrowserChunkContent {
53 chunking_context,
54 chunk,
55 content,
56 source_map,
57 }
58 .cell())
59 }
60
61 #[turbo_tasks::function]
62 pub(crate) async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
63 let this = self.await?;
64 let source_maps = *this
65 .chunking_context
66 .reference_chunk_source_maps(*ResolvedVc::upcast(this.chunk))
67 .await?;
68 let chunk_path;
70 let script_or_path = match *this.chunking_context.current_chunk_method().await? {
71 CurrentChunkMethod::StringLiteral => {
72 let output_root = this.chunking_context.output_root().await?;
73 let chunk_path_vc = this.chunk.path();
74 chunk_path = chunk_path_vc.await?;
75 let chunk_server_path = if let Some(path) = output_root.get_path_to(&chunk_path) {
76 path
77 } else {
78 turbobail!("chunk path {chunk_path} is not in output root {output_root}");
79 };
80 Either::Left(StringifyJs(chunk_server_path))
81 }
82 CurrentChunkMethod::DocumentCurrentScript => {
83 Either::Right(CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR)
84 }
85 };
86 let mut code = CodeBuilder::new(
87 source_maps,
88 *this.chunking_context.debug_ids_enabled().await?,
89 );
90
91 let supports_arrow_functions = *this
92 .chunking_context
93 .environment()
94 .runtime_versions()
95 .supports_arrow_functions()
96 .await?;
97 let content = this.content.await?;
98 let chunk_items = content.chunk_item_code_module_ids_and_paths().await?;
99 let strict_factory_mode = strict_factory_mode(&chunk_items, supports_arrow_functions);
100
101 let strict_chunk_wrapper =
102 strict_chunk_wrapper(strict_factory_mode, supports_arrow_functions);
103 if let Some((prefix, _)) = strict_chunk_wrapper {
104 code += prefix;
105 }
106
107 let chunk_loading_global = this.chunking_context.chunk_loading_global().await?;
115 write!(
116 code,
117 r#"(globalThis[{chunk_loading_global}] || (globalThis[{chunk_loading_global}] = [])).push([{script_or_path},"#,
120 chunk_loading_global = StringifyJs(&chunk_loading_global),
121 )?;
122 write_module_factories(
123 &mut code,
124 &chunk_items,
125 strict_factory_mode,
126 supports_arrow_functions,
127 )?;
128 write!(code, "\n]);")?;
129
130 if let Some((_, suffix)) = strict_chunk_wrapper {
131 code += suffix;
132 }
133
134 let mut code = code.build();
135
136 if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? {
137 code = minify(code, source_maps, mangle)?;
138 }
139
140 Ok(code.cell())
141 }
142
143 #[turbo_tasks::function]
144 pub(crate) async fn has_source_map(self: Vc<Self>) -> Result<Vc<bool>> {
145 Ok(Vc::cell(self.code().await?.has_source_map()))
146 }
147}
148
149#[turbo_tasks::value_impl]
150impl VersionedContent for EcmascriptBrowserChunkContent {
151 #[turbo_tasks::function]
152 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
153 let this = self.await?;
154
155 Ok(AssetContent::file(
156 FileContent::Content(File::from(
157 self.code()
158 .to_rope_with_magic_comments(|| *this.source_map)
159 .await?,
160 ))
161 .cell(),
162 ))
163 }
164
165 #[turbo_tasks::function]
166 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>> {
167 Vc::upcast(self.ecmascript_chunk_version())
168 }
169}
170
171#[turbo_tasks::value_impl]
172impl EcmascriptHmrChunkContent for EcmascriptBrowserChunkContent {
173 #[turbo_tasks::function]
174 fn entries(&self) -> Vc<EcmascriptChunkContentEntries> {
175 EcmascriptChunkContentEntries::new(*self.content)
176 }
177
178 #[turbo_tasks::function]
179 async fn ecmascript_chunk_version(&self) -> Result<Vc<EcmascriptChunkVersion>> {
180 Ok(EcmascriptChunkVersion::new(
181 self.chunking_context.output_root().owned().await?,
182 self.chunk.path().owned().await?,
183 *self.content,
184 *self.chunking_context.minify_type().await?,
185 ))
186 }
187}
188
189#[turbo_tasks::value_impl]
190impl MergeableVersionedContent for EcmascriptBrowserChunkContent {
191 #[turbo_tasks::function]
192 fn get_merger(&self) -> Vc<Box<dyn VersionedContentMerger>> {
193 Vc::upcast(EcmascriptChunkContentMerger::new())
194 }
195}
196
197#[turbo_tasks::value_impl]
198impl GenerateSourceMap for EcmascriptBrowserChunkContent {
199 #[turbo_tasks::function]
200 fn generate_source_map(self: Vc<Self>) -> Vc<FileContent> {
201 self.code().generate_source_map()
202 }
203
204 #[turbo_tasks::function]
205 async fn by_section(self: Vc<Self>, section: RcStr) -> Result<Vc<FileContent>> {
206 if let Ok(id) = ModuleId::parse(§ion) {
209 let entries = self.entries().await?;
210 for (entry_id, entry) in entries.iter() {
211 if id == *entry_id {
212 let sm = entry.code.generate_source_map();
213 return Ok(sm);
214 }
215 }
216 }
217
218 Ok(FileContent::NotFound.cell())
219 }
220}