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};
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(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, NonLocalValue, Encode, Decode)]
32enum CurrentChunkMethodWithData {
33 StringLiteral(RcStr),
34 DocumentCurrentScript,
35}
36
37#[turbo_tasks::value]
38pub struct EcmascriptDevChunkListContent {
39 current_chunk_method: CurrentChunkMethodWithData,
40 #[bincode(with = "turbo_bincode::indexmap")]
41 pub(super) chunks_contents: FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
42 source: EcmascriptDevChunkListSource,
43 chunk_loading_global: RcStr,
45}
46
47#[turbo_tasks::value_impl]
48impl EcmascriptDevChunkListContent {
49 #[turbo_tasks::function]
51 pub async fn new(chunk_list: Vc<EcmascriptDevChunkList>) -> Result<Vc<Self>> {
52 let chunk_list_ref = chunk_list.await?;
53 let output_root = chunk_list_ref.chunking_context.output_root().await?;
54 let current_chunk_method = match *chunk_list_ref
55 .chunking_context
56 .current_chunk_method()
57 .await?
58 {
59 CurrentChunkMethod::StringLiteral => {
60 let path = output_root
61 .get_path_to(&*chunk_list.path().await?)
62 .context("chunk list path not in output root")?
63 .into();
64 CurrentChunkMethodWithData::StringLiteral(path)
65 }
66 CurrentChunkMethod::DocumentCurrentScript => {
67 CurrentChunkMethodWithData::DocumentCurrentScript
68 }
69 };
70 let chunk_loading_global = (*chunk_list_ref
71 .chunking_context
72 .chunk_loading_global()
73 .await?)
74 .clone();
75 Ok(EcmascriptDevChunkListContent {
76 current_chunk_method,
77 chunks_contents: chunk_list_ref
78 .chunks
79 .await?
80 .iter()
81 .map(async |chunk| {
82 Ok((
83 output_root
84 .get_path_to(&*chunk.path().await?)
85 .map(|path| path.to_string()),
86 chunk.versioned_content().to_resolved().await?,
87 ))
88 })
89 .try_join()
90 .await?
91 .into_iter()
92 .filter_map(|(path, content)| path.map(|path| (path, content)))
93 .collect(),
94 source: chunk_list_ref.source,
95 chunk_loading_global,
96 }
97 .cell())
98 }
99
100 #[turbo_tasks::function]
102 pub async fn version(&self) -> Result<Vc<ChunkListVersion>> {
103 compute_chunk_list_version(&self.chunks_contents).await
104 }
105
106 #[turbo_tasks::function]
107 pub(super) async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
108 let this = self.await?;
109
110 let chunks = this
111 .chunks_contents
112 .keys()
113 .map(|s| s.as_str())
114 .collect::<Vec<_>>();
115
116 let script_or_path = match &this.current_chunk_method {
117 CurrentChunkMethodWithData::StringLiteral(path) => Either::Left(StringifyJs(path)),
118 CurrentChunkMethodWithData::DocumentCurrentScript => {
119 Either::Right(CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR)
120 }
121 };
122
123 let mut code = CodeBuilder::default();
124
125 let chunk_lists_global = format!("{}_CHUNK_LISTS", this.chunk_loading_global);
129 writedoc!(
130 code,
131 r#"
134 (globalThis[{chunk_lists_global}] || (globalThis[{chunk_lists_global}] = [])).push({{
135 script: {script_or_path},
136 chunks: {chunks},
137 source: {source}
138 }});
139 "#,
140 chunk_lists_global = StringifyJs(&chunk_lists_global),
141 chunks = StringifyJs(&chunks),
142 source = StringifyJs(&this.source),
143 )?;
144
145 Ok(Code::cell(code.build()))
146 }
147}
148
149#[turbo_tasks::value_impl]
150impl VersionedContent for EcmascriptDevChunkListContent {
151 #[turbo_tasks::function]
152 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
153 let code = self.code().await?;
154 Ok(AssetContent::file(
155 FileContent::Content(File::from(code.source_code().clone())).cell(),
156 ))
157 }
158
159 #[turbo_tasks::function]
160 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>> {
161 Vc::upcast(self.version())
162 }
163
164 #[turbo_tasks::function]
165 async fn update(
166 self: ResolvedVc<Self>,
167 from_version: ResolvedVc<Box<dyn Version>>,
168 ) -> Result<Vc<Update>> {
169 let this = self.await?;
170 update_chunk_list(&this.chunks_contents, self.version(), from_version).await
171 }
172}