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