Skip to content

Commit

Permalink
Support include in lint
Browse files Browse the repository at this point in the history
  • Loading branch information
fuma-nama committed Dec 22, 2024
1 parent 1810868 commit b71064a
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-donuts-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'fumadocs-core': patch
---

Support remark plugins & vfile input on `getTableOfContents`
52 changes: 37 additions & 15 deletions apps/docs/scripts/lint.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getTableOfContents } from 'fumadocs-core/server';
import fs from 'node:fs/promises';
import path from 'node:path';
import matter from 'gray-matter';
import { remarkInclude } from 'fumadocs-mdx/config';

async function readFromPath(file: string) {
const content = await fs
Expand All @@ -28,23 +29,44 @@ async function checkLinks() {
await fg('content/blog/**/*.mdx').then((files) => files.map(readFromPath)),
);

const docs = docsFiles.map(async (file) => {
const info = parseFilePath(path.relative('content/docs', file.path));

return {
value: getSlugs(info),
hashes: (
await getTableOfContents(
{
path: file.path,
value: file.content,
},
[remarkInclude],
)
).map((item) => item.url.slice(1)),
};
});

const blogs = blogFiles.map(async (file) => {
const info = parseFilePath(path.relative('content/blog', file.path));

return {
value: getSlugs(info)[0],
hashes: (
await getTableOfContents(
{
path: file.path,
value: file.content,
},
[remarkInclude],
)
).map((item) => item.url.slice(1)),
};
});

const scanned = await scanURLs({
populate: {
'(home)/blog/[slug]': blogFiles.map((file) => {
const info = parseFilePath(path.relative('content/blog', file.path));

return { value: getSlugs(info)[0] };
}),
'docs/[...slug]': docsFiles.map((file) => {
const info = parseFilePath(path.relative('content/docs', file.path));

return {
value: getSlugs(info),
hashes: file.data?._mdx?.mirror
? undefined
: getTableOfContents(file.content).map((item) => item.url.slice(1)),
};
}),
'(home)/blog/[slug]': await Promise.all(blogs),
'docs/[...slug]': await Promise.all(docs),
},
});

Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@
"shiki-transformers": "^1.0.1",
"tsconfig": "workspace:*",
"typescript": "^5.7.2",
"unified": "^11.0.5"
"unified": "^11.0.5",
"vfile": "^6.0.3"
},
"peerDependencies": {
"@oramacloud/client": "1.x.x || 2.x.x",
Expand Down
35 changes: 33 additions & 2 deletions packages/core/src/server/get-toc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { remark } from 'remark';
import type { ReactNode } from 'react';
import { remarkHeading } from '@/mdx-plugins/remark-heading';
import type { PluggableList } from 'unified';
import type { Compatible } from 'vfile';

export interface TOCItemType {
title: ReactNode;
Expand All @@ -13,9 +15,38 @@ export type TableOfContents = TOCItemType[];
/**
* Get Table of Contents from markdown/mdx document (using remark)
*
* @param content - Markdown content
* @param content - Markdown content or file
*/
export function getTableOfContents(content: string): TableOfContents {
export function getTableOfContents(content: Compatible): TableOfContents;

/**
* Get Table of Contents from markdown/mdx document (using remark)
*
* @param content - Markdown content or file
* @param remarkPlugins - remark plugins to be applied first
*/
export function getTableOfContents(
content: Compatible,
remarkPlugins: PluggableList,
): Promise<TableOfContents>;

export function getTableOfContents(
content: Compatible,
remarkPlugins?: PluggableList,
): TableOfContents | Promise<TableOfContents> {
if (remarkPlugins) {
return remark()
.use(remarkPlugins)
.use(remarkHeading)
.process(content)
.then((result) => {
if ('toc' in result.data) return result.data.toc as TableOfContents;

return [];
});
}

// compatible with previous versions
const result = remark().use(remarkHeading).processSync(content);

if ('toc' in result.data) return result.data.toc as TableOfContents;
Expand Down
2 changes: 1 addition & 1 deletion packages/mdx/src/config/cached.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import * as fs from 'node:fs';
import { loadConfig, type LoadedConfig } from '@/config/load';

const cache = new Map<
Expand Down
1 change: 1 addition & 0 deletions packages/mdx/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './types';
export * from './define';
export * from '../utils/mdx-options';
export { frontmatterSchema, metaSchema } from '../utils/schema';
export * from '../mdx-plugins/remark-include';
4 changes: 2 additions & 2 deletions packages/mdx/src/map/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'node:path';
import fs from 'node:fs';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import grayMatter from 'gray-matter';
import { getConfigHash, loadConfigCached } from '@/config/cached';
Expand Down
4 changes: 2 additions & 2 deletions packages/mdx/src/map/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'node:fs';
import path from 'node:path';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { type LoadedConfig } from '@/config/load';
import type { MetaFile } from '@/loader-mdx';
import { getTypeFromPath } from '@/utils/get-type-from-path';
Expand Down
56 changes: 56 additions & 0 deletions packages/mdx/src/mdx-plugins/remark-include.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Processor, Transformer } from 'unified';
import { visit } from 'unist-util-visit';
import type { Literal, Root } from 'mdast';
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import matter from 'gray-matter';

export function remarkInclude(this: Processor): Transformer<Root, Root> {
return async (tree, file) => {
const queue: Promise<void>[] = [];

visit(tree, ['mdxJsxFlowElement', 'paragraph'] as const, (node) => {
let specifier: string | undefined;

// without MDX, parse from paragraph nodes
if (node.type === 'paragraph' && node.children.length === 3) {
const [open, content, closure] = node.children;

if (
open.type === 'html' &&
open.value === '<include>' &&
content.type === 'text' &&
closure.type === 'html' &&
closure.value === '</include>'
) {
specifier = content.value.trim();
}
}

if (node.type === 'mdxJsxFlowElement' && node.name === 'include') {
const child = node.children.at(0) as Literal | undefined;

if (!child || child.type !== 'text') return;
specifier = child.value;
}

if (!specifier) return 'skip';
const targetPath = path.resolve(path.dirname(file.path), specifier);

queue.push(
fs.readFile(targetPath).then((content) => {
const parsed = this.parse(matter(content).content);

if (file.data._compiler) {
file.data._compiler.addDependency(targetPath);
}
Object.assign(node, parsed);
}),
);

return 'skip';
});

await Promise.all(queue);
};
}
4 changes: 2 additions & 2 deletions packages/mdx/src/postinstall.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'node:path';
import fs from 'node:fs';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { findConfigFile, loadConfig } from '@/config/load';
import { generateTypes } from '@/map/generate';

Expand Down
46 changes: 2 additions & 44 deletions packages/mdx/src/utils/build-mdx.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { createProcessor, type ProcessorOptions } from '@mdx-js/mdx';
import type { VFile } from 'vfile';
import type { Transformer } from 'unified';
import { visit } from 'unist-util-visit';
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
import type { Literal } from 'mdast';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import matter from 'gray-matter';
import { remarkInclude } from '@/mdx-plugins/remark-include';

type Processor = ReturnType<typeof createProcessor>;

Expand Down Expand Up @@ -50,38 +44,6 @@ declare module 'vfile' {
}
}

function remarkInclude(this: Processor): Transformer {
return async (tree, file) => {
const queue: Promise<void>[] = [];

visit(tree, 'mdxJsxFlowElement', (node: MdxJsxFlowElement) => {
if (node.name === 'include') {
const child = node.children.at(0) as Literal | undefined;

if (!child || child.type !== 'text') return;
const specifier = child.value;

const targetPath = path.resolve(path.dirname(file.path), specifier);

queue.push(
fs.readFile(targetPath).then((content) => {
const parsed = this.parse(matter(content).content);

if (file.data._compiler) {
file.data._compiler.addDependency(targetPath);
}
Object.assign(node, parsed);
}),
);
}

return 'skip';
});

await Promise.all(queue);
};
}

/**
* @param group - The cache group of MDX content, usually the collection name
* @param configHash - config hash
Expand Down Expand Up @@ -111,11 +73,7 @@ export function buildMDX(
outputFormat: 'program',
development: process.env.NODE_ENV === 'development',
...rest,
remarkPlugins: [
// @ts-expect-error -- processor
remarkInclude,
...(rest.remarkPlugins ?? []),
],
remarkPlugins: [remarkInclude, ...(rest.remarkPlugins ?? [])],
format,
}),

Expand Down
7 changes: 1 addition & 6 deletions packages/ui/src/theme/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const neutral: Preset = {
foreground: '0 0% 3.9%',
muted: '0 0% 96.1%',
'muted-foreground': '0 0% 45.1%',
popover: '0 0% 100%',
popover: '0 0% 98%',
'popover-foreground': '0 0% 15.1%',
card: '0 0% 94.7%',
'card-foreground': '0 0% 3.9%',
Expand Down Expand Up @@ -123,11 +123,6 @@ const neutral: Preset = {
ring: '0 0% 14.9%',
},
css: {
'#nd-sidebar': {
'--muted': '0deg 0% 89%',
'--secondary': '0deg 0% 99%',
'--muted-foreground': '0 0% 30%',
},
'.dark #nd-sidebar': {
'--muted': '0deg 0% 16%',
'--secondary': '0deg 0% 18%',
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b71064a

Please sign in to comment.