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(
124 this.source.ident().path().await?.clone_value(),
125 "MDX processing",
126 )
127 .await?
128 .connect()
129 .await?
130 .content)
131 }
132}
133
134#[turbo_tasks::function(operation)]
135fn transform_process_operation(asset: ResolvedVc<MdxTransformedAsset>) -> Vc<MdxTransformResult> {
136 asset.process()
137}
138
139#[turbo_tasks::value_impl]
140impl MdxTransformedAsset {
141 #[turbo_tasks::function]
142 async fn process(&self) -> Result<Vc<MdxTransformResult>> {
143 let content = self.source.content().await?;
144 let transform_options = self.options.await?;
145
146 let AssetContent::File(file) = &*content else {
147 anyhow::bail!("Unexpected mdx asset content");
148 };
149
150 let FileContent::Content(file) = &*file.await? else {
151 anyhow::bail!("Not able to read mdx file content");
152 };
153
154 let jsx_runtime = if let Some(runtime) = &transform_options.jsx_runtime {
155 match runtime.as_str() {
156 "automatic" => Some(mdxjs::JsxRuntime::Automatic),
157 "classic" => Some(mdxjs::JsxRuntime::Classic),
158 _ => None,
159 }
160 } else {
161 None
162 };
163
164 let parse_options = match transform_options.mdx_type {
165 Some(MdxParseConstructs::Gfm) => MdxParseOptions::gfm(),
166 _ => MdxParseOptions::default(),
167 };
168
169 let options = Options {
170 parse: parse_options,
171 development: transform_options.development.unwrap_or(false),
172 provider_import_source: transform_options
173 .provider_import_source
174 .clone()
175 .map(RcStr::into_owned),
176 jsx: transform_options.jsx.unwrap_or(false), jsx_runtime,
178 jsx_import_source: transform_options
179 .jsx_import_source
180 .clone()
181 .map(RcStr::into_owned),
182 filepath: Some(self.source.ident().path().await?.to_string()),
183 ..Default::default()
184 };
185
186 let result = compile(&file.content().to_str()?, &options);
187
188 match result {
189 Ok(mdx_jsx_component) => Ok(MdxTransformResult {
190 content: AssetContent::file(File::from(Rope::from(mdx_jsx_component)).into())
191 .to_resolved()
192 .await?,
193 }
194 .cell()),
195 Err(err) => {
196 let source = match err.place {
197 Some(p) => {
198 let (start, end) = match *p {
199 markdown::message::Place::Position(p) => (
202 SourcePos {
203 line: (p.start.line - 1) as u32,
204 column: (p.start.column - 1) as u32,
205 },
206 SourcePos {
207 line: (p.end.line - 1) as u32,
208 column: (p.end.column - 1) as u32,
209 },
210 ),
211 markdown::message::Place::Point(p) => {
212 let p = SourcePos {
213 line: (p.line - 1) as u32,
214 column: (p.column - 1) as u32,
215 };
216 (p, p)
217 }
218 };
219
220 IssueSource::from_line_col(self.source, start, end)
221 }
222 None => IssueSource::from_source_only(self.source),
223 };
224
225 MdxIssue {
226 source,
227 reason: RcStr::from(err.reason),
228 mdx_rule_id: RcStr::from(*err.rule_id),
229 mdx_source: RcStr::from(*err.source),
230 }
231 .resolved_cell()
232 .emit();
233
234 Ok(MdxTransformResult {
235 content: AssetContent::File(FileContent::NotFound.resolved_cell())
236 .resolved_cell(),
237 }
238 .cell())
239 }
240 }
241 }
242}
243
244#[turbo_tasks::value]
245struct MdxTransformResult {
246 content: ResolvedVc<AssetContent>,
247}
248
249#[turbo_tasks::value]
250struct MdxIssue {
251 source: IssueSource,
253 reason: RcStr,
255 mdx_rule_id: RcStr,
257 mdx_source: RcStr,
259}
260
261#[turbo_tasks::value_impl]
262impl Issue for MdxIssue {
263 #[turbo_tasks::function]
264 fn file_path(&self) -> Vc<FileSystemPath> {
265 self.source.file_path()
266 }
267
268 #[turbo_tasks::function]
269 fn source(&self) -> Vc<OptionIssueSource> {
270 Vc::cell(Some(self.source))
271 }
272
273 #[turbo_tasks::function]
274 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
275 IssueStage::Parse.cell()
276 }
277
278 #[turbo_tasks::function]
279 fn title(self: Vc<Self>) -> Vc<StyledString> {
280 StyledString::Text(rcstr!("MDX Parse Error")).cell()
281 }
282
283 #[turbo_tasks::function]
284 fn description(&self) -> Vc<OptionStyledString> {
285 Vc::cell(Some(
286 StyledString::Text(self.reason.clone()).resolved_cell(),
287 ))
288 }
289}
290
291pub fn register() {
292 turbo_tasks::register();
293 turbo_tasks_fs::register();
294 turbopack_core::register();
295 turbopack_ecmascript::register();
296 include!(concat!(env!("OUT_DIR"), "/register.rs"));
297}