turbopack_browser/ecmascript/list/
content.rs1use std::io::Write;
2
3use anyhow::{Context, Result};
4use bincode::{Decode, Encode};
5use either::Either;
6use indoc::writedoc;
7use serde::{Deserialize, Serialize};
8use turbo_rcstr::RcStr;
9use turbo_tasks::{FxIndexMap, NonLocalValue, ResolvedVc, TryJoinIterExt, Vc, trace::TraceRawVcs};
10use turbo_tasks_fs::{File, FileContent};
11use turbopack_core::{
12 asset::{Asset, AssetContent},
13 chunk::ChunkingContext,
14 code_builder::{Code, CodeBuilder},
15 output::OutputAsset,
16 version::{Update, Version, VersionedContent},
17};
18use turbopack_ecmascript::{
19 chunk_list::{
20 update::update_chunk_list,
21 version::{ChunkListVersion, compute_chunk_list_version},
22 },
23 utils::StringifyJs,
24};
25
26use super::asset::{EcmascriptDevChunkList, EcmascriptDevChunkListSource};
27use crate::chunking_context::{
28 CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR, CurrentChunkMethod,
29};
30
31#[derive(
32 Clone, Debug, Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, NonLocalValue, Encode, Decode,
33)]
34enum CurrentChunkMethodWithData {
35 StringLiteral(RcStr),
36 DocumentCurrentScript,
37}
38
39#[turbo_tasks::value]
41pub struct EcmascriptDevChunkListContent {
42 current_chunk_method: CurrentChunkMethodWithData,
43 #[bincode(with = "turbo_bincode::indexmap")]
44 pub(super) chunks_contents: FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
45 source: EcmascriptDevChunkListSource,
46 chunk_loading_global: RcStr,
48}
49
50#[turbo_tasks::value_impl]
51impl EcmascriptDevChunkListContent {
52 #[turbo_tasks::function]
54 pub async fn new(chunk_list: Vc<EcmascriptDevChunkList>) -> Result<Vc<Self>> {
55 let chunk_list_ref = chunk_list.await?;
56 let output_root = chunk_list_ref.chunking_context.output_root().await?;
57 let current_chunk_method = match *chunk_list_ref
58 .chunking_context
59 .current_chunk_method()
60 .await?
61 {
62 CurrentChunkMethod::StringLiteral => {
63 let path = output_root
64 .get_path_to(&*chunk_list.path().await?)
65 .context("chunk list path not in output root")?
66 .into();
67 CurrentChunkMethodWithData::StringLiteral(path)
68 }
69 CurrentChunkMethod::DocumentCurrentScript => {
70 CurrentChunkMethodWithData::DocumentCurrentScript
71 }
72 };
73 let chunk_loading_global = (*chunk_list_ref
74 .chunking_context
75 .chunk_loading_global()
76 .await?)
77 .clone();
78 Ok(EcmascriptDevChunkListContent {
79 current_chunk_method,
80 chunks_contents: chunk_list_ref
81 .chunks
82 .await?
83 .iter()
84 .map(async |chunk| {
85 Ok((
86 output_root
87 .get_path_to(&*chunk.path().await?)
88 .map(|path| path.to_string()),
89 chunk.versioned_content().to_resolved().await?,
90 ))
91 })
92 .try_join()
93 .await?
94 .into_iter()
95 .filter_map(|(path, content)| path.map(|path| (path, content)))
96 .collect(),
97 source: chunk_list_ref.source,
98 chunk_loading_global,
99 }
100 .cell())
101 }
102
103 #[turbo_tasks::function]
105 pub async fn version(&self) -> Result<Vc<ChunkListVersion>> {
106 compute_chunk_list_version(&self.chunks_contents).await
107 }
108
109 #[turbo_tasks::function]
110 pub(super) async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
111 let this = self.await?;
112
113 let chunks = this
114 .chunks_contents
115 .keys()
116 .map(|s| s.as_str())
117 .collect::<Vec<_>>();
118
119 let script_or_path = match &this.current_chunk_method {
120 CurrentChunkMethodWithData::StringLiteral(path) => Either::Left(StringifyJs(path)),
121 CurrentChunkMethodWithData::DocumentCurrentScript => {
122 Either::Right(CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR)
123 }
124 };
125
126 let mut code = CodeBuilder::default();
127
128 let chunk_lists_global = format!("{}_CHUNK_LISTS", this.chunk_loading_global);
132 writedoc!(
133 code,
134 r#"
137 (globalThis[{chunk_lists_global}] || (globalThis[{chunk_lists_global}] = [])).push({{
138 script: {script_or_path},
139 chunks: {chunks},
140 source: {source}
141 }});
142 "#,
143 chunk_lists_global = StringifyJs(&chunk_lists_global),
144 chunks = StringifyJs(&chunks),
145 source = StringifyJs(&this.source),
146 )?;
147
148 Ok(Code::cell(code.build()))
149 }
150}
151
152#[turbo_tasks::value_impl]
153impl VersionedContent for EcmascriptDevChunkListContent {
154 #[turbo_tasks::function]
155 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
156 let code = self.code().await?;
157 Ok(AssetContent::file(
158 FileContent::Content(File::from(code.source_code().clone())).cell(),
159 ))
160 }
161
162 #[turbo_tasks::function]
163 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>> {
164 Vc::upcast(self.version())
165 }
166
167 #[turbo_tasks::function]
168 async fn update(
169 self: ResolvedVc<Self>,
170 from_version: ResolvedVc<Box<dyn Version>>,
171 ) -> Result<Vc<Update>> {
172 let this = self.await?;
173 update_chunk_list(&this.chunks_contents, self.version(), from_version).await
174 }
175}