mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-20 20:46:58 +02:00
fix(content-blog): temporarily swallow feed mdxToHtml errors + feed refactor (#5753)
This commit is contained in:
parent
fd41239f4f
commit
29d13351a4
9 changed files with 234 additions and 109 deletions
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: MDX Blog Sample with require calls
|
||||
date: 2021-03-06
|
||||
---
|
||||
|
||||
Test MDX with require calls
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
<img src={useBaseUrl('/img/docusaurus.png')} />
|
||||
|
||||
<img src={require('../static/img/docusaurus.png').default} />
|
||||
|
||||
<img src={require('@site/static/img/docusaurus.png').default} />
|
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
|
@ -1,18 +1,23 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`blogFeed atom should not show feed without posts 1`] = `null`;
|
||||
|
||||
exports[`blogFeed atom shows feed item for each post 1`] = `
|
||||
"<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>
|
||||
<feed xmlns=\\"http://www.w3.org/2005/Atom\\">
|
||||
<id>https://docusaurus.io/myBaseUrl/blog</id>
|
||||
<title>Hello Blog</title>
|
||||
<updated>2021-03-05T00:00:00.000Z</updated>
|
||||
<updated>2021-03-06T00:00:00.000Z</updated>
|
||||
<generator>https://github.com/jpmonette/feed</generator>
|
||||
<link rel=\\"alternate\\" href=\\"https://docusaurus.io/myBaseUrl/blog\\"/>
|
||||
<subtitle>Hello Blog</subtitle>
|
||||
<icon>https://docusaurus.io/myBaseUrl/image/favicon.ico</icon>
|
||||
<rights>Copyright</rights>
|
||||
<entry>
|
||||
<title type=\\"html\\"><![CDATA[MDX Blog Sample with require calls]]></title>
|
||||
<id>MDX Blog Sample with require calls</id>
|
||||
<link href=\\"https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post\\"/>
|
||||
<updated>2021-03-06T00:00:00.000Z</updated>
|
||||
<summary type=\\"html\\"><![CDATA[Test MDX with require calls]]></summary>
|
||||
</entry>
|
||||
<entry>
|
||||
<title type=\\"html\\"><![CDATA[Full Blog Sample]]></title>
|
||||
<id>Full Blog Sample</id>
|
||||
|
@ -81,8 +86,6 @@ exports[`blogFeed atom shows feed item for each post 1`] = `
|
|||
</feed>"
|
||||
`;
|
||||
|
||||
exports[`blogFeed rss should not show feed without posts 1`] = `null`;
|
||||
|
||||
exports[`blogFeed rss shows feed item for each post 1`] = `
|
||||
"<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>
|
||||
<rss version=\\"2.0\\" xmlns:dc=\\"http://purl.org/dc/elements/1.1/\\" xmlns:content=\\"http://purl.org/rss/1.0/modules/content/\\">
|
||||
|
@ -90,10 +93,17 @@ exports[`blogFeed rss shows feed item for each post 1`] = `
|
|||
<title>Hello Blog</title>
|
||||
<link>https://docusaurus.io/myBaseUrl/blog</link>
|
||||
<description>Hello Blog</description>
|
||||
<lastBuildDate>Fri, 05 Mar 2021 00:00:00 GMT</lastBuildDate>
|
||||
<lastBuildDate>Sat, 06 Mar 2021 00:00:00 GMT</lastBuildDate>
|
||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||
<generator>https://github.com/jpmonette/feed</generator>
|
||||
<copyright>Copyright</copyright>
|
||||
<item>
|
||||
<title><![CDATA[MDX Blog Sample with require calls]]></title>
|
||||
<link>https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post</link>
|
||||
<guid>MDX Blog Sample with require calls</guid>
|
||||
<pubDate>Sat, 06 Mar 2021 00:00:00 GMT</pubDate>
|
||||
<description><![CDATA[Test MDX with require calls]]></description>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Full Blog Sample]]></title>
|
||||
<link>https://docusaurus.io/myBaseUrl/blog/mdx-blog-post</link>
|
|
@ -6,10 +6,12 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import {generateBlogFeed} from '../blogUtils';
|
||||
import {generateBlogFeed} from '../feed';
|
||||
import {LoadContext, I18n} from '@docusaurus/types';
|
||||
import {PluginOptions, BlogContentPaths} from '../types';
|
||||
import {DEFAULT_OPTIONS} from '../pluginOptionSchema';
|
||||
import {generateBlogPosts} from '../blogUtils';
|
||||
import {Feed} from 'feed';
|
||||
|
||||
const DefaultI18N: I18n = {
|
||||
currentLocale: 'en',
|
||||
|
@ -30,6 +32,23 @@ function getBlogContentPaths(siteDir: string): BlogContentPaths {
|
|||
};
|
||||
}
|
||||
|
||||
async function testGenerateFeeds(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Promise<Feed | null> {
|
||||
const blogPosts = await generateBlogPosts(
|
||||
getBlogContentPaths(context.siteDir),
|
||||
context,
|
||||
options,
|
||||
);
|
||||
|
||||
return generateBlogFeed({
|
||||
blogPosts,
|
||||
options,
|
||||
siteConfig: context.siteConfig,
|
||||
});
|
||||
}
|
||||
|
||||
describe('blogFeed', () => {
|
||||
(['atom', 'rss'] as const).forEach((feedType) => {
|
||||
describe(`${feedType}`, () => {
|
||||
|
@ -42,8 +61,7 @@ describe('blogFeed', () => {
|
|||
favicon: 'image/favicon.ico',
|
||||
};
|
||||
|
||||
const feed = await generateBlogFeed(
|
||||
getBlogContentPaths(siteDir),
|
||||
const feed = await testGenerateFeeds(
|
||||
{
|
||||
siteDir,
|
||||
siteConfig,
|
||||
|
@ -61,9 +79,8 @@ describe('blogFeed', () => {
|
|||
},
|
||||
} as PluginOptions,
|
||||
);
|
||||
const feedContent =
|
||||
feed && (feedType === 'rss' ? feed.rss2() : feed.atom1());
|
||||
expect(feedContent).toMatchSnapshot();
|
||||
|
||||
expect(feed).toEqual(null);
|
||||
});
|
||||
|
||||
test('shows feed item for each post', async () => {
|
||||
|
@ -76,8 +93,7 @@ describe('blogFeed', () => {
|
|||
favicon: 'image/favicon.ico',
|
||||
};
|
||||
|
||||
const feed = await generateBlogFeed(
|
||||
getBlogContentPaths(siteDir),
|
||||
const feed = await testGenerateFeeds(
|
||||
{
|
||||
siteDir,
|
||||
siteConfig,
|
|
@ -246,26 +246,29 @@ describe('loadBlog', () => {
|
|||
test('simple website blog dates localized', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||
const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr'));
|
||||
expect(blogPostsFrench).toHaveLength(7);
|
||||
expect(blogPostsFrench).toHaveLength(8);
|
||||
expect(blogPostsFrench[0].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"5 mars 2021"`,
|
||||
`"6 mars 2021"`,
|
||||
);
|
||||
expect(blogPostsFrench[1].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"16 août 2020"`,
|
||||
`"5 mars 2021"`,
|
||||
);
|
||||
expect(blogPostsFrench[2].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"15 août 2020"`,
|
||||
`"16 août 2020"`,
|
||||
);
|
||||
expect(blogPostsFrench[3].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"27 février 2020"`,
|
||||
`"15 août 2020"`,
|
||||
);
|
||||
expect(blogPostsFrench[4].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"2 janvier 2019"`,
|
||||
`"27 février 2020"`,
|
||||
);
|
||||
expect(blogPostsFrench[5].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"1 janvier 2019"`,
|
||||
`"2 janvier 2019"`,
|
||||
);
|
||||
expect(blogPostsFrench[6].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"1 janvier 2019"`,
|
||||
);
|
||||
expect(blogPostsFrench[7].metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"14 décembre 2018"`,
|
||||
);
|
||||
});
|
||||
|
@ -295,7 +298,8 @@ describe('loadBlog', () => {
|
|||
expect(blogPost.metadata.editUrl).toEqual(hardcodedEditUrl);
|
||||
});
|
||||
|
||||
expect(editUrlFunction).toHaveBeenCalledTimes(7);
|
||||
expect(editUrlFunction).toHaveBeenCalledTimes(8);
|
||||
|
||||
expect(editUrlFunction).toHaveBeenCalledWith({
|
||||
blogDirPath: 'blog',
|
||||
blogPath: 'date-matter.md',
|
||||
|
@ -314,6 +318,12 @@ describe('loadBlog', () => {
|
|||
permalink: '/blog/mdx-blog-post',
|
||||
locale: 'en',
|
||||
});
|
||||
expect(editUrlFunction).toHaveBeenCalledWith({
|
||||
blogDirPath: 'blog',
|
||||
blogPath: 'mdx-require-blog-post.mdx',
|
||||
permalink: '/blog/mdx-require-blog-post',
|
||||
locale: 'en',
|
||||
});
|
||||
expect(editUrlFunction).toHaveBeenCalledWith({
|
||||
blogDirPath: 'blog',
|
||||
blogPath: 'complex-slug.md',
|
||||
|
|
|
@ -9,7 +9,6 @@ import fs from 'fs-extra';
|
|||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
import readingTime from 'reading-time';
|
||||
import {Feed, Author as FeedAuthor} from 'feed';
|
||||
import {compact, keyBy, mapValues} from 'lodash';
|
||||
import {
|
||||
PluginOptions,
|
||||
|
@ -17,7 +16,6 @@ import {
|
|||
BlogContentPaths,
|
||||
BlogMarkdownLoaderOptions,
|
||||
BlogTags,
|
||||
Author,
|
||||
} from './types';
|
||||
import {
|
||||
parseMarkdownFile,
|
||||
|
@ -26,7 +24,6 @@ import {
|
|||
getEditUrl,
|
||||
getFolderContainingFile,
|
||||
posixPath,
|
||||
mdxToHtml,
|
||||
replaceMarkdownLinks,
|
||||
Globby,
|
||||
normalizeFrontMatterTags,
|
||||
|
@ -104,66 +101,6 @@ function formatBlogPostDate(locale: string, date: Date): string {
|
|||
}
|
||||
}
|
||||
|
||||
export async function generateBlogFeed(
|
||||
contentPaths: BlogContentPaths,
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Promise<Feed | null> {
|
||||
if (!options.feedOptions) {
|
||||
throw new Error(
|
||||
'Invalid options: "feedOptions" is not expected to be null.',
|
||||
);
|
||||
}
|
||||
const {siteConfig} = context;
|
||||
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
||||
if (!blogPosts.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {feedOptions, routeBasePath} = options;
|
||||
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
|
||||
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
|
||||
|
||||
const updated =
|
||||
(blogPosts[0] && blogPosts[0].metadata.date) ||
|
||||
new Date('2015-10-25T16:29:00.000-07:00');
|
||||
|
||||
const feed = new Feed({
|
||||
id: blogBaseUrl,
|
||||
title: feedOptions.title || `${title} Blog`,
|
||||
updated,
|
||||
language: feedOptions.language,
|
||||
link: blogBaseUrl,
|
||||
description: feedOptions.description || `${siteConfig.title} Blog`,
|
||||
favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
|
||||
copyright: feedOptions.copyright,
|
||||
});
|
||||
|
||||
function toFeedAuthor(author: Author): FeedAuthor {
|
||||
// TODO ask author emails?
|
||||
// RSS feed requires email to render authors
|
||||
return {name: author.name, link: author.url};
|
||||
}
|
||||
|
||||
blogPosts.forEach((post) => {
|
||||
const {
|
||||
id,
|
||||
metadata: {title: metadataTitle, permalink, date, description, authors},
|
||||
} = post;
|
||||
feed.addItem({
|
||||
title: metadataTitle,
|
||||
id,
|
||||
link: normalizeUrl([siteUrl, permalink]),
|
||||
date,
|
||||
description,
|
||||
content: mdxToHtml(post.content),
|
||||
author: authors.map(toFeedAuthor),
|
||||
});
|
||||
});
|
||||
|
||||
return feed;
|
||||
}
|
||||
|
||||
async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
||||
const result = await parseMarkdownFile(blogSourceAbsolute, {
|
||||
removeContentTitle: true,
|
||||
|
|
129
packages/docusaurus-plugin-content-blog/src/feed.ts
Normal file
129
packages/docusaurus-plugin-content-blog/src/feed.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* 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 {Feed, Author as FeedAuthor} from 'feed';
|
||||
import {PluginOptions, Author, BlogPost, FeedType} from './types';
|
||||
import {normalizeUrl, mdxToHtml} from '@docusaurus/utils';
|
||||
import {DocusaurusConfig} from '@docusaurus/types';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
// TODO this is temporary until we handle mdxToHtml better
|
||||
// It's hard to convert reliably JSX/require calls to an html feed content
|
||||
// See https://github.com/facebook/docusaurus/issues/5664
|
||||
function mdxToFeedContent(mdxContent: string): string | undefined {
|
||||
try {
|
||||
return mdxToHtml(mdxContent);
|
||||
} catch (e) {
|
||||
// TODO will we need a plugin option to configure how to handle such an error
|
||||
// Swallow the error on purpose for now, until we understand better the problem space
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateBlogFeed({
|
||||
blogPosts,
|
||||
options,
|
||||
siteConfig,
|
||||
}: {
|
||||
blogPosts: BlogPost[];
|
||||
options: PluginOptions;
|
||||
siteConfig: DocusaurusConfig;
|
||||
}): Promise<Feed | null> {
|
||||
if (!blogPosts.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {feedOptions, routeBasePath} = options;
|
||||
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
|
||||
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
|
||||
|
||||
const updated =
|
||||
(blogPosts[0] && blogPosts[0].metadata.date) ||
|
||||
new Date('2015-10-25T16:29:00.000-07:00'); // weird legacy magic date
|
||||
|
||||
const feed = new Feed({
|
||||
id: blogBaseUrl,
|
||||
title: feedOptions.title || `${title} Blog`,
|
||||
updated,
|
||||
language: feedOptions.language,
|
||||
link: blogBaseUrl,
|
||||
description: feedOptions.description || `${siteConfig.title} Blog`,
|
||||
favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
|
||||
copyright: feedOptions.copyright,
|
||||
});
|
||||
|
||||
function toFeedAuthor(author: Author): FeedAuthor {
|
||||
// TODO ask author emails?
|
||||
// RSS feed requires email to render authors
|
||||
return {name: author.name, link: author.url};
|
||||
}
|
||||
|
||||
blogPosts.forEach((post) => {
|
||||
const {
|
||||
id,
|
||||
metadata: {title: metadataTitle, permalink, date, description, authors},
|
||||
} = post;
|
||||
feed.addItem({
|
||||
title: metadataTitle,
|
||||
id,
|
||||
link: normalizeUrl([siteUrl, permalink]),
|
||||
date,
|
||||
description,
|
||||
content: mdxToFeedContent(post.content),
|
||||
author: authors.map(toFeedAuthor),
|
||||
});
|
||||
});
|
||||
|
||||
return feed;
|
||||
}
|
||||
|
||||
async function createBlogFeedFile({
|
||||
feed,
|
||||
feedType,
|
||||
filePath,
|
||||
}: {
|
||||
feed: Feed;
|
||||
feedType: FeedType;
|
||||
filePath: string;
|
||||
}) {
|
||||
const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
|
||||
try {
|
||||
await fs.outputFile(filePath, feedContent);
|
||||
} catch (err) {
|
||||
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createBlogFeedFiles({
|
||||
blogPosts,
|
||||
options,
|
||||
siteConfig,
|
||||
outDir,
|
||||
}: {
|
||||
blogPosts: BlogPost[];
|
||||
options: PluginOptions;
|
||||
siteConfig: DocusaurusConfig;
|
||||
outDir: string;
|
||||
}): Promise<void> {
|
||||
const feed = await generateBlogFeed({blogPosts, options, siteConfig});
|
||||
|
||||
const feedTypes = options.feedOptions.type;
|
||||
if (!feed || !feedTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
feedTypes.map(async function (feedType) {
|
||||
await createBlogFeedFile({
|
||||
feed,
|
||||
feedType,
|
||||
filePath: path.join(outDir, options.routeBasePath, `${feedType}.xml`),
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import {
|
||||
|
@ -49,13 +48,13 @@ import {
|
|||
} from '@docusaurus/types';
|
||||
import {Configuration} from 'webpack';
|
||||
import {
|
||||
generateBlogFeed,
|
||||
generateBlogPosts,
|
||||
getContentPathList,
|
||||
getSourceToPermalink,
|
||||
getBlogTags,
|
||||
} from './blogUtils';
|
||||
import {BlogPostFrontMatter} from './blogFrontMatter';
|
||||
import {createBlogFeedFiles} from './feed';
|
||||
|
||||
export default function pluginContentBlog(
|
||||
context: LoadContext,
|
||||
|
@ -69,10 +68,11 @@ export default function pluginContentBlog(
|
|||
|
||||
const {
|
||||
siteDir,
|
||||
siteConfig: {onBrokenMarkdownLinks, baseUrl},
|
||||
siteConfig,
|
||||
generatedFilesDir,
|
||||
i18n: {currentLocale},
|
||||
} = context;
|
||||
const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
|
||||
|
||||
const contentPaths: BlogContentPaths = {
|
||||
contentPath: path.resolve(siteDir, options.path),
|
||||
|
@ -519,29 +519,18 @@ export default function pluginContentBlog(
|
|||
return;
|
||||
}
|
||||
|
||||
const feed = await generateBlogFeed(contentPaths, context, options);
|
||||
|
||||
if (!feed) {
|
||||
// TODO: we shouldn't need to re-read the posts here!
|
||||
// postBuild should receive loadedContent
|
||||
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
||||
if (blogPosts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const feedTypes = options.feedOptions.type;
|
||||
|
||||
await Promise.all(
|
||||
feedTypes.map(async (feedType) => {
|
||||
const feedPath = path.join(
|
||||
await createBlogFeedFiles({
|
||||
blogPosts,
|
||||
options,
|
||||
outDir,
|
||||
options.routeBasePath,
|
||||
`${feedType}.xml`,
|
||||
);
|
||||
const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
|
||||
try {
|
||||
await fs.outputFile(feedPath, feedContent);
|
||||
} catch (err) {
|
||||
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
siteConfig,
|
||||
});
|
||||
},
|
||||
|
||||
injectHtmlTags({content}) {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: Blog post MDX require Feed tests
|
||||
authors:
|
||||
- slorber
|
||||
tags: [blog, docusaurus, long-long, long-long-long, long-long-long-long]
|
||||
---
|
||||
|
||||
Some MDX tests, mostly to test how the RSS feed render those
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
Test MDX with require calls
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
<img src={useBaseUrl('/img/docusaurus.png')} />
|
||||
|
||||
<img src={require('../../static/img/docusaurus.png').default} />
|
||||
|
||||
<img src={require('@site/static/img/docusaurus.png').default} />
|
Loading…
Add table
Add a link
Reference in a new issue