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