Skip to main content

turbopack_ecmascript/typescript/
mod.rs

1use anyhow::Result;
2use serde_json::Value as JsonValue;
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
5use turbo_tasks_fs::DirectoryContent;
6use turbopack_core::{
7    asset::Asset,
8    chunk::{ChunkingType, TracedMode},
9    ident::AssetIdent,
10    module::{Module, ModuleSideEffects},
11    raw_module::RawModule,
12    reference::{ModuleReference, ModuleReferences},
13    reference_type::CommonJsReferenceSubType,
14    resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
15    source::Source,
16};
17// TODO remove this
18pub use turbopack_resolve::typescript as resolve;
19use turbopack_resolve::{
20    ecmascript::{apply_cjs_specific_options, cjs_resolve},
21    typescript::{read_from_tsconfigs, read_tsconfigs, type_resolve},
22};
23
24#[turbo_tasks::value]
25pub struct TsConfigModuleAsset {
26    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
27    pub source: ResolvedVc<Box<dyn Source>>,
28}
29
30#[turbo_tasks::value_impl]
31impl TsConfigModuleAsset {
32    #[turbo_tasks::function]
33    pub fn new(
34        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
35        source: ResolvedVc<Box<dyn Source>>,
36    ) -> Vc<Self> {
37        Self::cell(TsConfigModuleAsset { origin, source })
38    }
39}
40
41#[turbo_tasks::value_impl]
42impl Module for TsConfigModuleAsset {
43    #[turbo_tasks::function]
44    fn ident(&self) -> Vc<AssetIdent> {
45        self.source.ident()
46    }
47
48    #[turbo_tasks::function]
49    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
50        Vc::cell(Some(self.source))
51    }
52
53    #[turbo_tasks::function]
54    async fn references(&self) -> Result<Vc<ModuleReferences>> {
55        let mut references = Vec::new();
56        let configs = read_tsconfigs(
57            self.source.content().file_content(),
58            self.source,
59            apply_cjs_specific_options(self.origin.into_trait_ref().await?.resolve_options()),
60        )
61        .await?;
62        references.extend(
63            configs[1..]
64                .iter()
65                .map(|(_, config_asset)| async move {
66                    Ok(ResolvedVc::upcast(
67                        TsExtendsReference::new(**config_asset)
68                            .to_resolved()
69                            .await?,
70                    ))
71                })
72                .try_join()
73                .await?,
74        );
75
76        // ts-node options
77        {
78            let compiler = read_from_tsconfigs(&configs, |json, source| {
79                json["ts-node"]["compiler"]
80                    .as_str()
81                    .map(|s| (source, s.to_string()))
82            })
83            .await?;
84            let compiler = match compiler {
85                Some((_, c)) => RcStr::from(c),
86                None => rcstr!("typescript"),
87            };
88            references.push(ResolvedVc::upcast(
89                CompilerReference::new(*self.origin, Request::parse(compiler.into()))
90                    .to_resolved()
91                    .await?,
92            ));
93            let require = read_from_tsconfigs(&configs, |json, source| {
94                if let JsonValue::Array(array) = &json["ts-node"]["require"] {
95                    Some(
96                        array
97                            .iter()
98                            .filter_map(|name| name.as_str().map(|s| (source, RcStr::from(s))))
99                            .collect::<Vec<_>>(),
100                    )
101                } else {
102                    None
103                }
104            })
105            .await?;
106            if let Some(require) = require {
107                for (_, request) in require {
108                    references.push(ResolvedVc::upcast(
109                        TsNodeRequireReference::new(*self.origin, Request::parse(request.into()))
110                            .to_resolved()
111                            .await?,
112                    ));
113                }
114            }
115        }
116        // compilerOptions
117        {
118            let types = read_from_tsconfigs(&configs, |json, source| {
119                if let JsonValue::Array(array) = &json["compilerOptions"]["types"] {
120                    Some(
121                        array
122                            .iter()
123                            .filter_map(|name| name.as_str().map(|s| (source, RcStr::from(s))))
124                            .collect::<Vec<_>>(),
125                    )
126                } else {
127                    None
128                }
129            })
130            .await?;
131            let types = if let Some(types) = types {
132                types
133            } else {
134                let mut all_types = Vec::new();
135                let mut current = self.source.ident().await?.path.parent();
136                loop {
137                    if let DirectoryContent::Entries(entries) =
138                        &*current.join("node_modules/@types")?.read_dir().await?
139                    {
140                        all_types.extend(entries.iter().filter_map(|(name, _)| {
141                            if name.starts_with('.') {
142                                None
143                            } else {
144                                Some((self.source, name.clone()))
145                            }
146                        }));
147                    }
148                    let parent = current.parent();
149                    if parent == current {
150                        break;
151                    }
152                    current = parent;
153                }
154                all_types
155            };
156            for (_, name) in types {
157                references.push(ResolvedVc::upcast(
158                    TsConfigTypesReference::new(
159                        *self.origin,
160                        Request::module(
161                            name.into(),
162                            RcStr::default().into(),
163                            RcStr::default(),
164                            RcStr::default(),
165                        ),
166                    )
167                    .to_resolved()
168                    .await?,
169                ));
170            }
171        }
172        Ok(Vc::cell(references))
173    }
174
175    #[turbo_tasks::function]
176    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
177        ModuleSideEffects::SideEffectful.cell()
178    }
179}
180
181#[turbo_tasks::value]
182#[derive(Hash, Debug, ValueToString)]
183#[value_to_string("compiler reference {request}")]
184pub struct CompilerReference {
185    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
186    pub request: ResolvedVc<Request>,
187}
188
189#[turbo_tasks::value_impl]
190impl CompilerReference {
191    #[turbo_tasks::function]
192    pub fn new(
193        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
194        request: ResolvedVc<Request>,
195    ) -> Vc<Self> {
196        Self::cell(CompilerReference { origin, request })
197    }
198}
199
200#[turbo_tasks::value_impl]
201impl ModuleReference for CompilerReference {
202    #[turbo_tasks::function]
203    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
204        cjs_resolve(
205            *self.origin,
206            *self.request,
207            CommonJsReferenceSubType::Undefined,
208            None,
209            ResolveErrorMode::Error,
210        )
211    }
212
213    fn chunking_type(&self) -> Option<ChunkingType> {
214        Some(ChunkingType::Traced {
215            mode: TracedMode::Transitive,
216        })
217    }
218}
219
220#[turbo_tasks::value]
221#[derive(Hash, Debug, ValueToString)]
222#[value_to_string("tsconfig extends {}", self.config.ident())]
223pub struct TsExtendsReference {
224    pub config: ResolvedVc<Box<dyn Source>>,
225}
226
227#[turbo_tasks::value_impl]
228impl TsExtendsReference {
229    #[turbo_tasks::function]
230    pub fn new(config: ResolvedVc<Box<dyn Source>>) -> Vc<Self> {
231        Self::cell(TsExtendsReference { config })
232    }
233}
234
235#[turbo_tasks::value_impl]
236impl ModuleReference for TsExtendsReference {
237    #[turbo_tasks::function]
238    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
239        Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
240            RawModule::new(*self.config).to_resolved().await?,
241        )))
242    }
243
244    fn chunking_type(&self) -> Option<ChunkingType> {
245        Some(ChunkingType::Traced {
246            mode: TracedMode::Transitive,
247        })
248    }
249}
250
251#[turbo_tasks::value]
252#[derive(Hash, Debug, ValueToString)]
253#[value_to_string("tsconfig tsnode require {request}")]
254pub struct TsNodeRequireReference {
255    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
256    pub request: ResolvedVc<Request>,
257}
258
259#[turbo_tasks::value_impl]
260impl TsNodeRequireReference {
261    #[turbo_tasks::function]
262    pub fn new(
263        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
264        request: ResolvedVc<Request>,
265    ) -> Vc<Self> {
266        Self::cell(TsNodeRequireReference { origin, request })
267    }
268}
269
270#[turbo_tasks::value_impl]
271impl ModuleReference for TsNodeRequireReference {
272    #[turbo_tasks::function]
273    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
274        cjs_resolve(
275            *self.origin,
276            *self.request,
277            CommonJsReferenceSubType::Undefined,
278            None,
279            ResolveErrorMode::Error,
280        )
281    }
282
283    fn chunking_type(&self) -> Option<ChunkingType> {
284        Some(ChunkingType::Traced {
285            mode: TracedMode::Transitive,
286        })
287    }
288}
289
290#[turbo_tasks::value]
291#[derive(Hash, Debug, ValueToString)]
292#[value_to_string("tsconfig types {request}")]
293pub struct TsConfigTypesReference {
294    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
295    pub request: ResolvedVc<Request>,
296}
297
298#[turbo_tasks::value_impl]
299impl TsConfigTypesReference {
300    #[turbo_tasks::function]
301    pub fn new(
302        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
303        request: ResolvedVc<Request>,
304    ) -> Vc<Self> {
305        Self::cell(TsConfigTypesReference { origin, request })
306    }
307}
308
309#[turbo_tasks::value_impl]
310impl ModuleReference for TsConfigTypesReference {
311    #[turbo_tasks::function]
312    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
313        type_resolve(*self.origin, *self.request)
314    }
315
316    fn chunking_type(&self) -> Option<ChunkingType> {
317        Some(ChunkingType::Traced {
318            mode: TracedMode::Transitive,
319        })
320    }
321}