Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package: compile-mdx #7044

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { compileMDX } from '@node-core/compile-mdx/compiler';
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
import { VFile } from 'vfile';

import ChangelogModal from '@/components/Downloads/ChangelogModal';
import { MDXRenderer } from '@/components/mdxRenderer';
import { compileMDX } from '@/next.mdx.compiler.mjs';
import { REHYPE_PLUGINS, REMARK_PLUGINS } from '@/mdx.plugins.mjs';
import { getGitHubAvatarUrl } from '@/util/gitHubUtils';

type Story = StoryObj<typeof ChangelogModal>;
Expand Down Expand Up @@ -189,10 +190,12 @@ export const Default: Story = {
render: (_, { loaded: { Content } }) => Content,
loaders: [
async ({ args: { children, ...props } }) => {
const { MDXContent } = await compileMDX(
new VFile(children?.toString()),
'md'
);
const { MDXContent } = await compileMDX({
source: new VFile(children?.toString()),
fileExtension: 'md',
rehypePlugins: REHYPE_PLUGINS,
remarkPlugins: REMARK_PLUGINS,
});

return {
Content: (
Expand Down
14 changes: 11 additions & 3 deletions apps/site/components/Downloads/Release/ReleaseCodeBox.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
'use client';

import {
shikiPromise,
highlightToHtml,
} from '@node-core/compile-mdx/utils/getHighlighter.js';
import { useTranslations } from 'next-intl';
import { useContext, useEffect, useState } from 'react';
import type { FC } from 'react';

import CodeBox from '@/components/Common/CodeBox';
import { ReleaseContext } from '@/providers/releaseProvider';
import { shikiPromise, highlightToHtml } from '@/util/getHighlighter';
import { LANGUAGES, DEFAULT_THEME } from '@/shiki.config.mjs';
import { getNodeDownloadSnippet } from '@/util/getNodeDownloadSnippet';

const memoizedShiki = shikiPromise.then(highlightToHtml);
const memoizedShiki = shikiPromise(LANGUAGES, DEFAULT_THEME).then(
highlightToHtml
);

const ReleaseCodeBox: FC = () => {
const { platform, os, release } = useContext(ReleaseContext);
Expand All @@ -23,7 +29,9 @@ const ReleaseCodeBox: FC = () => {
// but usually we should recommend users to download "major" versions
// since our Download Buttons get the latest minor of a major, it does make sense
// to request installation of a major via a package manager
memoizedShiki.then(shiki => shiki(updatedCode, 'bash')).then(setCode);
memoizedShiki
.then(shiki => shiki(updatedCode, 'bash', DEFAULT_THEME))
.then(setCode);
// Only react when the specific release number changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [release.versionWithPrefix, os, platform]);
Expand Down
10 changes: 8 additions & 2 deletions apps/site/components/MDX/CodeBox/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { compileMDX } from '@node-core/compile-mdx/compiler';
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
import { VFile } from 'vfile';

import { MDXRenderer } from '@/components/mdxRenderer';
import { compileMDX } from '@/next.mdx.compiler.mjs';
import { REHYPE_PLUGINS, REMARK_PLUGINS } from '@/mdx.plugins.mjs';

type Props = { children: string };

Expand Down Expand Up @@ -35,7 +36,12 @@ export default {
render: (_, { loaded: { Content } }) => Content,
loaders: [
async ({ args }) => {
const { MDXContent } = await compileMDX(new VFile(args.children), 'mdx');
const { MDXContent } = await compileMDX({
source: new VFile(args.children),
fileExtension: 'mdx',
rehypePlugins: REHYPE_PLUGINS,
remarkPlugins: REMARK_PLUGINS,
});

return { Content: <MDXRenderer Component={MDXContent} /> };
},
Expand Down
10 changes: 8 additions & 2 deletions apps/site/components/MDX/CodeTabs/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { compileMDX } from '@node-core/compile-mdx/compiler';
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
import { VFile } from 'vfile';

import { MDXRenderer } from '@/components/mdxRenderer';
import { compileMDX } from '@/next.mdx.compiler.mjs';
import { REHYPE_PLUGINS, REMARK_PLUGINS } from '@/mdx.plugins.mjs';

type Props = { children: string };

Expand Down Expand Up @@ -44,7 +45,12 @@ export default {
render: (_, { loaded: { Content } }) => Content,
loaders: [
async ({ args }) => {
const { MDXContent } = await compileMDX(new VFile(args.children), 'mdx');
const { MDXContent } = await compileMDX({
source: new VFile(args.children),
fileExtension: 'mdx',
rehypePlugins: REHYPE_PLUGINS,
remarkPlugins: REMARK_PLUGINS,
});

return { Content: <MDXRenderer Component={MDXContent} /> };
},
Expand Down
4 changes: 2 additions & 2 deletions apps/site/components/mdxRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { MDXComponents, MDXContent } from 'mdx/types';
import type { FC } from 'react';

import { htmlComponents, clientMdxComponents } from '@/next.mdx.use.client.mjs';
import { mdxComponents } from '@/next.mdx.use.mjs';
import { mdxComponents } from '@/mdx.use';
import { htmlComponents, clientMdxComponents } from '@/mdx.use.client';

// Combine all MDX Components to be used
const combinedComponents: MDXComponents = {
Expand Down
32 changes: 18 additions & 14 deletions apps/site/components/withChangelogModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client';

import { compileMDX } from '@node-core/compile-mdx/compiler';
import { useState, useEffect } from 'react';
import type { FC, ReactElement } from 'react';
import { VFile } from 'vfile';

import ChangelogModal from '@/components/Downloads/ChangelogModal';
import { REHYPE_PLUGINS, REMARK_PLUGINS } from '@/mdx.plugins.mjs';
import { clientMdxComponents, htmlComponents } from '@/mdx.use.client';
import changelogData from '@/next-data/changelogData';
import { compileMDX } from '@/next.mdx.compiler.mjs';
import { clientMdxComponents, htmlComponents } from '@/next.mdx.use.client.mjs';
import type { NodeRelease } from '@/types';
import {
getNodeJsChangelogAuthor,
Expand Down Expand Up @@ -49,18 +50,21 @@ const WithChangelogModal: FC<WithChangelogModalProps> = ({
// render the changelog heading as the "ChangelogModal" subheading
const changelogWithoutHeader = data.split('\n').slice(2).join('\n');

compileMDX(new VFile(changelogWithoutHeader), 'md').then(
({ MDXContent }) => {
// This is a tricky one. React states does not allow you to actually store React components
// hence we need to render the component within an Effect and set the state as a ReactElement
// which is a function that can be eval'd by React during runtime.
const renderedElement = (
<MDXContent components={clientComponents} />
);

setChangelogMDX(renderedElement);
}
);
compileMDX({
source: new VFile(changelogWithoutHeader),
fileExtension: 'md',
rehypePlugins: REHYPE_PLUGINS,
remarkPlugins: REMARK_PLUGINS,
}).then(({ MDXContent }) => {
// This is a tricky one. React states does not allow you to actually store React components
// hence we need to render the component within an Effect and set the state as a ReactElement
// which is a function that can be eval'd by React during runtime.
const renderedElement = (
<MDXContent components={clientComponents} />
);

setChangelogMDX(renderedElement);
});
}
} catch {
throw new Error(`Failed to fetch changelog for, ${versionWithPrefix}`);
Expand Down
13 changes: 6 additions & 7 deletions apps/site/next.mdx.mjs → apps/site/mdx.plugins.mjs
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
'use strict';

import rehypeShikiji from '@node-core/compile-mdx/shiki';
import remarkHeadings from '@vcarl/remark-headings';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeSlug from 'rehype-slug';
import remarkGfm from 'remark-gfm';
import readingTime from 'remark-reading-time';
import remarkReadingTime from 'remark-reading-time';

import rehypeShikiji from './next.mdx.shiki.mjs';
import { DEFAULT_THEME, LANGUAGES } from './shiki.config.mjs';

/**
* Provides all our Rehype Plugins that are used within MDX
*
* @type {Array<import('unified').Plugin>}
*/
export const NEXT_REHYPE_PLUGINS = [
export const REHYPE_PLUGINS = [
// Generates `id` attributes for headings (H1, ...)
rehypeSlug,
// Automatically add anchor links to headings (H1, ...)
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
// Transforms sequential code elements into code tabs and
// adds our syntax highlighter (Shikiji) to Codeboxes
rehypeShikiji,
rehypeShikiji(LANGUAGES, DEFAULT_THEME),
];

/**
* Provides all our Remark Plugins that are used within MDX
*
* @type {Array<import('unified').Plugin>}
*/
export const NEXT_REMARK_PLUGINS = [remarkGfm, remarkHeadings, readingTime];
export const REMARK_PLUGINS = [remarkGfm, remarkHeadings, remarkReadingTime];
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use strict';
import type { MDXComponents } from 'mdx/types';

import Blockquote from './components/Common/Blockquote';
import Button from './components/Common/Button';
Expand All @@ -10,8 +10,6 @@ import MDXImage from './components/MDX/Image';

/**
* A full list of React Components that we want to pass through to MDX
*
* @satisfies {import('mdx/types').MDXComponents}
*/
export const clientMdxComponents = {
// Renders MDX CodeTabs
Expand All @@ -20,12 +18,10 @@ export const clientMdxComponents = {
Button: Button,
// Links with External Arrow
LinkWithArrow: LinkWithArrow,
};
} as MDXComponents;

/**
* A full list of wired HTML elements into custom React Components
*
* @type {import('mdx/types').MDXComponents}
*/
export const htmlComponents = {
// Renders a Link Component for `a` tags
Expand All @@ -36,4 +32,4 @@ export const htmlComponents = {
pre: MDXCodeBox,
// Renders an Image Component for `img` tags
img: MDXImage,
};
} as MDXComponents;
6 changes: 2 additions & 4 deletions apps/site/next.mdx.use.mjs → apps/site/mdx.use.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use strict';
import type { MDXComponents } from 'mdx/types';

import DownloadButton from './components/Downloads/DownloadButton';
import DownloadLink from './components/Downloads/DownloadLink';
Expand All @@ -24,8 +24,6 @@ import WithNodeRelease from './components/withNodeRelease';

/**
* A full list of React Components that we want to pass through to MDX
*
* @satisfies {import('mdx/types').MDXComponents}
*/
export const mdxComponents = {
DownloadReleasesTable: DownloadReleasesTable,
Expand Down Expand Up @@ -73,4 +71,4 @@ export const mdxComponents = {
// Renders a Changelog Modal Link Button
ChangelogLink: ChangelogLink,
},
};
} satisfies MDXComponents;
29 changes: 25 additions & 4 deletions apps/site/next.dynamic.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { join, normalize, sep } from 'node:path';

import { compileMDX } from '@node-core/compile-mdx/compiler';
import matter from 'gray-matter';
import { cache } from 'react';
import { VFile } from 'vfile';

import { REHYPE_PLUGINS, REMARK_PLUGINS } from './mdx.plugins.mjs';
import { BASE_URL, BASE_PATH, IS_DEVELOPMENT } from './next.constants.mjs';
import {
IGNORED_ROUTES,
Expand All @@ -17,7 +19,7 @@ import {
import { getMarkdownFiles } from './next.helpers.mjs';
import { siteConfig } from './next.json.mjs';
import { availableLocaleCodes, defaultLocale } from './next.locales.mjs';
import { compileMDX } from './next.mdx.compiler.mjs';
import { createGitHubSlugger } from './util/gitHubUtils';

// This is the combination of the Application Base URL and Base PATH
const baseUrlAndPath = `${BASE_URL}${BASE_PATH}`;
Expand Down Expand Up @@ -163,17 +165,36 @@ const getDynamicRouter = async () => {
* @param {string} source
* @param {string} filename
*/
const _getMDXContent = async (source = '', filename = '') => {
const _getMDXContent = async (sourceString = '', filename = '') => {
// We create a VFile (Virtual File) to be able to access some contextual
// data post serialization (compilation) of the source Markdown into MDX
const sourceAsVirtualFile = new VFile(source);
const sourceAsVirtualFile = new VFile(sourceString);

// Gets the file extension of the file, to determine which parser and plugins to use
const fileExtension = filename.endsWith('.mdx') ? 'mdx' : 'md';

// This compiles our MDX source (VFile) into a final MDX-parsed VFile
// that then is passed as a string to the MDXProvider which will run the MDX Code
return compileMDX(sourceAsVirtualFile, fileExtension);
const { MDXContent, source } = await compileMDX({
source: sourceAsVirtualFile,
fileExtension: fileExtension,
rehypePlugins: REHYPE_PLUGINS,
remarkPlugins: REMARK_PLUGINS,
});

const slugger = createGitHubSlugger();

// Retrieve some parsed data from the VFile metadata
// such as frontmatter and Markdown headings
const { headings, matter: frontmatter, readingTime } = source.data;

headings.forEach(heading => {
// we re-sluggify the links to match the GitHub slugger
// since some also do not come with sluggifed links
heading.data = { ...heading.data, id: slugger(heading.value) };
});

return { MDXContent, headings, frontmatter, readingTime };
};

// Creates a Cached Version of the MDX Compiler
Expand Down
52 changes: 0 additions & 52 deletions apps/site/next.mdx.compiler.mjs

This file was deleted.

Loading
Loading