react-router-markdown 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -4
- package/dist/index.d.mts +1 -7
- package/dist/index.mjs.map +1 -1
- package/dist/vite.d.mts +0 -1
- package/dist/vite.mjs +5 -3
- package/dist/vite.mjs.map +1 -1
- package/package.json +14 -13
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# react-router-markdown
|
|
2
2
|
|
|
3
|
-
Vite plugin to
|
|
4
|
-
|
|
3
|
+
Vite plugin to import Markdown files as React components with frontmatter support. It follows React
|
|
4
|
+
Router's route module conventions, including the default component, `meta`, and `handle` exports,
|
|
5
|
+
but does not depend on React Router. Use it with React Router, any other React framework that
|
|
6
|
+
supports Vite, or a plain React + Vite app.
|
|
5
7
|
|
|
6
8
|
## Setup
|
|
7
9
|
|
|
@@ -50,14 +52,14 @@ export default defineConfig({
|
|
|
50
52
|
|
|
51
53
|
### Imports and TypeScript support
|
|
52
54
|
|
|
53
|
-
In order
|
|
55
|
+
In order for TypeScript to import `.md` files with type hints, add the following to your `vite.d.ts`
|
|
54
56
|
file:
|
|
55
57
|
|
|
56
58
|
```tsx
|
|
57
59
|
/// <reference types="react-router-markdown/references" />
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
Alternatively, you can add the following to your `tsconfig.json` file:
|
|
61
63
|
|
|
62
64
|
```json
|
|
63
65
|
{
|
|
@@ -68,3 +70,70 @@ Optionally, you can also add the following to your `tsconfig.json` file:
|
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
Now you can use any `.md` file as a React Router page.
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
`app/routes/about.md`:
|
|
77
|
+
|
|
78
|
+
```md
|
|
79
|
+
---
|
|
80
|
+
title: 'About Us'
|
|
81
|
+
meta:
|
|
82
|
+
- title: 'About Us - ACME'
|
|
83
|
+
- name: description
|
|
84
|
+
content: 'Meta description here'
|
|
85
|
+
- name: robots
|
|
86
|
+
content: 'index, follow'
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam elementum venenatis facilisis. Nam sit
|
|
90
|
+
amet finibus lectus.
|
|
91
|
+
|
|
92
|
+
Nunc lobortis neque magna, id mollis arcu pulvinar ultrices. Aenean mollis dolor nulla. Aliquam
|
|
93
|
+
aliquet tempus ultrices. Integer ac est finibus, efficitur orci faucibus, luctus odio. Vivamus vitae
|
|
94
|
+
venenatis quam, imperdiet rutrum massa.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`app/router.ts`:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { type RouteConfig, route } from '@react-router/dev/routes'
|
|
101
|
+
|
|
102
|
+
export default [route('about', './routes/about.md')] satisfies RouteConfig
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`app/components/markdown-wrapper.tsx`:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { cn } from '@/lib/utils'
|
|
109
|
+
|
|
110
|
+
export default function MarkdownWrapper({
|
|
111
|
+
children,
|
|
112
|
+
frontmatter,
|
|
113
|
+
}: {
|
|
114
|
+
children: React.ReactNode
|
|
115
|
+
frontmatter: {
|
|
116
|
+
title?: string
|
|
117
|
+
subtitle?: string
|
|
118
|
+
}
|
|
119
|
+
}) {
|
|
120
|
+
const hasHeader = frontmatter.title || frontmatter.subtitle
|
|
121
|
+
return (
|
|
122
|
+
<article className={cn('prose prose-slate dark:prose-invert max-w-none')}>
|
|
123
|
+
{hasHeader && (
|
|
124
|
+
<header className="mb-8 space-y-4">
|
|
125
|
+
{frontmatter.title && (
|
|
126
|
+
<h1 className={cn('text-4xl font-bold tracking-tight lg:text-5xl')}>
|
|
127
|
+
{frontmatter.title}
|
|
128
|
+
</h1>
|
|
129
|
+
)}
|
|
130
|
+
{frontmatter.subtitle && (
|
|
131
|
+
<p className="text-muted-foreground text-xl leading-7">{frontmatter.subtitle}</p>
|
|
132
|
+
)}
|
|
133
|
+
</header>
|
|
134
|
+
)}
|
|
135
|
+
<div className="space-y-6">{children}</div>
|
|
136
|
+
</article>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -8,12 +8,6 @@ type MarkdownPageProps = {
|
|
|
8
8
|
frontmatter: Record<string, unknown>;
|
|
9
9
|
}> | null;
|
|
10
10
|
};
|
|
11
|
-
declare function MarkdownPage({
|
|
12
|
-
parseHtml,
|
|
13
|
-
rawContent,
|
|
14
|
-
frontmatter,
|
|
15
|
-
Wrapper
|
|
16
|
-
}: MarkdownPageProps): React.ReactElement;
|
|
11
|
+
export declare function MarkdownPage({ parseHtml, rawContent, frontmatter, Wrapper }: MarkdownPageProps): React.ReactElement;
|
|
17
12
|
//#endregion
|
|
18
|
-
export { MarkdownPage };
|
|
19
13
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/markdow-page.ts"],"sourcesContent":["import { raw } from 'hast-util-raw'\nimport ReactMarkdown from 'react-markdown'\nimport { jsx } from 'react/jsx-runtime'\n\ntype HastNodes = Parameters<typeof raw>[0]\ntype HastOptions = NonNullable<Parameters<typeof raw>[1]>\ntype MarkdownPageProps = {\n parseHtml: boolean\n rawContent: string\n frontmatter: Record<string, unknown>\n Wrapper: React.ComponentType<{\n children: React.ReactNode\n frontmatter: Record<string, unknown>\n }> | null\n}\n\nfunction rehypeRaw(options: HastOptions) {\n return (tree: HastNodes, file: HastOptions['file']) => raw(tree, { ...options, file })\n}\n\nexport function MarkdownPage({\n parseHtml,\n rawContent,\n frontmatter,\n Wrapper,\n}: MarkdownPageProps): React.ReactElement {\n const rehypePlugins = parseHtml ? [rehypeRaw] : []\n const mdElement = jsx(ReactMarkdown, { children: rawContent, rehypePlugins: rehypePlugins })\n\n if (!Wrapper) {\n return mdElement\n }\n\n return jsx(Wrapper, {\n children: mdElement,\n frontmatter: frontmatter,\n })\n}\n"],"mappings":";;;;AAgBA,SAAS,UAAU,SAAsB;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/markdow-page.ts"],"sourcesContent":["import { raw } from 'hast-util-raw'\nimport ReactMarkdown from 'react-markdown'\nimport { jsx } from 'react/jsx-runtime'\n\ntype HastNodes = Parameters<typeof raw>[0]\ntype HastOptions = NonNullable<Parameters<typeof raw>[1]>\ntype MarkdownPageProps = {\n parseHtml: boolean\n rawContent: string\n frontmatter: Record<string, unknown>\n Wrapper: React.ComponentType<{\n children: React.ReactNode\n frontmatter: Record<string, unknown>\n }> | null\n}\n\nfunction rehypeRaw(options: HastOptions) {\n return (tree: HastNodes, file: HastOptions['file']) => raw(tree, { ...options, file })\n}\n\nexport function MarkdownPage({\n parseHtml,\n rawContent,\n frontmatter,\n Wrapper,\n}: MarkdownPageProps): React.ReactElement {\n const rehypePlugins = parseHtml ? [rehypeRaw] : []\n const mdElement = jsx(ReactMarkdown, { children: rawContent, rehypePlugins: rehypePlugins })\n\n if (!Wrapper) {\n return mdElement\n }\n\n return jsx(Wrapper, {\n children: mdElement,\n frontmatter: frontmatter,\n })\n}\n"],"mappings":";;;;AAgBA,SAAS,UAAU,SAAsB;CACvC,QAAQ,MAAiB,SAA8B,IAAI,MAAM;EAAE,GAAG;EAAS;CAAK,CAAC;AACvF;AAEA,SAAgB,aAAa,EAC3B,WACA,YACA,aACA,WACwC;CAExC,MAAM,YAAY,IAAI,eAAe;EAAE,UAAU;EAAY,eADvC,YAAY,CAAC,SAAS,IAAI,CAAC;CACyC,CAAC;CAE3F,IAAI,CAAC,SACH,OAAO;CAGT,OAAO,IAAI,SAAS;EAClB,UAAU;EACG;CACf,CAAC;AACH"}
|
package/dist/vite.d.mts
CHANGED
package/dist/vite.mjs
CHANGED
|
@@ -8,12 +8,14 @@ function reactRouterMarkdown(options = {}) {
|
|
|
8
8
|
enforce: "pre",
|
|
9
9
|
transform(code, id) {
|
|
10
10
|
if (!(id.split("?", 1)[0] ?? id).endsWith(".md")) return null;
|
|
11
|
-
const
|
|
11
|
+
const transformedCode = beforeTransform(code);
|
|
12
|
+
const { data, content } = matter(transformedCode);
|
|
12
13
|
const [transformedContent, transformedFrontmatter] = afterTransform(content, data || {});
|
|
13
14
|
const handleData = handleDataFn(transformedContent, transformedFrontmatter);
|
|
14
15
|
return {
|
|
15
16
|
code: [
|
|
16
|
-
|
|
17
|
+
"import { MarkdownPage } from 'react-router-markdown';",
|
|
18
|
+
"import { jsx } from 'react/jsx-runtime';",
|
|
17
19
|
wrapperModule ? `import Wrapper from ${JSON.stringify(wrapperModule)};` : "const Wrapper = null;",
|
|
18
20
|
"",
|
|
19
21
|
`export const frontmatter = ${JSON.stringify(transformedFrontmatter || {})};`,
|
|
@@ -23,7 +25,7 @@ function reactRouterMarkdown(options = {}) {
|
|
|
23
25
|
`export const handle = ${JSON.stringify(handleData)};`,
|
|
24
26
|
"",
|
|
25
27
|
`function Page() {
|
|
26
|
-
return
|
|
28
|
+
return jsx(MarkdownPage, {
|
|
27
29
|
Wrapper,
|
|
28
30
|
parseHtml: ${parseHtml ? "true" : "false"},
|
|
29
31
|
rawContent: rawContent,
|
package/dist/vite.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.mjs","names":[],"sources":["../src/lib/vite-plugin.ts","../src/vite.ts"],"sourcesContent":["import matter from 'gray-matter'\nimport { type Plugin } from 'vite'\n\nexport type ReactRouterMarkdownOptions<D = Record<string, any>> = {\n /**\n * Module specifier to import a wrapper component from.\n * Must default-export a React component.\n *\n * Example: '#/components/markdown/wrapper'\n * @default undefined\n */\n wrapperModule?: string\n /**\n * If true, any HTML tags in the markdown content will be parsed as HTML instead of escaped.\n * @default false\n */\n parseHtml?: boolean\n /**\n * A function to return the handle data to be used for each page.\n * It accepts the post-processed content and frontmatter as arguments.\n * @default undefined\n */\n handleData?: (content: string, frontmatter: Record<string, any>) => Partial<D>\n\n /**\n * A function to transform the raw MD file content before it is parsed.\n * @default undefined\n */\n beforeTransform?: (content: string) => string\n\n /**\n * A function to transform the parsed MD content and/or frontmatter after they are parsed.\n * It should return back the transformed content and frontmatter.\n * @default undefined\n */\n afterTransform?: (\n content: string,\n frontmatter: Record<string, any>,\n ) => [string, Record<string, any>]\n}\n\nexport function reactRouterMarkdown<D = Record<string, any>>(\n options: ReactRouterMarkdownOptions<D> = {},\n): Plugin {\n const {\n wrapperModule,\n parseHtml = false,\n handleData: handleDataFn = () => ({}) as Partial<D>,\n beforeTransform = (content) => content,\n afterTransform = (content, frontmatter) => [content, frontmatter],\n }: ReactRouterMarkdownOptions<D> = options\n\n return {\n name: 'react-router-markdown',\n enforce: 'pre',\n\n transform(code, id) {\n const filePath = id.split('?', 1)[0] ?? id\n if (!filePath.endsWith('.md')) return null\n\n const transformedCode = beforeTransform(code)\n const { data, content } = matter(transformedCode)\n const [transformedContent, transformedFrontmatter] = afterTransform(content, data || {})\n const handleData = handleDataFn(transformedContent, transformedFrontmatter)\n\n return {\n code: [\n
|
|
1
|
+
{"version":3,"file":"vite.mjs","names":[],"sources":["../src/lib/vite-plugin.ts","../src/vite.ts"],"sourcesContent":["import matter from 'gray-matter'\nimport { type Plugin } from 'vite'\n\nexport type ReactRouterMarkdownOptions<D = Record<string, any>> = {\n /**\n * Module specifier to import a wrapper component from.\n * Must default-export a React component.\n *\n * Example: '#/components/markdown/wrapper'\n * @default undefined\n */\n wrapperModule?: string\n /**\n * If true, any HTML tags in the markdown content will be parsed as HTML instead of escaped.\n * @default false\n */\n parseHtml?: boolean\n /**\n * A function to return the handle data to be used for each page.\n * It accepts the post-processed content and frontmatter as arguments.\n * @default undefined\n */\n handleData?: (content: string, frontmatter: Record<string, any>) => Partial<D>\n\n /**\n * A function to transform the raw MD file content before it is parsed.\n * @default undefined\n */\n beforeTransform?: (content: string) => string\n\n /**\n * A function to transform the parsed MD content and/or frontmatter after they are parsed.\n * It should return back the transformed content and frontmatter.\n * @default undefined\n */\n afterTransform?: (\n content: string,\n frontmatter: Record<string, any>,\n ) => [string, Record<string, any>]\n}\n\nexport function reactRouterMarkdown<D = Record<string, any>>(\n options: ReactRouterMarkdownOptions<D> = {},\n): Plugin {\n const {\n wrapperModule,\n parseHtml = false,\n handleData: handleDataFn = () => ({}) as Partial<D>,\n beforeTransform = (content) => content,\n afterTransform = (content, frontmatter) => [content, frontmatter],\n }: ReactRouterMarkdownOptions<D> = options\n\n return {\n name: 'react-router-markdown',\n enforce: 'pre',\n\n transform(code, id) {\n const filePath = id.split('?', 1)[0] ?? id\n if (!filePath.endsWith('.md')) return null\n\n const transformedCode = beforeTransform(code)\n const { data, content } = matter(transformedCode)\n const [transformedContent, transformedFrontmatter] = afterTransform(content, data || {})\n const handleData = handleDataFn(transformedContent, transformedFrontmatter)\n\n return {\n code: [\n \"import { MarkdownPage } from 'react-router-markdown';\",\n \"import { jsx } from 'react/jsx-runtime';\",\n wrapperModule\n ? `import Wrapper from ${JSON.stringify(wrapperModule)};`\n : 'const Wrapper = null;',\n '',\n `export const frontmatter = ${JSON.stringify(transformedFrontmatter || {})};`,\n `export const rawContent = ${JSON.stringify(transformedContent || '')};`,\n '',\n 'export const meta = () => frontmatter.meta || [];',\n `export const handle = ${JSON.stringify(handleData)};`,\n '',\n `function Page() {\n return jsx(MarkdownPage, {\n Wrapper,\n parseHtml: ${parseHtml ? 'true' : 'false'},\n rawContent: rawContent,\n frontmatter: frontmatter,\n })\n }\n export default Page;\n `,\n ].join('\\n'),\n map: null,\n }\n },\n }\n}\n","export { type ReactRouterMarkdownOptions } from './lib/vite-plugin'\n\nimport { reactRouterMarkdown } from './lib/vite-plugin'\n\nexport default reactRouterMarkdown\n"],"mappings":";;;AAyCA,SAAgB,oBACd,UAAyC,CAAC,GAClC;CACR,MAAM,EACJ,eACA,YAAY,OACZ,YAAY,sBAAsB,CAAC,IACnC,mBAAmB,YAAY,SAC/B,kBAAkB,SAAS,gBAAgB,CAAC,SAAS,WAAW,MAC/B;CAEnC,OAAO;EACL,MAAM;EACN,SAAS;EAET,UAAU,MAAM,IAAI;GAElB,IAAI,EADa,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,GAAA,CAC1B,SAAS,KAAK,GAAG,OAAO;GAEtC,MAAM,kBAAkB,gBAAgB,IAAI;GAC5C,MAAM,EAAE,MAAM,YAAY,OAAO,eAAe;GAChD,MAAM,CAAC,oBAAoB,0BAA0B,eAAe,SAAS,QAAQ,CAAC,CAAC;GACvF,MAAM,aAAa,aAAa,oBAAoB,sBAAsB;GAE1E,OAAO;IACL,MAAM;KACJ;KACA;KACA,gBACI,uBAAuB,KAAK,UAAU,aAAa,EAAE,KACrD;KACJ;KACA,8BAA8B,KAAK,UAAU,0BAA0B,CAAC,CAAC,EAAE;KAC3E,6BAA6B,KAAK,UAAU,sBAAsB,EAAE,EAAE;KACtE;KACA;KACA,yBAAyB,KAAK,UAAU,UAAU,EAAE;KACpD;KACA;;;2BAGiB,YAAY,SAAS,QAAQ;;;;;;;IAOhD,CAAC,CAAC,KAAK,IAAI;IACX,KAAK;GACP;EACF;CACF;AACF;;;AC1FA,IAAA,eAAe"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-router-markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Vite plugin to parse and use markdown files as React Router pages, including frontmatter support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"format": "oxfmt --write .",
|
|
45
45
|
"format:check": "oxfmt --check .",
|
|
46
46
|
"lint": "pnpm typecheck && pnpm publint",
|
|
47
|
+
"prepublishOnly": "pnpm build",
|
|
47
48
|
"publint": "publint",
|
|
48
49
|
"test": "vitest --run",
|
|
49
50
|
"test:coverage": "vitest --run --coverage",
|
|
@@ -56,20 +57,20 @@
|
|
|
56
57
|
"react-markdown": "^10.1.0"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"@types/react": "^19.
|
|
61
|
-
"@vitest/coverage-v8": "^
|
|
62
|
-
"oxfmt": "^0.
|
|
63
|
-
"publint": "^0.3.
|
|
64
|
-
"react": "^19.
|
|
65
|
-
"tsdown": "^0.
|
|
66
|
-
"typescript": "^
|
|
67
|
-
"vite": "^8.0
|
|
68
|
-
"vitest": "^
|
|
60
|
+
"@types/node": "^26.5.1",
|
|
61
|
+
"@types/react": "^19.3.0",
|
|
62
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
63
|
+
"oxfmt": "^0.67.0",
|
|
64
|
+
"publint": "^0.3.24",
|
|
65
|
+
"react": "^19.3.0",
|
|
66
|
+
"tsdown": "^0.23.0",
|
|
67
|
+
"typescript": "^7.0.2",
|
|
68
|
+
"vite": "^8.3.0",
|
|
69
|
+
"vitest": "^5.0.0"
|
|
69
70
|
},
|
|
70
71
|
"peerDependencies": {
|
|
71
72
|
"react": "^19.2.4",
|
|
72
|
-
"vite": "^
|
|
73
|
+
"vite": "^8.0.0"
|
|
73
74
|
},
|
|
74
|
-
"packageManager": "pnpm@
|
|
75
|
+
"packageManager": "pnpm@12.4.1"
|
|
75
76
|
}
|