mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 18:27:56 +02:00
feat(v2): add trailingSlash config option (#4908)
* POC: add trailingSlash option * integrate the preferFoldersOutput option of fork @slorber/static-site-generator-webpack-plugin * Fix broken links when using trailing slash => using md links is more reliable * fix TS issue * minor polish * fix doc page being sensitive to trailing slashes * Add tests for applyTrailingSlash * rename test files * extract and test applyRouteTrailingSlash * update snapshot * add trailing slash config to serve command * fix getSidebar() => still sensitive to trailing slash setting * never apply trailing slash to an anchor link * Add documentation for trailingSlash setting
This commit is contained in:
parent
77264f1eb0
commit
df8a900f9c
28 changed files with 469 additions and 67 deletions
|
@ -15,6 +15,7 @@
|
|||
"start:v2:baseUrl": "yarn workspace docusaurus-2-website start:baseUrl",
|
||||
"start:v2:bootstrap": "yarn workspace docusaurus-2-website start:bootstrap",
|
||||
"start:v2:blogOnly": "yarn workspace docusaurus-2-website start:blogOnly",
|
||||
"start:v2:deployPreview": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace docusaurus-2-website start",
|
||||
"examples:generate": "node generateExamples",
|
||||
"build": "yarn build:packages && yarn build:v2",
|
||||
"build:packages": "lerna run build --no-private",
|
||||
|
@ -22,7 +23,9 @@
|
|||
"build:v2": "yarn workspace docusaurus-2-website build",
|
||||
"build:v2:baseUrl": "yarn workspace docusaurus-2-website build:baseUrl",
|
||||
"build:v2:blogOnly": "yarn workspace docusaurus-2-website build:blogOnly",
|
||||
"build:v2:deployPreview": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace docusaurus-2-website build",
|
||||
"build:v2:en": "yarn workspace docusaurus-2-website build --locale en",
|
||||
"clear:v2": "yarn workspace docusaurus-2-website clear",
|
||||
"serve:v1": "serve website-1.x/build/docusaurus",
|
||||
"serve:v2": "yarn workspace docusaurus-2-website serve",
|
||||
"serve:v2:baseUrl": "serve website",
|
||||
|
|
|
@ -31,15 +31,36 @@ type DocPageContentProps = {
|
|||
readonly children: ReactNode;
|
||||
};
|
||||
|
||||
function getSidebar({versionMetadata, currentDocRoute}) {
|
||||
function addTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str : `${str}/`;
|
||||
}
|
||||
function removeTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str.slice(0, -1) : str;
|
||||
}
|
||||
|
||||
const {permalinkToSidebar, docsSidebars} = versionMetadata;
|
||||
|
||||
// With/without trailingSlash, we should always be able to get the appropriate sidebar
|
||||
// note: docs plugin permalinks currently never have trailing slashes
|
||||
// trailingSlash is handled globally at the framework level, not plugin level
|
||||
const sidebarName =
|
||||
permalinkToSidebar[currentDocRoute.path] ||
|
||||
permalinkToSidebar[addTrailingSlash(currentDocRoute.path)] ||
|
||||
permalinkToSidebar[removeTrailingSlash(currentDocRoute.path)];
|
||||
|
||||
const sidebar = docsSidebars[sidebarName];
|
||||
return {sidebar, sidebarName};
|
||||
}
|
||||
|
||||
function DocPageContent({
|
||||
currentDocRoute,
|
||||
versionMetadata,
|
||||
children,
|
||||
}: DocPageContentProps): JSX.Element {
|
||||
const {siteConfig, isClient} = useDocusaurusContext();
|
||||
const {pluginId, permalinkToSidebar, docsSidebars, version} = versionMetadata;
|
||||
const sidebarName = permalinkToSidebar[currentDocRoute.path];
|
||||
const sidebar = docsSidebars[sidebarName];
|
||||
const {pluginId, version} = versionMetadata;
|
||||
const {sidebarName, sidebar} = getSidebar({versionMetadata, currentDocRoute});
|
||||
|
||||
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
|
||||
const [hiddenSidebar, setHiddenSidebar] = useState(false);
|
||||
|
|
2
packages/docusaurus-types/src/index.d.ts
vendored
2
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -30,6 +30,8 @@ export interface DocusaurusConfig {
|
|||
tagline?: string;
|
||||
title: string;
|
||||
url: string;
|
||||
// trailingSlash undefined = legacy retrocompatible behavior => /file => /file/index.html
|
||||
trailingSlash: boolean | undefined;
|
||||
i18n: I18nConfig;
|
||||
onBrokenLinks: ReportingSeverity;
|
||||
onBrokenMarkdownLinks: ReportingSeverity;
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
"@docusaurus/types": "2.0.0-beta.0",
|
||||
"@docusaurus/utils": "2.0.0-beta.0",
|
||||
"@docusaurus/utils-validation": "2.0.0-beta.0",
|
||||
"@endiliey/static-site-generator-webpack-plugin": "^4.0.0",
|
||||
"@slorber/static-site-generator-webpack-plugin": "^4.0.0",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"babel-loader": "^8.2.2",
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
import React, {useEffect, useRef} from 'react';
|
||||
|
||||
import {NavLink, Link as RRLink} from 'react-router-dom';
|
||||
import useDocusaurusContext from './useDocusaurusContext';
|
||||
import isInternalUrl from './isInternalUrl';
|
||||
import ExecutionEnvironment from './ExecutionEnvironment';
|
||||
import {useLinksCollector} from '../LinksCollector';
|
||||
import {useBaseUrlUtils} from './useBaseUrl';
|
||||
import applyTrailingSlash from './applyTrailingSlash';
|
||||
|
||||
import type {LinkProps} from '@docusaurus/Link';
|
||||
import type docusaurus from '../docusaurus';
|
||||
|
@ -39,6 +41,9 @@ function Link({
|
|||
autoAddBaseUrl = true,
|
||||
...props
|
||||
}: LinkProps): JSX.Element {
|
||||
const {
|
||||
siteConfig: {trailingSlash},
|
||||
} = useDocusaurusContext();
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const linksCollector = useLinksCollector();
|
||||
|
||||
|
@ -69,11 +74,15 @@ function Link({
|
|||
|
||||
// TODO we should use ReactRouter basename feature instead!
|
||||
// Automatically apply base url in links that start with /
|
||||
const targetLink =
|
||||
let targetLink =
|
||||
typeof targetLinkWithoutPathnameProtocol !== 'undefined'
|
||||
? maybeAddBaseUrl(targetLinkWithoutPathnameProtocol)
|
||||
: undefined;
|
||||
|
||||
if (targetLink && isInternal) {
|
||||
targetLink = applyTrailingSlash(targetLink, trailingSlash);
|
||||
}
|
||||
|
||||
const preloaded = useRef(false);
|
||||
const LinkComponent = isNavLink ? NavLink : RRLink;
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* 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 applyTrailingSlash from '../applyTrailingSlash';
|
||||
|
||||
describe('applyTrailingSlash', () => {
|
||||
test('should apply to empty', () => {
|
||||
expect(applyTrailingSlash('', true)).toEqual('/');
|
||||
expect(applyTrailingSlash('', false)).toEqual('');
|
||||
expect(applyTrailingSlash('', undefined)).toEqual('');
|
||||
});
|
||||
|
||||
test('should apply to /', () => {
|
||||
expect(applyTrailingSlash('/', true)).toEqual('/');
|
||||
expect(applyTrailingSlash('/', false)).toEqual('');
|
||||
expect(applyTrailingSlash('/', undefined)).toEqual('/');
|
||||
});
|
||||
|
||||
test('should not apply to #anchor links ', () => {
|
||||
expect(applyTrailingSlash('#', true)).toEqual('#');
|
||||
expect(applyTrailingSlash('#', false)).toEqual('#');
|
||||
expect(applyTrailingSlash('#', undefined)).toEqual('#');
|
||||
expect(applyTrailingSlash('#anchor', true)).toEqual('#anchor');
|
||||
expect(applyTrailingSlash('#anchor', false)).toEqual('#anchor');
|
||||
expect(applyTrailingSlash('#anchor', undefined)).toEqual('#anchor');
|
||||
});
|
||||
|
||||
test('should apply to simple paths', () => {
|
||||
expect(applyTrailingSlash('abc', true)).toEqual('abc/');
|
||||
expect(applyTrailingSlash('abc', false)).toEqual('abc');
|
||||
expect(applyTrailingSlash('abc', undefined)).toEqual('abc');
|
||||
expect(applyTrailingSlash('abc/', true)).toEqual('abc/');
|
||||
expect(applyTrailingSlash('abc/', false)).toEqual('abc');
|
||||
expect(applyTrailingSlash('abc/', undefined)).toEqual('abc/');
|
||||
expect(applyTrailingSlash('/abc', true)).toEqual('/abc/');
|
||||
expect(applyTrailingSlash('/abc', false)).toEqual('/abc');
|
||||
expect(applyTrailingSlash('/abc', undefined)).toEqual('/abc');
|
||||
expect(applyTrailingSlash('/abc/', true)).toEqual('/abc/');
|
||||
expect(applyTrailingSlash('/abc/', false)).toEqual('/abc');
|
||||
expect(applyTrailingSlash('/abc/', undefined)).toEqual('/abc/');
|
||||
});
|
||||
|
||||
test('should apply to path with #anchor', () => {
|
||||
expect(applyTrailingSlash('/abc#anchor', true)).toEqual('/abc/#anchor');
|
||||
expect(applyTrailingSlash('/abc#anchor', false)).toEqual('/abc#anchor');
|
||||
expect(applyTrailingSlash('/abc#anchor', undefined)).toEqual('/abc#anchor');
|
||||
expect(applyTrailingSlash('/abc/#anchor', true)).toEqual('/abc/#anchor');
|
||||
expect(applyTrailingSlash('/abc/#anchor', false)).toEqual('/abc#anchor');
|
||||
expect(applyTrailingSlash('/abc/#anchor', undefined)).toEqual(
|
||||
'/abc/#anchor',
|
||||
);
|
||||
});
|
||||
|
||||
test('should apply to path with ?search', () => {
|
||||
expect(applyTrailingSlash('/abc?search', true)).toEqual('/abc/?search');
|
||||
expect(applyTrailingSlash('/abc?search', false)).toEqual('/abc?search');
|
||||
expect(applyTrailingSlash('/abc?search', undefined)).toEqual('/abc?search');
|
||||
expect(applyTrailingSlash('/abc/?search', true)).toEqual('/abc/?search');
|
||||
expect(applyTrailingSlash('/abc/?search', false)).toEqual('/abc?search');
|
||||
expect(applyTrailingSlash('/abc/?search', undefined)).toEqual(
|
||||
'/abc/?search',
|
||||
);
|
||||
});
|
||||
|
||||
test('should apply to path with ?search#anchor', () => {
|
||||
expect(applyTrailingSlash('/abc?search#anchor', true)).toEqual(
|
||||
'/abc/?search#anchor',
|
||||
);
|
||||
expect(applyTrailingSlash('/abc?search#anchor', false)).toEqual(
|
||||
'/abc?search#anchor',
|
||||
);
|
||||
expect(applyTrailingSlash('/abc?search#anchor', undefined)).toEqual(
|
||||
'/abc?search#anchor',
|
||||
);
|
||||
expect(applyTrailingSlash('/abc/?search#anchor', true)).toEqual(
|
||||
'/abc/?search#anchor',
|
||||
);
|
||||
expect(applyTrailingSlash('/abc/?search#anchor', false)).toEqual(
|
||||
'/abc?search#anchor',
|
||||
);
|
||||
expect(applyTrailingSlash('/abc/?search#anchor', undefined)).toEqual(
|
||||
'/abc/?search#anchor',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export default function applyTrailingSlash(
|
||||
path: string,
|
||||
trailingSlash: boolean | undefined,
|
||||
): string {
|
||||
// Never apply trailing slash to an anchor link
|
||||
if (path.startsWith('#')) {
|
||||
return path;
|
||||
}
|
||||
|
||||
function addTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str : `${str}/`;
|
||||
}
|
||||
function removeTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str.slice(0, -1) : str;
|
||||
}
|
||||
// undefined = legacy retrocompatible behavior
|
||||
if (typeof trailingSlash === 'undefined') {
|
||||
return path;
|
||||
}
|
||||
|
||||
// The trailing slash should be handled before the ?search#hash !
|
||||
const [pathname] = path.split(/[#?]/);
|
||||
const newPathname = trailingSlash
|
||||
? addTrailingSlash(pathname)
|
||||
: removeTrailingSlash(pathname);
|
||||
return path.replace(pathname, newPathname);
|
||||
}
|
|
@ -42,7 +42,7 @@ export default async function serve(
|
|||
}
|
||||
|
||||
const {
|
||||
siteConfig: {baseUrl},
|
||||
siteConfig: {baseUrl, trailingSlash},
|
||||
} = await loadSiteConfig({
|
||||
siteDir,
|
||||
customConfigFilePath: cliOptions.config,
|
||||
|
@ -67,6 +67,7 @@ export default async function serve(
|
|||
serveHandler(req, res, {
|
||||
cleanUrls: true,
|
||||
public: dir,
|
||||
trailingSlash,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -117,6 +117,7 @@ const ConfigSchema = Joi.object({
|
|||
favicon: Joi.string().required(),
|
||||
title: Joi.string().required(),
|
||||
url: URISchema.required(),
|
||||
trailingSlash: Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior!
|
||||
i18n: I18N_CONFIG_SCHEMA,
|
||||
onBrokenLinks: Joi.string()
|
||||
.equal('ignore', 'log', 'warn', 'error', 'throw')
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* 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 applyRouteTrailingSlash from '../applyRouteTrailingSlash';
|
||||
import {RouteConfig} from '@docusaurus/types';
|
||||
|
||||
function route(path: string, subRoutes?: string[]): RouteConfig {
|
||||
const result: RouteConfig = {path, component: 'any'};
|
||||
|
||||
if (subRoutes) {
|
||||
result.routes = subRoutes.map((subRoute) => route(subRoute));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
describe('applyRouteTrailingSlash', () => {
|
||||
test('apply to empty', () => {
|
||||
expect(applyRouteTrailingSlash(route(''), true)).toEqual(route('/'));
|
||||
expect(applyRouteTrailingSlash(route(''), false)).toEqual(route(''));
|
||||
expect(applyRouteTrailingSlash(route(''), undefined)).toEqual(route(''));
|
||||
});
|
||||
|
||||
test('apply to /', () => {
|
||||
expect(applyRouteTrailingSlash(route('/'), true)).toEqual(route('/'));
|
||||
expect(applyRouteTrailingSlash(route('/'), false)).toEqual(route('/'));
|
||||
expect(applyRouteTrailingSlash(route('/'), undefined)).toEqual(route('/'));
|
||||
});
|
||||
|
||||
test('apply to /abc', () => {
|
||||
expect(applyRouteTrailingSlash(route('/abc'), true)).toEqual(
|
||||
route('/abc/'),
|
||||
);
|
||||
expect(applyRouteTrailingSlash(route('/abc'), false)).toEqual(
|
||||
route('/abc'),
|
||||
);
|
||||
expect(applyRouteTrailingSlash(route('/abc'), undefined)).toEqual(
|
||||
route('/abc'),
|
||||
);
|
||||
});
|
||||
|
||||
test('apply to /abc/', () => {
|
||||
expect(applyRouteTrailingSlash(route('/abc/'), true)).toEqual(
|
||||
route('/abc/'),
|
||||
);
|
||||
expect(applyRouteTrailingSlash(route('/abc/'), false)).toEqual(
|
||||
route('/abc'),
|
||||
);
|
||||
expect(applyRouteTrailingSlash(route('/abc/'), undefined)).toEqual(
|
||||
route('/abc/'),
|
||||
);
|
||||
});
|
||||
|
||||
test('apply to /abc?search#anchor', () => {
|
||||
expect(applyRouteTrailingSlash(route('/abc?search#anchor'), true)).toEqual(
|
||||
route('/abc/?search#anchor'),
|
||||
);
|
||||
expect(applyRouteTrailingSlash(route('/abc?search#anchor'), false)).toEqual(
|
||||
route('/abc?search#anchor'),
|
||||
);
|
||||
expect(
|
||||
applyRouteTrailingSlash(route('/abc?search#anchor'), undefined),
|
||||
).toEqual(route('/abc?search#anchor'));
|
||||
});
|
||||
|
||||
test('apply to /abc/?search#anchor', () => {
|
||||
expect(applyRouteTrailingSlash(route('/abc/?search#anchor'), true)).toEqual(
|
||||
route('/abc/?search#anchor'),
|
||||
);
|
||||
expect(
|
||||
applyRouteTrailingSlash(route('/abc/?search#anchor'), false),
|
||||
).toEqual(route('/abc?search#anchor'));
|
||||
expect(
|
||||
applyRouteTrailingSlash(route('/abc/?search#anchor'), undefined),
|
||||
).toEqual(route('/abc/?search#anchor'));
|
||||
});
|
||||
|
||||
test('apply to subroutes', () => {
|
||||
expect(
|
||||
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), true),
|
||||
).toEqual(route('/abc/', ['/abc/1/', '/abc/2/']));
|
||||
expect(
|
||||
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), false),
|
||||
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
|
||||
expect(
|
||||
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), undefined),
|
||||
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
|
||||
});
|
||||
|
||||
test('apply for complex case', () => {
|
||||
expect(
|
||||
applyRouteTrailingSlash(
|
||||
route('/abc?search#anchor', ['/abc/1?search', '/abc/2#anchor']),
|
||||
true,
|
||||
),
|
||||
).toEqual(
|
||||
route('/abc/?search#anchor', ['/abc/1/?search', '/abc/2/#anchor']),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* 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 {RouteConfig} from '@docusaurus/types';
|
||||
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
|
||||
|
||||
export default function applyRouteTrailingSlash(
|
||||
route: RouteConfig,
|
||||
trailingSlash: boolean | undefined,
|
||||
) {
|
||||
// Never transform "/" to "" => cause router issues ("" catch everything)
|
||||
if (route.path === '/') {
|
||||
return route;
|
||||
}
|
||||
|
||||
function getNewRoutePath() {
|
||||
// undefined = legacy retrocompatible behavior
|
||||
if (typeof trailingSlash === 'undefined') {
|
||||
return route.path;
|
||||
}
|
||||
// The trailing slash should be handled before the ?search#hash !
|
||||
// For routing #anchor is normally not possible, but querystring remains possible
|
||||
const [pathname] = route.path.split(/[#?]/);
|
||||
const newPathname = trailingSlash
|
||||
? addTrailingSlash(pathname)
|
||||
: removeTrailingSlash(pathname);
|
||||
return route.path.replace(pathname, newPathname);
|
||||
}
|
||||
|
||||
return {
|
||||
...route,
|
||||
path: getNewRoutePath(),
|
||||
...(route.routes && {
|
||||
routes: route.routes.map((subroute) =>
|
||||
applyRouteTrailingSlash(subroute, trailingSlash),
|
||||
),
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -22,6 +22,7 @@ import chalk from 'chalk';
|
|||
import {DEFAULT_PLUGIN_ID} from '../../constants';
|
||||
import {chain} from 'lodash';
|
||||
import {localizePluginTranslationFile} from '../translations/translations';
|
||||
import applyRouteTrailingSlash from './applyRouteTrailingSlash';
|
||||
|
||||
export function sortConfig(routeConfigs: RouteConfig[]): void {
|
||||
// Sort the route config. This ensures that route with nested
|
||||
|
@ -136,8 +137,16 @@ export async function loadPlugins({
|
|||
const dataDirRoot = path.join(context.generatedFilesDir, plugin.name);
|
||||
const dataDir = path.join(dataDirRoot, pluginId);
|
||||
|
||||
const addRoute: PluginContentLoadedActions['addRoute'] = (config) =>
|
||||
pluginsRouteConfigs.push(config);
|
||||
const addRoute: PluginContentLoadedActions['addRoute'] = (
|
||||
initialRouteConfig,
|
||||
) => {
|
||||
// Trailing slash behavior is handled in a generic way for all plugins
|
||||
const finalRouteConfig = applyRouteTrailingSlash(
|
||||
initialRouteConfig,
|
||||
context.siteConfig.trailingSlash,
|
||||
);
|
||||
pluginsRouteConfigs.push(finalRouteConfig);
|
||||
};
|
||||
|
||||
const createData: PluginContentLoadedActions['createData'] = async (
|
||||
name,
|
||||
|
|
|
@ -10,6 +10,7 @@ Object {
|
|||
"@docusaurus/Link": "../../client/exports/Link.tsx",
|
||||
"@docusaurus/Noop": "../../client/exports/Noop.ts",
|
||||
"@docusaurus/Translate": "../../client/exports/Translate.tsx",
|
||||
"@docusaurus/applyTrailingSlash": "../../client/exports/applyTrailingSlash.tsx",
|
||||
"@docusaurus/constants": "../../client/exports/constants.ts",
|
||||
"@docusaurus/context": "../../client/exports/context.ts",
|
||||
"@docusaurus/isInternalUrl": "../../client/exports/isInternalUrl.ts",
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import StaticSiteGeneratorPlugin from '@endiliey/static-site-generator-webpack-plugin';
|
||||
import {Configuration} from 'webpack';
|
||||
import merge from 'webpack-merge';
|
||||
|
||||
|
@ -16,6 +15,9 @@ import WaitPlugin from './plugins/WaitPlugin';
|
|||
import LogPlugin from './plugins/LogPlugin';
|
||||
import {NODE_MAJOR_VERSION, NODE_MINOR_VERSION} from '../constants';
|
||||
|
||||
// Forked for Docusaurus: https://github.com/slorber/static-site-generator-webpack-plugin
|
||||
import StaticSiteGeneratorPlugin from '@slorber/static-site-generator-webpack-plugin';
|
||||
|
||||
export default function createServerConfig({
|
||||
props,
|
||||
onLinksCollected = () => {},
|
||||
|
@ -31,7 +33,7 @@ export default function createServerConfig({
|
|||
preBodyTags,
|
||||
postBodyTags,
|
||||
ssrTemplate,
|
||||
siteConfig: {noIndex},
|
||||
siteConfig: {noIndex, trailingSlash},
|
||||
} = props;
|
||||
const config = createBaseConfig(props, true);
|
||||
|
||||
|
@ -75,6 +77,7 @@ export default function createServerConfig({
|
|||
noIndex,
|
||||
},
|
||||
paths: ssgPaths,
|
||||
preferFoldersOutput: trailingSlash,
|
||||
}),
|
||||
|
||||
// Show compilation progress bar.
|
||||
|
|
|
@ -81,6 +81,24 @@ module.exports = {
|
|||
|
||||
## Optional fields {#optional-fields}
|
||||
|
||||
### `trailingSlash` {#trailing-slash}
|
||||
|
||||
- Type: `boolean | undefined`
|
||||
|
||||
Allow to customize the presence/absence of a trailing slash at the end of URLs/links, and how static HTML files are generated:
|
||||
|
||||
- `undefined` (default): keeps URLs untouched, and emit `/docs/myDoc/index.html` for `/docs/myDoc.md`
|
||||
- `true`: add trailing slashes to URLs/links, and emit `/docs/myDoc/index.html` for `/docs/myDoc.md`
|
||||
- `false`: remove trailing slashes from URLs/links, and emit `/docs/myDoc.html` for `/docs/myDoc.md`
|
||||
|
||||
:::tip
|
||||
|
||||
Each static hosting provider serve static files differently (this behavior may even change over time).
|
||||
|
||||
Refer to the [deployment guide](../deployment.mdx) and [slorber/trailing-slash-guide](https://github.com/slorber/trailing-slash-guide) to choose the appropriate setting.
|
||||
|
||||
:::
|
||||
|
||||
### `i18n` {#i18n}
|
||||
|
||||
- Type: `Object`
|
||||
|
|
|
@ -4,7 +4,7 @@ title: '📦 plugin-google-analytics'
|
|||
slug: '/api/plugins/@docusaurus/plugin-google-analytics'
|
||||
---
|
||||
|
||||
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag) instead.
|
||||
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag.md) instead.
|
||||
|
||||
## Installation {#installation}
|
||||
|
||||
|
|
|
@ -9,32 +9,60 @@ To build the static files of your website for production, run:
|
|||
npm run build
|
||||
```
|
||||
|
||||
Once it finishes, the static files will be generated within the `build/` directory.
|
||||
Once it finishes, the static files will be generated within the `build` directory.
|
||||
|
||||
You can deploy your site to static site hosting services such as [Vercel](https://vercel.com/), [GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Render](https://render.com/docs/static-sites), and [Surge](https://surge.sh/help/getting-started-with-surge). Docusaurus sites are statically rendered so they work without JavaScript too!
|
||||
:::note
|
||||
|
||||
## Testing Build Local {#testing-build-local}
|
||||
The only responsibility of Docusaurus is to build your site and emit static files in `build`.
|
||||
|
||||
It is important to test build before deploying to a production. Docusaurus includes a [`docusaurus serve`](cli.md#docusaurus-serve) command to test build locally.
|
||||
It is now up to you to choose how to host those static files.
|
||||
|
||||
:::
|
||||
|
||||
You can deploy your site to static site hosting services such as [Vercel](https://vercel.com/), [GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Render](https://render.com/docs/static-sites), [Surge](https://surge.sh/help/getting-started-with-surge)...
|
||||
|
||||
A Docusaurus site is statically rendered, and it can generally work without JavaScript!
|
||||
|
||||
## Testing your Build Locally {#testing-build-locally}
|
||||
|
||||
It is important to test your build locally before deploying to production.
|
||||
|
||||
Docusaurus includes a [`docusaurus serve`](cli.md#docusaurus-serve) command for that:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run serve
|
||||
```
|
||||
|
||||
## Self Hosting {#self-hosting}
|
||||
## Trailing slash configuration {#trailing-slashes}
|
||||
|
||||
:::warning
|
||||
Docusaurus has a [`trailingSlash` config](./api/docusaurus.config.js.md#trailing-slash), to allow customizing URLs/links and emitted filename patterns.
|
||||
|
||||
It is not the most performant solution
|
||||
The default value generally works fine.
|
||||
|
||||
Unfortunately, each static hosting provider has a **different behavior**, and deploying the exact same site to various hosts can lead to distinct results.
|
||||
|
||||
Depending on your host, it can be useful to change this config.
|
||||
|
||||
:::tip
|
||||
|
||||
Use [slorber/trailing-slash-guide](https://github.com/slorber/trailing-slash-guide) to understand better the behavior of your host and configure `trailingSlash` appropriately.
|
||||
|
||||
:::
|
||||
|
||||
Docusaurus can be self hosted using [`docusaurus serve`](cli.md#docusaurus-serve). Change port using `--port` and `--host` to change host.
|
||||
## Self-Hosting {#self-hosting}
|
||||
|
||||
Docusaurus can be self-hosted using [`docusaurus serve`](cli.md#docusaurus-serve). Change port using `--port` and `--host` to change host.
|
||||
|
||||
```bash npm2yarn
|
||||
npm run serve -- --build --port 80 --host 0.0.0.0
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
It is not the best option, compared to a static hosting provider / CDN.
|
||||
|
||||
:::
|
||||
|
||||
## Deploying to GitHub Pages {#deploying-to-github-pages}
|
||||
|
||||
Docusaurus provides an easy way to publish to [GitHub Pages](https://pages.github.com/). Which is hosting that comes for free with every GitHub repository.
|
||||
|
@ -54,10 +82,18 @@ First, modify your `docusaurus.config.js` and add the required params:
|
|||
|
||||
In case you want to use your custom domain for GitHub Pages, create a `CNAME` file in the `static` directory. Anything within the `static` directory will be copied to the root of the `build` directory for deployment.
|
||||
|
||||
When using a custom domain, you should be able to move back from `baseUrl: '/projectName/'` to `baseUrl: '/'`
|
||||
|
||||
You may refer to GitHub Pages' documentation [User, Organization, and Project Pages](https://help.github.com/en/articles/user-organization-and-project-pages) for more details.
|
||||
|
||||
:::
|
||||
|
||||
:::caution
|
||||
|
||||
GitHub Pages adds a trailing slash to Docusaurus URLs by default. Adjusting the `trailingSlash` setting can be useful.
|
||||
|
||||
:::
|
||||
|
||||
Example:
|
||||
|
||||
```jsx {3-6} title="docusaurus.config.js"
|
||||
|
@ -67,6 +103,7 @@ module.exports = {
|
|||
baseUrl: '/',
|
||||
projectName: 'endiliey.github.io',
|
||||
organizationName: 'endiliey',
|
||||
trailingSlash: false,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
@ -337,9 +374,17 @@ If you did not configure these build options, you may still go to "Site settings
|
|||
|
||||
Once properly configured with the above options, your site should deploy and automatically redeploy upon merging to your deploy branch, which defaults to `master`.
|
||||
|
||||
:::important
|
||||
:::warning
|
||||
|
||||
Make sure to disable Netlify setting `Pretty URLs` to prevent lowercased URLs, unnecessary redirects and 404 errors.
|
||||
By default, Netlify adds trailing slashes to Docusaurus URLs.
|
||||
|
||||
It is recommended to disable the Netlify setting `Post Processing > Asset Optimization > Pretty Urls` to prevent lowercased URLs, unnecessary redirects and 404 errors.
|
||||
|
||||
**Be very careful**: the `Disable asset optimization` global checkbox is broken and does not really disable the `Pretty URLs` setting in practice. Please make sure to **uncheck it independently**.
|
||||
|
||||
If you want to keep the `Pretty Urls` Netlify setting on, adjust the `trailingSlash` Docusaurus config appropriately.
|
||||
|
||||
Refer to [slorber/trailing-slash-guide](https://github.com/slorber/trailing-slash-guide) for more information.
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -13,16 +13,27 @@ Markdown docs have their own [Markdown frontmatter](../../api/plugins/plugin-con
|
|||
|
||||
## Referencing other documents {#referencing-other-documents}
|
||||
|
||||
If you want to reference another document file, you could use the name of the document you want to reference. Docusaurus will convert the file path to be the final website path (and remove the `.md`).
|
||||
If you want to reference another document file, you could use the relative path of the document you want to link to.
|
||||
|
||||
For example, if you are in `doc2.md` and you want to reference `doc1.md` and `folder/doc3.md`:
|
||||
Docusaurus will convert the file path to be the final document url path (and remove the `.md` extension).
|
||||
|
||||
For example, if you are in `folder/doc1.md` and you want to reference `folder/doc2.md`, `folder/subfolder/doc3.md` and `otherFolder/doc4.md`:
|
||||
|
||||
```md
|
||||
I am referencing a [document](doc1.md). Reference to another [document in a folder](folder/doc3.md).
|
||||
I am referencing a [document](doc2.md).
|
||||
|
||||
[Relative document](../doc2.md) referencing works as well.
|
||||
Reference to another [document in a subfolder](subfolder/doc3.md).
|
||||
|
||||
[Relative document](../otherFolder/doc4.md) referencing works as well.
|
||||
```
|
||||
|
||||
One benefit of this approach is that the links to external files will still work if you are viewing the file on GitHub.
|
||||
:::tip
|
||||
|
||||
Another benefit, for versioned docs, is that one versioned doc will link to another doc of the exact same version.
|
||||
It is better to use relative file paths links instead of relative links:
|
||||
|
||||
- links will keep working on the GitHub interface
|
||||
- you can customize the document slugs without having to update all the links
|
||||
- a versioned doc will link to another doc of the exact same version
|
||||
- relative links are very likely to break if you update the [`trailingSlash` config](../../api/docusaurus.config.js.md#trailing-slash)
|
||||
|
||||
:::
|
||||
|
|
|
@ -105,7 +105,7 @@ module.exports = {
|
|||
|
||||
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
|
||||
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
|
||||
```css title="/src/css/custom.css"
|
||||
html[data-theme='light'] .DocSearch {
|
||||
|
|
|
@ -54,6 +54,10 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
|
|||
baseUrl,
|
||||
baseUrlIssueBanner: true,
|
||||
url: 'https://docusaurus.io',
|
||||
// Dogfood both settings:
|
||||
// - force trailing slashes for deploy previews
|
||||
// - avoid trailing slashes in prod
|
||||
trailingSlash: isDeployPreview,
|
||||
stylesheets: [
|
||||
{
|
||||
href: 'https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css',
|
||||
|
@ -128,7 +132,11 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
|
|||
],
|
||||
[
|
||||
'@docusaurus/plugin-client-redirects',
|
||||
{
|
||||
isDeployPreview
|
||||
? // Plugin is disabled for deploy preview because we use trailing slashes on deploy previews
|
||||
// This plugin is sensitive to trailing slashes, and we don't care much about making it work on deploy previews
|
||||
{}
|
||||
: {
|
||||
fromExtensions: ['html'],
|
||||
createRedirects: function (path) {
|
||||
// redirect to /docs from /docs/introduction,
|
||||
|
|
|
@ -105,7 +105,7 @@ module.exports = {
|
|||
|
||||
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
|
||||
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
|
||||
```css title="/src/css/custom.css"
|
||||
html[data-theme='light'] .DocSearch {
|
||||
|
|
|
@ -105,7 +105,7 @@ module.exports = {
|
|||
|
||||
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
|
||||
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
|
||||
```css title="/src/css/custom.css"
|
||||
html[data-theme='light'] .DocSearch {
|
||||
|
|
|
@ -105,7 +105,7 @@ module.exports = {
|
|||
|
||||
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
|
||||
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
|
||||
```css title="/src/css/custom.css"
|
||||
html[data-theme='light'] .DocSearch {
|
||||
|
|
|
@ -4,7 +4,7 @@ title: '📦 plugin-google-analytics'
|
|||
slug: '/api/plugins/@docusaurus/plugin-google-analytics'
|
||||
---
|
||||
|
||||
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag) instead.
|
||||
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag.md) instead.
|
||||
|
||||
## Installation {#installation}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ module.exports = {
|
|||
|
||||
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
|
||||
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
|
||||
|
||||
```css title="/src/css/custom.css"
|
||||
html[data-theme='light'] .DocSearch {
|
||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -1296,17 +1296,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@endiliey/react-ideal-image/-/react-ideal-image-0.0.11.tgz#dc3803d04e1409cf88efa4bba0f67667807bdf27"
|
||||
integrity sha512-QxMjt/Gvur/gLxSoCy7VIyGGGrGmDN+VHcXkN3R2ApoWX0EYUE+hMgPHSW/PV6VVebZ1Nd4t2UnGRBDihu16JQ==
|
||||
|
||||
"@endiliey/static-site-generator-webpack-plugin@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz#94bfe58fd83aeda355de797fcb5112adaca3a6b1"
|
||||
integrity sha512-3MBqYCs30qk1OBRC697NqhGouYbs71D1B8hrk/AFJC6GwF2QaJOQZtA1JYAaGSe650sZ8r5ppRTtCRXepDWlng==
|
||||
dependencies:
|
||||
bluebird "^3.7.1"
|
||||
cheerio "^0.22.0"
|
||||
eval "^0.1.4"
|
||||
url "^0.11.0"
|
||||
webpack-sources "^1.4.3"
|
||||
|
||||
"@eslint/eslintrc@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"
|
||||
|
@ -3058,6 +3047,17 @@
|
|||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@slorber/static-site-generator-webpack-plugin@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz#0c8852146441aaa683693deaa5aee2f991d94841"
|
||||
integrity sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw==
|
||||
dependencies:
|
||||
bluebird "^3.7.1"
|
||||
cheerio "^0.22.0"
|
||||
eval "^0.1.4"
|
||||
url "^0.11.0"
|
||||
webpack-sources "^1.4.3"
|
||||
|
||||
"@stylelint/postcss-css-in-js@^0.37.2":
|
||||
version "0.37.2"
|
||||
resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2"
|
||||
|
|
Loading…
Add table
Reference in a new issue