@docusaurus/plugin-content-docs
Version:
Docs content plugin for Docusaurus
300 lines (299 loc) • 14.6 kB
JavaScript
;
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = __importDefault(require("lodash"));
const globby_1 = __importDefault(require("globby"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const utils_1 = require("@docusaurus/utils");
const order_1 = __importDefault(require("./order"));
const sidebars_1 = __importDefault(require("./sidebars"));
const metadata_1 = __importDefault(require("./metadata"));
const env_1 = __importDefault(require("./env"));
const version_1 = require("./version");
const DEFAULT_OPTIONS = {
path: 'docs',
routeBasePath: 'docs',
include: ['**/*.{md,mdx}'],
sidebarPath: '',
docLayoutComponent: '@theme/DocPage',
docItemComponent: '@theme/DocItem',
remarkPlugins: [],
rehypePlugins: [],
showLastUpdateTime: false,
showLastUpdateAuthor: false,
};
function pluginContentDocs(context, opts) {
const options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), opts);
const { siteDir, generatedFilesDir, baseUrl } = context;
const docsDir = path_1.default.resolve(siteDir, options.path);
const sourceToPermalink = {};
const dataDir = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-docs');
// Versioning
const env = env_1.default(siteDir);
const { versioning } = env;
const { versions, docsDir: versionedDir, sidebarsDir: versionedSidebarsDir, } = versioning;
const versionsNames = versions.map(version => `version-${version}`);
return {
name: 'docusaurus-plugin-content-docs',
extendCli(cli) {
cli
.command('docs:version')
.arguments('<version>')
.description('Tag a new version for docs')
.action(version => {
version_1.docsVersion(version, siteDir, {
path: options.path,
sidebarPath: options.sidebarPath,
});
});
},
getPathsToWatch() {
const { include } = options;
let globPattern = include.map(pattern => `${docsDir}/${pattern}`);
if (versioning.enabled) {
const docsGlob = lodash_1.default.flatten(include.map(pattern => versionsNames.map(versionName => `${versionedDir}/${versionName}/${pattern}`)));
const sidebarsGlob = versionsNames.map(versionName => `${versionedSidebarsDir}/${versionName}-sidebars.json`);
globPattern = [...globPattern, ...sidebarsGlob, ...docsGlob];
}
return [...globPattern, options.sidebarPath];
},
// Fetches blog contents and returns metadata for the contents.
async loadContent() {
const { include, sidebarPath } = options;
if (!fs_extra_1.default.existsSync(docsDir)) {
return null;
}
// Prepare metadata container.
const docsMetadataRaw = {};
const docsPromises = [];
// Metadata for default/ master docs files.
const docsFiles = await globby_1.default(include, {
cwd: docsDir,
});
docsPromises.push(Promise.all(docsFiles.map(async (source) => {
const metadata = await metadata_1.default({
source,
refDir: docsDir,
context,
options,
env,
});
docsMetadataRaw[metadata.id] = metadata;
})));
// Metadata for versioned docs
if (versioning.enabled) {
const versionedGlob = lodash_1.default.flatten(include.map(pattern => versionsNames.map(versionName => `${versionName}/${pattern}`)));
const versionedFiles = await globby_1.default(versionedGlob, {
cwd: versionedDir,
});
docsPromises.push(Promise.all(versionedFiles.map(async (source) => {
const metadata = await metadata_1.default({
source,
refDir: versionedDir,
context,
options,
env,
});
docsMetadataRaw[metadata.id] = metadata;
})));
}
// Load the sidebars & create docs ordering
const sidebarPaths = [
sidebarPath,
...versionsNames.map(versionName => `${versionedSidebarsDir}/${versionName}-sidebars.json`),
];
const loadedSidebars = sidebars_1.default(sidebarPaths);
const order = order_1.default(loadedSidebars);
await Promise.all(docsPromises);
// Construct inter-metadata relationship in docsMetadata
const docsMetadata = {};
const permalinkToSidebar = {};
const versionToSidebars = {};
Object.keys(docsMetadataRaw).forEach(currentID => {
var _a, _b, _c, _d, _e, _f;
const { next: nextID, previous: previousID, sidebar } = order[currentID] || {};
const previous = previousID
? {
title: (_b = (_a = docsMetadataRaw[previousID]) === null || _a === void 0 ? void 0 : _a.title, (_b !== null && _b !== void 0 ? _b : 'Previous')),
permalink: (_c = docsMetadataRaw[previousID]) === null || _c === void 0 ? void 0 : _c.permalink,
}
: undefined;
const next = nextID
? {
title: (_e = (_d = docsMetadataRaw[nextID]) === null || _d === void 0 ? void 0 : _d.title, (_e !== null && _e !== void 0 ? _e : 'Next')),
permalink: (_f = docsMetadataRaw[nextID]) === null || _f === void 0 ? void 0 : _f.permalink,
}
: undefined;
docsMetadata[currentID] = Object.assign(Object.assign({}, docsMetadataRaw[currentID]), { sidebar,
previous,
next });
// sourceToPermalink and permalinkToSidebar mapping
const { source, permalink, version } = docsMetadataRaw[currentID];
sourceToPermalink[source] = permalink;
if (sidebar) {
permalinkToSidebar[permalink] = sidebar;
if (versioning.enabled && version) {
if (!versionToSidebars[version]) {
versionToSidebars[version] = new Set();
}
versionToSidebars[version].add(sidebar);
}
}
});
const convertDocLink = (item) => {
const linkID = item.id;
const linkMetadata = docsMetadataRaw[linkID];
if (!linkMetadata) {
throw new Error(`Improper sidebars file, document with id '${linkID}' not found.`);
}
return {
type: 'link',
label: linkMetadata.sidebar_label || linkMetadata.title,
href: linkMetadata.permalink,
};
};
const normalizeItem = (item) => {
switch (item.type) {
case 'category':
return Object.assign(Object.assign({}, item), { items: item.items.map(normalizeItem) });
case 'ref':
case 'doc':
return convertDocLink(item);
case 'link':
default:
return item;
}
};
// Transform the sidebar so that all sidebar item will be in the form of 'link' or 'category' only
// This is what will be passed as props to the UI component
const docsSidebars = Object.entries(loadedSidebars).reduce((acc, [sidebarId, sidebarItems]) => {
acc[sidebarId] = sidebarItems.map(normalizeItem);
return acc;
}, {});
return {
docsMetadata,
docsDir,
docsSidebars,
permalinkToSidebar: utils_1.objectWithKeySorted(permalinkToSidebar),
versionToSidebars,
};
},
async contentLoaded({ content, actions }) {
if (!content || Object.keys(content.docsMetadata).length === 0) {
return;
}
const { docLayoutComponent, docItemComponent, routeBasePath } = options;
const { addRoute, createData } = actions;
const aliasedSource = (source) => `@docusaurus-plugin-content-docs/${path_1.default.relative(dataDir, source)}`;
const genRoutes = async (metadataItems) => {
const routes = await Promise.all(metadataItems.map(async (metadataItem) => {
const metadataPath = await createData(`${utils_1.docuHash(metadataItem.permalink)}.json`, JSON.stringify(metadataItem, null, 2));
return {
path: metadataItem.permalink,
component: docItemComponent,
exact: true,
modules: {
content: metadataItem.source,
metadata: aliasedSource(metadataPath),
},
};
}));
return routes.sort((a, b) => a.path > b.path ? 1 : b.path > a.path ? -1 : 0);
};
const addBaseRoute = async (docsBaseRoute, docsBaseMetadata, routes, priority) => {
const docsBaseMetadataPath = await createData(`${utils_1.docuHash(docsBaseRoute)}.json`, JSON.stringify(docsBaseMetadata, null, 2));
addRoute({
path: docsBaseRoute,
component: docLayoutComponent,
routes,
modules: {
docsMetadata: aliasedSource(docsBaseMetadataPath),
},
priority,
});
};
// If versioning is enabled, we cleverly chunk the generated routes to be by version
// and pick only needed base metadata
if (versioning.enabled) {
const docsMetadataByVersion = lodash_1.default.groupBy(Object.values(content.docsMetadata), 'version');
await Promise.all(Object.keys(docsMetadataByVersion).map(async (version) => {
const routes = await genRoutes(docsMetadataByVersion[version]);
const isLatestVersion = version === versioning.latestVersion;
const docsBasePermalink = utils_1.normalizeUrl([
baseUrl,
routeBasePath,
isLatestVersion ? '' : version,
]);
const docsBaseRoute = utils_1.normalizeUrl([docsBasePermalink, ':route']);
const neededSidebars = content.versionToSidebars[version] || new Set();
const docsBaseMetadata = {
docsSidebars: lodash_1.default.pick(content.docsSidebars, Array.from(neededSidebars)),
permalinkToSidebar: lodash_1.default.pickBy(content.permalinkToSidebar, sidebar => neededSidebars.has(sidebar)),
version,
};
// We want latest version route config to be placed last in the generated routeconfig.
// Otherwise, `/docs/next/foo` will match `/docs/:route` instead of `/docs/next/:route`
return addBaseRoute(docsBaseRoute, docsBaseMetadata, routes, isLatestVersion ? -1 : undefined);
}));
}
else {
const routes = await genRoutes(Object.values(content.docsMetadata));
const docsBaseMetadata = {
docsSidebars: content.docsSidebars,
permalinkToSidebar: content.permalinkToSidebar,
};
const docsBaseRoute = utils_1.normalizeUrl([baseUrl, routeBasePath, ':route']);
return addBaseRoute(docsBaseRoute, docsBaseMetadata, routes);
}
},
configureWebpack(_config, isServer, utils) {
const { getBabelLoader, getCacheLoader } = utils;
const { rehypePlugins, remarkPlugins } = options;
return {
resolve: {
alias: {
'@docusaurus-plugin-content-docs': dataDir,
},
},
module: {
rules: [
{
test: /(\.mdx?)$/,
include: [docsDir, versionedDir].filter(Boolean),
use: [
getCacheLoader(isServer),
getBabelLoader(isServer),
{
loader: '@docusaurus/mdx-loader',
options: {
remarkPlugins,
rehypePlugins,
},
},
{
loader: path_1.default.resolve(__dirname, './markdown/index.js'),
options: {
siteDir,
docsDir,
sourceToPermalink: sourceToPermalink,
versionedDir,
},
},
].filter(Boolean),
},
],
},
};
},
};
}
exports.default = pluginContentDocs;