Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(v2): markdown pages #2947

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/docusaurus-plugin-content-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/types": "^2.0.0-alpha.56",
"@docusaurus/mdx-loader": "^2.0.0-alpha.56",
"@docusaurus/utils": "^2.0.0-alpha.56",
"globby": "^10.0.1"
"@docusaurus/types": "^2.0.0-alpha.56",
"loader-utils": "^1.2.3",
"globby": "^10.0.1",
"remark-admonitions": "^1.2.1"
},
"peerDependencies": {
"@docusaurus/core": "^2.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 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.
*/

module.exports = {
title: 'My Site',
tagline: 'The tagline of my site',
url: 'https://your-docusaurus-test-site.com',
baseUrl: '/',
favicon: 'img/favicon.ico',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Markdown index page
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: mdx page
description: my mdx page
---
MDX page
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,44 @@
*/

import path from 'path';
import {loadContext} from '@docusaurus/core/lib/server';

import pluginContentPages from '../index';
import {LoadContext} from '@docusaurus/types';

describe('docusaurus-plugin-content-pages', () => {
test('simple pages', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const siteConfig = {
title: 'Hello',
baseUrl: '/',
url: 'https://docusaurus.io',
};
const context = {
siteDir,
siteConfig,
} as LoadContext;
const context = loadContext(siteDir);
const pluginPath = 'src/pages';
const plugin = pluginContentPages(context, {
path: pluginPath,
});

const pagesMetadatas = await plugin.loadContent();

expect(pagesMetadatas).toEqual([
{
type: 'jsx',
permalink: '/',
source: path.join('@site', pluginPath, 'index.js'),
},
{
type: 'jsx',
permalink: '/typescript',
source: path.join('@site', pluginPath, 'typescript.tsx'),
},
{
type: 'mdx',
permalink: '/hello/',
source: path.join('@site', pluginPath, 'hello', 'index.md'),
},
{
type: 'mdx',
permalink: '/hello/mdxPage',
source: path.join('@site', pluginPath, 'hello', 'mdxPage.mdx'),
},
{
type: 'jsx',
permalink: '/hello/world',
source: path.join('@site', pluginPath, 'hello', 'world.js'),
},
Expand Down
154 changes: 134 additions & 20 deletions packages/docusaurus-plugin-content-pages/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,50 @@
import globby from 'globby';
import fs from 'fs';
import path from 'path';
import {encodePath, fileToPath, aliasedSitePath} from '@docusaurus/utils';
import {LoadContext, Plugin} from '@docusaurus/types';
import {
encodePath,
fileToPath,
aliasedSitePath,
docuHash,
} from '@docusaurus/utils';
import {LoadContext, Plugin, ConfigureWebpackUtils} from '@docusaurus/types';
import {Configuration, Loader} from 'webpack';
import admonitions from 'remark-admonitions';

import {PluginOptions, LoadedContent} from './types';
import {PluginOptions, LoadedContent, Metadata} from './types';

const DEFAULT_OPTIONS: PluginOptions = {
path: 'src/pages', // Path to data on filesystem, relative to site dir.
routeBasePath: '', // URL Route.
include: ['**/*.{js,jsx,ts,tsx}'], // Extensions to include.
include: ['**/*.{js,jsx,ts,tsx,md,mdx}'], // Extensions to include.
mdxPageComponent: '@theme/MDXPage',
remarkPlugins: [],
rehypePlugins: [],
admonitions: {},
};

const isMarkdownSource = (source: string) =>
source.endsWith('.md') || source.endsWith('.mdx');

export default function pluginContentPages(
context: LoadContext,
opts: Partial<PluginOptions>,
): Plugin<LoadedContent | null> {
const options = {...DEFAULT_OPTIONS, ...opts};
const options: PluginOptions = {...DEFAULT_OPTIONS, ...opts};
if (options.admonitions) {
options.remarkPlugins = options.remarkPlugins.concat([
[admonitions, opts.admonitions || {}],
]);
}

const contentPath = path.resolve(context.siteDir, options.path);

const {siteDir, generatedFilesDir} = context;
const dataDir = path.join(
generatedFilesDir,
'docusaurus-plugin-content-pages',
);

return {
name: 'docusaurus-plugin-content-pages',

Expand All @@ -35,6 +61,16 @@ export default function pluginContentPages(
return [...globPattern];
},

getClientModules() {
const modules = [];

if (options.admonitions) {
modules.push(require.resolve('remark-admonitions/styles/infima.css'));
}

return modules;
},

async loadContent() {
const {include} = options;
const {siteConfig, siteDir} = context;
Expand All @@ -49,35 +85,113 @@ export default function pluginContentPages(
cwd: pagesDir,
});

return pagesFiles.map((relativeSource) => {
function toMetadata(relativeSource: string): Metadata {
const source = path.join(pagesDir, relativeSource);
const aliasedSource = aliasedSitePath(source, siteDir);
const pathName = encodePath(fileToPath(relativeSource));
// Default Language.
return {
permalink: pathName.replace(/^\//, baseUrl || ''),
source: aliasedSource,
};
});
const permalink = pathName.replace(/^\//, baseUrl || '');
if (isMarkdownSource(relativeSource)) {
return {
type: 'mdx',
permalink,
source: aliasedSource,
};
} else {
return {
type: 'jsx',
permalink,
source: aliasedSource,
};
}
}

return pagesFiles.map(toMetadata);
},

async contentLoaded({content, actions}) {
if (!content) {
return;
}

const {addRoute} = actions;
const {addRoute, createData} = actions;

await Promise.all(
content.map(async (metadataItem) => {
const {permalink, source} = metadataItem;
addRoute({
path: permalink,
component: source,
exact: true,
});
content.map(async (metadata) => {
const {permalink, source} = metadata;
if (metadata.type === 'mdx') {
await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${docuHash(metadata.source)}.json`,
JSON.stringify(metadata, null, 2),
);
addRoute({
path: permalink,
component: options.mdxPageComponent,
exact: true,
modules: {
content: source,
},
});
} else {
addRoute({
path: permalink,
component: source,
exact: true,
});
}
}),
);
},

configureWebpack(
_config: Configuration,
isServer: boolean,
{getBabelLoader, getCacheLoader}: ConfigureWebpackUtils,
) {
const {rehypePlugins, remarkPlugins} = options;
return {
resolve: {
alias: {
'~pages': dataDir,
},
},
module: {
rules: [
{
test: /(\.mdx?)$/,
include: [contentPath],
use: [
getCacheLoader(isServer),
getBabelLoader(isServer),
{
loader: require.resolve('@docusaurus/mdx-loader'),
options: {
remarkPlugins,
rehypePlugins,
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
metadataPath: (mdxPath: string) => {
const aliasedSource = aliasedSitePath(mdxPath, siteDir);
return path.join(
dataDir,
`${docuHash(aliasedSource)}.json`,
);
},
},
},
{
loader: path.resolve(__dirname, './markdownLoader.js'),
options: {
// siteDir,
// contentPath,
},
},
].filter(Boolean) as Loader[],
},
],
},
};
},
};
}
21 changes: 21 additions & 0 deletions packages/docusaurus-plugin-content-pages/src/markdownLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {loader} from 'webpack';
import {getOptions} from 'loader-utils';

const markdownLoader: loader.Loader = function (fileString) {
const callback = this.async();
const {} = getOptions(this);

// TODO provide additinal md processing here? like interlinking pages?
// fileString = linkify(fileString)

return callback && callback(null, fileString);
};

export default markdownLoader;
17 changes: 15 additions & 2 deletions packages/docusaurus-plugin-content-pages/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ export interface PluginOptions {
path: string;
routeBasePath: string;
include: string[];
mdxPageComponent: string;
remarkPlugins: ([Function, object] | Function)[];
rehypePlugins: string[];
admonitions: any;
}

export interface Metadata {
export type JSXPageMetadata = {
type: 'jsx';
permalink: string;
source: string;
}
};

export type MDXPageMetadata = {
type: 'mdx';
permalink: string;
source: string;
};

export type Metadata = JSXPageMetadata | MDXPageMetadata;

export type LoadedContent = Metadata[];
13 changes: 13 additions & 0 deletions packages/docusaurus-plugin-content-pages/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* 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.
*/

declare module 'remark-admonitions' {
type Options = any;

const plugin: (options?: Options) => void;
export = plugin;
}
32 changes: 32 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXPage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* 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 Layout from '@theme/Layout';
import {MDXProvider} from '@mdx-js/react';
import MDXComponents from '@theme/MDXComponents';

function MDXPage(props) {
const {content: MDXPageContent} = props;
const {frontMatter, metadata} = MDXPageContent;
const {title, description} = frontMatter;
const {permalink} = metadata;

return (
<Layout title={title} description={description} permalink={permalink}>
<main>
<div className="container margin-vert--lg padding-vert--lg">
<MDXProvider components={MDXComponents}>
<MDXPageContent />
</MDXProvider>
</div>
</main>
</Layout>
);
}

export default MDXPage;
Loading