use crate::test_utils::{ hast_util_to_swc::hast_util_to_swc, mdast_util_to_hast::mdast_util_to_hast, mdx_plugin_recma_document::{mdx_plugin_recma_document, Options as DocumentOptions}, mdx_plugin_recma_jsx_rewrite::{mdx_plugin_recma_jsx_rewrite, Options as RewriteOptions}, swc::{parse_esm, parse_expression, serialize}, }; use markdown::{to_mdast, Constructs, Location, ParseOptions}; pub use super::mdx_plugin_recma_document::JsxRuntime; use swc_common::comments::{Comments, SingleThreadedComments}; // use swc_ecma_transforms_react::{jsx, Options as JsxBuildOptions}; // use swc_ecma_visit::VisitMutWith; // To do: use `Format`. /// Format the file is in (default: `Format::Detect`). #[allow(dead_code)] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum Format { /// Use `Format::Markdown` for files with an extension in `md_extensions` /// and `Format::Mdx` otherwise. #[default] Detect, /// Treat file as MDX. Mdx, /// Treat file as plain vanilla markdown. Markdown, } // To do: use `OutputFormat`. /// Output format to generate (default: `OutputFormat::Program`). #[allow(dead_code)] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum OutputFormat { /// The `Program` format will use import statements to import the JSX /// runtime (and optionally provider) and use an export statement to yield /// the `MDXContent` component. #[default] Program, /// The `FunctionBody` format will get the JSX runtime (and optionally /// provider) from `arguments[0]`, rewrite export statements, and use a /// return statement to yield what was exported. /// Normally, this output format will throw on `import` (and /// `export … from`) statements, but you can support them by setting /// `options.useDynamicImport`. FunctionBody, } /// Configuration (optional). #[derive(Clone, Debug, Default)] pub struct Options { // /// List of markdown extensions, with dot. // /// // /// Default: `vec![".md".into(), ".markdown".into(), ".mdown".into(), ".mkdn".into(), ".mkd".into(), ".mdwn".into(), ".mkdown".into(), ".ron".into()]`. // pub md_extensions: Option>, // /// List of MDX extensions, with dot. // /// // /// Default: `vec![".mdx".into()]`. // pub mdx_extensions: Option>, // /// Format the file is in (default: `Format::Detect`). // pub format: Format, // /// To do: support `output_format: FunctionBody // /// Output format to generate (default: `OutputFormat::Program`). // /// // /// In most cases `OutputFormat::Program` should be used, as it results in a // /// whole program. // /// `OutputFormat::FunctionBody` can be used to compile to code that can be // /// `eval`ed. // /// In some cases, you might want to do that, such as when compiling on the // /// server and running on the client. // pub output_format: OutputFormat, // /// Whether to compile to dynamic import expressions (default: // /// `false`). // /// // /// This option applies when `options.output_format` is // /// `OutputFormat::FunctionBody`. // /// // /// This project can turn import statements (`import x from 'y'`) into // /// dynamic imports (`const {x} = await import('y')`). // /// This is useful because import statements only work at the top level of // /// JavaScript modules, whereas `import()` is available inside function // /// bodies. // /// // /// When you turn `use_dynamic_import` on, you should probably set // /// `options.base_url` too. // pub use_dynamic_import: bool, // /// Resolve `import`s (and `export … from`, `import.meta.url`) from this // /// URL (default: `None`, example: `Some("https://example.com/".into())`). // /// // /// Relative specifiers are non-absolute URLs that start with `/`, `./`, or // /// `../`. // /// For example: `/index.js`, `./folder/file.js`, or `../main.js`. // /// // /// This option is useful when code will run in a different place. // /// One example is when `.mdx` files are in path *a* but compiled to path // /// *b* and imports should run relative the path *b*. // /// Another example is when evaluating code, whether in Node or a browser. // pub base_url: Option, /// Whether to add extra information to error messages in generated code /// (default: `false`). pub development: bool, // To do: some alternative to generate source maps. // SourceMapGenerator /// Place to import a provider from (default: `None`, example: /// `Some("@mdx-js/react").into()`). /// /// Useful for runtimes that support context (React, Preact). /// The provider must export a `useMDXComponents`, which is called to /// access an object of components. pub provider_import_source: Option, /// Whether to keep JSX (default: `false`). /// /// The default is to compile JSX away so that the resulting file is /// immediately runnable. pub jsx: bool, /// JSX runtime to use (default: `Some(JsxRuntime::Automatic)`). /// /// The classic runtime compiles to calls such as `h('p')`, the automatic /// runtime compiles to /// `import _jsx from '$importSource/jsx-runtime'\n_jsx('p')`. pub jsx_runtime: Option, /// Place to import automatic JSX runtimes from (`Option`, default: /// `Some("react".into())`). /// /// When in the automatic runtime, this is used to define an import for /// `_Fragment`, `_jsx`, and `_jsxs`. pub jsx_import_source: Option, /// Pragma for JSX (default: `Some("React.createElement".into())`). /// /// When in the classic runtime, this is used as an identifier for function /// calls: `` to `React.createElement('x')`. /// /// You should most probably define `pragma_frag` and `pragma_import_source` /// too when changing this. pub pragma: Option, /// Pragma for JSX fragments (default: `Some("React.Fragment".into())`). /// /// When in the classic runtime, this is used as an identifier for /// fragments: `<>` to `React.createElement(React.Fragment)`. /// /// You should most probably define `pragma` and `pragma_import_source` /// too when changing this. pub pragma_frag: Option, /// Where to import the identifier of `pragma` from (default: /// `Some("react".into())`). /// /// When in the classic runtime, this is used to import the `pragma` /// function. /// To illustrate with an example: when `pragma` is `"a.b"` and /// `pragma_import_source` is `"c"`, the following will be generated: /// `import a from 'c'`. pub pragma_import_source: Option, } #[allow(dead_code)] pub fn mdx(value: &str, filepath: Option, options: &Options) -> Result { let parse_options = ParseOptions { constructs: Constructs::mdx(), mdx_esm_parse: Some(Box::new(parse_esm)), mdx_expression_parse: Some(Box::new(parse_expression)), ..ParseOptions::default() }; let document_options = DocumentOptions { pragma: options.pragma.clone(), pragma_frag: options.pragma_frag.clone(), pragma_import_source: options.pragma_import_source.clone(), jsx_import_source: options.jsx_import_source.clone(), jsx_runtime: options.jsx_runtime, }; let rewrite_options = RewriteOptions { development: options.development, provider_import_source: options.provider_import_source.clone(), }; let location = Location::new(value.as_bytes()); let mdast = to_mdast(value, &parse_options)?; let hast = mdast_util_to_hast(&mdast); let mut program = hast_util_to_swc(&hast, filepath, Some(&location))?; mdx_plugin_recma_document(&mut program, &document_options, Some(&location))?; mdx_plugin_recma_jsx_rewrite(&mut program, &rewrite_options, Some(&location)); // Add our comments. let comments = SingleThreadedComments::default(); for c in program.comments { comments.add_leading(c.span.lo, c); } println!("comments for swc: {:?}", comments); if !options.jsx { // let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); // let build_options = JsxBuildOptions { // next: Some(true), // throw_if_namespace: Some(false), // development: Some(options.development), // use_spread: Some(true), // refresh: None, // runtime: None, // import_source: None, // pragma: None, // pragma_frag: None, // use_builtins: None, // }; // let mark = Mark::fresh(Mark::default()); // program // .module // .visit_mut_with(&mut jsx(cm, Some(comments), build_options, mark)); } // To do: include comments. Ok(serialize(&program.module)) }