diff --git a/argos/playwright.config.ts b/argos/playwright.config.ts
index 31409769110c2..519227c75121b 100644
--- a/argos/playwright.config.ts
+++ b/argos/playwright.config.ts
@@ -14,6 +14,8 @@ import type {PlaywrightTestConfig} from '@playwright/test';
const config: PlaywrightTestConfig = {
testDir: './tests',
+ timeout: 60000,
+
reporter: [['list'], ['@argos-ci/playwright/reporter']],
// Run website production built
diff --git a/argos/tests/screenshot.spec.ts b/argos/tests/screenshot.spec.ts
index 70bee2af15c35..f6829a13a9d25 100644
--- a/argos/tests/screenshot.spec.ts
+++ b/argos/tests/screenshot.spec.ts
@@ -36,8 +36,12 @@ function isBlacklisted(pathname: string) {
}
// Some paths explicitly blacklisted
const BlacklistedPathnames: string[] = [
- '/feature-requests', // Flaky because of Canny widget
- '/community/canary', // Flaky because of dynamic canary version fetched from npm
+ // Flaky because of Canny widget
+ '/feature-requests',
+ // Flaky because of dynamic canary version fetched from npm
+ '/community/canary',
+ // Long blog post with many image carousels, often timeouts
+ '/blog/2022/08/01/announcing-docusaurus-2.0',
];
return (
diff --git a/packages/docusaurus-mdx-loader/src/loader.ts b/packages/docusaurus-mdx-loader/src/loader.ts
index 519b5da3b9cdc..a475220cd5c63 100644
--- a/packages/docusaurus-mdx-loader/src/loader.ts
+++ b/packages/docusaurus-mdx-loader/src/loader.ts
@@ -11,6 +11,7 @@ import {
parseFrontMatter,
escapePath,
getFileLoaderUtils,
+ getWebpackLoaderCompilerName,
} from '@docusaurus/utils';
import stringifyObject from 'stringify-object';
import preprocessor from './preprocessor';
@@ -134,10 +135,12 @@ export async function mdxLoader(
this: LoaderContext,
fileString: string,
): Promise {
+ const compilerName = getWebpackLoaderCompilerName(this);
const callback = this.async();
const filePath = this.resourcePath;
const reqOptions: Options = this.getOptions();
const {query} = this;
+
ensureMarkdownConfig(reqOptions);
const {frontMatter} = parseFrontMatter(fileString);
@@ -165,6 +168,7 @@ export async function mdxLoader(
content: preprocessedContent,
filePath,
frontMatter,
+ compilerName,
});
} catch (errorUnknown) {
const error = errorUnknown as Error;
diff --git a/packages/docusaurus-mdx-loader/src/processor.ts b/packages/docusaurus-mdx-loader/src/processor.ts
index b7d500c64a0d0..032cdf4e2ff5f 100644
--- a/packages/docusaurus-mdx-loader/src/processor.ts
+++ b/packages/docusaurus-mdx-loader/src/processor.ts
@@ -15,8 +15,10 @@ import details from './remark/details';
import head from './remark/head';
import mermaid from './remark/mermaid';
import transformAdmonitions from './remark/admonitions';
+import unusedDirectivesWarning from './remark/unusedDirectives';
import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
import {getFormat} from './format';
+import type {WebpackCompilerName} from '@docusaurus/utils';
import type {MDXFrontMatter} from './frontMatter';
import type {Options} from './loader';
import type {AdmonitionOptions} from './remark/admonitions';
@@ -37,10 +39,12 @@ type SimpleProcessor = {
content,
filePath,
frontMatter,
+ compilerName,
}: {
content: string;
filePath: string;
frontMatter: {[key: string]: unknown};
+ compilerName: WebpackCompilerName;
}) => Promise;
};
@@ -123,6 +127,7 @@ async function createProcessorFactory() {
gfm,
options.markdownConfig.mdx1Compat.comments ? comment : null,
...(options.remarkPlugins ?? []),
+ unusedDirectivesWarning,
].filter((plugin): plugin is MDXPlugin => Boolean(plugin));
// codeCompatPlugin needs to be applied last after user-provided plugins
@@ -167,12 +172,13 @@ async function createProcessorFactory() {
});
return {
- process: async ({content, filePath, frontMatter}) => {
+ process: async ({content, filePath, frontMatter, compilerName}) => {
const vfile = new VFile({
value: content,
path: filePath,
data: {
frontMatter,
+ compilerName,
},
});
return mdxProcessor.process(vfile).then((result) => ({
diff --git a/packages/docusaurus-mdx-loader/src/remark/contentTitle/index.ts b/packages/docusaurus-mdx-loader/src/remark/contentTitle/index.ts
index e2e9977dad923..c26bd1f0cce66 100644
--- a/packages/docusaurus-mdx-loader/src/remark/contentTitle/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/contentTitle/index.ts
@@ -35,6 +35,7 @@ const plugin: Plugin = function plugin(
const {toString} = await import('mdast-util-to-string');
visit(root, 'heading', (headingNode: Heading, index, parent) => {
if (headingNode.depth === 1) {
+ vfile.data.compilerName;
vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) {
parent!.children.splice(index, 1);
diff --git a/packages/docusaurus-mdx-loader/src/remark/mermaid/index.ts b/packages/docusaurus-mdx-loader/src/remark/mermaid/index.ts
index 7947d3387e1f9..7a5eac045601c 100644
--- a/packages/docusaurus-mdx-loader/src/remark/mermaid/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/mermaid/index.ts
@@ -6,6 +6,8 @@
*/
import visit from 'unist-util-visit';
+import {transformNode} from '../utils';
+
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
import type {Transformer} from 'unified';
import type {Code} from 'mdast';
@@ -16,10 +18,10 @@ import type {Code} from 'mdast';
// by theme-mermaid itself
export default function plugin(): Transformer {
return (root) => {
- visit(root, 'code', (node: Code, index, parent) => {
+ visit(root, 'code', (node: Code) => {
if (node.lang === 'mermaid') {
// TODO migrate to mdxJsxFlowElement? cf admonitions
- parent!.children.splice(index, 1, {
+ transformNode(node, {
type: 'mermaidCodeBlock',
data: {
hName: 'mermaid',
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
index 5bd9c1d9a9915..956de8a8df477 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
@@ -20,7 +20,7 @@ import visit from 'unist-util-visit';
import escapeHtml from 'escape-html';
import sizeOf from 'image-size';
import logger from '@docusaurus/logger';
-import {assetRequireAttributeValue} from '../utils';
+import {assetRequireAttributeValue, transformNode} from '../utils';
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
import type {Transformer} from 'unified';
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
@@ -110,14 +110,12 @@ ${(err as Error).message}`;
}
}
- Object.keys(jsxNode).forEach(
- (key) => delete jsxNode[key as keyof typeof jsxNode],
- );
-
- jsxNode.type = 'mdxJsxTextElement';
- jsxNode.name = 'img';
- jsxNode.attributes = attributes;
- jsxNode.children = [];
+ transformNode(jsxNode, {
+ type: 'mdxJsxTextElement',
+ name: 'img',
+ attributes,
+ children: [],
+ });
}
async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
index 71086023345ce..bddf42613f4f3 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
@@ -17,7 +17,7 @@ import {
} from '@docusaurus/utils';
import visit from 'unist-util-visit';
import escapeHtml from 'escape-html';
-import {assetRequireAttributeValue} from '../utils';
+import {assetRequireAttributeValue, transformNode} from '../utils';
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
import type {Transformer} from 'unified';
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
@@ -90,14 +90,12 @@ async function toAssetRequireNode(
const {children} = node;
- Object.keys(jsxNode).forEach(
- (key) => delete jsxNode[key as keyof typeof jsxNode],
- );
-
- jsxNode.type = 'mdxJsxTextElement';
- jsxNode.name = 'a';
- jsxNode.attributes = attributes;
- jsxNode.children = children;
+ transformNode(jsxNode, {
+ type: 'mdxJsxTextElement',
+ name: 'a',
+ attributes,
+ children,
+ });
}
async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) {
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/containerDirectives.md b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/containerDirectives.md
new file mode 100644
index 0000000000000..2a416f0ca72bc
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/containerDirectives.md
@@ -0,0 +1,19 @@
+:::danger
+
+Take care of snowstorms...
+
+:::
+
+:::unusedDirective
+
+unused directive content
+
+:::
+
+:::NotAContainerDirective with a phrase after
+
+:::
+
+Phrase before :::NotAContainerDirective
+
+:::
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/leafDirectives.md b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/leafDirectives.md
new file mode 100644
index 0000000000000..73fd60c77b676
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/leafDirectives.md
@@ -0,0 +1,5 @@
+::unusedLeafDirective
+
+Leaf directive in a phrase ::NotALeafDirective
+
+::NotALeafDirective with a phrase after
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/textDirectives.md b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/textDirectives.md
new file mode 100644
index 0000000000000..9410feb923bec
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/textDirectives.md
@@ -0,0 +1,17 @@
+Simple: textDirective1
+
+```sh
+Simple: textDirectiveCode
+```
+
+Simple:textDirective2
+
+Simple:textDirective3[label]
+
+Simple:textDirective4{age=42}
+
+Simple:textDirective5
+
+```sh
+Simple:textDirectiveCode
+```
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000000000..46285b8894e14
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__snapshots__/index.test.ts.snap
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`directives remark plugin - client compiler default behavior for container directives: console 1`] = `
+[
+ [
+ "[WARNING] Docusaurus found 1 unused Markdown directives in file "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/containerDirectives.md"
+- :::unusedDirective (7:1)
+Your content might render in an unexpected way. Visit https://github.com/facebook/docusaurus/pull/9394 to find out why and how to fix it.",
+ ],
+]
+`;
+
+exports[`directives remark plugin - client compiler default behavior for container directives: result 1`] = `
+"Take care of snowstorms...
+
+:::NotAContainerDirective with a phrase after
+:::
+Phrase before :::NotAContainerDirective
+:::
"
+`;
+
+exports[`directives remark plugin - client compiler default behavior for leaf directives: console 1`] = `
+[
+ [
+ "[WARNING] Docusaurus found 1 unused Markdown directives in file "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/leafDirectives.md"
+- ::unusedLeafDirective (1:1)
+Your content might render in an unexpected way. Visit https://github.com/facebook/docusaurus/pull/9394 to find out why and how to fix it.",
+ ],
+]
+`;
+
+exports[`directives remark plugin - client compiler default behavior for leaf directives: result 1`] = `
+"
+Leaf directive in a phrase ::NotALeafDirective
+::NotALeafDirective with a phrase after
"
+`;
+
+exports[`directives remark plugin - client compiler default behavior for text directives: console 1`] = `
+[
+ [
+ "[WARNING] Docusaurus found 2 unused Markdown directives in file "packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/__fixtures__/textDirectives.md"
+- :textDirective3 (9:7)
+- :textDirective4 (11:7)
+Your content might render in an unexpected way. Visit https://github.com/facebook/docusaurus/pull/9394 to find out why and how to fix it.",
+ ],
+]
+`;
+
+exports[`directives remark plugin - client compiler default behavior for text directives: result 1`] = `
+"Simple: textDirective1
+Simple: textDirectiveCode
+
+Simple:textDirective2
+Simple
label
+Simple
+Simple:textDirective5
+Simple:textDirectiveCode
+
"
+`;
+
+exports[`directives remark plugin - server compiler default behavior for container directives: result 1`] = `
+"Take care of snowstorms...
+
+:::NotAContainerDirective with a phrase after
+:::
+Phrase before :::NotAContainerDirective
+:::
"
+`;
+
+exports[`directives remark plugin - server compiler default behavior for leaf directives: result 1`] = `
+"
+Leaf directive in a phrase ::NotALeafDirective
+::NotALeafDirective with a phrase after
"
+`;
+
+exports[`directives remark plugin - server compiler default behavior for text directives: result 1`] = `
+"Simple: textDirective1
+Simple: textDirectiveCode
+
+Simple:textDirective2
+Simple
label
+Simple
+Simple:textDirective5
+Simple:textDirectiveCode
+
"
+`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
new file mode 100644
index 0000000000000..77910e1e442c3
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
@@ -0,0 +1,111 @@
+/**
+ * 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 remark2rehype from 'remark-rehype';
+import stringify from 'rehype-stringify';
+import vfile from 'to-vfile';
+import plugin from '../index';
+import admonition from '../../admonitions';
+import type {WebpackCompilerName} from '@docusaurus/utils';
+
+const processFixture = async (
+ name: string,
+ {compilerName}: {compilerName: WebpackCompilerName},
+) => {
+ const {remark} = await import('remark');
+ const {default: directives} = await import('remark-directive');
+
+ const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
+ const file = await vfile.read(filePath);
+ file.data.compilerName = compilerName;
+
+ const result = await remark()
+ .use(directives)
+ .use(admonition)
+ .use(plugin)
+ .use(remark2rehype)
+ .use(stringify)
+ .process(file);
+
+ return result.value;
+};
+
+describe('directives remark plugin - client compiler', () => {
+ const consoleMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
+ beforeEach(() => jest.clearAllMocks());
+
+ const options = {compilerName: 'client'} as const;
+
+ it('default behavior for container directives', async () => {
+ const result = await processFixture('containerDirectives', options);
+ expect(result).toMatchSnapshot('result');
+ expect(consoleMock).toHaveBeenCalledTimes(1);
+ expect(consoleMock.mock.calls).toMatchSnapshot('console');
+ });
+
+ it('default behavior for leaf directives', async () => {
+ const result = await processFixture('leafDirectives', options);
+ expect(result).toMatchSnapshot('result');
+ expect(consoleMock).toHaveBeenCalledTimes(1);
+ expect(consoleMock.mock.calls).toMatchSnapshot('console');
+ });
+
+ it('default behavior for text directives', async () => {
+ const result = await processFixture('textDirectives', options);
+ expect(result).toMatchSnapshot('result');
+ expect(consoleMock).toHaveBeenCalledTimes(1);
+ expect(consoleMock.mock.calls).toMatchSnapshot('console');
+ });
+});
+
+describe('directives remark plugin - server compiler', () => {
+ const consoleMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
+ beforeEach(() => jest.clearAllMocks());
+
+ const options = {compilerName: 'server'} as const;
+
+ it('default behavior for container directives', async () => {
+ const result = await processFixture('containerDirectives', options);
+ expect(result).toMatchSnapshot('result');
+ expect(consoleMock).toHaveBeenCalledTimes(0);
+ });
+
+ it('default behavior for leaf directives', async () => {
+ const result = await processFixture('leafDirectives', options);
+ expect(result).toMatchSnapshot('result');
+ expect(consoleMock).toHaveBeenCalledTimes(0);
+ });
+
+ it('default behavior for text directives', async () => {
+ const result = await processFixture('textDirectives', options);
+ expect(result).toMatchSnapshot('result');
+ expect(consoleMock).toHaveBeenCalledTimes(0);
+ });
+});
+
+describe('directives remark plugin - client result === server result', () => {
+ // It is important that client/server outputs are exactly the same
+ // otherwise React hydration mismatches can occur
+ async function testSameResult(name: string) {
+ const resultClient = await processFixture(name, {compilerName: 'client'});
+ const resultServer = await processFixture(name, {compilerName: 'server'});
+ expect(resultClient).toEqual(resultServer);
+ }
+
+ it('for containerDirectives', async () => {
+ await testSameResult('containerDirectives');
+ });
+
+ it('for leafDirectives', async () => {
+ await testSameResult('leafDirectives');
+ });
+
+ it('for textDirectives', async () => {
+ await testSameResult('textDirectives');
+ });
+});
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
new file mode 100644
index 0000000000000..99d0a1dfa6e70
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
@@ -0,0 +1,162 @@
+/**
+ * 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 process from 'process';
+import visit from 'unist-util-visit';
+import logger from '@docusaurus/logger';
+import {posixPath} from '@docusaurus/utils';
+import {transformNode} from '../utils';
+
+// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
+import type {Transformer, Processor, Parent} from 'unified';
+import type {
+ Directive,
+ TextDirective,
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
+} from 'mdast-util-directive';
+
+// 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
+// import type {Plugin} from 'unified';
+type Plugin = any; // TODO fix this asap
+
+type DirectiveType = Directive['type'];
+
+const directiveTypes: DirectiveType[] = [
+ 'containerDirective',
+ 'leafDirective',
+ 'textDirective',
+];
+
+const directivePrefixMap: {[key in DirectiveType]: string} = {
+ textDirective: ':',
+ leafDirective: '::',
+ containerDirective: ':::',
+};
+
+function formatDirectiveName(directive: Directive) {
+ const prefix = directivePrefixMap[directive.type];
+ if (!prefix) {
+ throw new Error(
+ `unexpected, no prefix found for directive of type ${directive.type}`,
+ );
+ }
+ // To simplify we don't display the eventual label/props of directives
+ return `${prefix}${directive.name}`;
+}
+
+function formatDirectivePosition(directive: Directive): string | undefined {
+ return directive.position?.start
+ ? logger.interpolate`number=${directive.position.start.line}:number=${directive.position.start.column}`
+ : undefined;
+}
+
+function formatUnusedDirectiveMessage(directive: Directive) {
+ const name = formatDirectiveName(directive);
+ const position = formatDirectivePosition(directive);
+
+ return `- ${name} ${position ? `(${position})` : ''}`;
+}
+
+function formatUnusedDirectivesMessage({
+ directives,
+ filePath,
+}: {
+ directives: Directive[];
+ filePath: string;
+}): string {
+ const supportUrl = 'https://github.com/facebook/docusaurus/pull/9394';
+ const customPath = posixPath(path.relative(process.cwd(), filePath));
+ const warningTitle = logger.interpolate`Docusaurus found ${directives.length} unused Markdown directives in file path=${customPath}`;
+ const customSupportUrl = logger.interpolate`url=${supportUrl}`;
+ const warningMessages = directives
+ .map(formatUnusedDirectiveMessage)
+ .join('\n');
+
+ return `${warningTitle}
+${warningMessages}
+Your content might render in an unexpected way. Visit ${customSupportUrl} to find out why and how to fix it.`;
+}
+
+function logUnusedDirectivesWarning({
+ directives,
+ filePath,
+}: {
+ directives: Directive[];
+ filePath: string;
+}) {
+ if (directives.length > 0) {
+ const message = formatUnusedDirectivesMessage({
+ directives,
+ filePath,
+ });
+ logger.warn(message);
+ }
+}
+
+function isTextDirective(directive: Directive): directive is TextDirective {
+ return directive.type === 'textDirective';
+}
+
+// A simple text directive is one without any label/props
+function isSimpleTextDirective(
+ directive: Directive,
+): directive is TextDirective {
+ if (isTextDirective(directive)) {
+ // Attributes in MDAST = Directive props
+ const hasAttributes =
+ directive.attributes && Object.keys(directive.attributes).length > 0;
+ // Children in MDAST = Directive label
+ const hasChildren = directive.children.length > 0;
+ return !hasAttributes && !hasChildren;
+ }
+ return false;
+}
+
+function transformSimpleTextDirectiveToString(textDirective: Directive) {
+ transformNode(textDirective, {
+ type: 'text',
+ value: `:${textDirective.name}`, // We ignore label/props on purpose here
+ });
+}
+
+function isUnusedDirective(directive: Directive) {
+ // If directive data is set (notably hName/hProperties set by admonitions)
+ // this usually means the directive has been handled by another plugin
+ return !directive.data;
+}
+
+const plugin: Plugin = function plugin(this: Processor): Transformer {
+ return (tree, file) => {
+ const unusedDirectives: Directive[] = [];
+
+ visit(tree, directiveTypes, (directive: Directive) => {
+ // If directive data is set (notably hName/hProperties set by admonitions)
+ // this usually means the directive has been handled by another plugin
+ if (isUnusedDirective(directive)) {
+ if (isSimpleTextDirective(directive)) {
+ transformSimpleTextDirectiveToString(directive);
+ } else {
+ unusedDirectives.push(directive);
+ }
+ }
+ });
+
+ // We only enable these warnings for the client compiler
+ // This avoids emitting duplicate warnings in prod mode
+ // Note: the client compiler is used in both dev/prod modes
+ if (file.data.compilerName === 'client') {
+ logUnusedDirectivesWarning({
+ directives: unusedDirectives,
+ filePath: file.path,
+ });
+ }
+ };
+};
+
+export default plugin;
diff --git a/packages/docusaurus-mdx-loader/src/remark/utils/index.ts b/packages/docusaurus-mdx-loader/src/remark/utils/index.ts
index 46e9591762397..3b29be807e2db 100644
--- a/packages/docusaurus-mdx-loader/src/remark/utils/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/utils/index.ts
@@ -6,7 +6,7 @@
*/
import escapeHtml from 'escape-html';
-import type {Parent} from 'unist';
+import type {Parent, Node} from 'unist';
import type {PhrasingContent, Heading} from 'mdast';
import type {
MdxJsxAttribute,
@@ -15,6 +15,27 @@ import type {
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
} from 'mdast-util-mdx';
+/**
+ * Util to transform one node type to another node type
+ * The input node is mutated in place
+ * @param node the node to mutate
+ * @param newNode what the original node should become become
+ */
+export function transformNode(
+ node: Node,
+ newNode: NewNode,
+): NewNode {
+ Object.keys(node).forEach((key) => {
+ // @ts-expect-error: unsafe but ok
+ delete node[key];
+ });
+ Object.keys(newNode).forEach((key) => {
+ // @ts-expect-error: unsafe but ok
+ node[key] = newNode[key];
+ });
+ return node as NewNode;
+}
+
export function stringifyContent(
node: Parent,
toString: (param: unknown) => string, // TODO weird but works
diff --git a/packages/docusaurus-mdx-loader/src/vfile-datamap.d.mts b/packages/docusaurus-mdx-loader/src/vfile-datamap.d.mts
new file mode 100644
index 0000000000000..2538305411c82
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/vfile-datamap.d.mts
@@ -0,0 +1,21 @@
+/**
+ * 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 type {WebpackCompilerName} from '@docusaurus/utils';
+
+declare module 'vfile' {
+ /*
+ This map registers the type of the data key of a VFile (TypeScript type).
+ This type can be augmented to register custom data types.
+ See https://github.com/vfile/vfile#datamap
+ */
+ interface DataMap {
+ frontMatter: {[key: string]: unknown};
+ compilerName: WebpackCompilerName;
+ contentTitle?: string;
+ }
+}
diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts
index d5cabc65301aa..5bb77a0c054f0 100644
--- a/packages/docusaurus-utils/src/index.ts
+++ b/packages/docusaurus-utils/src/index.ts
@@ -98,7 +98,11 @@ export {
createMatcher,
createAbsoluteFilePathMatcher,
} from './globUtils';
-export {getFileLoaderUtils} from './webpackUtils';
+export {
+ getFileLoaderUtils,
+ getWebpackLoaderCompilerName,
+ type WebpackCompilerName,
+} from './webpackUtils';
export {escapeShellArg} from './shellUtils';
export {loadFreshModule} from './moduleUtils';
export {
diff --git a/packages/docusaurus-utils/src/webpackUtils.ts b/packages/docusaurus-utils/src/webpackUtils.ts
index c1195d5764b37..411b5a0fee22b 100644
--- a/packages/docusaurus-utils/src/webpackUtils.ts
+++ b/packages/docusaurus-utils/src/webpackUtils.ts
@@ -11,7 +11,25 @@ import {
WEBPACK_URL_LOADER_LIMIT,
OUTPUT_STATIC_ASSETS_DIR_NAME,
} from './constants';
-import type {RuleSetRule} from 'webpack';
+import type {RuleSetRule, LoaderContext} from 'webpack';
+
+export type WebpackCompilerName = 'server' | 'client';
+
+export function getWebpackLoaderCompilerName(
+ context: LoaderContext,
+): WebpackCompilerName {
+ // eslint-disable-next-line no-underscore-dangle
+ const compilerName = context._compiler?.name;
+ switch (compilerName) {
+ case 'server':
+ case 'client':
+ return compilerName;
+ default:
+ throw new Error(
+ `Cannot get valid Docusaurus webpack compiler name. Found compilerName=${compilerName}`,
+ );
+ }
+}
type AssetFolder = 'images' | 'files' | 'fonts' | 'medias';
diff --git a/project-words.txt b/project-words.txt
index 1f9e8bada8efd..fdc8d43a57fc6 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -62,6 +62,7 @@ customizability
dabit
daishi
datagit
+datamap
datas
dbaeumer
décembre