UNPKG

mdxlayer

Version:

Transform your MDX content into typed, JSON-powered data with flexible schema validation.

256 lines (200 loc) • 5.23 kB
# `mdxlayer` Transform your `.mdx` content into strongly typed, structured JSON and TypeScript declarations with minimal config. Built for performance, flexibility, and DX. ## šŸš€ Features - 🧠 Validates frontmatter with [Tyne](https://tyne.estarlincito.com/) - ⚔ Generates structured `.json` and `.d.ts` from `.mdx` content - šŸ›  Add computed fields with `resolvedFields` - šŸ“ Language-based content grouping (`en`, `es`, `all`) - šŸ”„ Watch mode for development - āœ… Type-safe at every level - šŸ“¦ Simple CLI: `mdxlayer build` & `mdxlayer dev` ## šŸ“‘ Requirements - Node.js 18+ (ESM only) - `mdxlayer` installed globally or locally - MDX files must follow this rule: **no spaces in filenames** āœ… `about.mdx` āŒ `about me.mdx` ## šŸ“¦ Installation ```bash pnpm add -D mdxlayer # or npm install -D mdxlayer # or yarn add -D mdxlayer ``` ## āš™ļø Configuration Create a `mdxlayer.config.ts` file: ```ts import { defineConfig } from 'mdxlayer'; import { t } from 'tyne'; const ArticleSchema = t.object({ title: t.string(), category: t.string(), subcategory: t.string(), publishedTime: t.string(), modifiedTime: t.string(), description: t.string(), authors: t.array(t.string()), avatar: t.string(), cover: t.string(), coverAlt: t.string(), check: t.boolean(), lang: t.string(), tags: t.array(t.string()), }); export default defineConfig({ docType: 'Articles', contentDir: 'content', frontmatterSchema: ArticleSchema, resolvedFields: { title_: { resolve: (doc) => doc.title, type: t.string() }, slug: { resolve: (doc) => doc.file.replace(/\.mdx$/, ''), type: t.string(), }, }, }); ``` ## 🧠 TypeScript Setup Update your `tsconfig.json`: ```json { "compilerOptions": { "paths": { "mdxlayer/generated": ["./.mdxlayer/generated"] } }, "include": ["./.mdxlayer/generated"] } ``` ## šŸ“„ Example `.mdx` File ```mdx --- title: 'What Makes MDX Powerful' category: 'tech' subcategory: 'markdown' publishedTime: '01-04-2025' modifiedTime: '01-04-2025' description: 'A deep dive into what makes MDX a game-changer for modern content.' authors: ['Estarlincito'] avatar: '/assets/avatar.jpeg' cover: '/covers/mdx.png' coverAlt: 'MDX visual cover' check: true lang: 'en' tags: ['mdx', 'content', 'markdown'] --- Hello! <Introduction> > Lorem ipsum dolor sit amet, consectetur adipiscing elit. </Introduction> <Audio src='/audio/intro.mp3' type='audio/mp3' /> > _"MDX is where content meets interactivity."_ > **– Anonymous** ``` ## ✨ Usage in Code ```ts import { esArticles, enArticles, allArticles } from 'mdxlayer/generated'; console.log(esArticles); console.log(enArticles); console.log(allArticles); ``` ## šŸ–„ CLI Commands ```bash pnpm mdxlayer build # Generate static JSON and index.d.ts pnpm mdxlayer dev # Watch mode — regenerate on file changes pnpm mdxlayer build --config docs.config.js # This will load settings from `docs.config.js` mdxlayer build --config docs.config.js --out docs-content # This will load `docs.config.js` and output to `docs-content/` ``` ## šŸ“ Folder Structure ``` content/ ā”œā”€ā”€ what-makes-mdx-powerful.mdx ``` ## šŸ“ Output Structure ``` .mdxlayer/ ā”œā”€ā”€ cache/ │ ā”œā”€ā”€ compiled-config.js │ ā”œā”€ā”€ compiled-config.js.map │ ā”œā”€ā”€ data.json ā”œā”€ā”€ generated/ │ ā”œā”€ā”€ Articles/ │ │ ā”œā”€ā”€ what-makes-mdx-powerful.json │ ā”œā”€ā”€ types.d.ts │ ā”œā”€ā”€ index.d.ts │ └── index.js ā”œā”€ā”€ package.json ``` ## Plugins ## `next-mdxlayer` ## šŸ“¦ Installation ```bash pnpm add -D next-mdxlayer # or npm install -D next-mdxlayer # or yarn add -D next-mdxlayer ``` ## āš™ļø Configuration ```ts import { withMdxlayer } from 'next-mdxlayer'; /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, pageExtensions: ['js', 'ts', 'jsx', 'tsx', 'mdx'], experimental: { serverActions: true, }, }; export default withMdxlayer(nextConfig); ``` ```ts import { useMDXComponent } from 'next-mdxlayer/hook'; import { allServices } from 'mdxlayer/generated'; import MyAudioComponent from './audio' import MyIntroComponent from './intro' export default function MyPage() { const MDXComponent = useMDXComponent(allServices[0]._body.code); return ( <MDXComponent components={{ a: (props) => <a {...props} className="underline text-blue-600" />, img: (props) => <img {...props} style={{ maxWidth: '100%' }} />, Audio: MyAudioComponent, Introduction: MyIntroComponent, }} /> ); } ``` ## šŸ”© API ### `defineConfig(config)` Accepts the following shape: ```ts interface Config { name: string; contentDir: string; frontmatterSchema: TyneType; resolvedFields?: Record< string, { resolve: (doc: Doc) => any | Promise<any> } >; } ``` ### `Doc` ```ts interface Doc { _filePath: string; _id: string; _body: { raw: string; code: string }; // + fields from your frontmatterSchema } ``` ## šŸ“ License This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details. **Author:** Estarlin R ([estarlincito.com](https://estarlincito.com))