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,
10    raw_module::RawModule,
11    reference::{ModuleReference, ModuleReferences},
12    reference_type::{CommonJsReferenceSubType, ReferenceType},
13    resolve::{
14        ModuleResolveResult,
15        origin::{ResolveOrigin, ResolveOriginExt},
16        parse::Request,
17    },
18    source::Source,
19};
20// TODO remove this
21pub use turbopack_resolve::typescript as resolve;
22use turbopack_resolve::{
23    ecmascript::{apply_cjs_specific_options, cjs_resolve},
24    typescript::{read_from_tsconfigs, read_tsconfigs, type_resolve},
25};
26
27#[turbo_tasks::value]
28pub struct TsConfigModuleAsset {
29    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
30    pub source: ResolvedVc<Box<dyn Source>>,
31}
32
33#[turbo_tasks::value_impl]
34impl TsConfigModuleAsset {
35    #[turbo_tasks::function]
36    pub fn new(
37        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
38        source: ResolvedVc<Box<dyn Source>>,
39    ) -> Vc<Self> {
40        Self::cell(TsConfigModuleAsset { origin, source })
41    }
42}
43
44#[turbo_tasks::value_impl]
45impl Module for TsConfigModuleAsset {
46    #[turbo_tasks::function]
47    fn ident(&self) -> Vc<AssetIdent> {
48        self.source.ident()
49    }
50
51    #[turbo_tasks::function]
52    async fn references(&self) -> Result<Vc<ModuleReferences>> {
53        let mut references = Vec::new();
54        let configs = read_tsconfigs(
55            self.source.content().file_content(),
56            self.source,
57            apply_cjs_specific_options(
58                self.origin
59                    .resolve_options(ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined))
60                    .await?,
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
178#[turbo_tasks::value_impl]
179impl Asset for TsConfigModuleAsset {
180    #[turbo_tasks::function]
181    fn content(&self) -> Vc<AssetContent> {
182        self.source.content()
183    }
184}
185
186#[turbo_tasks::value]
187#[derive(Hash, Debug)]
188pub struct CompilerReference {
189    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
190    pub request: ResolvedVc<Request>,
191}
192
193#[turbo_tasks::value_impl]
194impl CompilerReference {
195    #[turbo_tasks::function]
196    pub fn new(
197        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
198        request: ResolvedVc<Request>,
199    ) -> Vc<Self> {
200        Self::cell(CompilerReference { origin, request })
201    }
202}
203
204#[turbo_tasks::value_impl]
205impl ModuleReference for CompilerReference {
206    #[turbo_tasks::function]
207    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
208        cjs_resolve(
209            *self.origin,
210            *self.request,
211            CommonJsReferenceSubType::Undefined,
212            None,
213            false,
214        )
215    }
216}
217
218#[turbo_tasks::value_impl]
219impl ValueToString for CompilerReference {
220    #[turbo_tasks::function]
221    async fn to_string(&self) -> Result<Vc<RcStr>> {
222        Ok(Vc::cell(
223            format!("compiler reference {}", self.request.to_string().await?).into(),
224        ))
225    }
226}
227
228#[turbo_tasks::value]
229#[derive(Hash, Debug)]
230pub struct TsExtendsReference {
231    pub config: ResolvedVc<Box<dyn Source>>,
232}
233
234#[turbo_tasks::value_impl]
235impl TsExtendsReference {
236    #[turbo_tasks::function]
237    pub fn new(config: ResolvedVc<Box<dyn Source>>) -> Vc<Self> {
238        Self::cell(TsExtendsReference { config })
239    }
240}
241
242#[turbo_tasks::value_impl]
243impl ModuleReference for TsExtendsReference {
244    #[turbo_tasks::function]
245    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
246        Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
247            RawModule::new(*ResolvedVc::upcast(self.config))
248                .to_resolved()
249                .await?,
250        )))
251    }
252}
253
254#[turbo_tasks::value_impl]
255impl ValueToString for TsExtendsReference {
256    #[turbo_tasks::function]
257    async fn to_string(&self) -> Result<Vc<RcStr>> {
258        Ok(Vc::cell(
259            format!(
260                "tsconfig extends {}",
261                self.config.ident().to_string().await?,
262            )
263            .into(),
264        ))
265    }
266}
267
268#[turbo_tasks::value]
269#[derive(Hash, Debug)]
270pub struct TsNodeRequireReference {
271    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
272    pub request: ResolvedVc<Request>,
273}
274
275#[turbo_tasks::value_impl]
276impl TsNodeRequireReference {
277    #[turbo_tasks::function]
278    pub fn new(
279        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
280        request: ResolvedVc<Request>,
281    ) -> Vc<Self> {
282        Self::cell(TsNodeRequireReference { origin, request })
283    }
284}
285
286#[turbo_tasks::value_impl]
287impl ModuleReference for TsNodeRequireReference {
288    #[turbo_tasks::function]
289    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
290        cjs_resolve(
291            *self.origin,
292            *self.request,
293            CommonJsReferenceSubType::Undefined,
294            None,
295            false,
296        )
297    }
298}
299
300#[turbo_tasks::value_impl]
301impl ValueToString for TsNodeRequireReference {
302    #[turbo_tasks::function]
303    async fn to_string(&self) -> Result<Vc<RcStr>> {
304        Ok(Vc::cell(
305            format!(
306                "tsconfig tsnode require {}",
307                self.request.to_string().await?
308            )
309            .into(),
310        ))
311    }
312}
313
314#[turbo_tasks::value]
315#[derive(Hash, Debug)]
316pub struct TsConfigTypesReference {
317    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
318    pub request: ResolvedVc<Request>,
319}
320
321#[turbo_tasks::value_impl]
322impl TsConfigTypesReference {
323    #[turbo_tasks::function]
324    pub fn new(
325        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
326        request: ResolvedVc<Request>,
327    ) -> Vc<Self> {
328        Self::cell(TsConfigTypesReference { origin, request })
329    }
330}
331
332#[turbo_tasks::value_impl]
333impl ModuleReference for TsConfigTypesReference {
334    #[turbo_tasks::function]
335    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
336        type_resolve(*self.origin, *self.request)
337    }
338}
339
340#[turbo_tasks::value_impl]
341impl ValueToString for TsConfigTypesReference {
342    #[turbo_tasks::function]
343    async fn to_string(&self) -> Result<Vc<RcStr>> {
344        Ok(Vc::cell(
345            format!("tsconfig types {}", self.request.to_string().await?,).into(),
346        ))
347    }
348}