react-router-markdown 0.9.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/LICENSE.md ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Javier Aguilar (itsjavi.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
17
+ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # react-router-markdown
2
+
3
+ Vite plugin to parse Markdown files as React Router pages. It supports defining React Router's meta
4
+ function returned data, as frontmatter.
5
+
6
+ ## Setup
7
+
8
+ ```bash
9
+ pnpm add react-router-markdown
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Vite config
15
+
16
+ ```tsx
17
+ // vite.config.ts
18
+
19
+ import { defineConfig } from 'vite'
20
+ import markdown from 'react-router-markdown/vite'
21
+
22
+ export default defineConfig({
23
+ plugins: [
24
+ markdown({
25
+ parseHtml: true,
26
+ wrapperModule: '@/components/markdown-wrapper',
27
+ handleData: (mdContent, frontmatter) => ({
28
+ layoutClass: 'mx-auto max-w-5xl lg:p-4',
29
+ title: frontmatter.title,
30
+ contentLength: mdContent.length,
31
+ }),
32
+ beforeTransform: (rawFileContent) =>
33
+ rawFileContent.replaceAll('%YEAR%', new Date().getFullYear().toString()),
34
+ afterTransform: (mdContent, frontmatter) => [mdContent, frontmatter],
35
+ }),
36
+ ],
37
+ })
38
+ ```
39
+
40
+ ### Plugin options
41
+
42
+ - `parseHtml?: boolean` - Parse HTML tags in markdown as HTML (default: `false`).
43
+ - `wrapperModule?: string` - Module specifier of a default-exported React wrapper component.
44
+ - `beforeTransform?: (content: string) => string` - Transform raw markdown before frontmatter
45
+ parsing.
46
+ - `afterTransform?: (content: string, frontmatter: Record<string, any>) => [string, Record<string, any>]` -
47
+ Transform parsed content/frontmatter.
48
+ - `handleData?: (content: string, frontmatter: Record<string, any>) => Record<string, any>` - Return
49
+ React Router `handle` export data.
50
+
51
+ ### Imports and TypeScript support
52
+
53
+ In order to TypeScript import `.md` files with type hints, add the following to your `vite.d.ts`
54
+ file:
55
+
56
+ ```tsx
57
+ /// <reference types="react-router-markdown/references" />
58
+ ```
59
+
60
+ Optionally, you can also add the following to your `tsconfig.json` file:
61
+
62
+ ```json
63
+ {
64
+ "compilerOptions": {
65
+ "types": ["react-router-markdown/references"]
66
+ }
67
+ }
68
+ ```
69
+
70
+ Now you can use any `.md` file as a React Router page.
@@ -0,0 +1,19 @@
1
+ //#region src/lib/markdow-page.d.ts
2
+ type MarkdownPageProps = {
3
+ parseHtml: boolean;
4
+ rawContent: string;
5
+ frontmatter: Record<string, unknown>;
6
+ Wrapper: React.ComponentType<{
7
+ children: React.ReactNode;
8
+ frontmatter: Record<string, unknown>;
9
+ }> | null;
10
+ };
11
+ declare function MarkdownPage({
12
+ parseHtml,
13
+ rawContent,
14
+ frontmatter,
15
+ Wrapper
16
+ }: MarkdownPageProps): React.ReactElement;
17
+ //#endregion
18
+ export { MarkdownPage };
19
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,25 @@
1
+ import { raw } from "hast-util-raw";
2
+ import ReactMarkdown from "react-markdown";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/lib/markdow-page.ts
5
+ function rehypeRaw(options) {
6
+ return (tree, file) => raw(tree, {
7
+ ...options,
8
+ file
9
+ });
10
+ }
11
+ function MarkdownPage({ parseHtml, rawContent, frontmatter, Wrapper }) {
12
+ const mdElement = jsx(ReactMarkdown, {
13
+ children: rawContent,
14
+ rehypePlugins: parseHtml ? [rehypeRaw] : []
15
+ });
16
+ if (!Wrapper) return mdElement;
17
+ return jsx(Wrapper, {
18
+ children: mdElement,
19
+ frontmatter
20
+ });
21
+ }
22
+ //#endregion
23
+ export { MarkdownPage };
24
+
25
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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;AACvC,SAAQ,MAAiB,SAA8B,IAAI,MAAM;EAAE,GAAG;EAAS;EAAM,CAAC;;AAGxF,SAAgB,aAAa,EAC3B,WACA,YACA,aACA,WACwC;CAExC,MAAM,YAAY,IAAI,eAAe;EAAE,UAAU;EAAY,eADvC,YAAY,CAAC,UAAU,GAAG,EAAE;EACyC,CAAC;AAE5F,KAAI,CAAC,QACH,QAAO;AAGT,QAAO,IAAI,SAAS;EAClB,UAAU;EACG;EACd,CAAC"}
@@ -0,0 +1,12 @@
1
+ // allows importing md files
2
+ // Add this to your vite.d.ts file: /// <reference types="react-router-markdown/references" />
3
+ declare module '*.md' {
4
+ export const frontmatter: Record<string, any>
5
+ export const rawContent: string
6
+ export const meta: () => any[]
7
+ export const handle: any
8
+
9
+ const Page: () => JSX.Element
10
+
11
+ export default Page
12
+ }
@@ -0,0 +1,39 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/lib/vite-plugin.d.ts
4
+ type ReactRouterMarkdownOptions<D = Record<string, any>> = {
5
+ /**
6
+ * Module specifier to import a wrapper component from.
7
+ * Must default-export a React component.
8
+ *
9
+ * Example: '#/components/markdown/wrapper'
10
+ * @default undefined
11
+ */
12
+ wrapperModule?: string;
13
+ /**
14
+ * If true, any HTML tags in the markdown content will be parsed as HTML instead of escaped.
15
+ * @default false
16
+ */
17
+ parseHtml?: boolean;
18
+ /**
19
+ * A function to return the handle data to be used for each page.
20
+ * It accepts the post-processed content and frontmatter as arguments.
21
+ * @default undefined
22
+ */
23
+ handleData?: (content: string, frontmatter: Record<string, any>) => Partial<D>;
24
+ /**
25
+ * A function to transform the raw MD file content before it is parsed.
26
+ * @default undefined
27
+ */
28
+ beforeTransform?: (content: string) => string;
29
+ /**
30
+ * A function to transform the parsed MD content and/or frontmatter after they are parsed.
31
+ * It should return back the transformed content and frontmatter.
32
+ * @default undefined
33
+ */
34
+ afterTransform?: (content: string, frontmatter: Record<string, any>) => [string, Record<string, any>];
35
+ };
36
+ declare function reactRouterMarkdown<D = Record<string, any>>(options?: ReactRouterMarkdownOptions<D>): Plugin;
37
+ //#endregion
38
+ export { type ReactRouterMarkdownOptions, reactRouterMarkdown as default };
39
+ //# sourceMappingURL=vite.d.mts.map
package/dist/vite.mjs ADDED
@@ -0,0 +1,47 @@
1
+ import matter from "gray-matter";
2
+ import "vite";
3
+ //#region src/lib/vite-plugin.ts
4
+ function reactRouterMarkdown(options = {}) {
5
+ const { wrapperModule, parseHtml = false, handleData: handleDataFn = () => ({}), beforeTransform = (content) => content, afterTransform = (content, frontmatter) => [content, frontmatter] } = options;
6
+ return {
7
+ name: "react-router-markdown",
8
+ enforce: "pre",
9
+ transform(code, id) {
10
+ if (!(id.split("?", 1)[0] ?? id).endsWith(".md")) return null;
11
+ const { data, content } = matter(beforeTransform(code));
12
+ const [transformedContent, transformedFrontmatter] = afterTransform(content, data || {});
13
+ const handleData = handleDataFn(transformedContent, transformedFrontmatter);
14
+ return {
15
+ code: [
16
+ `import { MarkdownPage } from 'react-router-markdown';`,
17
+ wrapperModule ? `import Wrapper from ${JSON.stringify(wrapperModule)};` : "const Wrapper = null;",
18
+ "",
19
+ `export const frontmatter = ${JSON.stringify(transformedFrontmatter || {})};`,
20
+ `export const rawContent = ${JSON.stringify(transformedContent || "")};`,
21
+ "",
22
+ "export const meta = () => frontmatter.meta || [];",
23
+ `export const handle = ${JSON.stringify(handleData)};`,
24
+ "",
25
+ `function Page() {
26
+ return _jsx(MarkdownPage, {
27
+ Wrapper,
28
+ parseHtml: ${parseHtml ? "true" : "false"},
29
+ rawContent: rawContent,
30
+ frontmatter: frontmatter,
31
+ })
32
+ }
33
+ export default Page;
34
+ `
35
+ ].join("\n"),
36
+ map: null
37
+ };
38
+ }
39
+ };
40
+ }
41
+ //#endregion
42
+ //#region src/vite.ts
43
+ var vite_default = reactRouterMarkdown;
44
+ //#endregion
45
+ export { vite_default as default };
46
+
47
+ //# sourceMappingURL=vite.mjs.map
@@ -0,0 +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 `import { MarkdownPage } from 'react-router-markdown';`,\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,EAAE,EACnC;CACR,MAAM,EACJ,eACA,YAAY,OACZ,YAAY,sBAAsB,EAAE,GACpC,mBAAmB,YAAY,SAC/B,kBAAkB,SAAS,gBAAgB,CAAC,SAAS,YAAY,KAChC;AAEnC,QAAO;EACL,MAAM;EACN,SAAS;EAET,UAAU,MAAM,IAAI;AAElB,OAAI,EADa,GAAG,MAAM,KAAK,EAAE,CAAC,MAAM,IAC1B,SAAS,MAAM,CAAE,QAAO;GAGtC,MAAM,EAAE,MAAM,YAAY,OADF,gBAAgB,KAAK,CACI;GACjD,MAAM,CAAC,oBAAoB,0BAA0B,eAAe,SAAS,QAAQ,EAAE,CAAC;GACxF,MAAM,aAAa,aAAa,oBAAoB,uBAAuB;AAE3E,UAAO;IACL,MAAM;KACJ;KACA,gBACI,uBAAuB,KAAK,UAAU,cAAc,CAAC,KACrD;KACJ;KACA,8BAA8B,KAAK,UAAU,0BAA0B,EAAE,CAAC,CAAC;KAC3E,6BAA6B,KAAK,UAAU,sBAAsB,GAAG,CAAC;KACtE;KACA;KACA,yBAAyB,KAAK,UAAU,WAAW,CAAC;KACpD;KACA;;;2BAGiB,YAAY,SAAS,QAAQ;;;;;;;KAO/C,CAAC,KAAK,KAAK;IACZ,KAAK;IACN;;EAEJ;;;;ACxFH,IAAA,eAAe"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "react-router-markdown",
3
+ "version": "0.9.0",
4
+ "description": "Vite plugin to parse and use markdown files as React Router pages, including frontmatter support.",
5
+ "keywords": [
6
+ "markdown",
7
+ "react-router",
8
+ "vite-plugin"
9
+ ],
10
+ "homepage": "https://github.com/itsjavi/react-router-markdown#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/itsjavi/react-router-markdown/issues"
13
+ },
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Javier Aguilar",
17
+ "url": "https://itsjavi.com"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/itsjavi/react-router-markdown.git"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "LICENSE.md",
26
+ "README.md"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.mts",
33
+ "import": "./dist/index.mjs"
34
+ },
35
+ "./vite": {
36
+ "types": "./dist/vite.d.mts",
37
+ "import": "./dist/vite.mjs"
38
+ },
39
+ "./references": "./dist/references.d.ts"
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "dev": "tsdown --watch",
44
+ "format": "oxfmt --write .",
45
+ "format:check": "oxfmt --check .",
46
+ "lint": "pnpm typecheck && pnpm publint",
47
+ "publint": "publint",
48
+ "test": "vitest --run",
49
+ "test:coverage": "vitest --run --coverage",
50
+ "test:watch": "vitest --watch",
51
+ "typecheck": "tsc --noEmit"
52
+ },
53
+ "dependencies": {
54
+ "gray-matter": "^4.0.3",
55
+ "hast-util-raw": "^9.1.0",
56
+ "react-markdown": "^10.1.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^25.5.0",
60
+ "@types/react": "^19.2.14",
61
+ "@vitest/coverage-v8": "^4.1.2",
62
+ "oxfmt": "^0.42.0",
63
+ "publint": "^0.3.18",
64
+ "react": "^19.2.4",
65
+ "tsdown": "^0.21.7",
66
+ "typescript": "^6.0.2",
67
+ "vite": "^8.0.3",
68
+ "vitest": "^4.1.2"
69
+ },
70
+ "peerDependencies": {
71
+ "react": "^19.2.4",
72
+ "vite": "^7.0.0 || ^8.0.0"
73
+ },
74
+ "packageManager": "pnpm@10.33.0"
75
+ }