turbopack_browser/ecmascript/
content.rs1use std::io::Write;
2
3use anyhow::{Result, bail};
4use either::Either;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{ResolvedVc, Vc};
7use turbo_tasks_fs::{File, rope::RopeBuilder};
8use turbopack_core::{
9 asset::AssetContent,
10 chunk::{ChunkingContext, MinifyType, ModuleId},
11 code_builder::{Code, CodeBuilder},
12 output::OutputAsset,
13 source_map::{GenerateSourceMap, OptionStringifiedSourceMap, SourceMapAsset},
14 version::{MergeableVersionedContent, Version, VersionedContent, VersionedContentMerger},
15};
16use turbopack_ecmascript::{chunk::EcmascriptChunkContent, minify::minify, utils::StringifyJs};
17
18use super::{
19 chunk::EcmascriptBrowserChunk, content_entry::EcmascriptBrowserChunkContentEntries,
20 merged::merger::EcmascriptBrowserChunkContentMerger, version::EcmascriptBrowserChunkVersion,
21};
22use crate::{
23 BrowserChunkingContext,
24 chunking_context::{CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR, CurrentChunkMethod},
25};
26
27#[turbo_tasks::value(serialization = "none")]
28pub struct EcmascriptBrowserChunkContent {
29 pub(super) chunking_context: ResolvedVc<BrowserChunkingContext>,
30 pub(super) chunk: ResolvedVc<EcmascriptBrowserChunk>,
31 pub(super) content: ResolvedVc<EcmascriptChunkContent>,
32 pub(super) source_map: ResolvedVc<SourceMapAsset>,
33}
34
35#[turbo_tasks::value_impl]
36impl EcmascriptBrowserChunkContent {
37 #[turbo_tasks::function]
38 pub(crate) fn new(
39 chunking_context: ResolvedVc<BrowserChunkingContext>,
40 chunk: ResolvedVc<EcmascriptBrowserChunk>,
41 content: ResolvedVc<EcmascriptChunkContent>,
42 source_map: ResolvedVc<SourceMapAsset>,
43 ) -> Result<Vc<Self>> {
44 Ok(EcmascriptBrowserChunkContent {
45 chunking_context,
46 chunk,
47 content,
48 source_map,
49 }
50 .cell())
51 }
52
53 #[turbo_tasks::function]
54 pub fn entries(&self) -> Vc<EcmascriptBrowserChunkContentEntries> {
55 EcmascriptBrowserChunkContentEntries::new(*self.content)
56 }
57}
58
59#[turbo_tasks::value_impl]
60impl EcmascriptBrowserChunkContent {
61 #[turbo_tasks::function]
62 pub(crate) async fn own_version(&self) -> Result<Vc<EcmascriptBrowserChunkVersion>> {
63 Ok(EcmascriptBrowserChunkVersion::new(
64 self.chunking_context.output_root().owned().await?,
65 self.chunk.path().owned().await?,
66 *self.content,
67 ))
68 }
69
70 #[turbo_tasks::function]
71 async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
72 let this = self.await?;
73 let source_maps = *this
74 .chunking_context
75 .reference_chunk_source_maps(*ResolvedVc::upcast(this.chunk))
76 .await?;
77 let chunk_path;
79 let script_or_path = match *this.chunking_context.current_chunk_method().await? {
80 CurrentChunkMethod::StringLiteral => {
81 let output_root = this.chunking_context.output_root().await?;
82 let chunk_path_vc = this.chunk.path();
83 chunk_path = chunk_path_vc.await?;
84 let chunk_server_path = if let Some(path) = output_root.get_path_to(&chunk_path) {
85 path
86 } else {
87 bail!(
88 "chunk path {} is not in output root {}",
89 chunk_path.to_string(),
90 output_root.to_string()
91 );
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(source_maps);
100
101 write!(
109 code,
110 "(globalThis.TURBOPACK || (globalThis.TURBOPACK = [])).push([{script_or_path},"
113 )?;
114
115 let content = this.content.await?;
116 let chunk_items = content.chunk_item_code_and_ids().await?;
117 for item in chunk_items {
118 for (id, item_code) in item {
119 write!(code, "\n{}, ", StringifyJs(&id))?;
120 code.push_code(item_code);
121 write!(code, ",")?;
122 }
123 }
124
125 write!(code, "\n]);")?;
126
127 let mut code = code.build();
128
129 if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? {
130 code = minify(code, source_maps, mangle)?;
131 }
132
133 Ok(code.cell())
134 }
135}
136
137#[turbo_tasks::value_impl]
138impl VersionedContent for EcmascriptBrowserChunkContent {
139 #[turbo_tasks::function]
140 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
141 let this = self.await?;
142 let code = self.code().await?;
143
144 let rope = if code.has_source_map() {
145 let mut rope_builder = RopeBuilder::default();
146 rope_builder.concat(code.source_code());
147 let source_map_path = this.source_map.path().await?;
148 write!(
149 rope_builder,
150 "\n\n//# sourceMappingURL={}",
151 urlencoding::encode(source_map_path.file_name())
152 )?;
153 rope_builder.build()
154 } else {
155 code.source_code().clone()
156 };
157
158 Ok(AssetContent::file(File::from(rope).into()))
159 }
160
161 #[turbo_tasks::function]
162 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>> {
163 Vc::upcast(self.own_version())
164 }
165}
166
167#[turbo_tasks::value_impl]
168impl MergeableVersionedContent for EcmascriptBrowserChunkContent {
169 #[turbo_tasks::function]
170 fn get_merger(&self) -> Vc<Box<dyn VersionedContentMerger>> {
171 Vc::upcast(EcmascriptBrowserChunkContentMerger::new())
172 }
173}
174
175#[turbo_tasks::value_impl]
176impl GenerateSourceMap for EcmascriptBrowserChunkContent {
177 #[turbo_tasks::function]
178 fn generate_source_map(self: Vc<Self>) -> Vc<OptionStringifiedSourceMap> {
179 self.code().generate_source_map()
180 }
181
182 #[turbo_tasks::function]
183 async fn by_section(self: Vc<Self>, section: RcStr) -> Result<Vc<OptionStringifiedSourceMap>> {
184 if let Ok(id) = ModuleId::parse(§ion) {
187 let entries = self.entries().await?;
188 for (entry_id, entry) in entries.iter() {
189 if id == **entry_id {
190 let sm = entry.code.generate_source_map();
191 return Ok(sm);
192 }
193 }
194 }
195
196 Ok(Vc::cell(None))
197 }
198}