mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
fix: allow html syntax in MDX v2 with format md (#8960)
* attempt to support html embeds in mdx with format md * refactor mdx loader + support embedding html in commonmark thanks to rehype-raw * extract processor code * refactor processor code * extract format + unit test * try to refactor processor * try to refactor processor * adjust md page * do not apply rehype-raw when format is mdx * fix lint issue
This commit is contained in:
parent
af9a4f2a2e
commit
07ad635b69
11 changed files with 491 additions and 173 deletions
4
jest/deps.d.ts
vendored
4
jest/deps.d.ts
vendored
|
@ -13,10 +13,6 @@ declare module 'to-vfile' {
|
|||
export function read(path: string, encoding?: string): Promise<VFile>;
|
||||
}
|
||||
|
||||
declare module 'remark-rehype';
|
||||
|
||||
declare module 'rehype-stringify';
|
||||
|
||||
declare module '@testing-utils/git' {
|
||||
const createTempRepo: typeof import('./utils/git').createTempRepo;
|
||||
export {createTempRepo};
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"image-size": "^1.0.2",
|
||||
"mdast-util-to-string": "^3.0.0",
|
||||
"mdast-util-mdx": "^2.0.0",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"remark-comment": "^1.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-directive": "^2.0.1",
|
||||
|
|
52
packages/docusaurus-mdx-loader/src/__tests__/format.test.ts
Normal file
52
packages/docusaurus-mdx-loader/src/__tests__/format.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {getFormat} from '../format';
|
||||
|
||||
describe('getFormat', () => {
|
||||
it('uses frontMatter format over anything else', () => {
|
||||
expect(getFormat({frontMatterFormat: 'md', filePath: 'xyz.md'})).toBe('md');
|
||||
expect(getFormat({frontMatterFormat: 'md', filePath: 'xyz.mdx'})).toBe(
|
||||
'md',
|
||||
);
|
||||
expect(getFormat({frontMatterFormat: 'mdx', filePath: 'xyz.md'})).toBe(
|
||||
'mdx',
|
||||
);
|
||||
expect(getFormat({frontMatterFormat: 'mdx', filePath: 'xyz.mdx'})).toBe(
|
||||
'mdx',
|
||||
);
|
||||
});
|
||||
|
||||
it('detects appropriate format from file extension', () => {
|
||||
expect(getFormat({frontMatterFormat: 'detect', filePath: 'xyz.md'})).toBe(
|
||||
'md',
|
||||
);
|
||||
expect(
|
||||
getFormat({frontMatterFormat: 'detect', filePath: 'xyz.markdown'}),
|
||||
).toBe('md');
|
||||
|
||||
expect(
|
||||
getFormat({frontMatterFormat: 'detect', filePath: 'folder/xyz.md'}),
|
||||
).toBe('md');
|
||||
expect(
|
||||
getFormat({frontMatterFormat: 'detect', filePath: 'folder/xyz.markdown'}),
|
||||
).toBe('md');
|
||||
expect(getFormat({frontMatterFormat: 'detect', filePath: 'xyz.mdx'})).toBe(
|
||||
'mdx',
|
||||
);
|
||||
expect(
|
||||
getFormat({frontMatterFormat: 'detect', filePath: 'folder/xyz.mdx'}),
|
||||
).toBe('mdx');
|
||||
|
||||
expect(
|
||||
getFormat({frontMatterFormat: 'detect', filePath: 'xyz.unknown'}),
|
||||
).toBe('mdx');
|
||||
expect(
|
||||
getFormat({frontMatterFormat: 'detect', filePath: 'folder/xyz.unknown'}),
|
||||
).toBe('mdx');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// import {createProcessor} from '../processor';
|
||||
// import type {Options} from '../loader';
|
||||
|
||||
/*
|
||||
async function testProcess({
|
||||
format,
|
||||
options,
|
||||
}: {
|
||||
format: 'md' | 'mdx';
|
||||
options: Options;
|
||||
}) {
|
||||
return async (content: string) => {
|
||||
const processor = await createProcessor({format, options});
|
||||
return processor.process(content);
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
describe('md processor', () => {
|
||||
it('parses simple commonmark', async () => {
|
||||
// TODO no tests for now, wait until ESM support
|
||||
// Jest does not support well ESM modules
|
||||
// It would require to vendor too much Unified modules as CJS
|
||||
// See https://mdxjs.com/docs/troubleshooting-mdx/#esm
|
||||
});
|
||||
});
|
40
packages/docusaurus-mdx-loader/src/format.ts
Normal file
40
packages/docusaurus-mdx-loader/src/format.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type {MDXFrontMatter} from './frontMatter';
|
||||
|
||||
// Copied from https://mdxjs.com/packages/mdx/#optionsmdextensions
|
||||
// Although we are likely to only use .md / .mdx anyway...
|
||||
const mdFormatExtensions = [
|
||||
'.md',
|
||||
'.markdown',
|
||||
'.mdown',
|
||||
'.mkdn',
|
||||
'.mkd',
|
||||
'.mdwn',
|
||||
'.mkdown',
|
||||
'.ron',
|
||||
];
|
||||
|
||||
function isMDFormat(filepath: string) {
|
||||
return mdFormatExtensions.includes(path.extname(filepath));
|
||||
}
|
||||
|
||||
export function getFormat({
|
||||
filePath,
|
||||
frontMatterFormat,
|
||||
}: {
|
||||
filePath: string;
|
||||
frontMatterFormat: MDXFrontMatter['format'];
|
||||
}): 'md' | 'mdx' {
|
||||
if (frontMatterFormat !== 'detect') {
|
||||
return frontMatterFormat;
|
||||
}
|
||||
// Bias toward mdx if unknown extension
|
||||
return isMDFormat(filePath) ? 'md' : 'mdx';
|
||||
}
|
|
@ -30,4 +30,6 @@ export type LoadedMDXContent<FrontMatter, Metadata, Assets = undefined> = {
|
|||
readonly assets: Assets;
|
||||
(): JSX.Element;
|
||||
};
|
||||
export type {Options, MDXPlugin, MDXOptions} from './loader';
|
||||
|
||||
export type {Options, MDXPlugin} from './loader';
|
||||
export type {MDXOptions} from './processor';
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import logger from '@docusaurus/logger';
|
||||
import {
|
||||
parseFrontMatter,
|
||||
|
@ -14,82 +13,26 @@ import {
|
|||
escapePath,
|
||||
getFileLoaderUtils,
|
||||
} from '@docusaurus/utils';
|
||||
import emoji from 'remark-emoji';
|
||||
import stringifyObject from 'stringify-object';
|
||||
import preprocessor from './preprocessor';
|
||||
import headings from './remark/headings';
|
||||
import toc from './remark/toc';
|
||||
import transformImage from './remark/transformImage';
|
||||
import transformLinks from './remark/transformLinks';
|
||||
import details from './remark/details';
|
||||
import head from './remark/head';
|
||||
import mermaid from './remark/mermaid';
|
||||
import transformAdmonitions from './remark/admonitions';
|
||||
import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
|
||||
import {validateMDXFrontMatter} from './frontMatter';
|
||||
import {createProcessorCached} from './processor';
|
||||
import type {MDXOptions} from './processor';
|
||||
|
||||
import type {MarkdownConfig} from '@docusaurus/types';
|
||||
import type {LoaderContext} from 'webpack';
|
||||
|
||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||
import type {Processor} from 'unified';
|
||||
import type {AdmonitionOptions} from './remark/admonitions';
|
||||
|
||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||
import type {ProcessorOptions} from '@mdx-js/mdx';
|
||||
|
||||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
||||
// This might change soon, likely after TS 5.2
|
||||
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
|
||||
type Pluggable = any; // TODO fix this asap
|
||||
|
||||
// Copied from https://mdxjs.com/packages/mdx/#optionsmdextensions
|
||||
// Although we are likely to only use .md / .mdx anyway...
|
||||
const mdFormatExtensions = [
|
||||
'.md',
|
||||
'.markdown',
|
||||
'.mdown',
|
||||
'.mkdn',
|
||||
'.mkd',
|
||||
'.mdwn',
|
||||
'.mkdown',
|
||||
'.ron',
|
||||
];
|
||||
|
||||
function isMDFormat(filepath: string) {
|
||||
return mdFormatExtensions.includes(path.extname(filepath));
|
||||
}
|
||||
|
||||
const {
|
||||
loaders: {inlineMarkdownImageFileLoader},
|
||||
} = getFileLoaderUtils();
|
||||
|
||||
const DEFAULT_OPTIONS: MDXOptions = {
|
||||
admonitions: true,
|
||||
rehypePlugins: [],
|
||||
remarkPlugins: [emoji, headings, toc],
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
beforeDefaultRehypePlugins: [],
|
||||
};
|
||||
|
||||
type CompilerCacheEntry = {
|
||||
mdCompiler: Processor;
|
||||
mdxCompiler: Processor;
|
||||
options: Options;
|
||||
};
|
||||
|
||||
const compilerCache = new Map<string | Options, CompilerCacheEntry>();
|
||||
|
||||
export type MDXPlugin = Pluggable;
|
||||
|
||||
export type MDXOptions = {
|
||||
admonitions: boolean | Partial<AdmonitionOptions>;
|
||||
remarkPlugins: MDXPlugin[];
|
||||
rehypePlugins: MDXPlugin[];
|
||||
beforeDefaultRemarkPlugins: MDXPlugin[];
|
||||
beforeDefaultRehypePlugins: MDXPlugin[];
|
||||
};
|
||||
|
||||
export type Options = Partial<MDXOptions> & {
|
||||
markdownConfig: MarkdownConfig;
|
||||
staticDirs: string[];
|
||||
|
@ -167,20 +110,6 @@ function createAssetsExportCode(assets: unknown) {
|
|||
return `{\n${codeLines.join('\n')}\n}`;
|
||||
}
|
||||
|
||||
function getAdmonitionsPlugins(
|
||||
admonitionsOption: MDXOptions['admonitions'],
|
||||
): MDXPlugin[] {
|
||||
if (admonitionsOption) {
|
||||
const plugin: MDXPlugin =
|
||||
admonitionsOption === true
|
||||
? transformAdmonitions
|
||||
: [transformAdmonitions, admonitionsOption];
|
||||
return [plugin];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// TODO temporary, remove this after v3.1?
|
||||
// Some plugin authors use our mdx-loader, despite it not being public API
|
||||
// see https://github.com/facebook/docusaurus/issues/8298
|
||||
|
@ -198,14 +127,10 @@ export async function mdxLoader(
|
|||
): Promise<void> {
|
||||
const callback = this.async();
|
||||
const filePath = this.resourcePath;
|
||||
const reqOptions = this.getOptions();
|
||||
const reqOptions: Options = this.getOptions();
|
||||
const {query} = this;
|
||||
ensureMarkdownConfig(reqOptions);
|
||||
|
||||
const {createProcessor} = await import('@mdx-js/mdx');
|
||||
const {default: gfm} = await import('remark-gfm');
|
||||
const {default: comment} = await import('remark-comment');
|
||||
const {default: directives} = await import('remark-directive');
|
||||
|
||||
const {frontMatter, content: contentWithTitle} = parseFrontMatter(fileString);
|
||||
const mdxFrontMatter = validateMDXFrontMatter(frontMatter.mdx);
|
||||
|
||||
|
@ -225,93 +150,16 @@ export async function mdxLoader(
|
|||
|
||||
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
||||
|
||||
if (!compilerCache.has(this.query)) {
|
||||
/*
|
||||
/!\ DO NOT PUT ANY ASYNC / AWAIT / DYNAMIC IMPORTS HERE
|
||||
This creates cache creation race conditions
|
||||
TODO extract this in a synchronous method
|
||||
*/
|
||||
|
||||
const remarkPlugins: MDXPlugin[] = [
|
||||
...(reqOptions.beforeDefaultRemarkPlugins ?? []),
|
||||
directives,
|
||||
...getAdmonitionsPlugins(reqOptions.admonitions ?? false),
|
||||
...DEFAULT_OPTIONS.remarkPlugins,
|
||||
details,
|
||||
head,
|
||||
...(reqOptions.markdownConfig.mermaid ? [mermaid] : []),
|
||||
[
|
||||
transformImage,
|
||||
{
|
||||
staticDirs: reqOptions.staticDirs,
|
||||
siteDir: reqOptions.siteDir,
|
||||
},
|
||||
],
|
||||
[
|
||||
transformLinks,
|
||||
{
|
||||
staticDirs: reqOptions.staticDirs,
|
||||
siteDir: reqOptions.siteDir,
|
||||
},
|
||||
],
|
||||
gfm,
|
||||
reqOptions.markdownConfig.mdx1Compat.comments ? comment : null,
|
||||
...(reqOptions.remarkPlugins ?? []),
|
||||
].filter((plugin): plugin is MDXPlugin => Boolean(plugin));
|
||||
|
||||
// codeCompatPlugin needs to be applied last after user-provided plugins
|
||||
// (after npm2yarn for example)
|
||||
remarkPlugins.push(codeCompatPlugin);
|
||||
|
||||
const rehypePlugins: MDXPlugin[] = [
|
||||
...(reqOptions.beforeDefaultRehypePlugins ?? []),
|
||||
...DEFAULT_OPTIONS.rehypePlugins,
|
||||
...(reqOptions.rehypePlugins ?? []),
|
||||
];
|
||||
|
||||
const options: ProcessorOptions & Options = {
|
||||
...reqOptions,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
providerImportSource: '@mdx-js/react',
|
||||
};
|
||||
|
||||
const compilerCacheEntry: CompilerCacheEntry = {
|
||||
mdCompiler: createProcessor({
|
||||
...options,
|
||||
format: 'md',
|
||||
}),
|
||||
mdxCompiler: createProcessor({
|
||||
...options,
|
||||
format: 'mdx',
|
||||
}),
|
||||
options,
|
||||
};
|
||||
|
||||
compilerCache.set(this.query, compilerCacheEntry);
|
||||
}
|
||||
|
||||
const {mdCompiler, mdxCompiler, options} = compilerCache.get(this.query)!;
|
||||
|
||||
function getCompiler() {
|
||||
const format =
|
||||
mdxFrontMatter.format === 'detect'
|
||||
? isMDFormat(filePath)
|
||||
? 'md'
|
||||
: 'mdx'
|
||||
: mdxFrontMatter.format;
|
||||
|
||||
return format === 'md' ? mdCompiler : mdxCompiler;
|
||||
}
|
||||
const processor = await createProcessorCached({
|
||||
filePath,
|
||||
reqOptions,
|
||||
query,
|
||||
mdxFrontMatter,
|
||||
});
|
||||
|
||||
let result: string;
|
||||
try {
|
||||
result = await getCompiler()
|
||||
.process({
|
||||
value: content,
|
||||
path: filePath,
|
||||
})
|
||||
.then((res) => res.toString());
|
||||
result = await processor.process({content, filePath});
|
||||
} catch (errorUnknown) {
|
||||
const error = errorUnknown as Error;
|
||||
return callback(
|
||||
|
@ -327,14 +175,14 @@ export async function mdxLoader(
|
|||
|
||||
// MDX partials are MDX files starting with _ or in a folder starting with _
|
||||
// Partial are not expected to have associated metadata files or front matter
|
||||
const isMDXPartial = options.isMDXPartial?.(filePath);
|
||||
const isMDXPartial = reqOptions.isMDXPartial?.(filePath);
|
||||
if (isMDXPartial && hasFrontMatter) {
|
||||
const errorMessage = `Docusaurus MDX partial files should not contain front matter.
|
||||
Those partial files use the _ prefix as a convention by default, but this is configurable.
|
||||
File at ${filePath} contains front matter that will be ignored:
|
||||
${JSON.stringify(frontMatter, null, 2)}`;
|
||||
|
||||
if (!options.isMDXPartialFrontMatterWarningDisabled) {
|
||||
if (!reqOptions.isMDXPartialFrontMatterWarningDisabled) {
|
||||
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
|
||||
if (shouldError) {
|
||||
return callback(new Error(errorMessage));
|
||||
|
@ -346,8 +194,11 @@ ${JSON.stringify(frontMatter, null, 2)}`;
|
|||
function getMetadataPath(): string | undefined {
|
||||
if (!isMDXPartial) {
|
||||
// Read metadata for this MDX and export it.
|
||||
if (options.metadataPath && typeof options.metadataPath === 'function') {
|
||||
return options.metadataPath(filePath);
|
||||
if (
|
||||
reqOptions.metadataPath &&
|
||||
typeof reqOptions.metadataPath === 'function'
|
||||
) {
|
||||
return reqOptions.metadataPath(filePath);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
|
241
packages/docusaurus-mdx-loader/src/processor.ts
Normal file
241
packages/docusaurus-mdx-loader/src/processor.ts
Normal file
|
@ -0,0 +1,241 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import emoji from 'remark-emoji';
|
||||
import headings from './remark/headings';
|
||||
import toc from './remark/toc';
|
||||
import transformImage from './remark/transformImage';
|
||||
import transformLinks from './remark/transformLinks';
|
||||
import details from './remark/details';
|
||||
import head from './remark/head';
|
||||
import mermaid from './remark/mermaid';
|
||||
import transformAdmonitions from './remark/admonitions';
|
||||
import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
|
||||
import {getFormat} from './format';
|
||||
import type {MDXFrontMatter} from './frontMatter';
|
||||
import type {Options} from './loader';
|
||||
import type {AdmonitionOptions} from './remark/admonitions';
|
||||
|
||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||
import type {ProcessorOptions} from '@mdx-js/mdx';
|
||||
|
||||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
||||
// This might change soon, likely after TS 5.2
|
||||
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
|
||||
type Pluggable = any; // TODO fix this asap
|
||||
|
||||
// TODO alt interface because impossible to import type Processor (ESM + TS :/)
|
||||
type SimpleProcessor = {
|
||||
process: ({
|
||||
content,
|
||||
filePath,
|
||||
}: {
|
||||
content: string;
|
||||
filePath: string;
|
||||
}) => Promise<string>;
|
||||
};
|
||||
|
||||
const DEFAULT_OPTIONS: MDXOptions = {
|
||||
admonitions: true,
|
||||
rehypePlugins: [],
|
||||
remarkPlugins: [emoji, headings, toc],
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
beforeDefaultRehypePlugins: [],
|
||||
};
|
||||
|
||||
export type MDXPlugin = Pluggable;
|
||||
|
||||
export type MDXOptions = {
|
||||
admonitions: boolean | Partial<AdmonitionOptions>;
|
||||
remarkPlugins: MDXPlugin[];
|
||||
rehypePlugins: MDXPlugin[];
|
||||
beforeDefaultRemarkPlugins: MDXPlugin[];
|
||||
beforeDefaultRehypePlugins: MDXPlugin[];
|
||||
};
|
||||
|
||||
function getAdmonitionsPlugins(
|
||||
admonitionsOption: MDXOptions['admonitions'],
|
||||
): MDXPlugin[] {
|
||||
if (admonitionsOption) {
|
||||
const plugin: MDXPlugin =
|
||||
admonitionsOption === true
|
||||
? transformAdmonitions
|
||||
: [transformAdmonitions, admonitionsOption];
|
||||
return [plugin];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Need to be async due to ESM dynamic imports...
|
||||
async function createProcessorFactory() {
|
||||
const {createProcessor: createMdxProcessor} = await import('@mdx-js/mdx');
|
||||
const {default: rehypeRaw} = await import('rehype-raw');
|
||||
const {default: gfm} = await import('remark-gfm');
|
||||
const {default: comment} = await import('remark-comment');
|
||||
const {default: directive} = await import('remark-directive');
|
||||
|
||||
// /!\ this method is synchronous on purpose
|
||||
// Using async code here can create cache entry race conditions!
|
||||
function createProcessorSync({
|
||||
options,
|
||||
format,
|
||||
}: {
|
||||
options: Options;
|
||||
format: 'md' | 'mdx';
|
||||
}): SimpleProcessor {
|
||||
const remarkPlugins: MDXPlugin[] = [
|
||||
...(options.beforeDefaultRemarkPlugins ?? []),
|
||||
directive,
|
||||
...getAdmonitionsPlugins(options.admonitions ?? false),
|
||||
...DEFAULT_OPTIONS.remarkPlugins,
|
||||
details,
|
||||
head,
|
||||
...(options.markdownConfig.mermaid ? [mermaid] : []),
|
||||
[
|
||||
transformImage,
|
||||
{
|
||||
staticDirs: options.staticDirs,
|
||||
siteDir: options.siteDir,
|
||||
},
|
||||
],
|
||||
[
|
||||
transformLinks,
|
||||
{
|
||||
staticDirs: options.staticDirs,
|
||||
siteDir: options.siteDir,
|
||||
},
|
||||
],
|
||||
gfm,
|
||||
options.markdownConfig.mdx1Compat.comments ? comment : null,
|
||||
...(options.remarkPlugins ?? []),
|
||||
].filter((plugin): plugin is MDXPlugin => Boolean(plugin));
|
||||
|
||||
// codeCompatPlugin needs to be applied last after user-provided plugins
|
||||
// (after npm2yarn for example)
|
||||
remarkPlugins.push(codeCompatPlugin);
|
||||
|
||||
const rehypePlugins: MDXPlugin[] = [
|
||||
...(options.beforeDefaultRehypePlugins ?? []),
|
||||
...DEFAULT_OPTIONS.rehypePlugins,
|
||||
...(options.rehypePlugins ?? []),
|
||||
];
|
||||
|
||||
if (format === 'md') {
|
||||
// This is what permits to embed HTML elements with format 'md'
|
||||
// See https://github.com/facebook/docusaurus/pull/8960
|
||||
// See https://github.com/mdx-js/mdx/pull/2295#issuecomment-1540085960
|
||||
const rehypeRawPlugin: MDXPlugin = [
|
||||
rehypeRaw,
|
||||
{
|
||||
passThrough: [
|
||||
'mdxFlowExpression',
|
||||
'mdxJsxFlowElement',
|
||||
'mdxJsxTextElement',
|
||||
'mdxTextExpression',
|
||||
'mdxjsEsm',
|
||||
],
|
||||
},
|
||||
];
|
||||
rehypePlugins.unshift(rehypeRawPlugin);
|
||||
}
|
||||
|
||||
const processorOptions: ProcessorOptions & Options = {
|
||||
...options,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
providerImportSource: '@mdx-js/react',
|
||||
};
|
||||
|
||||
const mdxProcessor = createMdxProcessor({
|
||||
...processorOptions,
|
||||
format,
|
||||
});
|
||||
|
||||
return {
|
||||
process: async ({content, filePath}) =>
|
||||
mdxProcessor
|
||||
.process({
|
||||
value: content,
|
||||
path: filePath,
|
||||
})
|
||||
.then((res) => res.toString()),
|
||||
};
|
||||
}
|
||||
|
||||
return {createProcessorSync};
|
||||
}
|
||||
|
||||
// Will be useful for tests
|
||||
export async function createProcessorUncached(parameters: {
|
||||
options: Options;
|
||||
format: 'md' | 'mdx';
|
||||
}): Promise<SimpleProcessor> {
|
||||
const {createProcessorSync} = await createProcessorFactory();
|
||||
return createProcessorSync(parameters);
|
||||
}
|
||||
|
||||
// We use different compilers depending on the file type (md vs mdx)
|
||||
type ProcessorsCacheEntry = {
|
||||
mdProcessor: SimpleProcessor;
|
||||
mdxProcessor: SimpleProcessor;
|
||||
};
|
||||
|
||||
// Compilers are cached so that Remark/Rehype plugins can run
|
||||
// expensive code during initialization
|
||||
const ProcessorsCache = new Map<string | Options, ProcessorsCacheEntry>();
|
||||
|
||||
async function createProcessorsCacheEntry({
|
||||
query,
|
||||
reqOptions,
|
||||
}: {
|
||||
query: string | Options;
|
||||
reqOptions: Options;
|
||||
}): Promise<ProcessorsCacheEntry> {
|
||||
const {createProcessorSync} = await createProcessorFactory();
|
||||
|
||||
const compilers = ProcessorsCache.get(query);
|
||||
if (compilers) {
|
||||
return compilers;
|
||||
}
|
||||
|
||||
const compilerCacheEntry: ProcessorsCacheEntry = {
|
||||
mdProcessor: createProcessorSync({
|
||||
options: reqOptions,
|
||||
format: 'md',
|
||||
}),
|
||||
mdxProcessor: createProcessorSync({
|
||||
options: reqOptions,
|
||||
format: 'mdx',
|
||||
}),
|
||||
};
|
||||
|
||||
ProcessorsCache.set(query, compilerCacheEntry);
|
||||
|
||||
return compilerCacheEntry;
|
||||
}
|
||||
|
||||
export async function createProcessorCached({
|
||||
filePath,
|
||||
mdxFrontMatter,
|
||||
query,
|
||||
reqOptions,
|
||||
}: {
|
||||
filePath: string;
|
||||
mdxFrontMatter: MDXFrontMatter;
|
||||
query: string | Options;
|
||||
reqOptions: Options;
|
||||
}): Promise<SimpleProcessor> {
|
||||
const compilers = await createProcessorsCacheEntry({query, reqOptions});
|
||||
|
||||
const format = getFormat({
|
||||
filePath,
|
||||
frontMatterFormat: mdxFrontMatter.format,
|
||||
});
|
||||
|
||||
return format === 'md' ? compilers.mdProcessor : compilers.mdxProcessor;
|
||||
}
|
|
@ -23,7 +23,7 @@ import BrowserWindow from '@site/src/components/BrowserWindow';
|
|||
|
||||
BrowserWindow content
|
||||
|
||||
<BrowserWindow/>
|
||||
</BrowserWindow>
|
||||
|
||||
export const answer = 42;
|
||||
|
||||
|
@ -42,3 +42,55 @@ note
|
|||
## Heading Id {#custom-heading-id}
|
||||
|
||||
Custom heading syntax `{#custom-heading-id}` still works
|
||||
|
||||
---
|
||||
|
||||
## HTML
|
||||
|
||||
### Styling
|
||||
|
||||
<span style="color: blue;">blue span</span>
|
||||
|
||||
<p style="color: green;">green p</p>
|
||||
|
||||
<button style="color: red;">red button</button>
|
||||
|
||||
<div style="border: solid; background-color: grey; color: lime; padding: 10px">
|
||||
lime <span style="color: red; margin: 10px;">red</span>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
### Embeds
|
||||
|
||||
#### Closed image tag:
|
||||
|
||||
<img src="/img/docusaurus.png"/>
|
||||
|
||||
<br/>
|
||||
|
||||
#### Unclosed image tag:
|
||||
|
||||
<img src="/img/docusaurus.png">
|
||||
|
||||
<br/>
|
||||
|
||||
### Iframe
|
||||
|
||||
<iframe src="/" style="width: 100%; height: 300px; border: solid red thick;"></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
### Security
|
||||
|
||||
```md
|
||||
<p>
|
||||
When pressing this button, no alert should be printed
|
||||
<button onClick="alert('unsafe');">Click me</button>
|
||||
</p>
|
||||
```
|
||||
|
||||
<p>
|
||||
When pressing this button, no alert should be printed
|
||||
<button onClick="alert('unsafe');">Click me</button>
|
||||
</p>
|
||||
|
|
|
@ -4,6 +4,8 @@ description: Markdown Page tests description
|
|||
wrapperClassName: docusaurus-markdown-example
|
||||
---
|
||||
|
||||
<button onClick={() => alert('unsafe :s')}>Should not alert</button>
|
||||
|
||||
# Markdown .mdx tests
|
||||
|
||||
This is a page generated from Markdown to illustrate the Markdown page feature and test some edge cases.
|
||||
|
|
48
yarn.lock
48
yarn.lock
|
@ -3268,6 +3268,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
|
||||
integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==
|
||||
|
||||
"@types/parse5@^6.0.0":
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
|
||||
integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
|
||||
|
||||
"@types/picomatch@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/picomatch/-/picomatch-2.3.0.tgz#75db5e75a713c5a83d5b76780c3da84a82806003"
|
||||
|
@ -8235,6 +8240,23 @@ hast-util-parse-selector@^3.0.0:
|
|||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
|
||||
hast-util-raw@^7.2.0:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz#dcb5b22a22073436dbdc4aa09660a644f4991d99"
|
||||
integrity sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/parse5" "^6.0.0"
|
||||
hast-util-from-parse5 "^7.0.0"
|
||||
hast-util-to-parse5 "^7.0.0"
|
||||
html-void-elements "^2.0.0"
|
||||
parse5 "^6.0.0"
|
||||
unist-util-position "^4.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
vfile "^5.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-to-estree@^2.0.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-2.3.2.tgz#11ab0cd2e70ecf0305151af56e636b1cdfbba0bf"
|
||||
|
@ -8272,6 +8294,18 @@ hast-util-to-html@^7.1.1:
|
|||
unist-util-is "^4.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
hast-util-to-parse5@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz#c49391bf8f151973e0c9adcd116b561e8daf29f3"
|
||||
integrity sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
comma-separated-tokens "^2.0.0"
|
||||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-to-string@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-1.0.4.tgz#9b24c114866bdb9478927d7e9c36a485ac728378"
|
||||
|
@ -8442,6 +8476,11 @@ html-void-elements@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483"
|
||||
integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==
|
||||
|
||||
html-void-elements@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
|
||||
integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
|
||||
|
||||
html-webpack-plugin@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50"
|
||||
|
@ -13833,6 +13872,15 @@ rehype-parse@^8.0.0:
|
|||
parse5 "^6.0.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
rehype-raw@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-6.1.1.tgz#81bbef3793bd7abacc6bf8335879d1b6c868c9d4"
|
||||
integrity sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
hast-util-raw "^7.2.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
rehype-stringify@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-8.0.0.tgz#9b6afb599bcf3165f10f93fc8548f9a03d2ec2ba"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue