mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 16:47:26 +02:00
feat(v2): docs versioning ❄️🔥 (#1983)
* wip: versioning * wip again * nits lint * refactor metadata code so that we can have inobject properties optimization, fix typing * remove buggy permalink code * modify versioned docs fixture such that foo/baz only exists in v1.0.0 * refactor metadata.ts so that there is less transformon object * more refactoring * reduce test fixtures, refactoring * refactoring readability * finish metadata part * refactor with readdir * first pass of implementation * fix mdx laoder * split generated routes by version for performance & smaller bundle * test data for demo * refactor with set * more tests * typo * fix typo * better temporary ui * stronger typing & docsVersion command * add 100% test coverage for docsVersion command * more test and delete manual docs cut * cut 2.0.0-alpha.35 docs * cut alpha.36 instead * copyright * delete versioned docs * stronger test on metadata * update typo
This commit is contained in:
parent
c413cff212
commit
9829f56b1e
45 changed files with 1852 additions and 395 deletions
|
@ -8,109 +8,24 @@
|
|||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import {parse, normalizeUrl, posixPath} from '@docusaurus/utils';
|
||||
import {DocusaurusConfig} from '@docusaurus/types';
|
||||
import {LoadContext} from '@docusaurus/types';
|
||||
|
||||
import lastUpdate from './lastUpdate';
|
||||
import {Order, MetadataRaw} from './types';
|
||||
import {MetadataRaw, LastUpdateData, MetadataOptions, Env} from './types';
|
||||
|
||||
type Args = {
|
||||
source: string;
|
||||
docsDir: string;
|
||||
order: Order;
|
||||
siteConfig: Partial<DocusaurusConfig>;
|
||||
docsBasePath: string;
|
||||
siteDir: string;
|
||||
editUrl?: string;
|
||||
showLastUpdateAuthor?: boolean;
|
||||
showLastUpdateTime?: boolean;
|
||||
refDir: string;
|
||||
context: LoadContext;
|
||||
options: MetadataOptions;
|
||||
env: Env;
|
||||
};
|
||||
|
||||
export default async function processMetadata({
|
||||
source,
|
||||
docsDir,
|
||||
order,
|
||||
siteConfig,
|
||||
docsBasePath,
|
||||
siteDir,
|
||||
editUrl,
|
||||
showLastUpdateAuthor,
|
||||
showLastUpdateTime,
|
||||
}: Args): Promise<MetadataRaw> {
|
||||
const filePath = path.join(docsDir, source);
|
||||
|
||||
const fileString = await fs.readFile(filePath, 'utf-8');
|
||||
const {frontMatter: metadata = {}, excerpt} = parse(fileString);
|
||||
|
||||
// Default id is the file name.
|
||||
if (!metadata.id) {
|
||||
metadata.id = path.basename(source, path.extname(source));
|
||||
}
|
||||
|
||||
if (metadata.id.includes('/')) {
|
||||
throw new Error('Document id cannot include "/".');
|
||||
}
|
||||
|
||||
// Default title is the id.
|
||||
if (!metadata.title) {
|
||||
metadata.title = metadata.id;
|
||||
}
|
||||
|
||||
if (!metadata.description) {
|
||||
metadata.description = excerpt;
|
||||
}
|
||||
|
||||
const dirName = path.dirname(source);
|
||||
if (dirName !== '.') {
|
||||
const prefix = dirName;
|
||||
if (prefix) {
|
||||
metadata.id = `${prefix}/${metadata.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it.
|
||||
const aliasedPath = `@site/${path.relative(siteDir, filePath)}`;
|
||||
metadata.source = aliasedPath;
|
||||
|
||||
// Build the permalink.
|
||||
const {baseUrl} = siteConfig;
|
||||
|
||||
// If user has own custom permalink defined in frontmatter
|
||||
// e.g: :baseUrl:docsUrl/:langPart/:versionPart/endiliey/:id
|
||||
if (metadata.permalink) {
|
||||
metadata.permalink = path.resolve(
|
||||
metadata.permalink
|
||||
.replace(/:baseUrl/, baseUrl)
|
||||
.replace(/:docsUrl/, docsBasePath)
|
||||
.replace(/:id/, metadata.id),
|
||||
);
|
||||
} else {
|
||||
metadata.permalink = normalizeUrl([baseUrl, docsBasePath, metadata.id]);
|
||||
}
|
||||
|
||||
// Determine order.
|
||||
const {id} = metadata;
|
||||
if (order[id]) {
|
||||
metadata.sidebar = order[id].sidebar;
|
||||
if (order[id].next) {
|
||||
metadata.next = order[id].next;
|
||||
}
|
||||
if (order[id].previous) {
|
||||
metadata.previous = order[id].previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (editUrl) {
|
||||
metadata.editUrl = normalizeUrl([
|
||||
editUrl,
|
||||
posixPath(path.relative(siteDir, filePath)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (metadata.custom_edit_url) {
|
||||
metadata.editUrl = metadata.custom_edit_url;
|
||||
delete metadata.custom_edit_url;
|
||||
}
|
||||
|
||||
async function lastUpdated(
|
||||
filePath: string,
|
||||
options: MetadataOptions,
|
||||
): Promise<LastUpdateData> {
|
||||
const {showLastUpdateAuthor, showLastUpdateTime} = options;
|
||||
if (showLastUpdateAuthor || showLastUpdateTime) {
|
||||
// Use fake data in dev for faster development
|
||||
const fileLastUpdateData =
|
||||
|
@ -118,20 +33,113 @@ export default async function processMetadata({
|
|||
? await lastUpdate(filePath)
|
||||
: {
|
||||
author: 'Author',
|
||||
timestamp: '1539502055',
|
||||
timestamp: 1539502055,
|
||||
};
|
||||
|
||||
if (fileLastUpdateData) {
|
||||
const {author, timestamp} = fileLastUpdateData;
|
||||
if (showLastUpdateAuthor && author) {
|
||||
metadata.lastUpdatedBy = author;
|
||||
}
|
||||
return {
|
||||
lastUpdatedAt: showLastUpdateTime ? timestamp : undefined,
|
||||
lastUpdatedBy: showLastUpdateAuthor ? author : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (showLastUpdateTime && timestamp) {
|
||||
metadata.lastUpdatedAt = timestamp;
|
||||
export default async function processMetadata({
|
||||
source,
|
||||
refDir,
|
||||
context,
|
||||
options,
|
||||
env,
|
||||
}: Args): Promise<MetadataRaw> {
|
||||
const {routeBasePath, editUrl} = options;
|
||||
const {siteDir, baseUrl} = context;
|
||||
const {versioning} = env;
|
||||
const filePath = path.join(refDir, source);
|
||||
|
||||
const fileString = await fs.readFile(filePath, 'utf-8');
|
||||
const {frontMatter = {}, excerpt} = parse(fileString);
|
||||
|
||||
// Default id is the file name.
|
||||
const baseID: string =
|
||||
frontMatter.id || path.basename(source, path.extname(source));
|
||||
if (baseID.includes('/')) {
|
||||
throw new Error('Document id cannot include "/".');
|
||||
}
|
||||
|
||||
// Default title is the id.
|
||||
const title: string = frontMatter.title || baseID;
|
||||
|
||||
const description: string = frontMatter.description || excerpt;
|
||||
|
||||
let version;
|
||||
let id = baseID;
|
||||
|
||||
// Append subdirectory as part of id.
|
||||
const dirName = path.dirname(source);
|
||||
if (dirName !== '.') {
|
||||
id = `${dirName}/${baseID}`;
|
||||
}
|
||||
|
||||
if (versioning.enabled) {
|
||||
if (/^version-/.test(dirName)) {
|
||||
const inferredVersion = dirName
|
||||
.split('/', 1)
|
||||
.shift()!
|
||||
.replace(/^version-/, '');
|
||||
if (inferredVersion && versioning.versions.includes(inferredVersion)) {
|
||||
version = inferredVersion;
|
||||
}
|
||||
} else {
|
||||
version = 'next';
|
||||
}
|
||||
}
|
||||
|
||||
return metadata as MetadataRaw;
|
||||
// The version portion of the url path. Eg: 'next', '1.0.0', and ''
|
||||
const versionPath =
|
||||
version && version !== versioning.latestVersion ? version : '';
|
||||
|
||||
// The last portion of the url path. Eg: 'foo/bar', 'bar'
|
||||
const routePath =
|
||||
version && version !== 'next'
|
||||
? id.replace(new RegExp(`^version-${version}/`), '')
|
||||
: id;
|
||||
const permalink = normalizeUrl([
|
||||
baseUrl,
|
||||
routeBasePath,
|
||||
versionPath,
|
||||
routePath,
|
||||
]);
|
||||
|
||||
const {sidebar_label, custom_edit_url} = frontMatter;
|
||||
|
||||
const relativePath = path.relative(siteDir, filePath);
|
||||
|
||||
// Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it.
|
||||
const aliasedPath = `@site/${relativePath}`;
|
||||
|
||||
const docsEditUrl = editUrl
|
||||
? normalizeUrl([editUrl, posixPath(relativePath)])
|
||||
: undefined;
|
||||
|
||||
const {lastUpdatedAt, lastUpdatedBy} = await lastUpdated(filePath, options);
|
||||
|
||||
// Assign all of object properties during instantiation (if possible) for NodeJS optimization
|
||||
// Adding properties to object after instantiation will cause hidden class transitions.
|
||||
const metadata: MetadataRaw = {
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
source: aliasedPath,
|
||||
permalink,
|
||||
editUrl: custom_edit_url || docsEditUrl,
|
||||
version,
|
||||
lastUpdatedBy,
|
||||
lastUpdatedAt,
|
||||
sidebar_label,
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue