diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbacd7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +bun.lockb \ No newline at end of file diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..c38ce92 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,18 @@ +type Predicate = + | (string | RegExp)[] + | RegExp + | string + | ((id: string) => boolean); + +export interface Options { + /** + * @default /[.]mdx?$/ + */ + extensions?: Predicate; +} + +export interface State extends babel.PluginPass { + opts: Options; +} + +export default function babelPluginSolidMdx(): import('@babel/core').PluginObj; diff --git a/index.js b/index.js new file mode 100644 index 0000000..71f70be --- /dev/null +++ b/index.js @@ -0,0 +1,85 @@ +/** + * @typedef {import('./index.js').Predicate} Predicate + * @typedef {import('@babel/core').types} types + * @typedef {import('@babel/core').types.MemberExpression} MemberExpression + * @typedef {import('@babel/core').types.JSXMemberExpression} JSXMemberExpression + * @typedef {import('@babel/core').types.JSXIdentifier} JSXIdentifier + */ + +/** + * @type {import('./index.js').default} + */ +export default function babelPluginSolidMdx(ctx) { + const t = /** @type {types} */ (ctx.types); + + return { + visitor: { + Program(path, state) { + const extensions = /** @type {Predicate[]} */ ( + 'extensions' in state.opts ? state.opts.extensions : /[.]mdx?$/ + ); + + if (!state.filename || !predicate(state.filename, extensions)) { + return; + } + + path.traverse({ + JSXOpeningElement(path) { + const { node } = path; + if (t.isJSXMemberExpression(node.name)) { + const component = t.jsxAttribute( + t.jsxIdentifier('component'), + t.jsxExpressionContainer(toMemberExpression(node.name)), + ); + + const attributes = [component, ...node.attributes]; + + const dynamic = t.jsxOpeningElement( + t.jsxIdentifier('Dynamic'), + attributes, + ); + + path.replaceWith(dynamic); + } + }, + }); + }, + }, + }; + + /** + * @param {JSXMemberExpression} expr + * @returns {MemberExpression} + */ + function toMemberExpression(expr) { + return t.memberExpression( + expr.object.type === 'JSXIdentifier' + ? toIdentifier(expr.object) + : toMemberExpression(expr.object), + toIdentifier(expr.property), + ); + } + + /** + * @param {JSXIdentifier} expr + */ + function toIdentifier(expr) { + return t.identifier(expr.name); + } +} + +/** + * @param {string} filename + * @param {Predicate} pred + */ +function predicate(filename, pred) { + if (typeof pred === 'function') { + return pred(filename); + } else if (Array.isArray(pred)) { + return pred.some((pred) => predicate(filename, pred)); + } else if (pred instanceof RegExp) { + return pred.test(filename); + } else { + return filename.endsWith(String(pred)); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..859d6d1 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "babel-plugin-solid-mdx", + "version": "0.0.1", + "description": "babel plugin to transform mdx to solid", + "license": "MIT", + "author": "Nyi Nyi Lwin ", + "repository": "mdynnl/babel-plugin-solid-mdx", + "bugs": "https://github.com/mdynnl/babel-plugin-solid-mdx/issues", + "sideEffects": false, + "type": "module", + "exports": "./index.js", + "files": [ + "index.d.ts", + "index.js" + ] +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..fb3d66e --- /dev/null +++ b/readme.md @@ -0,0 +1,88 @@ +# babel-plugin-solid-mdx + +[![NPM](https://img.shields.io/npm/v/babel-plugin-solid-mdx.svg)](https://www.npmjs.com/package/babel-plugin-solid-mdx) + +A simple babel plugin to transform mdx to solid + +## Install + +```bash +npm install --save-dev babel-plugin-solid-mdx +``` + +```bash +yarn add -D babel-plugin-solid-mdx +``` + +```bash +pnpm add -D babel-plugin-solid-mdx +``` + +```bash +bun add -d babel-plugin-solid-mdx +``` + +## Usage + +```ts +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; +import mdx from '@mdx-js/rollup'; +import solidMdx from 'babel-plugin-solid-mdx'; + +export default defineConfig({ + plugins: [ + // use @mdx-js/rollup before solidPlugin + { + enforce: 'pre', + ...mdx({ + jsx: true, + jsxImportSource: 'solid-js', + }), + }, + solidPlugin({ + // configure solid to compile `.mdx` files + extensions: ['.mdx'], + babel: { + // use the plugin to transform the mdx output + plugins: [solidMdx], + }, + }), + ], +}); +``` + +## Frontmatter Support + +```js +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; +import mdx from '@mdx-js/rollup'; +import solidMdx from 'babel-plugin-solid-mdx'; + +import remarkFrontmatter from 'remark-frontmatter'; +import remarkMdxFrontmatter from 'remark-mdx-frontmatter'; + +export default defineConfig({ + plugins: [ + { + enforce: 'pre', + ...mdx({ + jsx: true, + jsxImportSource: 'solid-js', + remarkPlugins: [ + // + remarkFrontmatter, + remarkMdxFrontmatter, + ], + }), + }, + solidPlugin({ + extensions: ['.mdx'], + babel: { + plugins: [solidMdx], + }, + }), + ], +}); +```