diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/mdx-require-blog-post.mdx b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/mdx-require-blog-post.mdx new file mode 100644 index 0000000000..5ff703b4cb --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/mdx-require-blog-post.mdx @@ -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'; + + + + + + diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/static/img/docusaurus.png b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/static/img/docusaurus.png new file mode 100644 index 0000000000..f458149e3c Binary files /dev/null and b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/static/img/docusaurus.png differ diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap similarity index 90% rename from packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap rename to packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap index 8914514ceb..8e600fd89a 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap @@ -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`] = ` " https://docusaurus.io/myBaseUrl/blog Hello Blog - 2021-03-05T00:00:00.000Z + 2021-03-06T00:00:00.000Z https://github.com/jpmonette/feed Hello Blog https://docusaurus.io/myBaseUrl/image/favicon.ico Copyright + + <![CDATA[MDX Blog Sample with require calls]]> + MDX Blog Sample with require calls + + 2021-03-06T00:00:00.000Z + + <![CDATA[Full Blog Sample]]> Full Blog Sample @@ -81,8 +86,6 @@ exports[`blogFeed atom shows feed item for each post 1`] = ` " `; -exports[`blogFeed rss should not show feed without posts 1`] = `null`; - exports[`blogFeed rss shows feed item for each post 1`] = ` " @@ -90,10 +93,17 @@ exports[`blogFeed rss shows feed item for each post 1`] = ` Hello Blog https://docusaurus.io/myBaseUrl/blog Hello Blog - Fri, 05 Mar 2021 00:00:00 GMT + Sat, 06 Mar 2021 00:00:00 GMT https://validator.w3.org/feed/docs/rss2.html https://github.com/jpmonette/feed Copyright + + <![CDATA[MDX Blog Sample with require calls]]> + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + MDX Blog Sample with require calls + Sat, 06 Mar 2021 00:00:00 GMT + + <![CDATA[Full Blog Sample]]> https://docusaurus.io/myBaseUrl/blog/mdx-blog-post diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/generateBlogFeed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts similarity index 82% rename from packages/docusaurus-plugin-content-blog/src/__tests__/generateBlogFeed.test.ts rename to packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index c9cc678e7b..d2261547a7 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/generateBlogFeed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -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 { + 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, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 238b4f04fe..0aa725b0bf 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -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', diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 9465103030..011eb4fa6a 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -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 { - 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, diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts new file mode 100644 index 0000000000..7d0ce97add --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/feed.ts @@ -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 { + 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 { + 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`), + }); + }), + ); +} diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 3cb75b946a..b7f2cf7ebd 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -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( - 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}.`); - } - }), - ); + await createBlogFeedFiles({ + blogPosts, + options, + outDir, + siteConfig, + }); }, injectHtmlTags({content}) { diff --git a/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx b/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx new file mode 100644 index 0000000000..29c0cd60fa --- /dev/null +++ b/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx @@ -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 + + + +Test MDX with require calls + +import useBaseUrl from '@docusaurus/useBaseUrl'; + + + + + +