;
-export type RemarkAndRehypePluginOptions = {
- remarkPlugins: RemarkOrRehypePlugin[];
- rehypePlugins: RemarkOrRehypePlugin[];
- beforeDefaultRemarkPlugins: RemarkOrRehypePlugin[];
- beforeDefaultRehypePlugins: RemarkOrRehypePlugin[];
-};
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/LICENSE b/packages/docusaurus-mdx-loader/src/remark/admonitions/LICENSE
new file mode 100644
index 000000000000..faa2221713df
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/LICENSE
@@ -0,0 +1,45 @@
+MIT License
+
+Copyright (c) 2020 Elvis Wolcott
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+---
+
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/README.md b/packages/docusaurus-mdx-loader/src/remark/admonitions/README.md
new file mode 100644
index 000000000000..3add9f4f32a4
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/README.md
@@ -0,0 +1,3 @@
+# Docusaurus admonitions
+
+Code from [remark-admonitions](https://github.com/remarkjs/remark-directive) (MIT license) has been copied to this folder, and highly customized for Docusaurus needs.
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/base.md b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/base.md
new file mode 100644
index 000000000000..a9d3e4159661
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/base.md
@@ -0,0 +1,25 @@
+The blog feature enables you to deploy in no time a full-featured blog.
+
+:::info Sample Title
+
+Check the [Blog Plugin API Reference documentation](./api/plugins/plugin-content-blog.md) for an exhaustive list of options.
+
+:::
+
+## Initial setup {#initial-setup}
+
+To set up your site's blog, start by creating a `blog` directory.
+
+:::tip
+
+Use the **[Fast Track](introduction.md#fast-track)** to understand Docusaurus in **5 minutes ⏱**!
+
+Use **[docusaurus.new](https://docusaurus.new)** to test Docusaurus immediately in your browser!
+
+:::
+
+++++tip
+
+Admonition with different syntax
+
+++++
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/interpolation.md b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/interpolation.md
new file mode 100644
index 000000000000..a20e62976bf7
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/interpolation.md
@@ -0,0 +1,7 @@
+Test admonition with interpolated title/body
+
+:::tip My `interpolated` **title**
+
+`body` **interpolated**
+
+:::
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap
new file mode 100644
index 000000000000..227eb28b3370
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap
@@ -0,0 +1,44 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`admonitions remark plugin base 1`] = `
+"The blog feature enables you to deploy in no time a full-featured blog.
+Check the Blog Plugin API Reference documentation for an exhaustive list of options.
+Initial setup {#initial-setup}
+To set up your site's blog, start by creating a blog
directory.
+Use the Fast Track to understand Docusaurus in 5 minutes ⏱!
Use docusaurus.new to test Docusaurus immediately in your browser!
+++++tip
+Admonition with different syntax
+++++
"
+`;
+
+exports[`admonitions remark plugin custom keywords 1`] = `
+"The blog feature enables you to deploy in no time a full-featured blog.
+:::info Sample Title
+Check the Blog Plugin API Reference documentation for an exhaustive list of options.
+:::
+Initial setup {#initial-setup}
+To set up your site's blog, start by creating a blog
directory.
+Use the Fast Track to understand Docusaurus in 5 minutes ⏱!
Use docusaurus.new to test Docusaurus immediately in your browser!
+++++tip
+Admonition with different syntax
+++++
"
+`;
+
+exports[`admonitions remark plugin custom tag 1`] = `
+"The blog feature enables you to deploy in no time a full-featured blog.
+:::info Sample Title
+Check the Blog Plugin API Reference documentation for an exhaustive list of options.
+:::
+Initial setup {#initial-setup}
+To set up your site's blog, start by creating a blog
directory.
+:::tip
+Use the Fast Track to understand Docusaurus in 5 minutes ⏱!
+Use docusaurus.new to test Docusaurus immediately in your browser!
+:::
+Admonition with different syntax
"
+`;
+
+exports[`admonitions remark plugin interpolation 1`] = `
+"Test admonition with interpolated title/body
+My interpolated
title <button style={{color: "red"}} onClick={() => alert("click")}>testbody
interpolated content
"
+`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts
new file mode 100644
index 000000000000..3794562bb16d
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts
@@ -0,0 +1,53 @@
+/**
+ * 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 remark from 'remark';
+import remark2rehype from 'remark-rehype';
+import stringify from 'rehype-stringify';
+
+import vfile from 'to-vfile';
+import plugin from '../index';
+import type {AdmonitionOptions} from '../index';
+
+const processFixture = async (
+ name: string,
+ options?: Partial,
+) => {
+ const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
+ const file = await vfile.read(filePath);
+
+ const result = await remark()
+ .use(plugin, options)
+ .use(remark2rehype)
+ .use(stringify)
+ .process(file);
+
+ return result.toString();
+};
+
+describe('admonitions remark plugin', () => {
+ it('base', async () => {
+ const result = await processFixture('base');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('custom keywords', async () => {
+ const result = await processFixture('base', {keywords: ['tip']});
+ expect(result).toMatchSnapshot();
+ });
+
+ it('custom tag', async () => {
+ const result = await processFixture('base', {tag: '++++'});
+ expect(result).toMatchSnapshot();
+ });
+
+ it('interpolation', async () => {
+ const result = await processFixture('interpolation');
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
new file mode 100644
index 000000000000..d6393b8c4529
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
@@ -0,0 +1,186 @@
+/**
+ * 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 visit from 'unist-util-visit';
+import type {Transformer, Processor, Plugin} from 'unified';
+import type {Literal} from 'mdast';
+
+const NEWLINE = '\n';
+
+export type AdmonitionOptions = {
+ tag: string;
+ keywords: string[];
+};
+
+export const DefaultAdmonitionOptions: AdmonitionOptions = {
+ tag: ':::',
+ keywords: [
+ 'secondary',
+ 'info',
+ 'success',
+ 'danger',
+ 'note',
+ 'tip',
+ 'warning',
+ 'important',
+ 'caution',
+ ],
+};
+
+function escapeRegExp(s: string): string {
+ return s.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&');
+}
+
+function normalizeOptions(
+ options: Partial,
+): AdmonitionOptions {
+ return {...DefaultAdmonitionOptions, ...options};
+}
+
+// This string value does not matter much
+// It is ignored because nodes are using hName/hProperties coming from HAST
+const admonitionNodeType = 'admonitionHTML';
+
+const plugin: Plugin = function plugin(
+ this: Processor,
+ optionsInput: Partial = {},
+): Transformer {
+ const options = normalizeOptions(optionsInput);
+
+ const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
+ const tag = escapeRegExp(options.tag);
+ const regex = new RegExp(`${tag}(${keywords})(?: *(.*))?\n`);
+ const escapeTag = new RegExp(escapeRegExp(`\\${options.tag}`), 'g');
+
+ // The tokenizer is called on blocks to determine if there is an admonition
+ // present and create tags for it
+ function blockTokenizer(this: any, eat: any, value: string, silent: boolean) {
+ // Stop if no match or match does not start at beginning of line
+ const match = regex.exec(value);
+ if (!match || match.index !== 0) {
+ return false;
+ }
+ // If silent return the match
+ if (silent) {
+ return true;
+ }
+
+ const now = eat.now();
+ const [opening, keyword, title] = match as string[] as [
+ string,
+ string,
+ string,
+ ];
+ const food = [];
+ const content = [];
+
+ let newValue = value;
+ // consume lines until a closing tag
+ let idx = newValue.indexOf(NEWLINE);
+ while (idx !== -1) {
+ // grab this line and eat it
+ const next = newValue.indexOf(NEWLINE, idx + 1);
+ const line =
+ next !== -1 ? newValue.slice(idx + 1, next) : newValue.slice(idx + 1);
+ food.push(line);
+ newValue = newValue.slice(idx + 1);
+ // the closing tag is NOT part of the content
+ if (line.startsWith(options.tag)) {
+ break;
+ }
+ content.push(line);
+ idx = newValue.indexOf(NEWLINE);
+ }
+
+ // consume the processed tag and replace escape sequences
+ const contentString = content.join(NEWLINE).replace(escapeTag, options.tag);
+ const add = eat(opening + food.join(NEWLINE));
+
+ // parse the content in block mode
+ const exit = this.enterBlock();
+ const contentNodes = this.tokenizeBlock(contentString, now);
+ exit();
+
+ const titleNodes = this.tokenizeInline(title, now);
+
+ const isSimpleTextTitle =
+ titleNodes.length === 1 && titleNodes[0].type === 'text';
+
+ const element = {
+ type: admonitionNodeType,
+ data: {
+ // hName/hProperties come from HAST
+ // See https://github.com/syntax-tree/mdast-util-to-hast#fields-on-nodes
+ hName: 'admonition',
+ hProperties: {
+ ...(title && isSimpleTextTitle && {title}),
+ type: keyword,
+ },
+ },
+ children: [
+ // For titles containing MDX syntax: create a custom element. The theme
+ // component will extract it and render it nicely.
+ //
+ // Temporary workaround, because it's complex in MDX v1 to emit
+ // interpolated JSX prop syntax (title={<>my title
>}).
+ // For this reason, we use children instead of the title prop.
+ title &&
+ !isSimpleTextTitle && {
+ type: admonitionNodeType,
+ data: {
+ hName: 'mdxAdmonitionTitle',
+ hProperties: {},
+ },
+ children: titleNodes,
+ },
+ ...contentNodes,
+ ].filter(Boolean),
+ };
+
+ return add(element);
+ }
+
+ // add tokenizer to parser after fenced code blocks
+ const Parser = this.Parser.prototype;
+ Parser.blockTokenizers.admonition = blockTokenizer;
+ Parser.blockMethods.splice(
+ Parser.blockMethods.indexOf('fencedCode') + 1,
+ 0,
+ 'admonition',
+ );
+ Parser.interruptParagraph.splice(
+ Parser.interruptParagraph.indexOf('fencedCode') + 1,
+ 0,
+ ['admonition'],
+ );
+ Parser.interruptList.splice(
+ Parser.interruptList.indexOf('fencedCode') + 1,
+ 0,
+ ['admonition'],
+ );
+ Parser.interruptBlockquote.splice(
+ Parser.interruptBlockquote.indexOf('fencedCode') + 1,
+ 0,
+ ['admonition'],
+ );
+
+ return (root) => {
+ // escape everything except admonitionHTML nodes
+ visit(
+ root,
+ (node: unknown): node is Literal =>
+ (node as Literal | undefined)?.type !== admonitionNodeType,
+ (node: Literal) => {
+ if (node.value) {
+ node.value = node.value.replace(escapeTag, options.tag);
+ }
+ },
+ );
+ };
+};
+
+export default plugin;
diff --git a/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts
index ee10568ba883..47a31c836fc8 100644
--- a/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts
@@ -13,13 +13,15 @@ import removePosition from 'unist-util-remove-position';
import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit';
import slug from '../index';
+import type {Plugin} from 'unified';
+import type {Parent} from 'unist';
-function process(doc, plugins = []) {
+function process(doc: string, plugins: Plugin[] = []) {
const processor = remark().use({plugins: [...plugins, slug]});
return removePosition(processor.runSync(processor.parse(doc)), true);
}
-function heading(label, id) {
+function heading(label: string | null, id: string) {
return u(
'heading',
{depth: 2, data: {id, hProperties: {id}}},
@@ -27,8 +29,8 @@ function heading(label, id) {
);
}
-describe('headings plugin', () => {
- test('should patch `id`s and `data.hProperties.id', () => {
+describe('headings remark plugin', () => {
+ it('patches `id`s and `data.hProperties.id', () => {
const result = process('# Normal\n\n## Table of Contents\n\n# Baz\n');
const expected = u('root', [
u(
@@ -55,13 +57,10 @@ describe('headings plugin', () => {
expect(result).toEqual(expected);
});
- test('should not overwrite `data` on headings', () => {
+ it('does not overwrite `data` on headings', () => {
const result = process('# Normal\n', [
- () => {
- function transform(tree) {
- tree.children[0].data = {foo: 'bar'};
- }
- return transform;
+ () => (root) => {
+ (root as Parent).children[0]!.data = {foo: 'bar'};
},
]);
const expected = u('root', [
@@ -78,13 +77,12 @@ describe('headings plugin', () => {
expect(result).toEqual(expected);
});
- test('should not overwrite `data.hProperties` on headings', () => {
+ it('does not overwrite `data.hProperties` on headings', () => {
const result = process('# Normal\n', [
- () => {
- function transform(tree) {
- tree.children[0].data = {hProperties: {className: ['foo']}};
- }
- return transform;
+ () => (root) => {
+ (root as Parent).children[0]!.data = {
+ hProperties: {className: ['foo']},
+ };
},
]);
const expected = u('root', [
@@ -101,7 +99,7 @@ describe('headings plugin', () => {
expect(result).toEqual(expected);
});
- test('should generate `id`s and `hProperties.id`s, based on `hProperties.id` if they exist', () => {
+ it('generates `id`s and `hProperties.id`s, based on `hProperties.id` if they exist', () => {
const result = process(
[
'## Something',
@@ -110,12 +108,9 @@ describe('headings plugin', () => {
'## Something also',
].join('\n\n'),
[
- () => {
- function transform(tree) {
- tree.children[1].data = {hProperties: {id: 'here'}};
- tree.children[3].data = {hProperties: {id: 'something'}};
- }
- return transform;
+ () => (root) => {
+ (root as Parent).children[1]!.data = {hProperties: {id: 'here'}};
+ (root as Parent).children[3]!.data = {hProperties: {id: 'something'}};
},
],
);
@@ -157,7 +152,7 @@ describe('headings plugin', () => {
expect(result).toEqual(expected);
});
- test('should create GitHub-style headings ids', () => {
+ it('creates GitHub-style headings ids', () => {
const result = process(
[
'## I ♥ unicode',
@@ -199,7 +194,9 @@ describe('headings plugin', () => {
const expected = u('root', [
heading('I ♥ unicode', 'i--unicode'),
heading('Dash-dash', 'dash-dash'),
+ // cSpell:ignore endash
heading('en–dash', 'endash'),
+ // cSpell:ignore emdash
heading('em–dash', 'emdash'),
heading('😄 unicode emoji', '-unicode-emoji'),
heading('😄-😄 unicode emoji', '--unicode-emoji'),
@@ -214,6 +211,7 @@ describe('headings plugin', () => {
heading(':ok_hand: Single', 'ok_hand-single'),
heading(
':ok_hand::hatched_chick: Two in a row with no spaces',
+ // cSpell:ignore handhatched
'ok_handhatched_chick-two-in-a-row-with-no-spaces',
),
heading(
@@ -225,7 +223,7 @@ describe('headings plugin', () => {
expect(result).toEqual(expected);
});
- test('should generate id from only text contents of headings if they contains HTML tags', () => {
+ it('generates id from only text contents of headings if they contains HTML tags', () => {
const result = process('# \n');
const expected = u('root', [
u(
@@ -245,17 +243,17 @@ describe('headings plugin', () => {
expect(result).toEqual(expected);
});
- test('should create custom headings ids', () => {
+ it('creates custom headings ids', () => {
const result = process(`
# Heading One {#custom_h1}
## Heading Two {#custom-heading-two}
-# With *Bold* {#custom-withbold}
+# With *Bold* {#custom-with-bold}
-# With *Bold* hello{#custom-withbold-hello}
+# With *Bold* hello{#custom-with-bold-hello}
-# With *Bold* hello2 {#custom-withbold-hello2}
+# With *Bold* hello2 {#custom-with-bold-hello2}
# Snake-cased ID {#this_is_custom_id}
@@ -266,9 +264,9 @@ describe('headings plugin', () => {
# {#text-after} custom ID
`);
- const headers = [];
+ const headers: {text: string; id: string}[] = [];
visit(result, 'heading', (node) => {
- headers.push({text: toString(node), id: node.data.id});
+ headers.push({text: toString(node), id: node.data!.id as string});
});
expect(headers).toEqual([
@@ -281,15 +279,15 @@ describe('headings plugin', () => {
text: 'Heading Two',
},
{
- id: 'custom-withbold',
+ id: 'custom-with-bold',
text: 'With Bold',
},
{
- id: 'custom-withbold-hello',
+ id: 'custom-with-bold-hello',
text: 'With Bold hello',
},
{
- id: 'custom-withbold-hello2',
+ id: 'custom-with-bold-hello2',
text: 'With Bold hello2',
},
{
diff --git a/packages/docusaurus-mdx-loader/src/remark/headings/index.ts b/packages/docusaurus-mdx-loader/src/remark/headings/index.ts
index c964b8d266c1..98aaa37f7552 100644
--- a/packages/docusaurus-mdx-loader/src/remark/headings/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/headings/index.ts
@@ -9,16 +9,15 @@
import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils';
import visit from 'unist-util-visit';
-import toString from 'mdast-util-to-string';
+import mdastToString from 'mdast-util-to-string';
import type {Transformer} from 'unified';
-import type {Parent} from 'unist';
import type {Heading, Text} from 'mdast';
export default function plugin(): Transformer {
return (root) => {
const slugs = createSlugger();
visit(root, 'heading', (headingNode: Heading) => {
- const data = headingNode.data || (headingNode.data = {});
+ const data = headingNode.data ?? (headingNode.data = {});
const properties = (data.hProperties || (data.hProperties = {})) as {
id: string;
};
@@ -30,16 +29,14 @@ export default function plugin(): Transformer {
const headingTextNodes = headingNode.children.filter(
({type}) => !['html', 'jsx'].includes(type),
);
- const heading = toString(
- headingTextNodes.length > 0
- ? ({children: headingTextNodes} as Parent)
- : headingNode,
+ const heading = mdastToString(
+ headingTextNodes.length > 0 ? headingTextNodes : headingNode,
);
// Support explicit heading IDs
const parsedHeading = parseMarkdownHeadingId(heading);
- id = parsedHeading.id || slugs.slug(heading);
+ id = parsedHeading.id ?? slugs.slug(heading);
if (parsedHeading.id) {
// When there's an id, it is always in the last child node
diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md
index 982f2df6563a..7f9152f5b851 100644
--- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md
+++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md
@@ -13,3 +13,5 @@ Lorem ipsum
Some content here
## I ♥ unicode.
+
+export const c = 1;
diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/no-heading-with-toc-export.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/no-heading-with-toc-export.md
new file mode 100644
index 000000000000..900b8900c1f9
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/no-heading-with-toc-export.md
@@ -0,0 +1,9 @@
+foo
+
+`bar`
+
+```js
+baz
+```
+
+export const toc = 1;
diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/no-heading.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/no-heading.md
new file mode 100644
index 000000000000..d02f9a00f460
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/no-heading.md
@@ -0,0 +1,7 @@
+foo
+
+`bar`
+
+```js
+baz
+```
diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap
index f82cda96d317..ba71805a2ab5 100644
--- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap
@@ -1,6 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`inline code should be escaped 1`] = `
+exports[`toc remark plugin does not overwrite TOC var if no TOC 1`] = `
+"foo
+
+\`bar\`
+
+\`\`\`js
+baz
+\`\`\`
+
+export const toc = 1;
+"
+`;
+
+exports[`toc remark plugin escapes inline code 1`] = `
"export const toc = [
{
value: '<Head />
',
@@ -48,7 +61,91 @@ exports[`inline code should be escaped 1`] = `
"
`;
-exports[`non text phrasing content 1`] = `
+exports[`toc remark plugin exports even with existing name 1`] = `
+"export const toc = [
+ {
+ value: 'Thanos',
+ id: 'thanos',
+ level: 2
+ },
+ {
+ value: 'Tony Stark',
+ id: 'tony-stark',
+ level: 2
+ },
+ {
+ value: 'Avengers',
+ id: 'avengers',
+ level: 3
+ }
+];
+
+## Thanos
+
+## Tony Stark
+
+### Avengers
+"
+`;
+
+exports[`toc remark plugin handles empty headings 1`] = `
+"export const toc = [];
+
+# Ignore this
+
+##
+
+## ![](an-image.svg)
+"
+`;
+
+exports[`toc remark plugin inserts below imports 1`] = `
+"import something from 'something';
+
+import somethingElse from 'something-else';
+
+export const toc = [
+ {
+ value: 'Title',
+ id: 'title',
+ level: 2
+ },
+ {
+ value: 'Test',
+ id: 'test',
+ level: 2
+ },
+ {
+ value: 'Again',
+ id: 'again',
+ level: 3
+ }
+];
+
+## Title
+
+## Test
+
+### Again
+
+Content.
+"
+`;
+
+exports[`toc remark plugin outputs empty array for no TOC 1`] = `
+"export const toc = [];
+
+foo
+
+\`bar\`
+
+\`\`\`js
+baz
+\`\`\`
+"
+`;
+
+exports[`toc remark plugin works on non text phrasing content 1`] = `
"export const toc = [
{
value: 'Emphasis',
@@ -88,3 +185,47 @@ exports[`non text phrasing content 1`] = `
## \`inline.code()\`
"
`;
+
+exports[`toc remark plugin works on text content 1`] = `
+"export const toc = [
+ {
+ value: 'Endi',
+ id: 'endi',
+ level: 3
+ },
+ {
+ value: 'Endi',
+ id: 'endi-1',
+ level: 2
+ },
+ {
+ value: 'Yangshun',
+ id: 'yangshun',
+ level: 3
+ },
+ {
+ value: 'I ♥ unicode.',
+ id: 'i--unicode',
+ level: 2
+ }
+];
+
+### Endi
+
+\`\`\`md
+## This is ignored
+\`\`\`
+
+## Endi
+
+Lorem ipsum
+
+### Yangshun
+
+Some content here
+
+## I ♥ unicode.
+
+export const c = 1;
+"
+`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts
index daf2488705a6..6df3e94e672b 100644
--- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts
@@ -12,197 +12,59 @@ import vfile from 'to-vfile';
import plugin from '../index';
import headings from '../../headings/index';
-const processFixture = async (name, options?) => {
+const processFixture = async (name: string) => {
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
const file = await vfile.read(filePath);
const result = await remark()
.use(headings)
.use(mdx)
- .use(plugin, options)
+ .use(plugin)
.process(file);
return result.toString();
};
-test('non text phrasing content', async () => {
- const result = await processFixture('non-text-content');
- expect(result).toMatchSnapshot();
-});
-
-test('inline code should be escaped', async () => {
- const result = await processFixture('inline-code');
- expect(result).toMatchSnapshot();
-});
-
-test('text content', async () => {
- const result = await processFixture('just-content');
- expect(result).toMatchInlineSnapshot(`
- "export const toc = [
- {
- value: 'Endi',
- id: 'endi',
- level: 3
- },
- {
- value: 'Endi',
- id: 'endi-1',
- level: 2
- },
- {
- value: 'Yangshun',
- id: 'yangshun',
- level: 3
- },
- {
- value: 'I ♥ unicode.',
- id: 'i--unicode',
- level: 2
- }
- ];
-
- ### Endi
-
- \`\`\`md
- ## This is ignored
- \`\`\`
-
- ## Endi
-
- Lorem ipsum
-
- ### Yangshun
-
- Some content here
-
- ## I ♥ unicode.
- "
- `);
-});
-
-test('should export even with existing name', async () => {
- const result = await processFixture('name-exist');
- expect(result).toMatchInlineSnapshot(`
- "export const toc = [
- {
- value: 'Thanos',
- id: 'thanos',
- level: 2
- },
- {
- value: 'Tony Stark',
- id: 'tony-stark',
- level: 2
- },
- {
- value: 'Avengers',
- id: 'avengers',
- level: 3
- }
- ];
-
- ## Thanos
-
- ## Tony Stark
-
- ### Avengers
- "
- `);
-});
-
-test('should export with custom name', async () => {
- const options = {
- name: 'customName',
- };
- const result = await processFixture('just-content', options);
- expect(result).toMatchInlineSnapshot(`
- "export const customName = [
- {
- value: 'Endi',
- id: 'endi',
- level: 3
- },
- {
- value: 'Endi',
- id: 'endi-1',
- level: 2
- },
- {
- value: 'Yangshun',
- id: 'yangshun',
- level: 3
- },
- {
- value: 'I ♥ unicode.',
- id: 'i--unicode',
- level: 2
- }
- ];
-
- ### Endi
-
- \`\`\`md
- ## This is ignored
- \`\`\`
-
- ## Endi
-
- Lorem ipsum
-
- ### Yangshun
-
- Some content here
-
- ## I ♥ unicode.
- "
- `);
-});
-
-test('should insert below imports', async () => {
- const result = await processFixture('insert-below-imports');
- expect(result).toMatchInlineSnapshot(`
- "import something from 'something';
-
- import somethingElse from 'something-else';
-
- export const toc = [
- {
- value: 'Title',
- id: 'title',
- level: 2
- },
- {
- value: 'Test',
- id: 'test',
- level: 2
- },
- {
- value: 'Again',
- id: 'again',
- level: 3
- }
- ];
-
- ## Title
-
- ## Test
-
- ### Again
-
- Content.
- "
- `);
-});
-
-test('empty headings', async () => {
- const result = await processFixture('empty-headings');
- expect(result).toMatchInlineSnapshot(`
- "export const toc = [];
-
- # Ignore this
-
- ##
-
- ## ![](an-image.svg)
- "
- `);
+describe('toc remark plugin', () => {
+ it('outputs empty array for no TOC', async () => {
+ const result = await processFixture('no-heading');
+ expect(result).toMatchSnapshot();
+ });
+
+ // A very implicit API: we allow users to hand-write the toc variable. It will
+ // get overwritten in most cases, but until we find a better way, better keep
+ // supporting this
+ it('does not overwrite TOC var if no TOC', async () => {
+ const result = await processFixture('no-heading-with-toc-export');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('works on non text phrasing content', async () => {
+ const result = await processFixture('non-text-content');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('escapes inline code', async () => {
+ const result = await processFixture('inline-code');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('works on text content', async () => {
+ const result = await processFixture('just-content');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('exports even with existing name', async () => {
+ const result = await processFixture('name-exist');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('inserts below imports', async () => {
+ const result = await processFixture('insert-below-imports');
+ expect(result).toMatchSnapshot();
+ });
+
+ it('handles empty headings', async () => {
+ const result = await processFixture('empty-headings');
+ expect(result).toMatchSnapshot();
+ });
});
diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/index.ts b/packages/docusaurus-mdx-loader/src/remark/toc/index.ts
index 0e25a5a46226..5e6c694dc339 100644
--- a/packages/docusaurus-mdx-loader/src/remark/toc/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/toc/index.ts
@@ -6,14 +6,14 @@
*/
import {parse, type ParserOptions} from '@babel/parser';
-import type {Identifier} from '@babel/types';
import traverse from '@babel/traverse';
import stringifyObject from 'stringify-object';
import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit';
import {toValue} from '../utils';
-import type {TOCItem} from '@docusaurus/types';
+import type {Identifier} from '@babel/types';
+import type {TOCItem} from '../..';
import type {Node, Parent} from 'unist';
import type {Heading, Literal} from 'mdast';
import type {Transformer} from 'unified';
@@ -23,15 +23,13 @@ const parseOptions: ParserOptions = {
sourceType: 'module',
};
+const name = 'toc';
+
const isImport = (child: Node): child is Literal => child.type === 'import';
const hasImports = (index: number) => index > -1;
const isExport = (child: Node): child is Literal => child.type === 'export';
-interface PluginOptions {
- name?: string;
-}
-
-const isTarget = (child: Literal, name: string) => {
+const isTarget = (child: Literal) => {
let found = false;
const ast = parse(child.value, parseOptions);
traverse(ast, {
@@ -44,14 +42,14 @@ const isTarget = (child: Literal, name: string) => {
return found;
};
-const getOrCreateExistingTargetIndex = (children: Node[], name: string) => {
+const getOrCreateExistingTargetIndex = (children: Node[]) => {
let importsIndex = -1;
let targetIndex = -1;
children.forEach((child, index) => {
if (isImport(child)) {
importsIndex = index;
- } else if (isExport(child) && isTarget(child, name)) {
+ } else if (isExport(child) && isTarget(child)) {
targetIndex = index;
}
});
@@ -70,16 +68,14 @@ const getOrCreateExistingTargetIndex = (children: Node[], name: string) => {
return targetIndex;
};
-export default function plugin(options: PluginOptions = {}): Transformer {
- const name = options.name || 'toc';
-
+export default function plugin(): Transformer {
return (root) => {
const headings: TOCItem[] = [];
- visit(root, 'heading', (child: Heading, _index, parent) => {
+ visit(root, 'heading', (child: Heading, index, parent) => {
const value = toString(child);
- // depth:1 headings are titles and not included in the TOC
+ // depth: 1 headings are titles and not included in the TOC
if (parent !== root || !value || child.depth < 2) {
return;
}
@@ -91,9 +87,9 @@ export default function plugin(options: PluginOptions = {}): Transformer {
});
});
const {children} = root as Parent;
- const targetIndex = getOrCreateExistingTargetIndex(children, name);
+ const targetIndex = getOrCreateExistingTargetIndex(children);
- if (headings && headings.length) {
+ if (headings.length) {
children[targetIndex]!.value = `export const ${name} = ${stringifyObject(
headings,
)};`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap
index 7602293159e2..8366a7b23e19 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transformImage plugin does not choke on invalid image 1`] = `
-"/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/invalid.png\\").default} />
+"/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/invalid.png").default} />
"
`;
@@ -9,7 +9,7 @@ exports[`transformImage plugin fail if image does not exist 1`] = `"Image packag
exports[`transformImage plugin fail if image relative path does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/notFound.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/fail2.md not found."`;
-exports[`transformImage plugin fail if image url is absent 1`] = `"Markdown image URL is mandatory in \\"packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/noUrl.md\\" file"`;
+exports[`transformImage plugin fail if image url is absent 1`] = `"Markdown image URL is mandatory in "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/noUrl.md" file"`;
exports[`transformImage plugin pathname protocol 1`] = `
"![img](/img/unchecked.png)
@@ -19,29 +19,29 @@ exports[`transformImage plugin pathname protocol 1`] = `
exports[`transformImage plugin transform md images to 1`] = `
"![img](https://example.com/img.png)
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default} width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default} width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2.png").default} width="256" height="82" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2.png").default} width="256" height="82" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2 copy.png\\").default} width=\\"256\\" height=\\"82\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2 copy.png").default} width="256" height="82" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"Title\\" width=\\"200\\" height=\\"200\\" /> /node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default} title="Title" width="200" height="200" /> /node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default} width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"'Quoted' title\\" width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default} title="'Quoted' title" width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default} width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#light'} width=\\"200\\" height=\\"200\\" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#dark'} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default + '#light'} width="200" height="200" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png").default + '#dark'} width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10\\").default} width=\\"200\\" height=\\"200\\" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10").default} width="200" height="200" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10").default} width="200" height="200" />
-/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default + '#light'} width=\\"200\\" height=\\"200\\" />
+/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10").default + '#light'} width="200" height="200" />
## Heading
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts
index dc1ed9e1710c..94a0c7475580 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts
@@ -5,20 +5,24 @@
* LICENSE file in the root directory of this source tree.
*/
+import {jest} from '@jest/globals';
import path from 'path';
import remark from 'remark';
import mdx from 'remark-mdx';
import vfile from 'to-vfile';
-import plugin from '../index';
+import plugin, {type PluginOptions} from '../index';
import headings from '../../headings/index';
-const processFixture = async (name, options) => {
+const processFixture = async (
+ name: string,
+ options: Partial,
+) => {
const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
const file = await vfile.read(filePath);
const result = await remark()
.use(headings)
.use(mdx)
- .use(plugin, {...options, filePath})
+ .use(plugin, {siteDir: __dirname, staticDirs: [], ...options})
.process(file);
return result.toString();
@@ -32,34 +36,34 @@ const staticDirs = [
const siteDir = path.join(__dirname, '__fixtures__');
describe('transformImage plugin', () => {
- test('fail if image does not exist', async () => {
+ it('fail if image does not exist', async () => {
await expect(
processFixture('fail', {staticDirs}),
).rejects.toThrowErrorMatchingSnapshot();
});
- test('fail if image relative path does not exist', async () => {
+ it('fail if image relative path does not exist', async () => {
await expect(
processFixture('fail2', {staticDirs}),
).rejects.toThrowErrorMatchingSnapshot();
});
- test('fail if image url is absent', async () => {
+ it('fail if image url is absent', async () => {
await expect(
processFixture('noUrl', {staticDirs}),
).rejects.toThrowErrorMatchingSnapshot();
});
- test('transform md images to ', async () => {
+ it('transform md images to ', async () => {
const result = await processFixture('img', {staticDirs, siteDir});
expect(result).toMatchSnapshot();
});
- test('pathname protocol', async () => {
+ it('pathname protocol', async () => {
const result = await processFixture('pathname', {staticDirs});
expect(result).toMatchSnapshot();
});
- test('does not choke on invalid image', async () => {
- const errorMock = jest.spyOn(console, 'warn').mockImplementation();
+ it('does not choke on invalid image', async () => {
+ const errorMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
const result = await processFixture('invalid-img', {staticDirs});
expect(result).toMatchSnapshot();
expect(errorMock).toBeCalledTimes(1);
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
index bcc37f633508..06892e3ed0b9 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
@@ -5,6 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
+import path from 'path';
+import url from 'url';
+import fs from 'fs-extra';
+import {promisify} from 'util';
+import logger from '@docusaurus/logger';
import {
toMessageRelativeFilePath,
posixPath,
@@ -13,21 +18,17 @@ import {
findAsyncSequential,
} from '@docusaurus/utils';
import visit from 'unist-util-visit';
-import path from 'path';
-import url from 'url';
-import fs from 'fs-extra';
import escapeHtml from 'escape-html';
import sizeOf from 'image-size';
-import {promisify} from 'util';
import type {Transformer} from 'unified';
+import type {Parent} from 'unist';
import type {Image, Literal} from 'mdast';
-import logger from '@docusaurus/logger';
const {
loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils();
-type PluginOptions = {
+export type PluginOptions = {
staticDirs: string[];
siteDir: string;
};
@@ -36,12 +37,13 @@ type Context = PluginOptions & {
filePath: string;
};
+type Target = [node: Image, index: number, parent: Parent];
+
async function toImageRequireNode(
- node: Image,
+ [node, index, parent]: Target,
imagePath: string,
filePath: string,
) {
- const jsxNode = node as Literal & Partial;
let relativeImagePath = posixPath(
path.relative(path.dirname(filePath), imagePath),
);
@@ -75,12 +77,12 @@ ${(err as Error).message}`;
}
}
- Object.keys(jsxNode).forEach(
- (key) => delete jsxNode[key as keyof typeof jsxNode],
- );
+ const jsxNode: Literal = {
+ type: 'jsx',
+ value: ``,
+ };
- (jsxNode as Literal).type = 'jsx';
- jsxNode.value = ``;
+ parent.children.splice(index, 1, jsxNode);
}
async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
@@ -103,7 +105,7 @@ async function getImageAbsolutePath(
await ensureImageFileExist(imageFilePath, filePath);
return imageFilePath;
} else if (path.isAbsolute(imagePath)) {
- // absolute paths are expected to exist in the static folder
+ // Absolute paths are expected to exist in the static folder.
const possiblePaths = staticDirs.map((dir) => path.join(dir, imagePath));
const imageFilePath = await findAsyncSequential(
possiblePaths,
@@ -120,7 +122,7 @@ async function getImageAbsolutePath(
}
return imageFilePath;
}
- // relative paths are resolved against the source file's folder
+ // Relative paths are resolved against the source file's folder.
const imageFilePath = path.join(
path.dirname(filePath),
decodeURIComponent(imagePath),
@@ -129,7 +131,8 @@ async function getImageAbsolutePath(
return imageFilePath;
}
-async function processImageNode(node: Image, context: Context) {
+async function processImageNode(target: Target, context: Context) {
+ const [node] = target;
if (!node.url) {
throw new Error(
`Markdown image URL is mandatory in "${toMessageRelativeFilePath(
@@ -151,15 +154,18 @@ async function processImageNode(node: Image, context: Context) {
// We try to convert image urls without protocol to images with require calls
// going through webpack ensures that image assets exist at build time
const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context);
- await toImageRequireNode(node, imagePath, context.filePath);
+ await toImageRequireNode(target, imagePath, context.filePath);
}
export default function plugin(options: PluginOptions): Transformer {
return async (root, vfile) => {
const promises: Promise[] = [];
- visit(root, 'image', (node: Image) => {
+ visit(root, 'image', (node: Image, index, parent) => {
promises.push(
- processImageNode(node, {...options, filePath: vfile.path!}),
+ processImageNode([node, index, parent!], {
+ ...options,
+ filePath: vfile.path!,
+ }),
);
});
await Promise.all(promises);
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md
index 407c853eb14f..a23a16a92832 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md
@@ -40,4 +40,4 @@
[json](./data.json)
-[static json](/staticjson.json)
+[static json](/static-json.json)
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/staticjson.json b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/static-json.json
similarity index 100%
rename from packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/staticjson.json
rename to packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/static-json.json
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap
index 1c27e176dcb7..0daf646c0574 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`transformAsset plugin fail if asset url is absent 1`] = `"Markdown link URL is mandatory in \\"packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/noUrl.md\\" file (title: asset, line: 1)."`;
+exports[`transformAsset plugin fail if asset url is absent 1`] = `"Markdown link URL is mandatory in "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/noUrl.md" file (title: asset, line: 1)."`;
exports[`transformAsset plugin fail if asset with site alias does not exist 1`] = `"Asset packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/foo.pdf used in packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/nonexistentSiteAlias.md not found."`;
@@ -12,15 +12,15 @@ exports[`transformAsset plugin pathname protocol 1`] = `
exports[`transformAsset plugin transform md links to 1`] = `
"[asset](https://example.com/asset.pdf)
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>asset
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>asset
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf').default}>asset with URL encoded chars
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf').default}>asset with URL encoded chars
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default + '#page=2'}>asset with hash
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default + '#page=2'}>asset with hash
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default} title=\\"Title\\">asset
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default} title="Title">asset
[page](noUrl.md)
@@ -34,24 +34,24 @@ exports[`transformAsset plugin transform md links to 1`] = `
[assets](/github/!file-loader!/assets.pdf)
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>asset
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>asset
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf').default}>asset2
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf').default}>asset2
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>staticAsset.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>staticAsset.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>@site/static/staticAsset.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>@site/static/staticAsset.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default + '#page=2'} title=\\"Title\\">@site/static/staticAsset.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default + '#page=2'} title="Title">@site/static/staticAsset.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>Just staticAsset.pdf, and /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>awesome staticAsset 2.pdf 'It is really "AWESOME"', but also /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>coded staticAsset 3.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>Just staticAsset.pdf, and /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>awesome staticAsset 2.pdf 'It is really "AWESOME"', but also /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>coded staticAsset 3.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png').default}>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png\\").default} width=\\"200\\" height=\\"200\\" />
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png').default}>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png").default} width="200" height="200" />
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>Stylized link to asset file
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>Stylized link to asset file
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json').default}>json
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json').default}>json
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticjson.json').default}>static json
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json').default}>static json
"
`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts
index 2b2ac7979dc3..181ddd4f7315 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts
@@ -10,21 +10,20 @@ import remark from 'remark';
import mdx from 'remark-mdx';
import vfile from 'to-vfile';
import plugin from '..';
-import transformImage from '../../transformImage';
+import transformImage, {type PluginOptions} from '../../transformImage';
-const processFixture = async (name: string, options?) => {
- const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
+const processFixture = async (name: string, options?: PluginOptions) => {
+ const siteDir = path.join(__dirname, `__fixtures__`);
const staticDirs = [
- path.join(__dirname, '__fixtures__/static'),
- path.join(__dirname, '__fixtures__/static2'),
+ path.join(siteDir, 'static'),
+ path.join(siteDir, 'static2'),
];
- const file = await vfile.read(filePath);
+ const file = await vfile.read(path.join(siteDir, `${name}.md`));
const result = await remark()
.use(mdx)
- .use(transformImage, {...options, filePath, staticDirs})
+ .use(transformImage, {...options, siteDir, staticDirs})
.use(plugin, {
...options,
- filePath,
staticDirs,
siteDir: path.join(__dirname, '__fixtures__'),
})
@@ -34,24 +33,24 @@ const processFixture = async (name: string, options?) => {
};
describe('transformAsset plugin', () => {
- test('fail if asset url is absent', async () => {
+ it('fail if asset url is absent', async () => {
await expect(
processFixture('noUrl'),
).rejects.toThrowErrorMatchingSnapshot();
});
- test('fail if asset with site alias does not exist', async () => {
+ it('fail if asset with site alias does not exist', async () => {
await expect(
processFixture('nonexistentSiteAlias'),
).rejects.toThrowErrorMatchingSnapshot();
});
- test('transform md links to ', async () => {
+ it('transform md links to ', async () => {
const result = await processFixture('asset');
expect(result).toMatchSnapshot();
});
- test('pathname protocol', async () => {
+ it('pathname protocol', async () => {
const result = await processFixture('pathname');
expect(result).toMatchSnapshot();
});
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
index b1e7d1467ed9..33602c0d26f4 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
@@ -5,6 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
+import path from 'path';
+import url from 'url';
+import fs from 'fs-extra';
import {
toMessageRelativeFilePath,
posixPath,
@@ -13,19 +16,17 @@ import {
findAsyncSequential,
} from '@docusaurus/utils';
import visit from 'unist-util-visit';
-import path from 'path';
-import url from 'url';
-import fs from 'fs-extra';
import escapeHtml from 'escape-html';
import {stringifyContent} from '../utils';
import type {Transformer} from 'unified';
+import type {Parent} from 'unist';
import type {Link, Literal} from 'mdast';
const {
loaders: {inlineMarkdownLinkFileLoader},
} = getFileLoaderUtils();
-type PluginOptions = {
+export type PluginOptions = {
staticDirs: string[];
siteDir: string;
};
@@ -34,14 +35,20 @@ type Context = PluginOptions & {
filePath: string;
};
-// transform the link node to a jsx link with a require() call
-function toAssetRequireNode(node: Link, assetPath: string, filePath: string) {
- const jsxNode = node as Literal & Partial;
- let relativeAssetPath = posixPath(
- path.relative(path.dirname(filePath), assetPath),
- );
+type Target = [node: Link, index: number, parent: Parent];
+
+/**
+ * Transforms the link node to a JSX `` element with a `require()` call.
+ */
+function toAssetRequireNode(
+ [node, index, parent]: Target,
+ assetPath: string,
+ filePath: string,
+) {
// require("assets/file.pdf") means requiring from a package called assets
- relativeAssetPath = `./${relativeAssetPath}`;
+ const relativeAssetPath = `./${posixPath(
+ path.relative(path.dirname(filePath), assetPath),
+ )}`;
const parsedUrl = url.parse(node.url);
const hash = parsedUrl.hash ?? '';
@@ -58,12 +65,12 @@ function toAssetRequireNode(node: Link, assetPath: string, filePath: string) {
const children = stringifyContent(node);
const title = node.title ? ` title="${escapeHtml(node.title)}"` : '';
- Object.keys(jsxNode).forEach(
- (key) => delete jsxNode[key as keyof typeof jsxNode],
- );
+ const jsxNode: Literal = {
+ type: 'jsx',
+ value: `${children}`,
+ };
- (jsxNode as Literal).type = 'jsx';
- jsxNode.value = `${children}`;
+ parent.children.splice(index, 1, jsxNode);
}
async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) {
@@ -104,12 +111,14 @@ async function getAssetAbsolutePath(
return null;
}
-async function processLinkNode(node: Link, context: Context) {
+async function processLinkNode(target: Target, context: Context) {
+ const [node] = target;
if (!node.url) {
- // try to improve error feedback
+ // Try to improve error feedback
// see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675
- const title = node.title || (node.children[0] as Literal)?.value || '?';
- const line = node?.position?.start?.line || '?';
+ const title =
+ node.title ?? (node.children[0] as Literal | undefined)?.value ?? '?';
+ const line = node.position?.start.line ?? '?';
throw new Error(
`Markdown link URL is mandatory in "${toMessageRelativeFilePath(
context.filePath,
@@ -135,15 +144,20 @@ async function processLinkNode(node: Link, context: Context) {
context,
);
if (assetPath) {
- toAssetRequireNode(node, assetPath, context.filePath);
+ toAssetRequireNode(target, assetPath, context.filePath);
}
}
export default function plugin(options: PluginOptions): Transformer {
return async (root, vfile) => {
const promises: Promise[] = [];
- visit(root, 'link', (node: Link) => {
- promises.push(processLinkNode(node, {...options, filePath: vfile.path!}));
+ visit(root, 'link', (node: Link, index, parent) => {
+ promises.push(
+ processLinkNode([node, index, parent!], {
+ ...options,
+ filePath: vfile.path!,
+ }),
+ );
});
await Promise.all(promises);
};
diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap
index c234d26fe0d1..9b8ed7b0ada3 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`unwrapMdxCodeBlocks should unwrap the mdx code blocks 1`] = `
+exports[`unwrapMdxCodeBlocks remark plugin unwraps the mdx code blocks 1`] = `
"# MDX code blocks test document
## Some basic markdown
@@ -17,7 +17,7 @@ text
import XYZ from 'xyz';
-
+
Test
@@ -32,30 +32,30 @@ import Avatar from 'avatar';
## Some complex MDX with nested code blocks
-
+
\`\`\`bash
GIT_USER= yarn deploy
\`\`\`
-
+
\`\`\`batch
- cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
+ cmd /C "set "GIT_USER=" && yarn deploy"
\`\`\`
-
+
\`\`\`powershell
-cmd /C 'set \\"GIT_USER=\\" && yarn deploy'
+cmd /C 'set "GIT_USER=" && yarn deploy'
\`\`\`
@@ -64,30 +64,30 @@ cmd /C 'set \\"GIT_USER=\\" && yarn deploy'
## Some complex MDX code block with nested code blocks
-
+
\`\`\`bash
GIT_USER= yarn deploy
\`\`\`
-
+
\`\`\`batch
-cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
+cmd /C "set "GIT_USER=" && yarn deploy"
\`\`\`
-
+
\`\`\`powershell
-cmd /C 'set \\"GIT_USER=\\" && yarn deploy'
+cmd /C 'set "GIT_USER=" && yarn deploy'
\`\`\`
@@ -95,20 +95,20 @@ cmd /C 'set \\"GIT_USER=\\" && yarn deploy'
"
`;
-exports[`unwrapMdxCodeBlocks should unwrap the mdx code blocks AST 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
+exports[`unwrapMdxCodeBlocks remark plugin unwraps the mdx code blocks AST 1`] = `
+{
+ "children": [
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 32,
"line": 1,
"offset": 31,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 3,
"line": 1,
"offset": 2,
@@ -120,13 +120,13 @@ Object {
],
"depth": 1,
"position": Position {
- "end": Object {
+ "end": {
"column": 32,
"line": 1,
"offset": 31,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
@@ -134,17 +134,17 @@ Object {
},
"type": "heading",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 23,
"line": 3,
"offset": 55,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 4,
"line": 3,
"offset": 36,
@@ -156,13 +156,13 @@ Object {
],
"depth": 2,
"position": Position {
- "end": Object {
+ "end": {
"column": 23,
"line": 3,
"offset": 55,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 3,
"offset": 33,
@@ -170,17 +170,17 @@ Object {
},
"type": "heading",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 5,
"line": 5,
"offset": 61,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 5,
"offset": 57,
@@ -191,13 +191,13 @@ Object {
},
],
"position": Position {
- "end": Object {
+ "end": {
"column": 5,
"line": 5,
"offset": 61,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 5,
"offset": 57,
@@ -205,19 +205,19 @@ Object {
},
"type": "paragraph",
},
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 6,
"line": 7,
"offset": 68,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 2,
"line": 7,
"offset": 64,
@@ -228,13 +228,13 @@ Object {
},
],
"position": Position {
- "end": Object {
+ "end": {
"column": 29,
"line": 7,
"offset": 91,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 7,
"offset": 63,
@@ -246,13 +246,13 @@ Object {
},
],
"position": Position {
- "end": Object {
+ "end": {
"column": 29,
"line": 7,
"offset": 91,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 7,
"offset": 63,
@@ -260,19 +260,19 @@ Object {
},
"type": "paragraph",
},
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 7,
"line": 9,
"offset": 99,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 3,
"line": 9,
"offset": 95,
@@ -283,13 +283,13 @@ Object {
},
],
"position": Position {
- "end": Object {
+ "end": {
"column": 9,
"line": 9,
"offset": 101,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 9,
"offset": 93,
@@ -299,13 +299,13 @@ Object {
},
],
"position": Position {
- "end": Object {
+ "end": {
"column": 9,
"line": 9,
"offset": 101,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 9,
"offset": 93,
@@ -313,18 +313,18 @@ Object {
},
"type": "paragraph",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"alt": "image",
"position": Position {
- "end": Object {
+ "end": {
"column": 43,
"line": 11,
"offset": 145,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 11,
"offset": 103,
@@ -336,13 +336,13 @@ Object {
},
],
"position": Position {
- "end": Object {
+ "end": {
"column": 43,
"line": 11,
"offset": 145,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 11,
"offset": 103,
@@ -350,17 +350,17 @@ Object {
},
"type": "paragraph",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 18,
"line": 13,
"offset": 164,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 4,
"line": 13,
"offset": 150,
@@ -372,13 +372,13 @@ Object {
],
"depth": 2,
"position": Position {
- "end": Object {
+ "end": {
"column": 18,
"line": 13,
"offset": 164,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 13,
"offset": 147,
@@ -386,15 +386,15 @@ Object {
},
"type": "heading",
},
- Object {
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 23,
"line": 15,
"offset": 188,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 15,
"offset": 166,
@@ -403,39 +403,39 @@ Object {
"type": "import",
"value": "import XYZ from 'xyz';",
},
- Object {
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 7,
"line": 19,
"offset": 287,
},
- "indent": Array [
+ "indent": [
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 17,
"offset": 190,
},
},
"type": "jsx",
- "value": "
+ "value": "
Test
",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 29,
"line": 21,
"offset": 317,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 4,
"line": 21,
"offset": 292,
@@ -447,13 +447,13 @@ Object {
],
"depth": 2,
"position": Position {
- "end": Object {
+ "end": {
"column": 29,
"line": 21,
"offset": 317,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 21,
"offset": 289,
@@ -461,16 +461,16 @@ Object {
},
"type": "heading",
},
- Object {
+ {
"lang": "mdx-code-block",
"meta": null,
"position": Position {
- "end": Object {
+ "end": {
"column": 4,
"line": 29,
"offset": 442,
},
- "indent": Array [
+ "indent": [
1,
1,
1,
@@ -478,7 +478,7 @@ Object {
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 23,
"offset": 319,
@@ -491,17 +491,17 @@ Object {
Sebastien Lorber
",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 44,
"line": 31,
"offset": 487,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 4,
"line": 31,
"offset": 447,
@@ -513,13 +513,13 @@ Object {
],
"depth": 2,
"position": Position {
- "end": Object {
+ "end": {
"column": 44,
"line": 31,
"offset": 487,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 31,
"offset": 444,
@@ -527,14 +527,14 @@ Object {
},
"type": "heading",
},
- Object {
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 25,
"line": 40,
"offset": 688,
},
- "indent": Array [
+ "indent": [
1,
1,
1,
@@ -543,7 +543,7 @@ Object {
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 33,
"offset": 489,
@@ -551,28 +551,28 @@ Object {
},
"type": "jsx",
"value": "
- ",
+ ",
},
- Object {
+ {
"lang": "bash",
"meta": null,
"position": Position {
- "end": Object {
+ "end": {
"column": 4,
"line": 44,
"offset": 740,
},
- "indent": Array [
+ "indent": [
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 42,
"offset": 690,
@@ -581,17 +581,17 @@ Object {
"type": "code",
"value": "GIT_USER= yarn deploy",
},
- Object {
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 28,
"line": 47,
"offset": 782,
},
- "indent": Array [
+ "indent": [
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 46,
"offset": 742,
@@ -599,22 +599,22 @@ Object {
},
"type": "jsx",
"value": "
- ",
+ ",
},
- Object {
+ {
"lang": null,
"meta": null,
"position": Position {
- "end": Object {
+ "end": {
"column": 8,
"line": 51,
"offset": 865,
},
- "indent": Array [
+ "indent": [
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 49,
"offset": 784,
@@ -622,20 +622,20 @@ Object {
},
"type": "code",
"value": "\`\`\`batch
-cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
+cmd /C "set "GIT_USER=" && yarn deploy"
\`\`\`",
},
- Object {
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 31,
"line": 54,
"offset": 910,
},
- "indent": Array [
+ "indent": [
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 53,
"offset": 867,
@@ -643,41 +643,41 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
},
"type": "jsx",
"value": "
- ",
+ ",
},
- Object {
+ {
"lang": "powershell",
"meta": null,
"position": Position {
- "end": Object {
+ "end": {
"column": 4,
"line": 58,
"offset": 986,
},
- "indent": Array [
+ "indent": [
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 56,
"offset": 912,
},
},
"type": "code",
- "value": "cmd /C 'set \\"GIT_USER=\\" && yarn deploy'",
+ "value": "cmd /C 'set "GIT_USER=" && yarn deploy'",
},
- Object {
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 8,
"line": 61,
"offset": 1008,
},
- "indent": Array [
+ "indent": [
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 60,
"offset": 988,
@@ -687,17 +687,17 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
"value": "
",
},
- Object {
- "children": Array [
- Object {
+ {
+ "children": [
+ {
"position": Position {
- "end": Object {
+ "end": {
"column": 55,
"line": 63,
"offset": 1064,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 4,
"line": 63,
"offset": 1013,
@@ -709,13 +709,13 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
],
"depth": 2,
"position": Position {
- "end": Object {
+ "end": {
"column": 55,
"line": 63,
"offset": 1064,
},
- "indent": Array [],
- "start": Object {
+ "indent": [],
+ "start": {
"column": 1,
"line": 63,
"offset": 1010,
@@ -723,16 +723,16 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
},
"type": "heading",
},
- Object {
+ {
"lang": "mdx-code-block",
"meta": null,
"position": Position {
- "end": Object {
+ "end": {
"column": 5,
"line": 95,
"offset": 1585,
},
- "indent": Array [
+ "indent": [
1,
1,
1,
@@ -764,7 +764,7 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
1,
1,
],
- "start": Object {
+ "start": {
"column": 1,
"line": 65,
"offset": 1066,
@@ -772,43 +772,43 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
},
"type": "code",
"value": "
-
+
\`\`\`bash
GIT_USER= yarn deploy
\`\`\`
-
+
\`\`\`batch
-cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\"
+cmd /C "set "GIT_USER=" && yarn deploy"
\`\`\`
-
+
\`\`\`powershell
-cmd /C 'set \\"GIT_USER=\\" && yarn deploy'
+cmd /C 'set "GIT_USER=" && yarn deploy'
\`\`\`
",
},
],
- "position": Object {
- "end": Object {
+ "position": {
+ "end": {
"column": 1,
"line": 96,
"offset": 1586,
},
- "start": Object {
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts
index fa49602667c5..13fe031f5b84 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts
@@ -5,33 +5,31 @@
* LICENSE file in the root directory of this source tree.
*/
-import {join} from 'path';
+import path from 'path';
import remark from 'remark';
import mdx from 'remark-mdx';
import vfile from 'to-vfile';
import plugin from '..';
-const processFixture = async (name) => {
- const path = join(__dirname, '__fixtures__', name);
- const file = await vfile.read(path);
+const processFixture = async (name: string) => {
+ const file = await vfile.read(path.join(__dirname, '__fixtures__', name));
const result = await remark().use(mdx).use(plugin).process(file);
return result.toString();
};
-const processFixtureAST = async (name) => {
- const path = join(__dirname, '__fixtures__', name);
- const file = await vfile.read(path);
+const processFixtureAST = async (name: string) => {
+ const file = await vfile.read(path.join(__dirname, '__fixtures__', name));
return remark().use(mdx).use(plugin).parse(file);
};
-describe('unwrapMdxCodeBlocks', () => {
- test('should unwrap the mdx code blocks', async () => {
+describe('unwrapMdxCodeBlocks remark plugin', () => {
+ it('unwraps the mdx code blocks', async () => {
const result = await processFixture('has-mdx-code-blocks.mdx');
expect(result).toMatchSnapshot();
});
// The AST output should be parsed correctly or the MDX loader won't work!
- test('should unwrap the mdx code blocks AST', async () => {
+ it('unwraps the mdx code blocks AST', async () => {
const result = await processFixtureAST('has-mdx-code-blocks.mdx');
expect(result).toMatchSnapshot();
});
diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts
index 82493e58c4cb..07aee3daeb89 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts
@@ -17,7 +17,7 @@ import type {Code, Parent} from 'mdast';
// See https://github.com/facebook/docusaurus/pull/4278
export default function plugin(this: Processor): Transformer {
return (root) => {
- visit(root, 'code', (node: Code, _index, parent) => {
+ visit(root, 'code', (node: Code, index, parent) => {
if (node.lang === 'mdx-code-block') {
const newChildren = (this.parse(node.value) as Parent).children;
diff --git a/packages/docusaurus-mdx-loader/src/remark/utils/index.ts b/packages/docusaurus-mdx-loader/src/remark/utils/index.ts
index e735eea28e33..3bf275085bf5 100644
--- a/packages/docusaurus-mdx-loader/src/remark/utils/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/utils/index.ts
@@ -15,7 +15,7 @@ export function stringifyContent(node: Parent): string {
}
export function toValue(node: PhrasingContent | Heading): string {
- switch (node?.type) {
+ switch (node.type) {
case 'text':
return escapeHtml(node.value);
case 'heading':
@@ -31,7 +31,6 @@ export function toValue(node: PhrasingContent | Heading): string {
case 'link':
return stringifyContent(node);
default:
+ return toString(node);
}
-
- return toString(node);
}
diff --git a/packages/docusaurus-mdx-loader/tsconfig.json b/packages/docusaurus-mdx-loader/tsconfig.json
index aee99fc0f38e..3cdfe2a88c91 100644
--- a/packages/docusaurus-mdx-loader/tsconfig.json
+++ b/packages/docusaurus-mdx-loader/tsconfig.json
@@ -1,11 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
+ "noEmit": false,
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo",
"sourceMap": true,
"declarationMap": true,
+ "module": "commonjs",
"rootDir": "src",
- "outDir": "lib"
- }
+ "outDir": "lib",
+ "types": []
+ },
+ "include": ["src"],
+ "exclude": ["**/__tests__/**"]
}
diff --git a/packages/docusaurus-migrate/.npmignore b/packages/docusaurus-migrate/.npmignore
index ba8304029eb7..e4132af42172 100644
--- a/packages/docusaurus-migrate/.npmignore
+++ b/packages/docusaurus-migrate/.npmignore
@@ -1,5 +1,4 @@
-copyUntypedFiles.mjs
-.tsbuildinfo
+.tsbuildinfo*
tsconfig*
__tests__
diff --git a/packages/docusaurus-migrate/bin/index.mjs b/packages/docusaurus-migrate/bin/index.mjs
index baf72762aeb7..8be0d9d3bc9d 100755
--- a/packages/docusaurus-migrate/bin/index.mjs
+++ b/packages/docusaurus-migrate/bin/index.mjs
@@ -8,14 +8,16 @@
// @ts-check
+import path from 'path';
+import {createRequire} from 'module';
import logger from '@docusaurus/logger';
import semver from 'semver';
import cli from 'commander';
-import path from 'path';
-import {createRequire} from 'module';
const moduleRequire = createRequire(import.meta.url);
-const requiredVersion = moduleRequire('../package.json').engines.node;
+const requiredVersion = /** @type {import("../package.json")} */ (
+ moduleRequire('../package.json')
+).engines.node;
if (!semver.satisfies(process.version, requiredVersion)) {
logger.error('Minimum Node.js version not met :(');
@@ -25,26 +27,26 @@ if (!semver.satisfies(process.version, requiredVersion)) {
// See https://github.com/facebook/docusaurus/pull/6860
const {migrateDocusaurusProject, migrateMDToMDX} =
- moduleRequire('../lib/index.js');
+ /** @type {import("../lib/index.js")} */ (moduleRequire('../lib/index.js'));
cli
.command('migrate [siteDir] [newDir]')
.option('--mdx', 'try to migrate MD to MDX too')
.option('--page', 'try to migrate pages too')
.description('Migrate between versions of Docusaurus website.')
- .action((siteDir = '.', newDir = '.', {mdx, page} = {}) => {
+ .action(async (siteDir = '.', newDir = '.', {mdx, page} = {}) => {
const sitePath = path.resolve(siteDir);
const newSitePath = path.resolve(newDir);
- migrateDocusaurusProject(sitePath, newSitePath, mdx, page);
+ await migrateDocusaurusProject(sitePath, newSitePath, mdx, page);
});
cli
.command('mdx [siteDir] [newDir]')
.description('Migrate markdown files to MDX.')
- .action((siteDir = '.', newDir = '.') => {
+ .action(async (siteDir = '.', newDir = '.') => {
const sitePath = path.resolve(siteDir);
const newSitePath = path.resolve(newDir);
- migrateMDToMDX(sitePath, newSitePath);
+ await migrateMDToMDX(sitePath, newSitePath);
});
cli.parse(process.argv);
diff --git a/packages/docusaurus-migrate/package.json b/packages/docusaurus-migrate/package.json
index abf7fc7be3b5..efd00b2b2f2a 100644
--- a/packages/docusaurus-migrate/package.json
+++ b/packages/docusaurus-migrate/package.json
@@ -1,14 +1,14 @@
{
"name": "@docusaurus/migrate",
- "version": "2.0.0-beta.17",
+ "version": "2.0.0-beta.21",
"description": "A CLI tool to migrate from older versions of Docusaurus.",
"license": "MIT",
"engines": {
- "node": ">=14"
+ "node": ">=16.14"
},
"scripts": {
- "build": "tsc -p tsconfig.build.json",
- "watch": "tsc -p tsconfig.build.json --watch"
+ "build": "tsc --build",
+ "watch": "tsc --build --watch"
},
"repository": {
"type": "git",
@@ -22,27 +22,28 @@
"docusaurus-migrate": "bin/index.mjs"
},
"dependencies": {
- "@babel/preset-env": "^7.16.11",
- "@docusaurus/logger": "2.0.0-beta.17",
- "@docusaurus/utils": "2.0.0-beta.17",
+ "@babel/core": "^7.18.2",
+ "@babel/preset-env": "^7.18.2",
+ "@docusaurus/logger": "2.0.0-beta.21",
+ "@docusaurus/utils": "2.0.0-beta.21",
"@mapbox/hast-util-to-jsx": "^2.0.0",
- "color": "^4.0.1",
+ "color": "^4.2.3",
"commander": "^5.1.0",
- "fs-extra": "^10.0.1",
+ "fs-extra": "^10.1.0",
"hast-util-to-string": "^1.0.4",
- "html-tags": "^3.1.0",
+ "html-tags": "^3.2.0",
"import-fresh": "^3.3.0",
- "jscodeshift": "^0.13.0",
+ "jscodeshift": "^0.13.1",
"rehype-parse": "^7.0.1",
"remark-parse": "^8.0.2",
"remark-stringify": "^8.1.0",
- "semver": "^7.3.4",
- "tslib": "^2.3.1",
- "unified": "^9.2.1",
- "unist-util-visit": "^2.0.2"
+ "semver": "^7.3.7",
+ "tslib": "^2.4.0",
+ "unified": "^9.2.2",
+ "unist-util-visit": "^2.0.3"
},
"devDependencies": {
"@types/color": "^3.0.3",
- "@types/jscodeshift": "^0.11.2"
+ "@types/jscodeshift": "^0.11.5"
}
}
diff --git a/packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap
new file mode 100644
index 000000000000..4fd3d63d75ff
--- /dev/null
+++ b/packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap
@@ -0,0 +1,565 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`migration CLI migrates complex website: copy 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/blog",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/blog",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/pages/en",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/pages",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/static",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/static",
+ ],
+]
+`;
+
+exports[`migration CLI migrates complex website: mkdirp 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/pages",
+ ],
+]
+`;
+
+exports[`migration CLI migrates complex website: mkdirs 1`] = `[]`;
+
+exports[`migration CLI migrates complex website: write 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/docusaurus.config.js",
+ "module.exports={
+ "title": "Docusaurus",
+ "tagline": "Easy to Maintain Open Source Documentation Websites",
+ "url": "https://docusaurus.io",
+ "baseUrl": "/",
+ "organizationName": "facebook",
+ "projectName": "docusaurus",
+ "noIndex": false,
+ "scripts": [
+ "https://buttons.github.io/buttons.js",
+ "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",
+ "/js/code-blocks-buttons.js"
+ ],
+ "favicon": "img/docusaurus.ico",
+ "customFields": {
+ "users": {
+ "caption": "DevSpace",
+ "image": "/img/users/devspace.svg",
+ "infoLink": "https://devspace.cloud/docs/",
+ "fbOpenSource": false,
+ "pinned": false
+ },
+ "translationRecruitingLink": "https://crowdin.com/project/docusaurus",
+ "facebookAppId": "199138890728411"
+ },
+ "onBrokenLinks": "log",
+ "onBrokenMarkdownLinks": "log",
+ "presets": [
+ [
+ "@docusaurus/preset-classic",
+ {
+ "docs": {
+ "showLastUpdateAuthor": true,
+ "showLastUpdateTime": true,
+ "editUrl": "https://github.com/facebook/docusaurus/edit/main/docs/"
+ },
+ "blog": {},
+ "theme": {
+ "customCss": "../complex_website/src/css/customTheme.css"
+ },
+ "googleAnalytics": {
+ "trackingID": "UA-44373548-31"
+ }
+ }
+ ]
+ ],
+ "plugins": [],
+ "themeConfig": {
+ "navbar": {
+ "title": "Docusaurus",
+ "logo": {
+ "src": "img/docusaurus.svg"
+ },
+ "items": [
+ {
+ "to": "docs/installation",
+ "label": "Docs",
+ "position": "left"
+ },
+ {
+ "to": "docs/tutorial-setup",
+ "label": "Tutorial",
+ "position": "left"
+ },
+ {
+ "to": "/users",
+ "label": "Users",
+ "position": "left"
+ },
+ {
+ "href": "https://github.com/facebook/docusaurus",
+ "label": "GitHub",
+ "position": "left"
+ }
+ ]
+ },
+ "image": "img/docusaurus.png",
+ "footer": {
+ "links": [
+ {
+ "title": "Community",
+ "items": [
+ {
+ "label": "Twitter",
+ "to": "https://twitter.com/docusaurus"
+ }
+ ]
+ }
+ ],
+ "copyright": "Copyright © 2022 Facebook Inc.",
+ "logo": {
+ "src": "img/docusaurus_monochrome.svg"
+ }
+ },
+ "algolia": {
+ "apiKey": "3eb9507824b8be89e7a199ecaa1a9d2c",
+ "indexName": "docusaurus",
+ "algoliaOptions": {
+ "facetFilters": [
+ "language:LANGUAGE",
+ "version:VERSION"
+ ]
+ }
+ }
+ }
+}",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/package.json",
+ "{
+ "name": "docusaurus-1-website",
+ "version": "2.0.0-alpha.58",
+ "private": true,
+ "scripts": {
+ "start": "docusaurus start",
+ "build": "docusaurus build",
+ "publish-gh-pages": "docusaurus-publish",
+ "examples": "docusaurus-examples",
+ "write-translations": "docusaurus-write-translations",
+ "docusaurus-version": "docusaurus-version",
+ "rename-version": "docusaurus-rename-version",
+ "crowdin-upload": "crowdin --config ../crowdin.yaml upload sources --auto-update -b master",
+ "crowdin-download": "crowdin --config ../crowdin.yaml download -b master",
+ "swizzle": "docusaurus swizzle",
+ "deploy": "docusaurus deploy",
+ "docusaurus": "docusaurus"
+ },
+ "dependencies": {
+ "@docusaurus/core": "",
+ "@docusaurus/preset-classic": "",
+ "clsx": "^1.1.1",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ }
+}",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/css/customTheme.css",
+ ":root{
+ --ifm-color-primary-lightest: #3CAD6E;
+ --ifm-color-primary-lighter: #359962;
+ --ifm-color-primary-light: #33925D;
+ --ifm-color-primary: #2E8555;
+ --ifm-color-primary-dark: #29784C;
+ --ifm-color-primary-darker: #277148;
+ --ifm-color-primary-darkest: #205D3B;
+}
+",
+ ],
+]
+`;
+
+exports[`migration CLI migrates missing versions: copy 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/blog",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/blog",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/pages/en",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/pages",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/static",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/static",
+ ],
+]
+`;
+
+exports[`migration CLI migrates missing versions: mkdirp 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/pages",
+ ],
+]
+`;
+
+exports[`migration CLI migrates missing versions: mkdirs 1`] = `[]`;
+
+exports[`migration CLI migrates missing versions: write 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/docusaurus.config.js",
+ "module.exports={
+ "title": "Docusaurus",
+ "tagline": "Easy to Maintain Open Source Documentation Websites",
+ "url": "https://docusaurus.io",
+ "baseUrl": "/",
+ "organizationName": "facebook",
+ "projectName": "docusaurus",
+ "noIndex": false,
+ "scripts": [
+ "https://buttons.github.io/buttons.js",
+ "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",
+ "/js/code-blocks-buttons.js"
+ ],
+ "favicon": "img/docusaurus.ico",
+ "customFields": {
+ "users": {
+ "caption": "DevSpace",
+ "image": "/img/users/devspace.svg",
+ "infoLink": "https://devspace.cloud/docs/",
+ "fbOpenSource": false,
+ "pinned": false
+ },
+ "translationRecruitingLink": "https://crowdin.com/project/docusaurus",
+ "facebookAppId": "199138890728411"
+ },
+ "onBrokenLinks": "log",
+ "onBrokenMarkdownLinks": "log",
+ "presets": [
+ [
+ "@docusaurus/preset-classic",
+ {
+ "docs": {
+ "showLastUpdateAuthor": true,
+ "showLastUpdateTime": true,
+ "editUrl": "https://github.com/facebook/docusaurus/edit/main/docs/"
+ },
+ "blog": {},
+ "theme": {
+ "customCss": "../missing_version_website/src/css/customTheme.css"
+ },
+ "googleAnalytics": {
+ "trackingID": "UA-44373548-31"
+ }
+ }
+ ]
+ ],
+ "plugins": [],
+ "themeConfig": {
+ "navbar": {
+ "title": "Docusaurus",
+ "logo": {
+ "src": "img/docusaurus.svg"
+ },
+ "items": [
+ {
+ "to": "docs/installation",
+ "label": "Docs",
+ "position": "left"
+ },
+ {
+ "to": "docs/tutorial-setup",
+ "label": "Tutorial",
+ "position": "left"
+ },
+ {
+ "to": "/users",
+ "label": "Users",
+ "position": "left"
+ },
+ {
+ "href": "https://github.com/facebook/docusaurus",
+ "label": "GitHub",
+ "position": "left"
+ }
+ ]
+ },
+ "image": "img/docusaurus.png",
+ "footer": {
+ "links": [
+ {
+ "title": "Community",
+ "items": [
+ {
+ "label": "Twitter",
+ "to": "https://twitter.com/docusaurus"
+ }
+ ]
+ }
+ ],
+ "copyright": "Copyright © 2022 Facebook Inc.",
+ "logo": {
+ "src": "img/docusaurus_monochrome.svg"
+ }
+ },
+ "algolia": {
+ "apiKey": "3eb9507824b8be89e7a199ecaa1a9d2c",
+ "indexName": "docusaurus",
+ "algoliaOptions": {
+ "facetFilters": [
+ "language:LANGUAGE",
+ "version:VERSION"
+ ]
+ }
+ }
+ }
+}",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/package.json",
+ "{
+ "name": "docusaurus-1-website",
+ "version": "2.0.0-alpha.58",
+ "private": true,
+ "scripts": {
+ "start": "docusaurus start",
+ "build": "docusaurus build",
+ "publish-gh-pages": "docusaurus-publish",
+ "examples": "docusaurus-examples",
+ "write-translations": "docusaurus-write-translations",
+ "docusaurus-version": "docusaurus-version",
+ "rename-version": "docusaurus-rename-version",
+ "crowdin-upload": "crowdin --config ../crowdin.yaml upload sources --auto-update -b master",
+ "crowdin-download": "crowdin --config ../crowdin.yaml download -b master",
+ "swizzle": "docusaurus swizzle",
+ "deploy": "docusaurus deploy",
+ "docusaurus": "docusaurus"
+ },
+ "dependencies": {
+ "@docusaurus/core": "",
+ "@docusaurus/preset-classic": "",
+ "clsx": "^1.1.1",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ }
+}",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/css/customTheme.css",
+ ":root{
+ --ifm-color-primary-lightest: #3CAD6E;
+ --ifm-color-primary-lighter: #359962;
+ --ifm-color-primary-light: #33925D;
+ --ifm-color-primary: #2E8555;
+ --ifm-color-primary-dark: #29784C;
+ --ifm-color-primary-darker: #277148;
+ --ifm-color-primary-darkest: #205D3B;
+}
+",
+ ],
+]
+`;
+
+exports[`migration CLI migrates simple website: copy 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/website/pages/en",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/pages",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/website/static",
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/static",
+ ],
+]
+`;
+
+exports[`migration CLI migrates simple website: mkdirp 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/pages",
+ ],
+]
+`;
+
+exports[`migration CLI migrates simple website: mkdirs 1`] = `[]`;
+
+exports[`migration CLI migrates simple website: write 1`] = `
+[
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/docusaurus.config.js",
+ "module.exports={
+ "title": "Docusaurus",
+ "tagline": "Easy to Maintain Open Source Documentation Websites",
+ "url": "https://docusaurus.io",
+ "baseUrl": "/",
+ "organizationName": "facebook",
+ "projectName": "docusaurus",
+ "noIndex": false,
+ "scripts": [
+ "https://buttons.github.io/buttons.js",
+ "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",
+ "/js/code-blocks-buttons.js"
+ ],
+ "favicon": "img/docusaurus.ico",
+ "customFields": {
+ "users": {
+ "caption": "DevSpace",
+ "image": "/img/users/devspace.svg",
+ "infoLink": "https://devspace.cloud/docs/",
+ "fbOpenSource": false,
+ "pinned": false
+ },
+ "translationRecruitingLink": "https://crowdin.com/project/docusaurus",
+ "facebookAppId": "199138890728411"
+ },
+ "onBrokenLinks": "log",
+ "onBrokenMarkdownLinks": "log",
+ "presets": [
+ [
+ "@docusaurus/preset-classic",
+ {
+ "docs": {
+ "showLastUpdateAuthor": true,
+ "showLastUpdateTime": true,
+ "editUrl": "https://github.com/facebook/docusaurus/edit/main/docs/",
+ "path": "../simple_website/docs"
+ },
+ "blog": {},
+ "theme": {
+ "customCss": "../simple_website/src/css/customTheme.css"
+ },
+ "googleAnalytics": {
+ "trackingID": "UA-44373548-31"
+ }
+ }
+ ]
+ ],
+ "plugins": [],
+ "themeConfig": {
+ "navbar": {
+ "title": "Docusaurus",
+ "logo": {
+ "src": "img/docusaurus.svg"
+ },
+ "items": [
+ {
+ "to": "docs/installation",
+ "label": "Docs",
+ "position": "left"
+ },
+ {
+ "to": "docs/tutorial-setup",
+ "label": "Tutorial",
+ "position": "left"
+ },
+ {
+ "to": "/users",
+ "label": "Users",
+ "position": "left"
+ },
+ {
+ "href": "https://github.com/facebook/docusaurus",
+ "label": "GitHub",
+ "position": "left"
+ }
+ ]
+ },
+ "image": "img/docusaurus.png",
+ "footer": {
+ "links": [
+ {
+ "title": "Community",
+ "items": [
+ {
+ "label": "Twitter",
+ "to": "https://twitter.com/docusaurus"
+ }
+ ]
+ }
+ ],
+ "copyright": "Copyright © 2022 Facebook Inc.",
+ "logo": {
+ "src": "img/docusaurus_monochrome.svg"
+ }
+ },
+ "algolia": {
+ "apiKey": "3eb9507824b8be89e7a199ecaa1a9d2c",
+ "indexName": "docusaurus",
+ "algoliaOptions": {
+ "facetFilters": [
+ "language:LANGUAGE",
+ "version:VERSION"
+ ]
+ }
+ }
+ }
+}",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/package.json",
+ "{
+ "name": "docusaurus-1-website",
+ "version": "2.0.0-alpha.58",
+ "private": true,
+ "scripts": {
+ "start": "docusaurus start",
+ "build": "docusaurus build",
+ "publish-gh-pages": "docusaurus-publish",
+ "examples": "docusaurus-examples",
+ "write-translations": "docusaurus-write-translations",
+ "docusaurus-version": "docusaurus-version",
+ "rename-version": "docusaurus-rename-version",
+ "crowdin-upload": "crowdin --config ../crowdin.yaml upload sources --auto-update -b master",
+ "crowdin-download": "crowdin --config ../crowdin.yaml download -b master",
+ "swizzle": "docusaurus swizzle",
+ "deploy": "docusaurus deploy",
+ "docusaurus": "docusaurus"
+ },
+ "dependencies": {
+ "@docusaurus/core": "",
+ "@docusaurus/preset-classic": "",
+ "clsx": "^1.1.1",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ }
+}",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/css/customTheme.css",
+ ":root{
+ --ifm-color-primary-lightest: #3CAD6E;
+ --ifm-color-primary-lighter: #359962;
+ --ifm-color-primary-light: #33925D;
+ --ifm-color-primary: #2E8555;
+ --ifm-color-primary-dark: #29784C;
+ --ifm-color-primary-darker: #277148;
+ --ifm-color-primary-darkest: #205D3B;
+}
+",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/docs/api-commands.md",
+ "---
+id: commands
+title: CLI Commands
+---
+## Doc
+",
+ ],
+ [
+ "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/docs/api-doc-markdown.md",
+ "---
+id: doc-markdown
+title: Markdown Features
+---
+## Doc
+",
+ ],
+]
+`;
diff --git a/packages/docusaurus-migrate/src/__tests__/__snapshots__/migration.test.ts.snap b/packages/docusaurus-migrate/src/__tests__/__snapshots__/migration.test.ts.snap
deleted file mode 100644
index b5be0f1ead24..000000000000
--- a/packages/docusaurus-migrate/src/__tests__/__snapshots__/migration.test.ts.snap
+++ /dev/null
@@ -1,565 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`migration test complex website: copy 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/blog",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/blog",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/pages/en",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/pages",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/static",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/static",
- ],
-]
-`;
-
-exports[`migration test complex website: mkdirp 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/pages",
- ],
-]
-`;
-
-exports[`migration test complex website: mkdirs 1`] = `Array []`;
-
-exports[`migration test complex website: write 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/docusaurus.config.js",
- "module.exports={
- \\"title\\": \\"Docusaurus\\",
- \\"tagline\\": \\"Easy to Maintain Open Source Documentation Websites\\",
- \\"url\\": \\"https://docusaurus.io\\",
- \\"baseUrl\\": \\"/\\",
- \\"organizationName\\": \\"facebook\\",
- \\"projectName\\": \\"docusaurus\\",
- \\"noIndex\\": false,
- \\"scripts\\": [
- \\"https://buttons.github.io/buttons.js\\",
- \\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\",
- \\"/js/code-blocks-buttons.js\\"
- ],
- \\"favicon\\": \\"img/docusaurus.ico\\",
- \\"customFields\\": {
- \\"users\\": {
- \\"caption\\": \\"DevSpace\\",
- \\"image\\": \\"/img/users/devspace.svg\\",
- \\"infoLink\\": \\"https://devspace.cloud/docs/\\",
- \\"fbOpenSource\\": false,
- \\"pinned\\": false
- },
- \\"translationRecruitingLink\\": \\"https://crowdin.com/project/docusaurus\\",
- \\"facebookAppId\\": \\"199138890728411\\"
- },
- \\"onBrokenLinks\\": \\"log\\",
- \\"onBrokenMarkdownLinks\\": \\"log\\",
- \\"presets\\": [
- [
- \\"@docusaurus/preset-classic\\",
- {
- \\"docs\\": {
- \\"showLastUpdateAuthor\\": true,
- \\"showLastUpdateTime\\": true,
- \\"editUrl\\": \\"https://github.com/facebook/docusaurus/edit/main/docs/\\"
- },
- \\"blog\\": {},
- \\"theme\\": {
- \\"customCss\\": \\"../complex_website/src/css/customTheme.css\\"
- },
- \\"googleAnalytics\\": {
- \\"trackingID\\": \\"UA-44373548-31\\"
- }
- }
- ]
- ],
- \\"plugins\\": [],
- \\"themeConfig\\": {
- \\"navbar\\": {
- \\"title\\": \\"Docusaurus\\",
- \\"logo\\": {
- \\"src\\": \\"img/docusaurus.svg\\"
- },
- \\"items\\": [
- {
- \\"to\\": \\"docs/installation\\",
- \\"label\\": \\"Docs\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"to\\": \\"docs/tutorial-setup\\",
- \\"label\\": \\"Tutorial\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"to\\": \\"/users\\",
- \\"label\\": \\"Users\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"href\\": \\"https://github.com/facebook/docusaurus\\",
- \\"label\\": \\"GitHub\\",
- \\"position\\": \\"left\\"
- }
- ]
- },
- \\"image\\": \\"img/docusaurus.png\\",
- \\"footer\\": {
- \\"links\\": [
- {
- \\"title\\": \\"Community\\",
- \\"items\\": [
- {
- \\"label\\": \\"Twitter\\",
- \\"to\\": \\"https://twitter.com/docusaurus\\"
- }
- ]
- }
- ],
- \\"copyright\\": \\"Copyright © 2022 Facebook Inc.\\",
- \\"logo\\": {
- \\"src\\": \\"img/docusaurus_monochrome.svg\\"
- }
- },
- \\"algolia\\": {
- \\"apiKey\\": \\"3eb9507824b8be89e7a199ecaa1a9d2c\\",
- \\"indexName\\": \\"docusaurus\\",
- \\"algoliaOptions\\": {
- \\"facetFilters\\": [
- \\"language:LANGUAGE\\",
- \\"version:VERSION\\"
- ]
- }
- }
- }
-}",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/package.json",
- "{
- \\"name\\": \\"docusaurus-1-website\\",
- \\"version\\": \\"2.0.0-alpha.58\\",
- \\"private\\": true,
- \\"scripts\\": {
- \\"start\\": \\"docusaurus start\\",
- \\"build\\": \\"docusaurus build\\",
- \\"publish-gh-pages\\": \\"docusaurus-publish\\",
- \\"examples\\": \\"docusaurus-examples\\",
- \\"write-translations\\": \\"docusaurus-write-translations\\",
- \\"docusaurus-version\\": \\"docusaurus-version\\",
- \\"rename-version\\": \\"docusaurus-rename-version\\",
- \\"crowdin-upload\\": \\"crowdin --config ../crowdin.yaml upload sources --auto-update -b master\\",
- \\"crowdin-download\\": \\"crowdin --config ../crowdin.yaml download -b master\\",
- \\"swizzle\\": \\"docusaurus swizzle\\",
- \\"deploy\\": \\"docusaurus deploy\\",
- \\"docusaurus\\": \\"docusaurus\\"
- },
- \\"dependencies\\": {
- \\"@docusaurus/core\\": \\"\\",
- \\"@docusaurus/preset-classic\\": \\"\\",
- \\"clsx\\": \\"^1.1.1\\",
- \\"react\\": \\"^17.0.2\\",
- \\"react-dom\\": \\"^17.0.2\\"
- }
-}",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/css/customTheme.css",
- ":root{
- --ifm-color-primary-lightest: #3CAD6E;
- --ifm-color-primary-lighter: #359962;
- --ifm-color-primary-light: #33925D;
- --ifm-color-primary: #2E8555;
- --ifm-color-primary-dark: #29784C;
- --ifm-color-primary-darker: #277148;
- --ifm-color-primary-darkest: #205D3B;
-}
-",
- ],
-]
-`;
-
-exports[`migration test missing versions: copy 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/blog",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/blog",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/pages/en",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/pages",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/static",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/static",
- ],
-]
-`;
-
-exports[`migration test missing versions: mkdirp 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/pages",
- ],
-]
-`;
-
-exports[`migration test missing versions: mkdirs 1`] = `Array []`;
-
-exports[`migration test missing versions: write 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/docusaurus.config.js",
- "module.exports={
- \\"title\\": \\"Docusaurus\\",
- \\"tagline\\": \\"Easy to Maintain Open Source Documentation Websites\\",
- \\"url\\": \\"https://docusaurus.io\\",
- \\"baseUrl\\": \\"/\\",
- \\"organizationName\\": \\"facebook\\",
- \\"projectName\\": \\"docusaurus\\",
- \\"noIndex\\": false,
- \\"scripts\\": [
- \\"https://buttons.github.io/buttons.js\\",
- \\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\",
- \\"/js/code-blocks-buttons.js\\"
- ],
- \\"favicon\\": \\"img/docusaurus.ico\\",
- \\"customFields\\": {
- \\"users\\": {
- \\"caption\\": \\"DevSpace\\",
- \\"image\\": \\"/img/users/devspace.svg\\",
- \\"infoLink\\": \\"https://devspace.cloud/docs/\\",
- \\"fbOpenSource\\": false,
- \\"pinned\\": false
- },
- \\"translationRecruitingLink\\": \\"https://crowdin.com/project/docusaurus\\",
- \\"facebookAppId\\": \\"199138890728411\\"
- },
- \\"onBrokenLinks\\": \\"log\\",
- \\"onBrokenMarkdownLinks\\": \\"log\\",
- \\"presets\\": [
- [
- \\"@docusaurus/preset-classic\\",
- {
- \\"docs\\": {
- \\"showLastUpdateAuthor\\": true,
- \\"showLastUpdateTime\\": true,
- \\"editUrl\\": \\"https://github.com/facebook/docusaurus/edit/main/docs/\\"
- },
- \\"blog\\": {},
- \\"theme\\": {
- \\"customCss\\": \\"../missing_version_website/src/css/customTheme.css\\"
- },
- \\"googleAnalytics\\": {
- \\"trackingID\\": \\"UA-44373548-31\\"
- }
- }
- ]
- ],
- \\"plugins\\": [],
- \\"themeConfig\\": {
- \\"navbar\\": {
- \\"title\\": \\"Docusaurus\\",
- \\"logo\\": {
- \\"src\\": \\"img/docusaurus.svg\\"
- },
- \\"items\\": [
- {
- \\"to\\": \\"docs/installation\\",
- \\"label\\": \\"Docs\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"to\\": \\"docs/tutorial-setup\\",
- \\"label\\": \\"Tutorial\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"to\\": \\"/users\\",
- \\"label\\": \\"Users\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"href\\": \\"https://github.com/facebook/docusaurus\\",
- \\"label\\": \\"GitHub\\",
- \\"position\\": \\"left\\"
- }
- ]
- },
- \\"image\\": \\"img/docusaurus.png\\",
- \\"footer\\": {
- \\"links\\": [
- {
- \\"title\\": \\"Community\\",
- \\"items\\": [
- {
- \\"label\\": \\"Twitter\\",
- \\"to\\": \\"https://twitter.com/docusaurus\\"
- }
- ]
- }
- ],
- \\"copyright\\": \\"Copyright © 2022 Facebook Inc.\\",
- \\"logo\\": {
- \\"src\\": \\"img/docusaurus_monochrome.svg\\"
- }
- },
- \\"algolia\\": {
- \\"apiKey\\": \\"3eb9507824b8be89e7a199ecaa1a9d2c\\",
- \\"indexName\\": \\"docusaurus\\",
- \\"algoliaOptions\\": {
- \\"facetFilters\\": [
- \\"language:LANGUAGE\\",
- \\"version:VERSION\\"
- ]
- }
- }
- }
-}",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/package.json",
- "{
- \\"name\\": \\"docusaurus-1-website\\",
- \\"version\\": \\"2.0.0-alpha.58\\",
- \\"private\\": true,
- \\"scripts\\": {
- \\"start\\": \\"docusaurus start\\",
- \\"build\\": \\"docusaurus build\\",
- \\"publish-gh-pages\\": \\"docusaurus-publish\\",
- \\"examples\\": \\"docusaurus-examples\\",
- \\"write-translations\\": \\"docusaurus-write-translations\\",
- \\"docusaurus-version\\": \\"docusaurus-version\\",
- \\"rename-version\\": \\"docusaurus-rename-version\\",
- \\"crowdin-upload\\": \\"crowdin --config ../crowdin.yaml upload sources --auto-update -b master\\",
- \\"crowdin-download\\": \\"crowdin --config ../crowdin.yaml download -b master\\",
- \\"swizzle\\": \\"docusaurus swizzle\\",
- \\"deploy\\": \\"docusaurus deploy\\",
- \\"docusaurus\\": \\"docusaurus\\"
- },
- \\"dependencies\\": {
- \\"@docusaurus/core\\": \\"\\",
- \\"@docusaurus/preset-classic\\": \\"\\",
- \\"clsx\\": \\"^1.1.1\\",
- \\"react\\": \\"^17.0.2\\",
- \\"react-dom\\": \\"^17.0.2\\"
- }
-}",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/css/customTheme.css",
- ":root{
- --ifm-color-primary-lightest: #3CAD6E;
- --ifm-color-primary-lighter: #359962;
- --ifm-color-primary-light: #33925D;
- --ifm-color-primary: #2E8555;
- --ifm-color-primary-dark: #29784C;
- --ifm-color-primary-darker: #277148;
- --ifm-color-primary-darkest: #205D3B;
-}
-",
- ],
-]
-`;
-
-exports[`migration test simple website: copy 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/website/pages/en",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/pages",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/website/static",
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/static",
- ],
-]
-`;
-
-exports[`migration test simple website: mkdirp 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/pages",
- ],
-]
-`;
-
-exports[`migration test simple website: mkdirs 1`] = `Array []`;
-
-exports[`migration test simple website: write 1`] = `
-Array [
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/docusaurus.config.js",
- "module.exports={
- \\"title\\": \\"Docusaurus\\",
- \\"tagline\\": \\"Easy to Maintain Open Source Documentation Websites\\",
- \\"url\\": \\"https://docusaurus.io\\",
- \\"baseUrl\\": \\"/\\",
- \\"organizationName\\": \\"facebook\\",
- \\"projectName\\": \\"docusaurus\\",
- \\"noIndex\\": false,
- \\"scripts\\": [
- \\"https://buttons.github.io/buttons.js\\",
- \\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\",
- \\"/js/code-blocks-buttons.js\\"
- ],
- \\"favicon\\": \\"img/docusaurus.ico\\",
- \\"customFields\\": {
- \\"users\\": {
- \\"caption\\": \\"DevSpace\\",
- \\"image\\": \\"/img/users/devspace.svg\\",
- \\"infoLink\\": \\"https://devspace.cloud/docs/\\",
- \\"fbOpenSource\\": false,
- \\"pinned\\": false
- },
- \\"translationRecruitingLink\\": \\"https://crowdin.com/project/docusaurus\\",
- \\"facebookAppId\\": \\"199138890728411\\"
- },
- \\"onBrokenLinks\\": \\"log\\",
- \\"onBrokenMarkdownLinks\\": \\"log\\",
- \\"presets\\": [
- [
- \\"@docusaurus/preset-classic\\",
- {
- \\"docs\\": {
- \\"showLastUpdateAuthor\\": true,
- \\"showLastUpdateTime\\": true,
- \\"editUrl\\": \\"https://github.com/facebook/docusaurus/edit/main/docs/\\",
- \\"path\\": \\"../simple_website/docs\\"
- },
- \\"blog\\": {},
- \\"theme\\": {
- \\"customCss\\": \\"../simple_website/src/css/customTheme.css\\"
- },
- \\"googleAnalytics\\": {
- \\"trackingID\\": \\"UA-44373548-31\\"
- }
- }
- ]
- ],
- \\"plugins\\": [],
- \\"themeConfig\\": {
- \\"navbar\\": {
- \\"title\\": \\"Docusaurus\\",
- \\"logo\\": {
- \\"src\\": \\"img/docusaurus.svg\\"
- },
- \\"items\\": [
- {
- \\"to\\": \\"docs/installation\\",
- \\"label\\": \\"Docs\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"to\\": \\"docs/tutorial-setup\\",
- \\"label\\": \\"Tutorial\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"to\\": \\"/users\\",
- \\"label\\": \\"Users\\",
- \\"position\\": \\"left\\"
- },
- {
- \\"href\\": \\"https://github.com/facebook/docusaurus\\",
- \\"label\\": \\"GitHub\\",
- \\"position\\": \\"left\\"
- }
- ]
- },
- \\"image\\": \\"img/docusaurus.png\\",
- \\"footer\\": {
- \\"links\\": [
- {
- \\"title\\": \\"Community\\",
- \\"items\\": [
- {
- \\"label\\": \\"Twitter\\",
- \\"to\\": \\"https://twitter.com/docusaurus\\"
- }
- ]
- }
- ],
- \\"copyright\\": \\"Copyright © 2022 Facebook Inc.\\",
- \\"logo\\": {
- \\"src\\": \\"img/docusaurus_monochrome.svg\\"
- }
- },
- \\"algolia\\": {
- \\"apiKey\\": \\"3eb9507824b8be89e7a199ecaa1a9d2c\\",
- \\"indexName\\": \\"docusaurus\\",
- \\"algoliaOptions\\": {
- \\"facetFilters\\": [
- \\"language:LANGUAGE\\",
- \\"version:VERSION\\"
- ]
- }
- }
- }
-}",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/package.json",
- "{
- \\"name\\": \\"docusaurus-1-website\\",
- \\"version\\": \\"2.0.0-alpha.58\\",
- \\"private\\": true,
- \\"scripts\\": {
- \\"start\\": \\"docusaurus start\\",
- \\"build\\": \\"docusaurus build\\",
- \\"publish-gh-pages\\": \\"docusaurus-publish\\",
- \\"examples\\": \\"docusaurus-examples\\",
- \\"write-translations\\": \\"docusaurus-write-translations\\",
- \\"docusaurus-version\\": \\"docusaurus-version\\",
- \\"rename-version\\": \\"docusaurus-rename-version\\",
- \\"crowdin-upload\\": \\"crowdin --config ../crowdin.yaml upload sources --auto-update -b master\\",
- \\"crowdin-download\\": \\"crowdin --config ../crowdin.yaml download -b master\\",
- \\"swizzle\\": \\"docusaurus swizzle\\",
- \\"deploy\\": \\"docusaurus deploy\\",
- \\"docusaurus\\": \\"docusaurus\\"
- },
- \\"dependencies\\": {
- \\"@docusaurus/core\\": \\"\\",
- \\"@docusaurus/preset-classic\\": \\"\\",
- \\"clsx\\": \\"^1.1.1\\",
- \\"react\\": \\"^17.0.2\\",
- \\"react-dom\\": \\"^17.0.2\\"
- }
-}",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/css/customTheme.css",
- ":root{
- --ifm-color-primary-lightest: #3CAD6E;
- --ifm-color-primary-lighter: #359962;
- --ifm-color-primary-light: #33925D;
- --ifm-color-primary: #2E8555;
- --ifm-color-primary-dark: #29784C;
- --ifm-color-primary-darker: #277148;
- --ifm-color-primary-darkest: #205D3B;
-}
-",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/docs/api-commands.md",
- "---
-id: commands
-title: CLI Commands
----
-## Doc
-",
- ],
- Array [
- "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/docs/api-doc-markdown.md",
- "---
-id: doc-markdown
-title: Markdown Features
----
-## Doc
-",
- ],
-]
-`;
diff --git a/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts b/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts
index 1359b2b574ab..c75d885f34e5 100644
--- a/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts
+++ b/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts
@@ -7,21 +7,22 @@
import {shouldQuotifyFrontMatter} from '../frontMatter';
-describe('frontMatter', () => {
- test('shouldQuotifyFrontMatter', () => {
- expect(shouldQuotifyFrontMatter(['id', 'value'])).toEqual(false);
+describe('shouldQuotifyFrontMatter', () => {
+ it('works', () => {
+ expect(shouldQuotifyFrontMatter(['id', 'value'])).toBe(false);
expect(
shouldQuotifyFrontMatter([
'title',
+ // cSpell:ignore sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ
"Some title front matter with allowed special chars like sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+-_?'`()[]§%€$",
]),
- ).toEqual(false);
+ ).toBe(false);
- expect(shouldQuotifyFrontMatter(['title', 'Special char :'])).toEqual(true);
+ expect(shouldQuotifyFrontMatter(['title', 'Special char :'])).toBe(true);
- expect(shouldQuotifyFrontMatter(['title', 'value!'])).toEqual(false);
- expect(shouldQuotifyFrontMatter(['title', '!value'])).toEqual(true);
+ expect(shouldQuotifyFrontMatter(['title', 'value!'])).toBe(false);
+ expect(shouldQuotifyFrontMatter(['title', '!value'])).toBe(true);
- expect(shouldQuotifyFrontMatter(['tags', '[tag1, tag2]'])).toEqual(false);
+ expect(shouldQuotifyFrontMatter(['tags', '[tag1, tag2]'])).toBe(false);
});
});
diff --git a/packages/docusaurus-migrate/src/__tests__/migration.test.ts b/packages/docusaurus-migrate/src/__tests__/index.test.ts
similarity index 76%
rename from packages/docusaurus-migrate/src/__tests__/migration.test.ts
rename to packages/docusaurus-migrate/src/__tests__/index.test.ts
index d3b6bd631554..306f7e394a12 100644
--- a/packages/docusaurus-migrate/src/__tests__/migration.test.ts
+++ b/packages/docusaurus-migrate/src/__tests__/index.test.ts
@@ -5,35 +5,36 @@
* LICENSE file in the root directory of this source tree.
*/
-import {migrateDocusaurusProject} from '../index';
+import {jest} from '@jest/globals';
import path from 'path';
import fs from 'fs-extra';
import {posixPath} from '@docusaurus/utils';
+import {migrateDocusaurusProject} from '../index';
async function testMigration(siteDir: string, newDir: string) {
- const writeMock = jest.spyOn(fs, 'outputFile').mockImplementation();
- const mkdirpMock = jest.spyOn(fs, 'mkdirp').mockImplementation();
- const mkdirsMock = jest.spyOn(fs, 'mkdirs').mockImplementation();
- const copyMock = jest.spyOn(fs, 'copy').mockImplementation();
+ const writeMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {});
+ const mkdirpMock = jest.spyOn(fs, 'mkdirp').mockImplementation(() => {});
+ const mkdirsMock = jest.spyOn(fs, 'mkdirs').mockImplementation(() => {});
+ const copyMock = jest.spyOn(fs, 'copy').mockImplementation(() => {});
await migrateDocusaurusProject(siteDir, newDir, true, true);
expect(
writeMock.mock.calls.sort((a, b) =>
- posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)),
+ posixPath(a[0]).localeCompare(posixPath(b[0])),
),
).toMatchSnapshot('write');
expect(
mkdirpMock.mock.calls.sort((a, b) =>
- posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)),
+ posixPath(a[0]).localeCompare(posixPath(b[0])),
),
).toMatchSnapshot('mkdirp');
expect(
mkdirsMock.mock.calls.sort((a, b) =>
- posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)),
+ posixPath(a[0]).localeCompare(posixPath(b[0])),
),
).toMatchSnapshot('mkdirs');
expect(
copyMock.mock.calls.sort((a, b) =>
- posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)),
+ posixPath(a[0]).localeCompare(posixPath(b[0])),
),
).toMatchSnapshot('copy');
writeMock.mockRestore();
@@ -42,20 +43,21 @@ async function testMigration(siteDir: string, newDir: string) {
copyMock.mockRestore();
}
-describe('migration test', () => {
+describe('migration CLI', () => {
const fixtureDir = path.join(__dirname, '__fixtures__');
- test('simple website', async () => {
+ it('migrates simple website', async () => {
const siteDir = path.join(fixtureDir, 'simple_website', 'website');
const newDir = path.join(fixtureDir, 'migrated_simple_site');
await testMigration(siteDir, newDir);
});
- test('complex website', async () => {
+
+ it('migrates complex website', async () => {
const siteDir = path.join(fixtureDir, 'complex_website', 'website');
const newDir = path.join(fixtureDir, 'migrated_complex_site');
await testMigration(siteDir, newDir);
});
- test('missing versions', async () => {
+ it('migrates missing versions', async () => {
const siteDir = path.join(fixtureDir, 'missing_version_website', 'website');
const newDir = path.join(fixtureDir, 'migrated_missing_version_site');
await testMigration(siteDir, newDir);
diff --git a/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts b/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts
index 2f57bb7cb207..6e77e277e042 100644
--- a/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts
+++ b/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts
@@ -10,7 +10,7 @@ import {createConfigFile} from '../index';
import type {VersionOneConfig} from '../types';
describe('create config', () => {
- test('simple test', () => {
+ it('simple test', () => {
const v1Config: VersionOneConfig = importFresh(
`${__dirname}/__fixtures__/sourceSiteConfig.js`,
);
diff --git a/packages/docusaurus-migrate/src/deps.d.ts b/packages/docusaurus-migrate/src/deps.d.ts
index ca55197f76d4..cab75c696fa2 100644
--- a/packages/docusaurus-migrate/src/deps.d.ts
+++ b/packages/docusaurus-migrate/src/deps.d.ts
@@ -5,6 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
-declare module '@mapbox/hast-util-to-jsx';
+declare module '@mapbox/hast-util-to-jsx' {
+ import type {Node} from 'unist';
-declare module 'hast-util-to-string';
+ export default function toJsx(node: Node): string;
+}
+
+declare module 'hast-util-to-string' {
+ import type {Node} from 'unist';
+
+ export default function toString(node: Node): string;
+}
diff --git a/packages/docusaurus-migrate/src/frontMatter.ts b/packages/docusaurus-migrate/src/frontMatter.ts
index 4905863cf5c0..6ec040296d74 100644
--- a/packages/docusaurus-migrate/src/frontMatter.ts
+++ b/packages/docusaurus-migrate/src/frontMatter.ts
@@ -40,12 +40,7 @@ export default function extractMetadata(content: string): Data {
lines.slice(0, -1).forEach((line) => {
const keyValue = line.split(':') as [string, ...string[]];
const key = keyValue[0].trim();
- let value = keyValue.slice(1).join(':').trim();
- try {
- value = JSON.parse(value);
- } catch (err) {
- // Ignore the error as it means it's not a JSON value.
- }
+ const value = keyValue.slice(1).join(':').trim();
metadata[key] = value;
});
return {metadata, rawContent: both.content};
@@ -69,7 +64,7 @@ export function shouldQuotifyFrontMatter([key, value]: [
// TODO this is not ideal to have to maintain such a list of allowed chars
// maybe we should quotify if gray-matter throws instead?
return !String(value).match(
- // cSpell:ignore sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ
- /^[\w .\-sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+_?'`()[\]§%€$]+$/,
+ // cSpell:ignore àáâãäåçèéêëìíîïðòóôõöùúûüýÿ
+ /^[\w .\-àáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+?'`()[\]§%€$]+$/,
);
}
diff --git a/packages/docusaurus-migrate/src/index.ts b/packages/docusaurus-migrate/src/index.ts
index 6d527e44db31..bab7fd688222 100644
--- a/packages/docusaurus-migrate/src/index.ts
+++ b/packages/docusaurus-migrate/src/index.ts
@@ -5,25 +5,23 @@
* LICENSE file in the root directory of this source tree.
*/
+import path from 'path';
import fs from 'fs-extra';
-import importFresh from 'import-fresh';
import logger from '@docusaurus/logger';
-import {Globby} from '@docusaurus/utils';
+import {Globby, DOCUSAURUS_VERSION} from '@docusaurus/utils';
+import importFresh from 'import-fresh';
import Color from 'color';
+import extractMetadata, {shouldQuotifyFrontMatter} from './frontMatter';
+import migratePage from './transform';
+import sanitizeMD from './sanitizeMD';
+
import type {
SidebarEntry,
SidebarEntries,
VersionOneConfig,
VersionTwoConfig,
} from './types';
-import extractMetadata, {shouldQuotifyFrontMatter} from './frontMatter';
-import migratePage from './transform';
-import sanitizeMD from './sanitizeMD';
-import path from 'path';
-
-const DOCUSAURUS_VERSION = (importFresh('../package.json') as {version: string})
- .version;
async function walk(dir: string): Promise {
const results: string[] = [];
@@ -31,7 +29,7 @@ async function walk(dir: string): Promise {
for (const file of list) {
const fullPath = `${dir}/${file}`;
const stat = await fs.stat(fullPath);
- if (stat && stat.isDirectory()) {
+ if (stat.isDirectory()) {
results.push(...(await walk(fullPath)));
} else {
results.push(fullPath);
@@ -67,7 +65,7 @@ ${
type MigrationContext = {
siteDir: string;
newDir: string;
- deps: Record;
+ deps: {[key: string]: string};
shouldMigrateMdFiles: boolean;
shouldMigratePages: boolean;
v1Config: VersionOneConfig;
@@ -81,9 +79,10 @@ export async function migrateDocusaurusProject(
shouldMigratePages: boolean = false,
): Promise {
async function createMigrationContext(): Promise {
- const v1Config = importFresh(`${siteDir}/siteConfig`) as VersionOneConfig;
+ const v1Config = (await import(`${siteDir}/siteConfig`))
+ .default as VersionOneConfig;
logger.info('Starting migration from v1 to v2...');
- const deps: Record = {
+ const deps = {
'@docusaurus/core': DOCUSAURUS_VERSION,
'@docusaurus/preset-classic': DOCUSAURUS_VERSION,
clsx: '^1.1.1',
@@ -206,8 +205,8 @@ export function createConfigFile({
'v1Config' | 'siteDir' | 'newDir'
>): VersionTwoConfig {
const siteConfig = v1Config;
- const customConfigFields: Record = {};
- // add fields that are unknown to v2 to customConfigFields
+ const customConfigFields: {[key: string]: unknown} = {};
+ // Add fields that are unknown to v2 to customConfigFields
Object.keys(siteConfig).forEach((key) => {
const knownFields = [
'title',
@@ -443,15 +442,15 @@ async function migrateBlogFiles(context: MigrationContext) {
async function handleVersioning(context: MigrationContext) {
const {siteDir, newDir} = context;
if (await fs.pathExists(path.join(siteDir, 'versions.json'))) {
- const loadedVersions: string[] = JSON.parse(
- await fs.readFile(path.join(siteDir, 'versions.json'), 'utf-8'),
- );
+ const loadedVersions = (await fs.readJSON(
+ path.join(siteDir, 'versions.json'),
+ )) as string[];
await fs.copyFile(
path.join(siteDir, 'versions.json'),
path.join(newDir, 'versions.json'),
);
const versions = loadedVersions.reverse();
- const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'mgi');
+ const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'gim');
await migrateVersionedSidebar(context, versions, versionRegex);
await fs.mkdirp(path.join(newDir, 'versioned_docs'));
await migrateVersionedDocs(context, versions, versionRegex);
@@ -473,7 +472,7 @@ async function migrateVersionedDocs(
versions.reverse().map(async (version, index) => {
if (index === 0) {
await fs.copy(
- path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'),
+ path.join(siteDir, '..', context.v1Config.customDocsPath ?? 'docs'),
path.join(newDir, 'versioned_docs', `version-${version}`),
);
await fs.copy(
@@ -487,7 +486,11 @@ async function migrateVersionedDocs(
path.join(newDir, 'versioned_docs', `version-${version}`),
);
await fs.copy(
- path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
+ path.join(
+ newDir,
+ 'versioned_docs',
+ `version-${versions[index - 1]!}`,
+ ),
path.join(newDir, 'versioned_docs', `version-${version}`),
);
await fs.copy(
@@ -496,7 +499,11 @@ async function migrateVersionedDocs(
);
} catch {
await fs.copy(
- path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
+ path.join(
+ newDir,
+ 'versioned_docs',
+ `version-${versions[index - 1]!}`,
+ ),
path.join(newDir, 'versioned_docs', `version-${version}`),
);
}
@@ -542,7 +549,7 @@ async function migrateVersionedSidebar(
`version-${version}-sidebars.json`,
);
try {
- sidebarEntries = JSON.parse(await fs.readFile(sidebarPath, 'utf-8'));
+ sidebarEntries = (await fs.readJSON(sidebarPath)) as SidebarEntries;
} catch {
sidebars.push({version, entries: sidebars[i - 1]!.entries});
return;
@@ -550,7 +557,9 @@ async function migrateVersionedSidebar(
const newSidebar = Object.entries(sidebarEntries).reduce(
(topLevel: SidebarEntries, value) => {
const key = value[0].replace(versionRegex, '');
- topLevel[key] = Object.entries(value[1]).reduce((acc, val) => {
+ topLevel[key] = Object.entries(value[1]).reduce<{
+ [key: string]: (string | {[key: string]: unknown})[];
+ }>((acc, val) => {
acc[val[0].replace(versionRegex, '')] = (
val[1] as SidebarEntry[]
).map((item) => {
@@ -564,7 +573,7 @@ async function migrateVersionedSidebar(
};
});
return acc;
- }, {} as Record>>);
+ }, {});
return topLevel;
},
{},
@@ -573,33 +582,32 @@ async function migrateVersionedSidebar(
}
await Promise.all(
sidebars.map(async (sidebar) => {
- const newSidebar = Object.entries(sidebar.entries).reduce(
- (acc, val) => {
- const key = `version-${sidebar.version}/${val[0]}`;
- acc[key] = Object.entries(val[1]).map((value) => ({
- type: 'category',
- label: value[0],
- items: (value[1] as SidebarEntry[]).map((sidebarItem) => {
- if (typeof sidebarItem === 'string') {
- return {
- type: 'doc',
- id: `version-${sidebar.version}/${sidebarItem}`,
- };
- }
+ const newSidebar = Object.entries(
+ sidebar.entries,
+ ).reduce((acc, val) => {
+ const key = `version-${sidebar.version}/${val[0]}`;
+ acc[key] = Object.entries(val[1]).map((value) => ({
+ type: 'category',
+ label: value[0],
+ items: (value[1] as SidebarEntry[]).map((sidebarItem) => {
+ if (typeof sidebarItem === 'string') {
return {
- type: 'category',
- label: sidebarItem.label,
- items: sidebarItem.ids.map((id) => ({
- type: 'doc',
- id: `version-${sidebar.version}/${id}`,
- })),
+ type: 'doc',
+ id: `version-${sidebar.version}/${sidebarItem}`,
};
- }),
- }));
- return acc;
- },
- {} as SidebarEntries,
- );
+ }
+ return {
+ type: 'category',
+ label: sidebarItem.label,
+ items: sidebarItem.ids.map((id) => ({
+ type: 'doc',
+ id: `version-${sidebar.version}/${id}`,
+ })),
+ };
+ }),
+ }));
+ return acc;
+ }, {});
await fs.outputFile(
path.join(
newDir,
@@ -701,12 +709,12 @@ async function migrateLatestDocs(context: MigrationContext) {
async function migratePackageFile(context: MigrationContext): Promise {
const {deps, siteDir, newDir} = context;
- const packageFile = importFresh(`${siteDir}/package.json`) as {
- scripts?: Record;
- dependencies?: Record;
- devDependencies?: Record;
+ const packageFile = importFresh<{
+ scripts?: {[key: string]: string};
+ dependencies?: {[key: string]: string};
+ devDependencies?: {[key: string]: string};
[otherKey: string]: unknown;
- };
+ }>(`${siteDir}/package.json`);
packageFile.scripts = {
...packageFile.scripts,
start: 'docusaurus start',
diff --git a/packages/docusaurus-migrate/src/sanitizeMD.ts b/packages/docusaurus-migrate/src/sanitizeMD.ts
index 55bd803aea51..61e3904cb0aa 100644
--- a/packages/docusaurus-migrate/src/sanitizeMD.ts
+++ b/packages/docusaurus-migrate/src/sanitizeMD.ts
@@ -37,7 +37,7 @@ export default function sanitizeMD(code: string): string {
const htmlTree = unified().use(parse).parse(markdownString);
visit(htmlTree, 'element', (node: Element) => {
- if (!tags[node.tagName as string]) {
+ if (!tags[node.tagName]) {
(node as Element | Text).type = 'text';
(node as Element & Partial>).value =
node.tagName + toText(node);
diff --git a/packages/docusaurus-migrate/src/types.ts b/packages/docusaurus-migrate/src/types.ts
index 1da33918953c..39b5fc2d53ae 100644
--- a/packages/docusaurus-migrate/src/types.ts
+++ b/packages/docusaurus-migrate/src/types.ts
@@ -33,11 +33,11 @@ export type SidebarEntry =
export type SidebarEntries = {
[key: string]:
- | Record
- | Array | string>;
+ | {[key: string]: unknown}
+ | ({[key: string]: unknown} | string)[];
};
-export interface VersionTwoConfig {
+export type VersionTwoConfig = {
baseUrl: string;
favicon: string;
tagline?: string;
@@ -49,7 +49,7 @@ export interface VersionTwoConfig {
githubHost?: string;
onBrokenLinks: string;
onBrokenMarkdownLinks: string;
- plugins: Array<[string, {[key: string]: unknown}]>;
+ plugins: [string, {[key: string]: unknown}][];
themes?: [];
presets: [[string, ClassicPresetEntries]];
themeConfig: {
@@ -58,23 +58,20 @@ export interface VersionTwoConfig {
logo?: {
src?: string;
};
- items: Array | null>;
+ items: ({[key: string]: unknown} | null)[];
};
image?: string;
footer: {
- links: Array<{
+ links: {
title: string;
- items: Array<{
- label: string;
- to: string;
- }>;
- }>;
+ items: {label: string; to: string}[];
+ }[];
copyright?: string;
logo: {
src?: string;
};
};
- algolia?: Record;
+ algolia?: {[key: string]: unknown};
};
customFields: {
[key: string]: unknown;
@@ -93,7 +90,7 @@ export interface VersionTwoConfig {
[key: string]: unknown;
}
)[];
-}
+};
export type VersionOneConfig = {
title?: string;
@@ -104,26 +101,26 @@ export type VersionOneConfig = {
organizationName?: string;
projectName?: string;
noIndex?: boolean;
- headerLinks?: Array<{doc: string; href: string; label: string; page: string}>;
+ headerLinks?: {doc: string; href: string; label: string; page: string}[];
headerIcon?: string;
favicon?: string;
colors?: {primaryColor: string};
copyright?: string;
editUrl?: string;
customDocsPath?: string;
- users?: Array>;
+ users?: {[key: string]: unknown}[];
disableHeaderTitle?: string;
disableTitleTagline?: string;
- separateCss?: Array>;
+ separateCss?: {[key: string]: unknown}[];
footerIcon?: string;
translationRecruitingLink?: string;
- algolia?: Record;
+ algolia?: {[key: string]: unknown};
gaTrackingId?: string;
gaGtag?: boolean;
- highlight?: Record;
- markdownPlugins?: Array<() => void>;
- scripts?: Array<{src: string; [key: string]: unknown} | string>;
- stylesheets?: Array<{href: string; [key: string]: unknown} | string>;
+ highlight?: {[key: string]: unknown};
+ markdownPlugins?: (() => void)[];
+ scripts?: ({src: string; [key: string]: unknown} | string)[];
+ stylesheets?: ({href: string; [key: string]: unknown} | string)[];
facebookAppId?: string;
facebookComments?: true;
facebookPixelId?: string;
@@ -133,5 +130,5 @@ export type VersionOneConfig = {
ogImage?: string;
cleanUrl?: boolean;
scrollToTop?: boolean;
- scrollToTopOptions?: Record;
+ scrollToTopOptions?: {[key: string]: unknown};
};
diff --git a/packages/docusaurus-migrate/tsconfig.build.json b/packages/docusaurus-migrate/tsconfig.build.json
index 358fdb6f6e60..e43e58accad2 100644
--- a/packages/docusaurus-migrate/tsconfig.build.json
+++ b/packages/docusaurus-migrate/tsconfig.build.json
@@ -1,10 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
+ "noEmit": false,
+ "composite": true,
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo",
+ "module": "commonjs",
"rootDir": "src",
"outDir": "lib"
},
- "include": ["src"]
+ "include": ["src"],
+ "exclude": ["**/__tests__/**"]
}
diff --git a/packages/docusaurus-migrate/tsconfig.json b/packages/docusaurus-migrate/tsconfig.json
index 419de04867a2..5c73d3ac4eca 100644
--- a/packages/docusaurus-migrate/tsconfig.json
+++ b/packages/docusaurus-migrate/tsconfig.json
@@ -1,11 +1,11 @@
-// For editor typechecking; includes bin
{
- "extends": "./tsconfig.build.json",
+ "extends": "../../tsconfig.json",
+ "references": [{"path": "./tsconfig.build.json"}],
"compilerOptions": {
"noEmit": true,
"module": "esnext",
- "allowJs": true,
"rootDir": "."
},
- "include": ["src", "bin"]
+ "include": ["bin"],
+ "exclude": ["**/__tests__/**"]
}
diff --git a/packages/docusaurus-module-type-aliases/package.json b/packages/docusaurus-module-type-aliases/package.json
index 44cb25e3a571..26d08a22247a 100644
--- a/packages/docusaurus-module-type-aliases/package.json
+++ b/packages/docusaurus-module-type-aliases/package.json
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/module-type-aliases",
- "version": "2.0.0-beta.17",
+ "version": "2.0.0-beta.21",
"description": "Docusaurus module type aliases.",
"types": "./src/index.d.ts",
"publishConfig": {
@@ -12,11 +12,14 @@
"directory": "packages/docusaurus-module-type-aliases"
},
"dependencies": {
- "@docusaurus/types": "2.0.0-beta.17",
+ "@docusaurus/react-loadable": "5.5.2",
+ "@docusaurus/types": "2.0.0-beta.21",
+ "@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
"@types/react-router-dom": "*",
- "react-helmet-async": "*"
+ "react-helmet-async": "*",
+ "react-loadable": "npm:@docusaurus/react-loadable@5.5.2"
},
"peerDependencies": {
"react": "*",
diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts
index 80291f92f409..c8f2c995f012 100644
--- a/packages/docusaurus-module-type-aliases/src/index.d.ts
+++ b/packages/docusaurus-module-type-aliases/src/index.d.ts
@@ -8,7 +8,7 @@
declare module '@generated/client-modules' {
import type {ClientModule} from '@docusaurus/types';
- const clientModules: readonly (ClientModule & {default: ClientModule})[];
+ const clientModules: readonly (ClientModule & {default?: ClientModule})[];
export default clientModules;
}
@@ -20,65 +20,56 @@ declare module '@generated/docusaurus.config' {
}
declare module '@generated/site-metadata' {
- import type {DocusaurusSiteMetadata} from '@docusaurus/types';
+ import type {SiteMetadata} from '@docusaurus/types';
- const siteMetadata: DocusaurusSiteMetadata;
+ const siteMetadata: SiteMetadata;
export = siteMetadata;
}
declare module '@generated/registry' {
- const registry: {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- readonly [key: string]: [() => Promise, string, string];
- };
+ import type {Registry} from '@docusaurus/types';
+
+ const registry: Registry;
export default registry;
}
declare module '@generated/routes' {
- import type {RouteConfig} from 'react-router-config';
+ import type {RouteConfig as RRRouteConfig} from 'react-router-config';
+ import type Loadable from 'react-loadable';
- export type Route = {
- readonly path: string;
- readonly component: RouteConfig['component'];
- readonly exact?: boolean;
- readonly routes?: Route[];
+ type RouteConfig = RRRouteConfig & {
+ path: string;
+ component: ReturnType;
};
- const routes: Route[];
+ const routes: RouteConfig[];
export default routes;
}
declare module '@generated/routesChunkNames' {
- import type {RouteChunksTree} from '@docusaurus/types';
+ import type {RouteChunkNames} from '@docusaurus/types';
- const routesChunkNames: Record;
+ const routesChunkNames: RouteChunkNames;
export = routesChunkNames;
}
declare module '@generated/globalData' {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const globalData: Record;
+ import type {GlobalData} from '@docusaurus/types';
+
+ const globalData: GlobalData;
export = globalData;
}
declare module '@generated/i18n' {
- const i18n: {
- defaultLocale: string;
- locales: [string, ...string[]];
- currentLocale: string;
- localeConfigs: Record<
- string,
- {
- label: string;
- direction: string;
- htmlLang: string;
- }
- >;
- };
+ import type {I18n} from '@docusaurus/types';
+
+ const i18n: I18n;
export = i18n;
}
declare module '@generated/codeTranslations' {
- const codeTranslations: Record;
+ import type {CodeTranslations} from '@docusaurus/types';
+
+ const codeTranslations: CodeTranslations;
export = codeTranslations;
}
@@ -86,10 +77,9 @@ declare module '@theme-original/*';
declare module '@theme-init/*';
declare module '@theme/Error' {
- export interface Props {
- readonly error: Error;
- readonly tryAgain: () => void;
- }
+ import type {FallbackParams} from '@docusaurus/ErrorBoundary';
+
+ export interface Props extends FallbackParams {}
export default function Error(props: Props): JSX.Element;
}
@@ -98,8 +88,6 @@ declare module '@theme/Layout' {
export interface Props {
readonly children?: ReactNode;
- readonly title?: string;
- readonly description?: string;
}
export default function Layout(props: Props): JSX.Element;
}
@@ -123,24 +111,34 @@ declare module '@theme/Root' {
export default function Root({children}: Props): JSX.Element;
}
+declare module '@theme/SiteMetadata' {
+ export default function SiteMetadata(): JSX.Element;
+}
+
declare module '@docusaurus/constants' {
export const DEFAULT_PLUGIN_ID: 'default';
}
declare module '@docusaurus/ErrorBoundary' {
import type {ReactNode} from 'react';
- import type ErrorComponent from '@theme/Error';
+
+ export type FallbackParams = {
+ readonly error: Error;
+ readonly tryAgain: () => void;
+ };
+
+ export type FallbackFunction = (params: FallbackParams) => JSX.Element;
export interface Props {
- readonly fallback?: typeof ErrorComponent;
+ readonly fallback?: FallbackFunction;
readonly children: ReactNode;
}
export default function ErrorBoundary(props: Props): JSX.Element;
}
declare module '@docusaurus/Head' {
- import type {HelmetProps} from 'react-helmet-async';
import type {ReactNode} from 'react';
+ import type {HelmetProps} from 'react-helmet-async';
export type Props = HelmetProps & {children: ReactNode};
@@ -149,8 +147,9 @@ declare module '@docusaurus/Head' {
declare module '@docusaurus/Link' {
import type {CSSProperties, ComponentProps} from 'react';
+ import type {NavLinkProps as RRNavLinkProps} from 'react-router-dom';
- type NavLinkProps = Partial;
+ type NavLinkProps = Partial;
export type Props = NavLinkProps &
ComponentProps<'a'> & {
readonly className?: string;
@@ -160,7 +159,7 @@ declare module '@docusaurus/Link' {
readonly href?: string;
readonly autoAddBaseUrl?: boolean;
- // escape hatch in case broken links check is annoying for a specific link
+ /** Escape hatch in case broken links check doesn't make sense. */
readonly 'data-noBrokenLinkCheck'?: boolean;
};
export default function Link(props: Props): JSX.Element;
@@ -174,10 +173,9 @@ declare module '@docusaurus/Interpolate' {
? Key | ExtractInterpolatePlaceholders
: never;
- export type InterpolateValues<
- Str extends string,
- Value extends ReactNode,
- > = Record, Value>;
+ export type InterpolateValues = {
+ [key in ExtractInterpolatePlaceholders]: Value;
+ };
// If all the values are plain strings, interpolate returns a simple string
export function interpolate(
@@ -250,6 +248,12 @@ declare module '@docusaurus/useDocusaurusContext' {
export default function useDocusaurusContext(): DocusaurusContext;
}
+declare module '@docusaurus/useRouteContext' {
+ import type {PluginRouteContext} from '@docusaurus/types';
+
+ export default function useRouteContext(): PluginRouteContext;
+}
+
declare module '@docusaurus/useIsBrowser' {
export default function useIsBrowser(): boolean;
}
@@ -316,17 +320,31 @@ declare module '@docusaurus/renderRoutes' {
}
declare module '@docusaurus/useGlobalData' {
- export function useAllPluginInstancesData(
+ import type {GlobalData, UseDataOptions} from '@docusaurus/types';
+
+ export function useAllPluginInstancesData(
+ pluginName: string,
+ options: {failfast: true},
+ ): GlobalData[string];
+
+ export function useAllPluginInstancesData(
+ pluginName: string,
+ options?: UseDataOptions,
+ ): GlobalData[string] | undefined;
+
+ export function usePluginData(
pluginName: string,
- ): Record;
+ pluginId: string | undefined,
+ options: {failfast: true},
+ ): NonNullable;
- export function usePluginData(
+ export function usePluginData(
pluginName: string,
pluginId?: string,
- ): T;
+ options?: UseDataOptions,
+ ): GlobalData[string][string];
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- export default function useGlobalData(): Record;
+ export default function useGlobalData(): GlobalData;
}
declare module '*.svg' {
@@ -346,3 +364,10 @@ declare module '*.css' {
const src: string;
export default src;
}
+
+interface Window {
+ docusaurus: {
+ prefetch: (url: string) => false | Promise;
+ preload: (url: string) => false | Promise;
+ };
+}
diff --git a/packages/docusaurus-plugin-client-redirects/.npmignore b/packages/docusaurus-plugin-client-redirects/.npmignore
index f2b994d4e359..03c9ae1e1b54 100644
--- a/packages/docusaurus-plugin-client-redirects/.npmignore
+++ b/packages/docusaurus-plugin-client-redirects/.npmignore
@@ -1,4 +1,3 @@
-copyUntypedFiles.mjs
-.tsbuildinfo
+.tsbuildinfo*
tsconfig*
__tests__
diff --git a/packages/docusaurus-plugin-client-redirects/package.json b/packages/docusaurus-plugin-client-redirects/package.json
index 20b2aedbcf38..57f3e138b7cb 100644
--- a/packages/docusaurus-plugin-client-redirects/package.json
+++ b/packages/docusaurus-plugin-client-redirects/package.json
@@ -1,9 +1,9 @@
{
"name": "@docusaurus/plugin-client-redirects",
- "version": "2.0.0-beta.17",
+ "version": "2.0.0-beta.21",
"description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js",
- "types": "src/plugin-client-redirects.d.ts",
+ "types": "lib/index.d.ts",
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
@@ -18,24 +18,24 @@
},
"license": "MIT",
"dependencies": {
- "@docusaurus/core": "2.0.0-beta.17",
- "@docusaurus/logger": "2.0.0-beta.17",
- "@docusaurus/utils": "2.0.0-beta.17",
- "@docusaurus/utils-common": "2.0.0-beta.17",
- "@docusaurus/utils-validation": "2.0.0-beta.17",
+ "@docusaurus/core": "2.0.0-beta.21",
+ "@docusaurus/logger": "2.0.0-beta.21",
+ "@docusaurus/utils": "2.0.0-beta.21",
+ "@docusaurus/utils-common": "2.0.0-beta.21",
+ "@docusaurus/utils-validation": "2.0.0-beta.21",
"eta": "^1.12.3",
- "fs-extra": "^10.0.1",
+ "fs-extra": "^10.1.0",
"lodash": "^4.17.21",
- "tslib": "^2.3.1"
+ "tslib": "^2.4.0"
},
"devDependencies": {
- "@docusaurus/types": "2.0.0-beta.17"
+ "@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=16.14"
}
}
diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap
index 785f7e3478dc..a9ce789f9861 100644
--- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap
+++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`collectRedirects should throw if plugin option redirects contain invalid to paths 1`] = `
+exports[`collectRedirects throw if plugin option redirects contain invalid to paths 1`] = `
"You are trying to create client-side redirections to paths that do not exist:
- /this/path/does/not/exist2
- /this/path/does/not/exist2
@@ -12,16 +12,16 @@ Valid paths you can redirect to:
"
`;
-exports[`collectRedirects should throw if redirect creator creates array of array redirect 1`] = `
+exports[`collectRedirects throws if redirect creator creates array of array redirect 1`] = `
"Some created redirects are invalid:
-- {\\"from\\":[\\"/fromPath\\"],\\"to\\":\\"/\\"} => Validation error: \\"from\\" must be a string
+- {"from":["/fromPath"],"to":"/"} => Validation error: "from" must be a string
"
`;
-exports[`collectRedirects should throw if redirect creator creates invalid redirects 1`] = `
+exports[`collectRedirects throws if redirect creator creates invalid redirects 1`] = `
"Some created redirects are invalid:
-- {\\"from\\":\\"https://google.com/\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
-- {\\"from\\":\\"//abc\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
-- {\\"from\\":\\"/def?queryString=toto\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
+- {"from":"https://google.com/","to":"/"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
+- {"from":"//abc","to":"/"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
+- {"from":"/def?queryString=toto","to":"/"} => Validation error: "from" is not a valid pathname. Pathname should start with slash and not contain any domain or query string.
"
`;
diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap
index 7fa3afa57de0..a98e1c3f7ed7 100644
--- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap
+++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap
@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`createRedirectPageContent should encode uri special chars 1`] = `
+exports[`createRedirectPageContent encodes uri special chars 1`] = `
"
-
-
-
+
+
+
@@ -103,22 +105,21 @@ function BaseUrlIssueBannerEnabled() {
/**
* We want to help the users with a bad baseUrl configuration (very common
- * error) Help message is inlined, and hidden if JS or CSS is able to load
+ * error). Help message is inlined, and hidden if JS or CSS is able to load.
+ *
+ * This component only inserts the base URL banner for the homepage, to avoid
+ * polluting every statically rendered page.
+ *
* Note: it might create false positives (ie network failures): not a big deal
- * Note: we only inline this for the homepage to avoid polluting all the site's
- * pages
+ *
* @see https://github.com/facebook/docusaurus/pull/3621
*/
-export default function BaseUrlIssueBanner(): JSX.Element | null {
+export default function MaybeBaseUrlIssueBanner(): JSX.Element | null {
const {
siteConfig: {baseUrl, baseUrlIssueBanner},
} = useDocusaurusContext();
const {pathname} = useLocation();
-
- // returns true for the homepage during SRR
const isHomePage = pathname === baseUrl;
-
const enabled = baseUrlIssueBanner && isHomePage;
-
- return enabled ? : null;
+ return enabled ? : null;
}
diff --git a/packages/docusaurus/src/client/baseUrlIssueBanner/styles.module.css b/packages/docusaurus/src/client/BaseUrlIssueBanner/styles.module.css
similarity index 100%
rename from packages/docusaurus/src/client/baseUrlIssueBanner/styles.module.css
rename to packages/docusaurus/src/client/BaseUrlIssueBanner/styles.module.css
diff --git a/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx b/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx
new file mode 100644
index 000000000000..e9cb7f8d5f5a
--- /dev/null
+++ b/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx
@@ -0,0 +1,61 @@
+/**
+ * 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 {useLayoutEffect, type ReactElement} from 'react';
+import clientModules from '@generated/client-modules';
+import type {ClientModule} from '@docusaurus/types';
+import type {Location} from 'history';
+
+export function dispatchLifecycleAction(
+ lifecycleAction: K,
+ ...args: Parameters>
+): () => void {
+ const callbacks = clientModules.map((clientModule) => {
+ const lifecycleFunction = (clientModule.default?.[lifecycleAction] ??
+ clientModule[lifecycleAction]) as
+ | ((
+ ...a: Parameters>
+ ) => (() => void) | void)
+ | undefined;
+
+ return lifecycleFunction?.(...args);
+ });
+ return () => callbacks.forEach((cb) => cb?.());
+}
+
+function scrollAfterNavigation(location: Location) {
+ const {hash} = location;
+ if (!hash) {
+ window.scrollTo(0, 0);
+ } else {
+ const id = decodeURIComponent(hash.substring(1));
+ const element = document.getElementById(id);
+ element?.scrollIntoView();
+ }
+}
+
+function ClientLifecyclesDispatcher({
+ children,
+ location,
+ previousLocation,
+}: {
+ children: ReactElement;
+ location: Location;
+ previousLocation: Location | null;
+}): JSX.Element {
+ useLayoutEffect(() => {
+ if (previousLocation !== location) {
+ if (previousLocation) {
+ scrollAfterNavigation(location);
+ }
+ dispatchLifecycleAction('onRouteDidUpdate', {previousLocation, location});
+ }
+ }, [previousLocation, location]);
+ return children;
+}
+
+export default ClientLifecyclesDispatcher;
diff --git a/packages/docusaurus/src/client/LinksCollector.tsx b/packages/docusaurus/src/client/LinksCollector.tsx
index 70009f093c9a..d0fb33b9ec03 100644
--- a/packages/docusaurus/src/client/LinksCollector.tsx
+++ b/packages/docusaurus/src/client/LinksCollector.tsx
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import React, {type ReactNode, useContext, createContext} from 'react';
+import React, {type ReactNode, useContext} from 'react';
type LinksCollector = {
collectLink: (link: string) => void;
@@ -26,16 +26,15 @@ export const createStatefulLinksCollector = (): StatefulLinksCollector => {
};
};
-const Context = createContext({
+const Context = React.createContext({
collectLink: () => {
- // noop by default for client
- // we only use the broken links checker server-side
+ // No-op for client. We only use the broken links checker server-side.
},
});
export const useLinksCollector = (): LinksCollector => useContext(Context);
-export function ProvideLinksCollector({
+export function LinksCollectorProvider({
children,
linksCollector,
}: {
diff --git a/packages/docusaurus/src/client/PendingNavigation.tsx b/packages/docusaurus/src/client/PendingNavigation.tsx
index 56e7e9c062b5..e1e38f40f817 100644
--- a/packages/docusaurus/src/client/PendingNavigation.tsx
+++ b/packages/docusaurus/src/client/PendingNavigation.tsx
@@ -6,38 +6,37 @@
*/
import React from 'react';
-import {Route, withRouter, type RouteComponentProps} from 'react-router-dom';
-import type {RouteConfig} from 'react-router-config';
-import nprogress from 'nprogress';
-
-import clientLifecyclesDispatcher from './client-lifecycles-dispatcher';
+import {Route} from 'react-router-dom';
+import ClientLifecyclesDispatcher, {
+ dispatchLifecycleAction,
+} from './ClientLifecyclesDispatcher';
+import ExecutionEnvironment from './exports/ExecutionEnvironment';
import preload from './preload';
-import normalizeLocation from './normalizeLocation';
import type {Location} from 'history';
-import './nprogress.css';
-
-nprogress.configure({showSpinner: false});
-
-interface Props extends RouteComponentProps {
- readonly routes: RouteConfig[];
- readonly delay: number;
+type Props = {
readonly location: Location;
-}
-interface State {
+ readonly children: JSX.Element;
+};
+type State = {
nextRouteHasLoaded: boolean;
-}
+};
class PendingNavigation extends React.Component {
- previousLocation: Location | null;
- progressBarTimeout: NodeJS.Timeout | null;
+ private previousLocation: Location | null;
+ private routeUpdateCleanupCb: () => void;
constructor(props: Props) {
super(props);
// previousLocation doesn't affect rendering, hence not stored in state.
this.previousLocation = null;
- this.progressBarTimeout = null;
+ this.routeUpdateCleanupCb = ExecutionEnvironment.canUseDOM
+ ? dispatchLifecycleAction('onRouteUpdate', {
+ previousLocation: null,
+ location: this.props.location,
+ })
+ : () => {};
this.state = {
nextRouteHasLoaded: true,
};
@@ -45,83 +44,48 @@ class PendingNavigation extends React.Component {
// Intercept location update and still show current route until next route
// is done loading.
- override shouldComponentUpdate(nextProps: Props, nextState: State) {
- const routeDidChange = nextProps.location !== this.props.location;
- const {routes, delay} = this.props;
-
- // If `routeDidChange` is true, means the router is trying to navigate to a
- // new route. We will preload the new route.
- if (routeDidChange) {
- const nextLocation = normalizeLocation(nextProps.location);
- this.startProgressBar(delay);
- // Save the location first.
- this.previousLocation = normalizeLocation(this.props.location);
- this.setState({
- nextRouteHasLoaded: false,
- });
-
- // Load data while the old screen remains.
- preload(routes, nextLocation.pathname)
- .then(() => {
- clientLifecyclesDispatcher.onRouteUpdate({
- previousLocation: this.previousLocation,
- location: nextLocation,
- });
- // Route has loaded, we can reset previousLocation.
- this.previousLocation = null;
- this.setState({nextRouteHasLoaded: true}, this.stopProgressBar);
- const {hash} = nextLocation;
- if (!hash) {
- window.scrollTo(0, 0);
- } else {
- const id = decodeURIComponent(hash.substring(1));
- const element = document.getElementById(id);
- if (element) {
- element.scrollIntoView();
- }
- }
- })
- .catch((e) => console.warn(e));
- return false;
+ override shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
+ if (nextProps.location === this.props.location) {
+ // `nextRouteHasLoaded` is false means there's a pending route transition.
+ // Don't update until it's done.
+ return nextState.nextRouteHasLoaded;
}
- // There's a pending route transition. Don't update until it's done.
- if (!nextState.nextRouteHasLoaded) {
- return false;
- }
-
- // Route has loaded, we can update now.
- return true;
- }
-
- private clearProgressBarTimeout() {
- if (this.progressBarTimeout) {
- clearTimeout(this.progressBarTimeout);
- this.progressBarTimeout = null;
- }
- }
-
- private startProgressBar(delay: number) {
- this.clearProgressBarTimeout();
- this.progressBarTimeout = setTimeout(() => {
- clientLifecyclesDispatcher.onRouteUpdateDelayed({
- location: normalizeLocation(this.props.location),
- });
- nprogress.start();
- }, delay);
- }
-
- private stopProgressBar() {
- this.clearProgressBarTimeout();
- nprogress.done();
+ // props.location being different means the router is trying to navigate to
+ // a new route. We will preload the new route.
+ const nextLocation = nextProps.location;
+ // Save the location first.
+ this.previousLocation = this.props.location;
+ this.setState({nextRouteHasLoaded: false});
+ this.routeUpdateCleanupCb = dispatchLifecycleAction('onRouteUpdate', {
+ previousLocation: this.previousLocation,
+ location: nextLocation,
+ });
+
+ // Load data while the old screen remains. Force preload instead of using
+ // `window.docusaurus`, because we want to avoid loading screen even when
+ // user is on saveData
+ preload(nextLocation.pathname)
+ .then(() => {
+ this.routeUpdateCleanupCb();
+ this.setState({nextRouteHasLoaded: true});
+ })
+ .catch((e: unknown) => console.warn(e));
+ return false;
}
- override render() {
+ override render(): JSX.Element {
const {children, location} = this.props;
+ // Use a controlled to trick all descendants into rendering the old
+ // location.
return (
- children} />
+
+ children} />
+
);
}
}
-export default withRouter(PendingNavigation);
+export default PendingNavigation;
diff --git a/packages/docusaurus/src/client/SiteMetadataDefaults.tsx b/packages/docusaurus/src/client/SiteMetadataDefaults.tsx
new file mode 100644
index 000000000000..e3ba631fbbe5
--- /dev/null
+++ b/packages/docusaurus/src/client/SiteMetadataDefaults.tsx
@@ -0,0 +1,29 @@
+/**
+ * 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 React from 'react';
+import Head from '@docusaurus/Head';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+
+export default function SiteMetadataDefaults(): JSX.Element {
+ const {
+ siteConfig: {favicon, title},
+ i18n: {currentLocale, localeConfigs},
+ } = useDocusaurusContext();
+ const faviconUrl = useBaseUrl(favicon);
+ const {htmlLang, direction: htmlDir} = localeConfigs[currentLocale]!;
+
+ return (
+
+
+ {title}
+
+ {favicon && }
+
+ );
+}
diff --git a/packages/docusaurus/src/client/__tests__/__mocks__/@generated/routes.ts b/packages/docusaurus/src/client/__tests__/__mocks__/@generated/routes.ts
new file mode 100644
index 000000000000..ce2206070e1d
--- /dev/null
+++ b/packages/docusaurus/src/client/__tests__/__mocks__/@generated/routes.ts
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ */
+
+export default [
+ {
+ path: '/page.html',
+ exact: true,
+ component: '',
+ },
+ {
+ path: '/docs',
+ exact: false,
+ component: '',
+ routes: [
+ {
+ path: '/docs/installation',
+ exact: true,
+ component: '',
+ },
+ ],
+ },
+ {
+ path: '*',
+ component: '',
+ },
+];
diff --git a/packages/docusaurus/src/client/__tests__/browserContext.test.tsx b/packages/docusaurus/src/client/__tests__/browserContext.test.tsx
new file mode 100644
index 000000000000..bfb8c211cf03
--- /dev/null
+++ b/packages/docusaurus/src/client/__tests__/browserContext.test.tsx
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ *
+ * @jest-environment jsdom
+ */
+
+// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573
+// eslint-disable-next-line header/header
+import React from 'react';
+import {renderHook} from '@testing-library/react-hooks/server';
+import {BrowserContextProvider} from '../browserContext';
+import useIsBrowser from '../exports/useIsBrowser';
+
+describe('BrowserContextProvider', () => {
+ const {result, hydrate} = renderHook(() => useIsBrowser(), {
+ wrapper: ({children}) => (
+ {children}
+ ),
+ });
+ it('has value false on first render', () => {
+ expect(result.current).toBe(false);
+ });
+ it('has value true on hydration', () => {
+ hydrate();
+ expect(result.current).toBe(true);
+ });
+});
diff --git a/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx b/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx
new file mode 100644
index 000000000000..d11bb52a68aa
--- /dev/null
+++ b/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx
@@ -0,0 +1,41 @@
+/**
+ * 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.
+ *
+ * @jest-environment jsdom
+ */
+
+// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573
+// eslint-disable-next-line header/header
+import React from 'react';
+import {renderHook} from '@testing-library/react-hooks/server';
+import {DocusaurusContextProvider} from '../docusaurusContext';
+import useDocusaurusContext from '../exports/useDocusaurusContext';
+
+// This test currently isn't quite useful because the @generated aliases point
+// to the empty modules. Maybe we can point that to fixtures in the future.
+describe('DocusaurusContextProvider', () => {
+ const {result, hydrate} = renderHook(() => useDocusaurusContext(), {
+ wrapper: ({children}) => (
+ {children}
+ ),
+ });
+ const value = result.current;
+ it('returns right value', () => {
+ expect(value).toMatchInlineSnapshot(`
+ {
+ "codeTranslations": {},
+ "globalData": {},
+ "i18n": {},
+ "siteConfig": {},
+ "siteMetadata": {},
+ }
+ `);
+ });
+ it('has reference-equal value on hydration', () => {
+ hydrate();
+ expect(result.current).toBe(value);
+ });
+});
diff --git a/packages/docusaurus/src/client/__tests__/flat.test.ts b/packages/docusaurus/src/client/__tests__/flat.test.ts
index 4d176c008643..358a0c547d16 100644
--- a/packages/docusaurus/src/client/__tests__/flat.test.ts
+++ b/packages/docusaurus/src/client/__tests__/flat.test.ts
@@ -8,7 +8,7 @@
import flat from '../flat';
describe('flat', () => {
- test('nested', () => {
+ it('nested', () => {
expect(
flat({
foo: {
@@ -32,7 +32,7 @@ describe('flat', () => {
});
});
- test('primitives', () => {
+ it('primitives', () => {
const primitives = {
String: 'good morning',
Number: 1234.99,
@@ -41,12 +41,12 @@ describe('flat', () => {
null: null,
undefined,
};
- Object.keys(primitives).forEach((key) => {
+ (Object.keys(primitives) as (keyof typeof primitives)[]).forEach((key) => {
const value = primitives[key];
expect(
flat({
foo: {
- bar: value,
+ bar: value as string,
},
}),
).toEqual({
@@ -55,7 +55,7 @@ describe('flat', () => {
});
});
- test('multiple keys', () => {
+ it('multiple keys', () => {
expect(
flat({
foo: {
@@ -69,7 +69,7 @@ describe('flat', () => {
});
});
- test('empty object', () => {
+ it('empty object', () => {
expect(
flat({
foo: {
@@ -81,7 +81,7 @@ describe('flat', () => {
});
});
- test('array', () => {
+ it('array', () => {
expect(
flat({
hello: [{world: {again: 'foo'}}, {lorem: 'ipsum'}],
diff --git a/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts b/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts
index bb6e92738921..fcc23ba63cd3 100644
--- a/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts
+++ b/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts
@@ -5,16 +5,26 @@
* LICENSE file in the root directory of this source tree.
*/
+import {jest} from '@jest/globals';
import normalizeLocation from '../normalizeLocation';
+import type {Location} from 'history';
describe('normalizeLocation', () => {
- test('rewrite locations with index.html', () => {
+ it('rewrites locations with index.html', () => {
+ expect(
+ normalizeLocation({
+ pathname: '/index.html',
+ } as Location),
+ ).toEqual({
+ pathname: '/',
+ });
+
expect(
normalizeLocation({
pathname: '/docs/introduction/index.html',
search: '?search=foo',
hash: '#features',
- }),
+ } as Location),
).toEqual({
pathname: '/docs/introduction',
search: '?search=foo',
@@ -26,7 +36,7 @@ describe('normalizeLocation', () => {
pathname: '/index.html',
search: '',
hash: '#features',
- }),
+ } as Location),
).toEqual({
pathname: '/',
search: '',
@@ -34,7 +44,34 @@ describe('normalizeLocation', () => {
});
});
- test('untouched pathnames', () => {
+ it('removes html extension', () => {
+ expect(
+ normalizeLocation({
+ pathname: '/docs/installation.html',
+ } as Location),
+ ).toEqual({
+ pathname: '/docs/installation',
+ });
+ expect(
+ normalizeLocation({
+ pathname: '/docs/introduction/foo.html',
+ search: '',
+ hash: '#bar',
+ } as Location),
+ ).toEqual({
+ pathname: '/docs/introduction/foo',
+ search: '',
+ hash: '#bar',
+ });
+ });
+
+ it('does not strip extension if the route location has one', () => {
+ expect(normalizeLocation({pathname: '/page.html'} as Location)).toEqual({
+ pathname: '/page.html',
+ });
+ });
+
+ it('leaves pathnames untouched', () => {
const replaceMock = jest.spyOn(String.prototype, 'replace');
expect(
@@ -42,7 +79,7 @@ describe('normalizeLocation', () => {
pathname: '/docs/introduction',
search: '',
hash: '#features',
- }),
+ } as Location),
).toEqual({
pathname: '/docs/introduction',
search: '',
@@ -55,7 +92,7 @@ describe('normalizeLocation', () => {
pathname: '/docs/introduction',
search: '',
hash: '#features',
- }),
+ } as Location),
).toEqual({
pathname: '/docs/introduction',
search: '',
@@ -63,22 +100,10 @@ describe('normalizeLocation', () => {
});
expect(replaceMock).toBeCalledTimes(1);
- expect(
- normalizeLocation({
- pathname: '/docs/introduction/foo.html',
- search: '',
- hash: '#bar',
- }),
- ).toEqual({
- pathname: '/docs/introduction/foo.html',
- search: '',
- hash: '#bar',
- });
-
expect(
normalizeLocation({
pathname: '/',
- }),
+ } as Location),
).toEqual({
pathname: '/',
});
diff --git a/packages/docusaurus/src/client/__tests__/routeContext.test.tsx b/packages/docusaurus/src/client/__tests__/routeContext.test.tsx
new file mode 100644
index 000000000000..558cf157a66f
--- /dev/null
+++ b/packages/docusaurus/src/client/__tests__/routeContext.test.tsx
@@ -0,0 +1,89 @@
+/**
+ * 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 React from 'react';
+import {renderHook} from '@testing-library/react-hooks/server';
+import {RouteContextProvider} from '../routeContext';
+import useRouteContext from '../exports/useRouteContext';
+
+describe('RouteContextProvider', () => {
+ it('creates root route context registered by plugin', () => {
+ expect(
+ renderHook(() => useRouteContext(), {
+ wrapper: ({children}) => (
+
+ {children}
+
+ ),
+ }).result.current,
+ ).toEqual({plugin: {id: 'test', name: 'test'}});
+ });
+ it('throws if there is no route context at all', () => {
+ expect(
+ () =>
+ renderHook(() => useRouteContext(), {
+ wrapper: ({children}) => (
+ {children}
+ ),
+ }).result.current,
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"Unexpected: no Docusaurus route context found"`,
+ );
+ });
+ it('throws if there is no parent context created by plugin', () => {
+ expect(
+ () =>
+ renderHook(() => useRouteContext(), {
+ wrapper: ({children}) => (
+
+ {children}
+
+ ),
+ }).result.current,
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"Unexpected: Docusaurus topmost route context has no \`plugin\` attribute"`,
+ );
+ });
+ it('merges route context created by parent', () => {
+ expect(
+ renderHook(() => useRouteContext(), {
+ wrapper: ({children}) => (
+
+
+ {children}
+
+
+ ),
+ }).result.current,
+ ).toEqual({
+ data: {some: 'data', someMore: 'data'},
+ plugin: {id: 'test', name: 'test'},
+ });
+ });
+ it('never overrides the plugin attribute', () => {
+ expect(
+ renderHook(() => useRouteContext(), {
+ wrapper: ({children}) => (
+
+
+ {children}
+
+
+ ),
+ }).result.current,
+ ).toEqual({
+ data: {some: 'data', someMore: 'data'},
+ plugin: {id: 'test', name: 'test'},
+ });
+ });
+});
diff --git a/packages/docusaurus/src/client/exports/browserContext.tsx b/packages/docusaurus/src/client/browserContext.tsx
similarity index 100%
rename from packages/docusaurus/src/client/exports/browserContext.tsx
rename to packages/docusaurus/src/client/browserContext.tsx
diff --git a/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts b/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts
deleted file mode 100644
index 57982c394313..000000000000
--- a/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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 clientModules from '@generated/client-modules';
-import type {ClientModule} from '@docusaurus/types';
-
-function dispatchLifecycleAction(
- lifecycleAction: K,
- args: Parameters>,
-) {
- clientModules.forEach((clientModule) => {
- const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ??
- clientModule[lifecycleAction]) as
- | ((...a: Parameters>) => void)
- | undefined;
-
- lifecycleFunction?.(...args);
- });
-}
-
-const clientLifecyclesDispatchers: Required = {
- onRouteUpdate(...args) {
- dispatchLifecycleAction('onRouteUpdate', args);
- },
- onRouteUpdateDelayed(...args) {
- dispatchLifecycleAction('onRouteUpdateDelayed', args);
- },
-};
-
-export default clientLifecyclesDispatchers;
diff --git a/packages/docusaurus/src/client/clientEntry.tsx b/packages/docusaurus/src/client/clientEntry.tsx
index db766906b996..8e53d92e1021 100644
--- a/packages/docusaurus/src/client/clientEntry.tsx
+++ b/packages/docusaurus/src/client/clientEntry.tsx
@@ -6,11 +6,10 @@
*/
import React from 'react';
-import {hydrate, render} from 'react-dom';
+import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import {HelmetProvider} from 'react-helmet-async';
-import routes from '@generated/routes';
import ExecutionEnvironment from './exports/ExecutionEnvironment';
import App from './App';
import preload from './preload';
@@ -30,8 +29,9 @@ if (ExecutionEnvironment.canUseDOM) {
// first-load experience.
// For development, there is no existing markup so we had to render it.
// We also preload async component to avoid first-load loading screen.
- const renderMethod = process.env.NODE_ENV === 'production' ? hydrate : render;
- preload(routes, window.location.pathname).then(() => {
+ const renderMethod =
+ process.env.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.render;
+ preload(window.location.pathname).then(() => {
renderMethod(
diff --git a/packages/docusaurus/src/client/docusaurus.ts b/packages/docusaurus/src/client/docusaurus.ts
index 0c024c0fc43b..da1236cacede 100644
--- a/packages/docusaurus/src/client/docusaurus.ts
+++ b/packages/docusaurus/src/client/docusaurus.ts
@@ -12,50 +12,45 @@ import prefetchHelper from './prefetch';
import preloadHelper from './preload';
import flat from './flat';
-const fetched: Record = {};
-const loaded: Record = {};
+const fetched = new Set();
+const loaded = new Set();
declare global {
// eslint-disable-next-line camelcase, no-underscore-dangle
const __webpack_require__: {gca: (name: string) => string};
interface Navigator {
- connection: {effectiveType: string; saveData: boolean};
+ connection?: {effectiveType: string; saveData: boolean};
}
}
// If user is on slow or constrained connection.
const isSlowConnection = () =>
- navigator.connection?.effectiveType.includes('2g') &&
+ navigator.connection?.effectiveType.includes('2g') ||
navigator.connection?.saveData;
const canPrefetch = (routePath: string) =>
- !isSlowConnection() && !loaded[routePath] && !fetched[routePath];
+ !isSlowConnection() && !loaded.has(routePath) && !fetched.has(routePath);
const canPreload = (routePath: string) =>
- !isSlowConnection() && !loaded[routePath];
-
-// Remove the last part containing the route hash
-// input: /blog/2018/12/14/Happy-First-Birthday-Slash-fe9
-// output: /blog/2018/12/14/Happy-First-Birthday-Slash
-const removeRouteNameHash = (str: string) => str.replace(/-[^-]+$/, '');
+ !isSlowConnection() && !loaded.has(routePath);
const getChunkNamesToLoad = (path: string): string[] =>
Object.entries(routesChunkNames)
.filter(
- ([routeNameWithHash]) => removeRouteNameHash(routeNameWithHash) === path,
+ // Remove the last part containing the route hash
+ // input: /blog/2018/12/14/Happy-First-Birthday-Slash-fe9
+ // output: /blog/2018/12/14/Happy-First-Birthday-Slash
+ ([routeNameWithHash]) =>
+ routeNameWithHash.replace(/-[^-]+$/, '') === path,
)
- .flatMap(([, routeChunks]) =>
- // flat() is useful for nested chunk names, it's not like array.flat()
- Object.values(flat(routeChunks)),
- );
+ .flatMap(([, routeChunks]) => Object.values(flat(routeChunks)));
const docusaurus = {
- prefetch: (routePath: string): boolean => {
+ prefetch(routePath: string): false | Promise {
if (!canPrefetch(routePath)) {
return false;
}
- // Prevent future duplicate prefetch of routePath.
- fetched[routePath] = true;
+ fetched.add(routePath);
// Find all webpack chunk names needed.
const matches = matchRoutes(routes, routePath);
@@ -65,33 +60,32 @@ const docusaurus = {
);
// Prefetch all webpack chunk assets file needed.
- chunkNamesNeeded.forEach((chunkName) => {
- // "__webpack_require__.gca" is a custom function provided by
- // ChunkAssetPlugin. Pass it the chunkName or chunkId you want to load and
- // it will return the URL for that chunk.
- // eslint-disable-next-line camelcase
- const chunkAsset = __webpack_require__.gca(chunkName);
-
- // In some cases, webpack might decide to optimize further & hence the
- // chunk assets are merged to another chunk/previous chunk.
- // Hence, we can safely filter it out/don't need to load it.
- if (chunkAsset && !/undefined/.test(chunkAsset)) {
- prefetchHelper(chunkAsset);
- }
- });
-
- return true;
+ return Promise.all(
+ chunkNamesNeeded.map((chunkName) => {
+ // "__webpack_require__.gca" is injected by ChunkAssetPlugin. Pass it
+ // the name of the chunk you want to load and it will return its URL.
+ // eslint-disable-next-line camelcase
+ const chunkAsset = __webpack_require__.gca(chunkName);
+
+ // In some cases, webpack might decide to optimize further, leading to
+ // the chunk assets being merged to another chunk. In this case, we can
+ // safely filter it out and don't need to load it.
+ if (chunkAsset && !chunkAsset.includes('undefined')) {
+ return prefetchHelper(chunkAsset);
+ }
+ return Promise.resolve();
+ }),
+ );
},
- preload: (routePath: string): boolean => {
+ preload(routePath: string): false | Promise {
if (!canPreload(routePath)) {
return false;
}
-
- loaded[routePath] = true;
- preloadHelper(routes, routePath);
- return true;
+ loaded.add(routePath);
+ return preloadHelper(routePath);
},
};
-export default docusaurus;
+// This object is directly mounted onto window, better freeze it
+export default Object.freeze(docusaurus);
diff --git a/packages/docusaurus/src/client/exports/docusaurusContext.tsx b/packages/docusaurus/src/client/docusaurusContext.tsx
similarity index 99%
rename from packages/docusaurus/src/client/exports/docusaurusContext.tsx
rename to packages/docusaurus/src/client/docusaurusContext.tsx
index 5023b1eced70..5bed42737f37 100644
--- a/packages/docusaurus/src/client/exports/docusaurusContext.tsx
+++ b/packages/docusaurus/src/client/docusaurusContext.tsx
@@ -6,13 +6,12 @@
*/
import React, {type ReactNode} from 'react';
-import type {DocusaurusContext} from '@docusaurus/types';
-
import siteConfig from '@generated/docusaurus.config';
import globalData from '@generated/globalData';
import i18n from '@generated/i18n';
import codeTranslations from '@generated/codeTranslations';
import siteMetadata from '@generated/site-metadata';
+import type {DocusaurusContext} from '@docusaurus/types';
// Static value on purpose: don't make it dynamic!
// Using context is still useful for testability reasons.
diff --git a/packages/docusaurus/src/client/exports/BrowserOnly.tsx b/packages/docusaurus/src/client/exports/BrowserOnly.tsx
index 025049b782de..16e64ef06d01 100644
--- a/packages/docusaurus/src/client/exports/BrowserOnly.tsx
+++ b/packages/docusaurus/src/client/exports/BrowserOnly.tsx
@@ -7,16 +7,14 @@
import React, {isValidElement} from 'react';
import useIsBrowser from '@docusaurus/useIsBrowser';
+import type {Props} from '@docusaurus/BrowserOnly';
// Similar comp to the one described here:
// https://www.joshwcomeau.com/react/the-perils-of-rehydration/#abstractions
export default function BrowserOnly({
children,
fallback,
-}: {
- children: () => JSX.Element;
- fallback?: JSX.Element;
-}): JSX.Element | null {
+}: Props): JSX.Element | null {
const isBrowser = useIsBrowser();
if (isBrowser) {
@@ -27,7 +25,7 @@ export default function BrowserOnly({
throw new Error(`Docusaurus error: The children of must be a "render function", e.g.