turbopack_ecmascript/chunk/
placeable.rs1use anyhow::Result;
2use turbo_rcstr::rcstr;
3use turbo_tasks::{ResolvedVc, TryFlatJoinIterExt, Vc};
4use turbo_tasks_fs::{
5 FileJsonContent, FileSystemPath,
6 glob::{Glob, GlobOptions},
7};
8use turbopack_core::{
9 asset::Asset,
10 chunk::ChunkableModule,
11 error::PrettyPrintError,
12 file_source::FileSource,
13 issue::{
14 Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
15 OptionStyledString, StyledString,
16 },
17 module::Module,
18 resolve::{FindContextFileResult, find_context_file, package_json},
19};
20
21use crate::references::{
22 async_module::OptionAsyncModule,
23 esm::{EsmExport, EsmExports},
24};
25
26#[turbo_tasks::value_trait]
27pub trait EcmascriptChunkPlaceable: ChunkableModule + Module + Asset {
28 #[turbo_tasks::function]
29 fn get_exports(self: Vc<Self>) -> Vc<EcmascriptExports>;
30 #[turbo_tasks::function]
31 fn get_async_module(self: Vc<Self>) -> Vc<OptionAsyncModule> {
32 Vc::cell(None)
33 }
34}
35
36#[turbo_tasks::value]
37enum SideEffectsValue {
38 None,
39 Constant(bool),
40 Glob(ResolvedVc<Glob>),
41}
42
43#[turbo_tasks::function]
44async fn side_effects_from_package_json(
45 package_json: FileSystemPath,
46) -> Result<Vc<SideEffectsValue>> {
47 let package_json_file = FileSource::new(package_json).to_resolved().await?;
48 let package_json = &*package_json_file.content().parse_json().await?;
49 if let FileJsonContent::Content(content) = package_json
50 && let Some(side_effects) = content.get("sideEffects")
51 {
52 if let Some(side_effects) = side_effects.as_bool() {
53 return Ok(SideEffectsValue::Constant(side_effects).cell());
54 } else if let Some(side_effects) = side_effects.as_array() {
55 let globs = side_effects
56 .iter()
57 .filter_map(|side_effect| {
58 if let Some(side_effect) = side_effect.as_str() {
59 if side_effect.contains('/') {
60 Some(Glob::new(
61 side_effect.strip_prefix("./").unwrap_or(side_effect).into(),
62 GlobOptions::default(),
63 ))
64 } else {
65 Some(Glob::new(
66 format!("**/{side_effect}").into(),
67 GlobOptions::default(),
68 ))
69 }
70 } else {
71 SideEffectsInPackageJsonIssue {
72 source: IssueSource::from_source_only(ResolvedVc::upcast(
74 package_json_file,
75 )),
76 description: Some(
77 StyledString::Text(
78 format!(
79 "Each element in sideEffects must be a string, but found \
80 {side_effect:?}"
81 )
82 .into(),
83 )
84 .resolved_cell(),
85 ),
86 }
87 .resolved_cell()
88 .emit();
89 None
90 }
91 })
92 .map(|glob| async move {
93 match glob.resolve().await {
94 Ok(glob) => Ok(Some(glob)),
95 Err(err) => {
96 SideEffectsInPackageJsonIssue {
97 source: IssueSource::from_source_only(ResolvedVc::upcast(
99 package_json_file,
100 )),
101 description: Some(
102 StyledString::Text(
103 format!(
104 "Invalid glob in sideEffects: {}",
105 PrettyPrintError(&err)
106 )
107 .into(),
108 )
109 .resolved_cell(),
110 ),
111 }
112 .resolved_cell()
113 .emit();
114 Ok(None)
115 }
116 }
117 })
118 .try_flat_join()
119 .await?;
120 return Ok(
121 SideEffectsValue::Glob(Glob::alternatives(globs).to_resolved().await?).cell(),
122 );
123 } else {
124 SideEffectsInPackageJsonIssue {
125 source: IssueSource::from_source_only(ResolvedVc::upcast(package_json_file)),
127 description: Some(
128 StyledString::Text(
129 format!(
130 "sideEffects must be a boolean or an array, but found {side_effects:?}"
131 )
132 .into(),
133 )
134 .resolved_cell(),
135 ),
136 }
137 .resolved_cell()
138 .emit();
139 }
140 }
141 Ok(SideEffectsValue::None.cell())
142}
143
144#[turbo_tasks::value]
145struct SideEffectsInPackageJsonIssue {
146 source: IssueSource,
147 description: Option<ResolvedVc<StyledString>>,
148}
149
150#[turbo_tasks::value_impl]
151impl Issue for SideEffectsInPackageJsonIssue {
152 #[turbo_tasks::function]
153 fn stage(&self) -> Vc<IssueStage> {
154 IssueStage::Parse.cell()
155 }
156
157 fn severity(&self) -> IssueSeverity {
158 IssueSeverity::Warning
159 }
160
161 #[turbo_tasks::function]
162 fn file_path(&self) -> Vc<FileSystemPath> {
163 self.source.file_path()
164 }
165
166 #[turbo_tasks::function]
167 fn title(&self) -> Vc<StyledString> {
168 StyledString::Text(rcstr!("Invalid value for sideEffects in package.json")).cell()
169 }
170
171 #[turbo_tasks::function]
172 fn description(&self) -> Vc<OptionStyledString> {
173 Vc::cell(self.description)
174 }
175
176 #[turbo_tasks::function]
177 fn source(&self) -> Vc<OptionIssueSource> {
178 Vc::cell(Some(self.source))
179 }
180}
181
182#[turbo_tasks::value(shared)]
183#[derive(Copy, Clone)]
184pub enum SideEffectsDeclaration {
185 SideEffectFree,
186 SideEffectful,
187 None,
188}
189
190#[turbo_tasks::function]
191pub async fn get_side_effect_free_declaration(
192 path: FileSystemPath,
193 side_effect_free_packages: Option<Vc<Glob>>,
194) -> Result<Vc<SideEffectsDeclaration>> {
195 if let Some(side_effect_free_packages) = side_effect_free_packages
196 && side_effect_free_packages.await?.matches(&path.path)
197 {
198 return Ok(SideEffectsDeclaration::SideEffectFree.cell());
199 }
200
201 let find_package_json = find_context_file(path.parent(), package_json(), false).await?;
202
203 if let FindContextFileResult::Found(package_json, _) = &*find_package_json {
204 match *side_effects_from_package_json(package_json.clone()).await? {
205 SideEffectsValue::None => {}
206 SideEffectsValue::Constant(side_effects) => {
207 return Ok(if side_effects {
208 SideEffectsDeclaration::SideEffectful
209 } else {
210 SideEffectsDeclaration::SideEffectFree
211 }
212 .cell());
213 }
214 SideEffectsValue::Glob(glob) => {
215 if let Some(rel_path) = package_json.parent().get_relative_path_to(&path) {
216 let rel_path = rel_path.strip_prefix("./").unwrap_or(&rel_path);
217 return Ok(if glob.await?.matches(rel_path) {
218 SideEffectsDeclaration::SideEffectful
219 } else {
220 SideEffectsDeclaration::SideEffectFree
221 }
222 .cell());
223 }
224 }
225 }
226 }
227
228 Ok(SideEffectsDeclaration::None.cell())
229}
230
231#[turbo_tasks::value(shared)]
232pub enum EcmascriptExports {
233 EsmExports(ResolvedVc<EsmExports>),
235 DynamicNamespace,
237 CommonJs,
239 EmptyCommonJs,
241 Value,
243 Unknown,
245 None,
247}
248
249#[turbo_tasks::value_impl]
250impl EcmascriptExports {
251 #[turbo_tasks::function]
252 pub async fn split_locals_and_reexports(&self) -> Result<Vc<bool>> {
253 Ok(match self {
254 EcmascriptExports::EsmExports(exports) => {
255 let exports = exports.await?;
256 let has_reexports = !exports.star_exports.is_empty()
257 || exports.exports.iter().any(|(_, export)| {
258 matches!(
259 export,
260 EsmExport::ImportedBinding(..) | EsmExport::ImportedNamespace(_)
261 )
262 });
263 Vc::cell(has_reexports)
264 }
265 _ => Vc::cell(false),
266 })
267 }
268}