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, ValueToString)]
182#[value_to_string("compiler reference {request}")]
183pub struct CompilerReference {
184    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
185    pub request: ResolvedVc<Request>,
186}
187
188#[turbo_tasks::value_impl]
189impl CompilerReference {
190    #[turbo_tasks::function]
191    pub fn new(
192        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
193        request: ResolvedVc<Request>,
194    ) -> Vc<Self> {
195        Self::cell(CompilerReference { origin, request })
196    }
197}
198
199#[turbo_tasks::value_impl]
200impl ModuleReference for CompilerReference {
201    #[turbo_tasks::function]
202    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
203        cjs_resolve(
204            *self.origin,
205            *self.request,
206            CommonJsReferenceSubType::Undefined,
207            None,
208            ResolveErrorMode::Error,
209        )
210    }
211}
212
213#[turbo_tasks::value]
214#[derive(Hash, Debug, ValueToString)]
215#[value_to_string("tsconfig extends {}", self.config.ident())]
216pub struct TsExtendsReference {
217    pub config: ResolvedVc<Box<dyn Source>>,
218}
219
220#[turbo_tasks::value_impl]
221impl TsExtendsReference {
222    #[turbo_tasks::function]
223    pub fn new(config: ResolvedVc<Box<dyn Source>>) -> Vc<Self> {
224        Self::cell(TsExtendsReference { config })
225    }
226}
227
228#[turbo_tasks::value_impl]
229impl ModuleReference for TsExtendsReference {
230    #[turbo_tasks::function]
231    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
232        Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
233            RawModule::new(*self.config).to_resolved().await?,
234        )))
235    }
236}
237
238#[turbo_tasks::value]
239#[derive(Hash, Debug, ValueToString)]
240#[value_to_string("tsconfig tsnode require {request}")]
241pub struct TsNodeRequireReference {
242    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
243    pub request: ResolvedVc<Request>,
244}
245
246#[turbo_tasks::value_impl]
247impl TsNodeRequireReference {
248    #[turbo_tasks::function]
249    pub fn new(
250        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
251        request: ResolvedVc<Request>,
252    ) -> Vc<Self> {
253        Self::cell(TsNodeRequireReference { origin, request })
254    }
255}
256
257#[turbo_tasks::value_impl]
258impl ModuleReference for TsNodeRequireReference {
259    #[turbo_tasks::function]
260    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
261        cjs_resolve(
262            *self.origin,
263            *self.request,
264            CommonJsReferenceSubType::Undefined,
265            None,
266            ResolveErrorMode::Error,
267        )
268    }
269}
270
271#[turbo_tasks::value]
272#[derive(Hash, Debug, ValueToString)]
273#[value_to_string("tsconfig types {request}")]
274pub struct TsConfigTypesReference {
275    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
276    pub request: ResolvedVc<Request>,
277}
278
279#[turbo_tasks::value_impl]
280impl TsConfigTypesReference {
281    #[turbo_tasks::function]
282    pub fn new(
283        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
284        request: ResolvedVc<Request>,
285    ) -> Vc<Self> {
286        Self::cell(TsConfigTypesReference { origin, request })
287    }
288}
289
290#[turbo_tasks::value_impl]
291impl ModuleReference for TsConfigTypesReference {
292    #[turbo_tasks::function]
293    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
294        type_resolve(*self.origin, *self.request)
295    }
296}