1#![feature(min_specialization)]
2#![feature(arbitrary_self_types)]
3#![feature(arbitrary_self_types_pointers)]
4
5use anyhow::Result;
6use mdxjs::{MdxParseOptions, Options, compile};
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::{ResolvedVc, ValueDefault, Vc};
9use turbo_tasks_fs::{File, FileContent, FileSystemPath, rope::Rope};
10use turbopack_core::{
11 asset::{Asset, AssetContent},
12 ident::AssetIdent,
13 issue::{
14 Issue, IssueDescriptionExt, IssueExt, IssueSource, IssueStage, OptionIssueSource,
15 OptionStyledString, StyledString,
16 },
17 source::Source,
18 source_pos::SourcePos,
19 source_transform::SourceTransform,
20};
21
22#[turbo_tasks::value(shared, operation)]
23#[derive(Hash, Debug, Clone)]
24#[serde(rename_all = "camelCase")]
25pub enum MdxParseConstructs {
26 Commonmark,
27 Gfm,
28}
29
30#[turbo_tasks::value(shared, operation)]
34#[derive(Hash, Debug, Clone)]
35#[serde(rename_all = "camelCase", default)]
36pub struct MdxTransformOptions {
37 pub development: Option<bool>,
38 pub jsx: Option<bool>,
39 pub jsx_runtime: Option<RcStr>,
40 pub jsx_import_source: Option<RcStr>,
41 pub provider_import_source: Option<RcStr>,
45 pub mdx_type: Option<MdxParseConstructs>,
47}
48
49impl Default for MdxTransformOptions {
50 fn default() -> Self {
51 Self {
52 development: Some(true),
53 jsx: Some(false),
54 jsx_runtime: None,
55 jsx_import_source: None,
56 provider_import_source: None,
57 mdx_type: Some(MdxParseConstructs::Commonmark),
58 }
59 }
60}
61
62#[turbo_tasks::value_impl]
63impl MdxTransformOptions {
64 #[turbo_tasks::function]
65 fn default_private() -> Vc<Self> {
66 Self::cell(Default::default())
67 }
68}
69
70impl ValueDefault for MdxTransformOptions {
71 fn value_default() -> Vc<Self> {
72 Self::default_private()
73 }
74}
75
76#[turbo_tasks::value]
77pub struct MdxTransform {
78 options: ResolvedVc<MdxTransformOptions>,
79}
80
81#[turbo_tasks::value_impl]
82impl MdxTransform {
83 #[turbo_tasks::function]
84 pub fn new(options: ResolvedVc<MdxTransformOptions>) -> Vc<Self> {
85 MdxTransform { options }.cell()
86 }
87}
88
89#[turbo_tasks::value_impl]
90impl SourceTransform for MdxTransform {
91 #[turbo_tasks::function]
92 fn transform(&self, source: ResolvedVc<Box<dyn Source>>) -> Vc<Box<dyn Source>> {
93 Vc::upcast(
94 MdxTransformedAsset {
95 options: self.options,
96 source,
97 }
98 .cell(),
99 )
100 }
101}
102
103#[turbo_tasks::value]
104struct MdxTransformedAsset {
105 options: ResolvedVc<MdxTransformOptions>,
106 source: ResolvedVc<Box<dyn Source>>,
107}
108
109#[turbo_tasks::value_impl]
110impl Source for MdxTransformedAsset {
111 #[turbo_tasks::function]
112 fn ident(&self) -> Vc<AssetIdent> {
113 self.source.ident().rename_as(rcstr!("*.tsx"))
114 }
115}
116
117#[turbo_tasks::value_impl]
118impl Asset for MdxTransformedAsset {
119 #[turbo_tasks::function]
120 async fn content(self: ResolvedVc<Self>) -> Result<Vc<AssetContent>> {
121 let this = self.await?;
122 Ok(*transform_process_operation(self)
123 .issue_file_path(this.source.ident().path().owned().await?, "MDX processing")
124 .await?
125 .connect()
126 .await?
127 .content)
128 }
129}
130
131#[turbo_tasks::function(operation)]
132fn transform_process_operation(asset: ResolvedVc<MdxTransformedAsset>) -> Vc<MdxTransformResult> {
133 asset.process()
134}
135
136#[turbo_tasks::value_impl]
137impl MdxTransformedAsset {
138 #[turbo_tasks::function]
139 async fn process(&self) -> Result<Vc<MdxTransformResult>> {
140 let content = self.source.content().await?;
141 let transform_options = self.options.await?;
142
143 let AssetContent::File(file) = &*content else {
144 anyhow::bail!("Unexpected mdx asset content");
145 };
146
147 let FileContent::Content(file) = &*file.await? else {
148 anyhow::bail!("Not able to read mdx file content");
149 };
150
151 let jsx_runtime = if let Some(runtime) = &transform_options.jsx_runtime {
152 match runtime.as_str() {
153 "automatic" => Some(mdxjs::JsxRuntime::Automatic),
154 "classic" => Some(mdxjs::JsxRuntime::Classic),
155 _ => None,
156 }
157 } else {
158 None
159 };
160
161 let parse_options = match transform_options.mdx_type {
162 Some(MdxParseConstructs::Gfm) => MdxParseOptions::gfm(),
163 _ => MdxParseOptions::default(),
164 };
165
166 let options = Options {
167 parse: parse_options,
168 development: transform_options.development.unwrap_or(false),
169 provider_import_source: transform_options
170 .provider_import_source
171 .clone()
172 .map(RcStr::into_owned),
173 jsx: transform_options.jsx.unwrap_or(false), jsx_runtime,
175 jsx_import_source: transform_options
176 .jsx_import_source
177 .clone()
178 .map(RcStr::into_owned),
179 filepath: Some(self.source.ident().path().await?.to_string()),
180 ..Default::default()
181 };
182
183 let result = compile(&file.content().to_str()?, &options);
184
185 match result {
186 Ok(mdx_jsx_component) => Ok(MdxTransformResult {
187 content: AssetContent::file(File::from(Rope::from(mdx_jsx_component)).into())
188 .to_resolved()
189 .await?,
190 }
191 .cell()),
192 Err(err) => {
193 let source = match err.place {
194 Some(p) => {
195 let (start, end) = match *p {
196 markdown::message::Place::Position(p) => (
199 SourcePos {
200 line: (p.start.line - 1) as u32,
201 column: (p.start.column - 1) as u32,
202 },
203 SourcePos {
204 line: (p.end.line - 1) as u32,
205 column: (p.end.column - 1) as u32,
206 },
207 ),
208 markdown::message::Place::Point(p) => {
209 let p = SourcePos {
210 line: (p.line - 1) as u32,
211 column: (p.column - 1) as u32,
212 };
213 (p, p)
214 }
215 };
216
217 IssueSource::from_line_col(self.source, start, end)
218 }
219 None => IssueSource::from_source_only(self.source),
220 };
221
222 MdxIssue {
223 source,
224 reason: RcStr::from(err.reason),
225 mdx_rule_id: RcStr::from(*err.rule_id),
226 mdx_source: RcStr::from(*err.source),
227 }
228 .resolved_cell()
229 .emit();
230
231 Ok(MdxTransformResult {
232 content: AssetContent::File(FileContent::NotFound.resolved_cell())
233 .resolved_cell(),
234 }
235 .cell())
236 }
237 }
238 }
239}
240
241#[turbo_tasks::value]
242struct MdxTransformResult {
243 content: ResolvedVc<AssetContent>,
244}
245
246#[turbo_tasks::value]
247struct MdxIssue {
248 source: IssueSource,
250 reason: RcStr,
252 mdx_rule_id: RcStr,
254 mdx_source: RcStr,
256}
257
258#[turbo_tasks::value_impl]
259impl Issue for MdxIssue {
260 #[turbo_tasks::function]
261 fn file_path(&self) -> Vc<FileSystemPath> {
262 self.source.file_path()
263 }
264
265 #[turbo_tasks::function]
266 fn source(&self) -> Vc<OptionIssueSource> {
267 Vc::cell(Some(self.source))
268 }
269
270 #[turbo_tasks::function]
271 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
272 IssueStage::Parse.cell()
273 }
274
275 #[turbo_tasks::function]
276 fn title(self: Vc<Self>) -> Vc<StyledString> {
277 StyledString::Text(rcstr!("MDX Parse Error")).cell()
278 }
279
280 #[turbo_tasks::function]
281 fn description(&self) -> Vc<OptionStyledString> {
282 Vc::cell(Some(
283 StyledString::Text(self.reason.clone()).resolved_cell(),
284 ))
285 }
286}
287
288pub fn register() {
289 turbo_tasks::register();
290 turbo_tasks_fs::register();
291 turbopack_core::register();
292 turbopack_ecmascript::register();
293 include!(concat!(env!("OUT_DIR"), "/register.rs"));
294}