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