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