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...

+

unused directive content

+

:::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...

+

unused directive content

+

:::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