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::{EcmascriptChunkContent, EcmascriptChunkContentEntries},
18 minify::minify,
19 utils::StringifyJs,
20};
21
22use super::{
23 chunk::EcmascriptBrowserChunk, merged::merger::EcmascriptBrowserChunkContentMerger,
24 version::EcmascriptBrowserChunkVersion,
25};
26use crate::{
27 BrowserChunkingContext,
28 chunking_context::{CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR, CurrentChunkMethod},
29};
30
31#[turbo_tasks::value(serialization = "skip")]
32pub struct EcmascriptBrowserChunkContent {
33 pub(super) chunking_context: ResolvedVc<BrowserChunkingContext>,
34 pub(super) chunk: ResolvedVc<EcmascriptBrowserChunk>,
35 pub(super) content: ResolvedVc<EcmascriptChunkContent>,
36 pub(super) source_map: ResolvedVc<SourceMapAsset>,
37}
38
39#[turbo_tasks::value_impl]
40impl EcmascriptBrowserChunkContent {
41 #[turbo_tasks::function]
42 pub(crate) fn new(
43 chunking_context: ResolvedVc<BrowserChunkingContext>,
44 chunk: ResolvedVc<EcmascriptBrowserChunk>,
45 content: ResolvedVc<EcmascriptChunkContent>,
46 source_map: ResolvedVc<SourceMapAsset>,
47 ) -> Result<Vc<Self>> {
48 Ok(EcmascriptBrowserChunkContent {
49 chunking_context,
50 chunk,
51 content,
52 source_map,
53 }
54 .cell())
55 }
56
57 #[turbo_tasks::function]
58 pub fn entries(&self) -> Vc<EcmascriptChunkContentEntries> {
59 EcmascriptChunkContentEntries::new(*self.content)
60 }
61}
62
63#[turbo_tasks::value_impl]
64impl EcmascriptBrowserChunkContent {
65 #[turbo_tasks::function]
66 pub(crate) async fn own_version(&self) -> Result<Vc<EcmascriptBrowserChunkVersion>> {
67 Ok(EcmascriptBrowserChunkVersion::new(
68 self.chunking_context.output_root().owned().await?,
69 self.chunk.path().owned().await?,
70 *self.content,
71 ))
72 }
73
74 #[turbo_tasks::function]
75 async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
76 let this = self.await?;
77 let source_maps = *this
78 .chunking_context
79 .reference_chunk_source_maps(*ResolvedVc::upcast(this.chunk))
80 .await?;
81 let chunk_path;
83 let script_or_path = match *this.chunking_context.current_chunk_method().await? {
84 CurrentChunkMethod::StringLiteral => {
85 let output_root = this.chunking_context.output_root().await?;
86 let chunk_path_vc = this.chunk.path();
87 chunk_path = chunk_path_vc.await?;
88 let chunk_server_path = if let Some(path) = output_root.get_path_to(&chunk_path) {
89 path
90 } else {
91 turbobail!("chunk path {chunk_path} is not in output root {output_root}");
92 };
93 Either::Left(StringifyJs(chunk_server_path))
94 }
95 CurrentChunkMethod::DocumentCurrentScript => {
96 Either::Right(CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR)
97 }
98 };
99 let mut code = CodeBuilder::new(
100 source_maps,
101 *this.chunking_context.debug_ids_enabled().await?,
102 );
103
104 let chunk_loading_global = this.chunking_context.chunk_loading_global().await?;
112 write!(
113 code,
114 r#"(globalThis[{chunk_loading_global}] || (globalThis[{chunk_loading_global}] = [])).push([{script_or_path},"#,
117 chunk_loading_global = StringifyJs(&chunk_loading_global),
118 )?;
119
120 let content = this.content.await?;
121 let mut chunk_items = content.chunk_item_code_and_ids().await?;
122 chunk_items.sort_by_key(|item| item.first().map(|(id, _)| id.clone()));
123 for item in &chunk_items {
124 for (id, item_code) in &**item {
125 write!(code, "\n{}, ", StringifyJs(id))?;
126 code.push_code(item_code);
127 write!(code, ",")?;
128 }
129 }
130
131 write!(code, "\n]);")?;
132
133 let mut code = code.build();
134
135 if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? {
136 code = minify(code, source_maps, mangle)?;
137 }
138
139 Ok(code.cell())
140 }
141}
142
143#[turbo_tasks::value_impl]
144impl VersionedContent for EcmascriptBrowserChunkContent {
145 #[turbo_tasks::function]
146 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
147 let this = self.await?;
148
149 Ok(AssetContent::file(
150 FileContent::Content(File::from(
151 self.code()
152 .to_rope_with_magic_comments(|| *this.source_map)
153 .await?,
154 ))
155 .cell(),
156 ))
157 }
158
159 #[turbo_tasks::function]
160 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>> {
161 Vc::upcast(self.own_version())
162 }
163}
164
165#[turbo_tasks::value_impl]
166impl MergeableVersionedContent for EcmascriptBrowserChunkContent {
167 #[turbo_tasks::function]
168 fn get_merger(&self) -> Vc<Box<dyn VersionedContentMerger>> {
169 Vc::upcast(EcmascriptBrowserChunkContentMerger::new())
170 }
171}
172
173#[turbo_tasks::value_impl]
174impl GenerateSourceMap for EcmascriptBrowserChunkContent {
175 #[turbo_tasks::function]
176 fn generate_source_map(self: Vc<Self>) -> Vc<FileContent> {
177 self.code().generate_source_map()
178 }
179
180 #[turbo_tasks::function]
181 async fn by_section(self: Vc<Self>, section: RcStr) -> Result<Vc<FileContent>> {
182 if let Ok(id) = ModuleId::parse(§ion) {
185 let entries = self.entries().await?;
186 for (entry_id, entry) in entries.iter() {
187 if id == *entry_id {
188 let sm = entry.code.generate_source_map();
189 return Ok(sm);
190 }
191 }
192 }
193
194 Ok(FileContent::NotFound.cell())
195 }
196}