diff --git a/.changeset/spicy-swans-check.md b/.changeset/spicy-swans-check.md new file mode 100644 index 0000000000..a233d175db --- /dev/null +++ b/.changeset/spicy-swans-check.md @@ -0,0 +1,7 @@ +--- +'nextra': patch +'nextra-theme-blog': patch +'nextra-theme-docs': patch +--- + +feat(nextra/docs/blog): allow import `.md`/`.mdx` as well diff --git a/examples/swr-site/components/external.mdx b/examples/swr-site/components/external.mdx new file mode 100644 index 0000000000..df768c14ff --- /dev/null +++ b/examples/swr-site/components/external.mdx @@ -0,0 +1,5 @@ +import Callout from 'nextra-theme-docs/callout' + + + This `` comes from `external.mdx` + diff --git a/examples/swr-site/pages/docs/advanced/markdown-import.en-US.mdx b/examples/swr-site/pages/docs/advanced/markdown-import.en-US.mdx new file mode 100644 index 0000000000..13cc753cd2 --- /dev/null +++ b/examples/swr-site/pages/docs/advanced/markdown-import.en-US.mdx @@ -0,0 +1,26 @@ +import External from '../../../components/external.mdx' +import IndexPage from '../../index.en-US.mdx' + +# Markdown import + +## Import of markdown file + +```mdx +import External from '../../../components/external.mdx' + + +``` + + + +## Import of another page + +You can also import complete pages: + +```mdx filename="not-index.md" +import IndexPage from '../../index.en-US.mdx' + + +``` + + diff --git a/examples/swr-site/pages/docs/advanced/meta.en-US.json b/examples/swr-site/pages/docs/advanced/meta.en-US.json index f46023fe70..d303499f98 100644 --- a/examples/swr-site/pages/docs/advanced/meta.en-US.json +++ b/examples/swr-site/pages/docs/advanced/meta.en-US.json @@ -11,6 +11,7 @@ "cache": "Cache", "performance": "Performance", "react-native": "React Native", + "markdown-import": "Markdown import", "more": "More: A Super Super Super Super Long Directory", "file-name.with.DOTS": "Filenames with dots" } diff --git a/packages/nextra-theme-blog/src/index.tsx b/packages/nextra-theme-blog/src/index.tsx index c97aa21ab8..d6e3ef7212 100644 --- a/packages/nextra-theme-blog/src/index.tsx +++ b/packages/nextra-theme-blog/src/index.tsx @@ -14,7 +14,6 @@ import getTags from './utils/get-tags' import sortDate from './utils/sort-date' import type { PageMapItem, PageOpt } from 'nextra' import type { NextraBlogTheme } from './types' -const isProduction = process.env.NODE_ENV === 'production' // comments const Cusdis = dynamic( // @ts-ignore @@ -217,27 +216,13 @@ const createLayout = (opts: PageOpt, _config: NextraBlogTheme) => { }, _config ) - let layoutUsed = false - const Page = ({ children }: { children: React.ReactChildren }) => { - if (!layoutUsed && isProduction) { - throw new Error( - '[Nextra] Please add the `getLayout` logic to your _app.js, see https://nextjs.org/docs/basic-features/layouts#per-page-layouts.' - ) - } - return children - } - const Layout = (page: React.ReactChildren) => { - layoutUsed = true - return ( - - - - ) - } + + const Page = ({ children }: { children: React.ReactChildren }) => children + const Layout = (page: React.ReactChildren) => ( + + + + ) Page.getLayout = Layout return Page } diff --git a/packages/nextra-theme-docs/src/index.tsx b/packages/nextra-theme-docs/src/index.tsx index 3a19dbc456..8f9363115f 100644 --- a/packages/nextra-theme-docs/src/index.tsx +++ b/packages/nextra-theme-docs/src/index.tsx @@ -22,8 +22,6 @@ import './polyfill' import Breadcrumb from './breadcrumb' import renderComponent from './utils/render-component' -const isProduction = process.env.NODE_ENV === 'production' - function useDirectoryInfo(pageMap: PageMapItem[]) { const { locale, defaultLocale, asPath } = useRouter() @@ -258,18 +256,10 @@ interface DocsLayoutProps extends PageOpt { } const createLayout = (opts: DocsLayoutProps, _config: DocsThemeConfig) => { const extendedConfig = Object.assign({}, defaultConfig, _config, opts) - let layoutUsed = false - const Page = ({ children }: { children: React.ReactChildren }) => { - if (!layoutUsed && isProduction) { - throw new Error( - '[Nextra] Please add the `getLayout` logic to your _app.js, see https://nextjs.org/docs/basic-features/layouts#per-page-layouts.' - ) - } - return children - } - Page.getLayout = (page: any) => { - layoutUsed = true - return ( + + const Page = ({ children }: { children: React.ReactChildren }) => children + + Page.getLayout = (page: any) => ( { ) - } + return Page } diff --git a/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap b/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap index 0ac2d3799c..ab0cbf8a8d 100644 --- a/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap +++ b/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap @@ -52,6 +52,11 @@ exports[`Page Process > pageMap en-US 1`] = ` "name": "file-name.with.DOTS", "route": "/docs/advanced/file-name.with.DOTS", }, + { + "locale": "en-US", + "name": "markdown-import", + "route": "/docs/advanced/markdown-import", + }, { "locale": "en-US", "meta": { @@ -66,6 +71,7 @@ exports[`Page Process > pageMap en-US 1`] = ` }, "cache": "Cache", "file-name.with.DOTS": "Filenames with dots", + "markdown-import": "Markdown import", "more": "More: A Super Super Super Super Long Directory", "performance": "Performance", "react-native": "React Native", @@ -434,6 +440,11 @@ exports[`Page Process > pageMap zh-CN 1`] = ` "name": "react-native", "route": "/docs/advanced/react-native", }, + { + "locale": "en-US", + "name": "markdown-import", + "route": "/docs/advanced/markdown-import", + }, ], "name": "advanced", "route": "/docs/advanced", diff --git a/packages/nextra/src/loader.ts b/packages/nextra/src/loader.ts index 4d3f410fe0..d8813c3c0a 100644 --- a/packages/nextra/src/loader.ts +++ b/packages/nextra/src/loader.ts @@ -10,7 +10,7 @@ import { addPage } from './content-dump' import { parseFileName } from './utils' import { compileMdx } from './compile' import { getPageMap, findPagesDir } from './page-map' -import { collectFiles } from './plugin' +import { collectFiles, collectMdx } from './plugin' import { MARKDOWN_EXTENSION_REGEX, IS_PRODUCTION } from './constants' // TODO: create this as a webpack plugin. @@ -67,6 +67,12 @@ async function loader( ? pageMapCache.get()! : await collectFiles(pagesDir, '/') + // mdx is imported but is outside the `pages` directory + if (!fileMap[resourcePath]) { + fileMap[resourcePath] = await collectMdx(resourcePath) + context.addMissingDependency(resourcePath) + } + const [pageMap, route, title] = getPageMap( resourcePath, pageMapResult, @@ -74,11 +80,7 @@ async function loader( defaultLocale ) - if (!IS_PRODUCTION) { - // Add the entire directory `pages` as the dependency - // so we can generate the correct page map. - context.addContextDependency(pagesDir) - } else { + if (IS_PRODUCTION) { // We only add meta files as dependencies for production build, // so we can do incremental builds. Object.entries(fileMap).forEach(([filePath, { name, meta, locale }]) => { @@ -90,6 +92,10 @@ async function loader( context.addDependency(filePath) } }) + } else { + // Add the entire directory `pages` as the dependency, + // so we can generate the correct page map. + context.addContextDependency(pagesDir) } // Extract frontMatter information if it exists diff --git a/packages/nextra/src/plugin.ts b/packages/nextra/src/plugin.ts index 149de985fc..21cdb935b7 100644 --- a/packages/nextra/src/plugin.ts +++ b/packages/nextra/src/plugin.ts @@ -13,6 +13,19 @@ import { MARKDOWN_EXTENSION_REGEX } from './constants' const readdir = promisify(fs.readdir) const readFile = promisify(fs.readFile) +export const collectMdx = async (filePath: string, route = '') => { + const { name, locale } = parseFileName(filePath) + + const content = await readFile(filePath, 'utf8') + const { data } = grayMatter(content) + return { + name, + route, + locale, + ...(Object.keys(data).length && { frontMatter: data }) + } +} + export async function collectFiles( dir: string, route = '/', @@ -24,33 +37,26 @@ export async function collectFiles( await Promise.all( files.map(async f => { const filePath = path.resolve(dir, f.name) - const { name, locale, ext } = parseFileName(f.name) + const { name, locale, ext } = parseFileName(filePath) const fileRoute = slash(path.join(route, name.replace(/^index$/, ''))) if (f.isDirectory()) { - if (fileRoute === '/api') return null - const { items: children } = await collectFiles( - filePath, - fileRoute, - fileMap - ) - if (!children || !children.length) return null + if (fileRoute === '/api') return + const { items } = await collectFiles(filePath, fileRoute, fileMap) + if (!items.length) return return { name: f.name, - children, + children: items, route: fileRoute } - } else if (MARKDOWN_EXTENSION_REGEX.test(ext)) { - const content = await readFile(filePath, 'utf8') - const { data } = grayMatter(content) - fileMap[filePath] = { - name, - route: fileRoute, - locale, - ...(Object.keys(data).length && { frontMatter: data }) - } + } + + if (MARKDOWN_EXTENSION_REGEX.test(ext)) { + fileMap[filePath] = await collectMdx(filePath, fileRoute) return fileMap[filePath] - } else if (ext === '.json' && name === 'meta') { + } + + if (ext === '.json' && name === 'meta') { const content = await readFile(filePath, 'utf8') fileMap[filePath] = { name: 'meta.json',