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