mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
fix(v2): fix recent baseurl issues (#3093)
* try to fix the baseUrl related issues * fix some newly detected broken links * fix baseurl and broken link issues * try to configure netlify to use baseUrl deployment * add proper netlify settings? * add proper netlify settings? * add proper netlify settings? * test commit * try to fix the redirects * cleanup working conf * minor redirect fix
This commit is contained in:
parent
27f384a67c
commit
811c7ae4e9
30 changed files with 2639 additions and 146 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -27,3 +27,6 @@ packages/docusaurus-plugin-debug/lib/
|
||||||
packages/docusaurus-plugin-sitemap/lib/
|
packages/docusaurus-plugin-sitemap/lib/
|
||||||
packages/docusaurus-plugin-ideal-image/lib/
|
packages/docusaurus-plugin-ideal-image/lib/
|
||||||
packages/docusaurus-theme-classic/lib/
|
packages/docusaurus-theme-classic/lib/
|
||||||
|
|
||||||
|
website/netlifyDeploy
|
||||||
|
_redirects
|
||||||
|
|
26
netlify.toml
Normal file
26
netlify.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
# Note: this file's config override the Netlify UI admin config
|
||||||
|
|
||||||
|
# default/production build
|
||||||
|
[build]
|
||||||
|
base = "/"
|
||||||
|
command = "yarn netlify:build:production"
|
||||||
|
publish = "website/build"
|
||||||
|
|
||||||
|
# we build deploy previews with a /build/ baseUrl on purpose
|
||||||
|
# permits to test that baseUrl works fine (this often breaks!)
|
||||||
|
[context.deploy-preview]
|
||||||
|
command = "yarn netlify:build:deployPreview"
|
||||||
|
publish = "website/netlifyDeploy"
|
||||||
|
|
||||||
|
# TODO this does not seem to work
|
||||||
|
# workaroud: a _redirect file is created in website/netlifyDeploy
|
||||||
|
# can't we have context-based redirects with Netlify? :'(
|
||||||
|
[[context.deploy-preview.redirects]]
|
||||||
|
from = "/build/*"
|
||||||
|
to = "/build/404.html"
|
||||||
|
status = 200
|
||||||
|
[[context.deploy-preview.redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/build/"
|
||||||
|
|
12
package.json
12
package.json
|
@ -7,20 +7,29 @@
|
||||||
"packages/docusaurus-init/templates/*"
|
"packages/docusaurus-init/templates/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"testBaseUrl": "yarn build:v2:baseUrl && yarn serve:v2:baseUrl",
|
||||||
"start": "yarn build:packages && yarn start:v2",
|
"start": "yarn build:packages && yarn start:v2",
|
||||||
"start:v1": "yarn workspace docusaurus-1-website start",
|
"start:v1": "yarn workspace docusaurus-1-website start",
|
||||||
"start:v2": "yarn workspace docusaurus-2-website start",
|
"start:v2": "yarn workspace docusaurus-2-website start",
|
||||||
"start:v2:watch": "nodemon --watch \"./packages/*/lib/**/*.*\" --exec \"yarn start:v2\"",
|
"start:v2:watch": "nodemon --watch \"./packages/*/lib/**/*.*\" --exec \"yarn start:v2\"",
|
||||||
|
"start:v2:baseUrl": "BASE_URL='/build/' yarn start:v2",
|
||||||
"build": "yarn build:packages && yarn build:v2",
|
"build": "yarn build:packages && yarn build:v2",
|
||||||
"build:packages": "lerna run build --no-private",
|
"build:packages": "lerna run build --no-private",
|
||||||
"build:v1": "yarn workspace docusaurus-1-website build",
|
"build:v1": "yarn workspace docusaurus-1-website build",
|
||||||
"build:v2": "yarn workspace docusaurus-2-website build",
|
"build:v2": "yarn workspace docusaurus-2-website build",
|
||||||
|
"build:v2:baseUrl": "BASE_URL='/build/' yarn build:v2",
|
||||||
"serve:v1": "serve website-1.x/build/docusaurus",
|
"serve:v1": "serve website-1.x/build/docusaurus",
|
||||||
"serve:v2": "serve website/build",
|
"serve:v2": "serve website/build",
|
||||||
|
"serve:v2:baseUrl": "serve website",
|
||||||
"serve:v2:ssl": "yarn serve:v2:ssl:gencert && yarn serve:v2:ssl:message && yarn serve:v2:ssl:serve",
|
"serve:v2:ssl": "yarn serve:v2:ssl:gencert && yarn serve:v2:ssl:message && yarn serve:v2:ssl:serve",
|
||||||
"serve:v2:ssl:gencert": "openssl req -x509 -nodes -days 365 -newkey rsa:4096 -subj \"/C=US/ST=Docusaurus/L=Anywhere/O=Dis/CN=localhost\" -keyout ./website/.docusaurus/selfsigned.key -out ./website/.docusaurus/selfsigned.crt",
|
"serve:v2:ssl:gencert": "openssl req -x509 -nodes -days 365 -newkey rsa:4096 -subj \"/C=US/ST=Docusaurus/L=Anywhere/O=Dis/CN=localhost\" -keyout ./website/.docusaurus/selfsigned.key -out ./website/.docusaurus/selfsigned.crt",
|
||||||
"serve:v2:ssl:message": "echo '\n\n\nServing Docusaurus with HTTPS on localhost requires to disable the Chrome security: chrome://flags/#allow-insecure-localhost\n\n\n'",
|
"serve:v2:ssl:message": "echo '\n\n\nServing Docusaurus with HTTPS on localhost requires to disable the Chrome security: chrome://flags/#allow-insecure-localhost\n\n\n'",
|
||||||
"serve:v2:ssl:serve": "serve website/build --ssl-cert ./website/.docusaurus/selfsigned.crt --ssl-key ./website/.docusaurus/selfsigned.key",
|
"serve:v2:ssl:serve": "serve website/build --ssl-cert ./website/.docusaurus/selfsigned.crt --ssl-key ./website/.docusaurus/selfsigned.key",
|
||||||
|
"netlify:build:production": "yarn build:v2",
|
||||||
|
"netlify:build:deployPreview": "yarn build:v2:baseUrl && yarn netlify:build:deployPreview:moveBuild && yarn netlify:build:deployPreview:redirects",
|
||||||
|
"netlify:build:deployPreview:moveBuild": "yarn rimraf website/netlifyDeploy && mkdir website/netlifyDeploy && mv website/build website/netlifyDeploy",
|
||||||
|
"netlify:build:deployPreview:redirects": "echo 'Writing Netlify baseUrl deployPreview _redirects file' && echo '/build/* /build/404.html 200' >> website/netlifyDeploy/_redirects && echo '/* /build/' >> website/netlifyDeploy/_redirects",
|
||||||
|
"netlify:test": "yarn netlify:build:deployPreview && yarn netlify dev --debug",
|
||||||
"changelog": "lerna-changelog",
|
"changelog": "lerna-changelog",
|
||||||
"postinstall": "yarn build:packages",
|
"postinstall": "yarn build:packages",
|
||||||
"prettier": "prettier --config .prettierrc --write \"**/*.{js,ts}\"",
|
"prettier": "prettier --config .prettierrc --write \"**/*.{js,ts}\"",
|
||||||
|
@ -37,8 +46,8 @@
|
||||||
"clear": "yarn rimraf website/.docusaurus && rimraf -rf website/node_modules/.cache && yarn lerna exec 'yarn rimraf lib' --ignore docusaurus"
|
"clear": "yarn rimraf website/.docusaurus && rimraf -rf website/node_modules/.cache && yarn lerna exec 'yarn rimraf lib' --ignore docusaurus"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
|
||||||
"@babel/cli": "^7.9.0",
|
"@babel/cli": "^7.9.0",
|
||||||
|
"@babel/core": "^7.9.0",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||||
"@babel/preset-typescript": "^7.9.0",
|
"@babel/preset-typescript": "^7.9.0",
|
||||||
|
@ -88,6 +97,7 @@
|
||||||
"lerna": "^3.19.0",
|
"lerna": "^3.19.0",
|
||||||
"lerna-changelog": "^1.0.1",
|
"lerna-changelog": "^1.0.1",
|
||||||
"lint-staged": "^10.1.2",
|
"lint-staged": "^10.1.2",
|
||||||
|
"netlify-cli": "^2.58.0",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.4",
|
||||||
"prettier": "^2.0.2",
|
"prettier": "^2.0.2",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
|
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
|
||||||
|
"@docusaurus/core": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/types": "^2.0.0-alpha.58",
|
"@docusaurus/types": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/utils": "^2.0.0-alpha.58",
|
"@docusaurus/utils": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/core": "2.0.0-alpha.58",
|
|
||||||
"@hapi/joi": "^17.1.1",
|
"@hapi/joi": "^17.1.1",
|
||||||
"feed": "^4.1.0",
|
"feed": "^4.1.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
|
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
|
||||||
|
"@docusaurus/core": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/types": "^2.0.0-alpha.58",
|
"@docusaurus/types": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/utils": "^2.0.0-alpha.58",
|
"@docusaurus/utils": "^2.0.0-alpha.58",
|
||||||
"execa": "^3.4.0",
|
"execa": "^3.4.0",
|
||||||
|
@ -33,8 +34,7 @@
|
||||||
"lodash.pickby": "^4.6.0",
|
"lodash.pickby": "^4.6.0",
|
||||||
"lodash.sortby": "^4.6.0",
|
"lodash.sortby": "^4.6.0",
|
||||||
"remark-admonitions": "^1.2.1",
|
"remark-admonitions": "^1.2.1",
|
||||||
"shelljs": "^0.8.4",
|
"shelljs": "^0.8.4"
|
||||||
"@docusaurus/core": "^2.0.0-alpha.58"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
|
|
|
@ -178,7 +178,7 @@ Object {
|
||||||
"pluginName": Object {
|
"pluginName": Object {
|
||||||
"pluginId": Object {
|
"pluginId": Object {
|
||||||
"latestVersionName": null,
|
"latestVersionName": null,
|
||||||
"path": "docs",
|
"path": "/docs",
|
||||||
"versions": Array [
|
"versions": Array [
|
||||||
Object {
|
Object {
|
||||||
"docs": Array [
|
"docs": Array [
|
||||||
|
@ -469,7 +469,7 @@ Object {
|
||||||
"pluginName": Object {
|
"pluginName": Object {
|
||||||
"pluginId": Object {
|
"pluginId": Object {
|
||||||
"latestVersionName": "1.0.1",
|
"latestVersionName": "1.0.1",
|
||||||
"path": "docs",
|
"path": "/docs",
|
||||||
"versions": Array [
|
"versions": Array [
|
||||||
Object {
|
Object {
|
||||||
"docs": Array [
|
"docs": Array [
|
||||||
|
|
|
@ -20,12 +20,12 @@ describe('docsClientUtils', () => {
|
||||||
test('getActivePlugin', () => {
|
test('getActivePlugin', () => {
|
||||||
const data: Record<string, GlobalPluginData> = {
|
const data: Record<string, GlobalPluginData> = {
|
||||||
pluginIosId: {
|
pluginIosId: {
|
||||||
path: 'ios',
|
path: '/ios',
|
||||||
latestVersionName: 'xyz',
|
latestVersionName: 'xyz',
|
||||||
versions: [],
|
versions: [],
|
||||||
},
|
},
|
||||||
pluginAndroidId: {
|
pluginAndroidId: {
|
||||||
path: 'android',
|
path: '/android',
|
||||||
latestVersionName: 'xyz',
|
latestVersionName: 'xyz',
|
||||||
versions: [],
|
versions: [],
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const getActivePlugin = (
|
||||||
const activeEntry = Object.entries(allPluginDatas).find(
|
const activeEntry = Object.entries(allPluginDatas).find(
|
||||||
([_id, pluginData]) => {
|
([_id, pluginData]) => {
|
||||||
return !!matchPath(pathname, {
|
return !!matchPath(pathname, {
|
||||||
path: `/${pluginData.path}`,
|
path: pluginData.path,
|
||||||
exact: false,
|
exact: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -323,7 +323,7 @@ Available document ids=
|
||||||
const {addRoute, createData, setGlobalData} = actions;
|
const {addRoute, createData, setGlobalData} = actions;
|
||||||
|
|
||||||
const pluginInstanceGlobalData: GlobalPluginData = {
|
const pluginInstanceGlobalData: GlobalPluginData = {
|
||||||
path: options.path,
|
path: normalizeUrl([baseUrl, options.path]),
|
||||||
latestVersionName: versioning.latestVersion,
|
latestVersionName: versioning.latestVersion,
|
||||||
// Initialized empty, will be mutated
|
// Initialized empty, will be mutated
|
||||||
versions: [],
|
versions: [],
|
||||||
|
|
|
@ -24,10 +24,6 @@ export default {
|
||||||
return children;
|
return children;
|
||||||
},
|
},
|
||||||
a: (props: ComponentProps<'a'>): JSX.Element => {
|
a: (props: ComponentProps<'a'>): JSX.Element => {
|
||||||
if (/\.[^./]+$/.test(props.href || '')) {
|
|
||||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
|
||||||
return <a {...props} />;
|
|
||||||
}
|
|
||||||
return <Link {...props} />;
|
return <Link {...props} />;
|
||||||
},
|
},
|
||||||
pre: (props: ComponentProps<'div'>): JSX.Element => (
|
pre: (props: ComponentProps<'div'>): JSX.Element => (
|
||||||
|
|
|
@ -28,10 +28,8 @@ function NavLink({
|
||||||
activeClassName?: string;
|
activeClassName?: string;
|
||||||
prependBaseUrlToHref?: string;
|
prependBaseUrlToHref?: string;
|
||||||
} & ComponentProps<'a'>) {
|
} & ComponentProps<'a'>) {
|
||||||
const toUrl = useBaseUrl(to);
|
|
||||||
const activeBaseUrl = useBaseUrl(activeBasePath);
|
const activeBaseUrl = useBaseUrl(activeBasePath);
|
||||||
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
|
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
{...(href
|
{...(href
|
||||||
|
@ -43,7 +41,7 @@ function NavLink({
|
||||||
: {
|
: {
|
||||||
isNavLink: true,
|
isNavLink: true,
|
||||||
activeClassName,
|
activeClassName,
|
||||||
to: toUrl,
|
to,
|
||||||
...(activeBasePath || activeBaseRegex
|
...(activeBasePath || activeBaseRegex
|
||||||
? {
|
? {
|
||||||
isActive: (_match, location) =>
|
isActive: (_match, location) =>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {NavLink, Link as RRLink} from 'react-router-dom';
|
||||||
import isInternalUrl from './isInternalUrl';
|
import isInternalUrl from './isInternalUrl';
|
||||||
import ExecutionEnvironment from './ExecutionEnvironment';
|
import ExecutionEnvironment from './ExecutionEnvironment';
|
||||||
import {useLinksCollector} from '../LinksCollector';
|
import {useLinksCollector} from '../LinksCollector';
|
||||||
|
import {useBaseUrlUtils} from './useBaseUrl';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -21,15 +22,37 @@ declare global {
|
||||||
interface Props {
|
interface Props {
|
||||||
readonly isNavLink?: boolean;
|
readonly isNavLink?: boolean;
|
||||||
readonly to?: string;
|
readonly to?: string;
|
||||||
readonly activeClassName?: string;
|
|
||||||
readonly href?: string;
|
readonly href?: string;
|
||||||
|
readonly activeClassName?: string;
|
||||||
readonly children?: ReactNode;
|
readonly children?: ReactNode;
|
||||||
|
|
||||||
|
// escape hatch in case broken links check is annoying for a specific link
|
||||||
|
readonly 'data-noBrokenLinkCheck'?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Link({isNavLink, activeClassName, ...props}: Props): JSX.Element {
|
function Link({
|
||||||
|
isNavLink,
|
||||||
|
to,
|
||||||
|
href,
|
||||||
|
activeClassName,
|
||||||
|
'data-noBrokenLinkCheck': noBrokenLinkCheck,
|
||||||
|
...props
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const {withBaseUrl} = useBaseUrlUtils();
|
||||||
const linksCollector = useLinksCollector();
|
const linksCollector = useLinksCollector();
|
||||||
const {to, href} = props;
|
|
||||||
const targetLink = to || href;
|
// IMPORTANT: using to or href should not change anything
|
||||||
|
// For example, MDX links will ALWAYS give us the href props
|
||||||
|
// Using one prop or the other should not be used to distinguish
|
||||||
|
// internal links (/docs/myDoc) from external links (https://github.com)
|
||||||
|
const targetLinkUnprefixed = to || href;
|
||||||
|
|
||||||
|
// Automatically apply base url in links
|
||||||
|
const targetLink =
|
||||||
|
typeof targetLinkUnprefixed !== 'undefined'
|
||||||
|
? withBaseUrl(targetLinkUnprefixed)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const isInternal = isInternalUrl(targetLink);
|
const isInternal = isInternalUrl(targetLink);
|
||||||
const preloaded = useRef(false);
|
const preloaded = useRef(false);
|
||||||
const LinkComponent = isNavLink ? NavLink : RRLink;
|
const LinkComponent = isNavLink ? NavLink : RRLink;
|
||||||
|
@ -89,10 +112,7 @@ function Link({isNavLink, activeClassName, ...props}: Props): JSX.Element {
|
||||||
const isAnchorLink = targetLink?.startsWith('#') ?? false;
|
const isAnchorLink = targetLink?.startsWith('#') ?? false;
|
||||||
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
|
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
|
||||||
|
|
||||||
if (targetLink && isInternal && !isAnchorLink) {
|
if (targetLink && isInternal && !isAnchorLink && !noBrokenLinkCheck) {
|
||||||
if (targetLink && targetLink.startsWith('/http')) {
|
|
||||||
console.log('collectLink', props);
|
|
||||||
}
|
|
||||||
linksCollector.collectLink(targetLink);
|
linksCollector.collectLink(targetLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,8 @@ describe('useBaseUrl', () => {
|
||||||
expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual(
|
expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual(
|
||||||
'https://v2.docusaurus.io/docusaurus/hello/byebye',
|
'https://v2.docusaurus.io/docusaurus/hello/byebye',
|
||||||
);
|
);
|
||||||
|
expect(useBaseUrl('/docusaurus/')).toEqual('/docusaurus/');
|
||||||
|
expect(useBaseUrl('/docusaurus/hello')).toEqual('/docusaurus/hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,5 +127,7 @@ describe('useBaseUrlUtils().withBaseUrl()', () => {
|
||||||
expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual(
|
expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual(
|
||||||
'https://v2.docusaurus.io/docusaurus/hello/byebye',
|
'https://v2.docusaurus.io/docusaurus/hello/byebye',
|
||||||
);
|
);
|
||||||
|
expect(withBaseUrl('/docusaurus/')).toEqual('/docusaurus/');
|
||||||
|
expect(withBaseUrl('/docusaurus/hello')).toEqual('/docusaurus/hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,11 +6,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import useDocusaurusContext from './useDocusaurusContext';
|
import useDocusaurusContext from './useDocusaurusContext';
|
||||||
import isInternalUrl from './isInternalUrl';
|
import {hasProtocol} from './isInternalUrl';
|
||||||
|
|
||||||
type BaseUrlOptions = Partial<{
|
type BaseUrlOptions = Partial<{
|
||||||
// note: if the url has a protocol, we never prepend it
|
|
||||||
// (it never makes any sense to do so)
|
|
||||||
forcePrependBaseUrl: boolean;
|
forcePrependBaseUrl: boolean;
|
||||||
absolute: boolean;
|
absolute: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
@ -25,7 +23,8 @@ function addBaseUrl(
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInternalUrl(url)) {
|
// it never makes sense to add a base url to an url with a protocol
|
||||||
|
if (hasProtocol(url)) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +32,11 @@ function addBaseUrl(
|
||||||
return baseUrl + url;
|
return baseUrl + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
const basePath = baseUrl + url.replace(/^\//, '');
|
// sometimes we try to add baseurl to an url that already has a baseurl
|
||||||
|
// we should avoid adding the baseurl twice
|
||||||
|
const shouldAddBaseUrl = !url.startsWith(baseUrl);
|
||||||
|
|
||||||
|
const basePath = shouldAddBaseUrl ? baseUrl + url.replace(/^\//, '') : url;
|
||||||
|
|
||||||
return absolute ? siteUrl + basePath : basePath;
|
return absolute ? siteUrl + basePath : basePath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default async function build(
|
||||||
outDir,
|
outDir,
|
||||||
generatedFilesDir,
|
generatedFilesDir,
|
||||||
plugins,
|
plugins,
|
||||||
siteConfig: {onBrokenLinks},
|
siteConfig: {baseUrl, onBrokenLinks},
|
||||||
routes,
|
routes,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -139,7 +139,13 @@ export default async function build(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
handleBrokenLinks({allCollectedLinks, routes, onBrokenLinks});
|
await handleBrokenLinks({
|
||||||
|
allCollectedLinks,
|
||||||
|
routes,
|
||||||
|
onBrokenLinks,
|
||||||
|
outDir,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
const relativeDir = path.relative(process.cwd(), outDir);
|
const relativeDir = path.relative(process.cwd(), outDir);
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
@ -11,5 +11,6 @@ export const CONFIG_FILE_NAME = 'docusaurus.config.js';
|
||||||
export const GENERATED_FILES_DIR_NAME = '.docusaurus';
|
export const GENERATED_FILES_DIR_NAME = '.docusaurus';
|
||||||
export const SRC_DIR_NAME = 'src';
|
export const SRC_DIR_NAME = 'src';
|
||||||
export const STATIC_DIR_NAME = 'static';
|
export const STATIC_DIR_NAME = 'static';
|
||||||
|
export const STATIC_ASSETS_DIR_NAME = 'assets'; // webpack file-loader files
|
||||||
export const THEME_PATH = `${SRC_DIR_NAME}/theme`;
|
export const THEME_PATH = `${SRC_DIR_NAME}/theme`;
|
||||||
export const DEFAULT_PORT = 3000;
|
export const DEFAULT_PORT = 3000;
|
||||||
|
|
|
@ -5,7 +5,8 @@ exports[`brokenLinks getBrokenLinksErrorMessage 1`] = `
|
||||||
|
|
||||||
- Page path = /docs/mySourcePage:
|
- Page path = /docs/mySourcePage:
|
||||||
-> link to ./myBrokenLink (resolved as: /docs/myBrokenLink)
|
-> link to ./myBrokenLink (resolved as: /docs/myBrokenLink)
|
||||||
-> link to ../otherBrokenLink (resolved as: /otherBrokenLink),
|
-> link to ../otherBrokenLink (resolved as: /otherBrokenLink)
|
||||||
|
|
||||||
|
|
||||||
- Page path = /otherSourcePage:
|
- Page path = /otherSourcePage:
|
||||||
-> link to /badLink
|
-> link to /badLink
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
import {matchRoutes, RouteConfig as RRRouteConfig} from 'react-router-config';
|
import {matchRoutes, RouteConfig as RRRouteConfig} from 'react-router-config';
|
||||||
import resolvePathname from 'resolve-pathname';
|
import resolvePathname from 'resolve-pathname';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import fs from 'fs-extra';
|
||||||
import {mapValues, pickBy, flatMap} from 'lodash';
|
import {mapValues, pickBy, flatMap} from 'lodash';
|
||||||
import {RouteConfig, OnBrokenLinks} from '@docusaurus/types';
|
import {RouteConfig, OnBrokenLinks} from '@docusaurus/types';
|
||||||
|
import {removePrefix} from '@docusaurus/utils';
|
||||||
|
|
||||||
function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] {
|
function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] {
|
||||||
// @ts-expect-error: types incompatible???
|
// @ts-expect-error: types incompatible???
|
||||||
|
@ -107,37 +109,79 @@ export function getBrokenLinksErrorMessage(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`Broken links found!` +
|
`Broken links found!` +
|
||||||
`${Object.entries(allBrokenLinks).map(([pagePath, brokenLinks]) =>
|
`${Object.entries(allBrokenLinks)
|
||||||
pageBrokenLinksMessage(pagePath, brokenLinks),
|
.map(([pagePath, brokenLinks]) =>
|
||||||
)}
|
pageBrokenLinksMessage(pagePath, brokenLinks),
|
||||||
|
)
|
||||||
|
.join('\n')}
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleBrokenLinks({
|
// If a file actually exist on the file system, we know the link is valid
|
||||||
|
// even if docusaurus does not know about this file, so we don't report it
|
||||||
|
async function filterExistingFileLinks({
|
||||||
|
baseUrl,
|
||||||
|
outDir,
|
||||||
|
allCollectedLinks,
|
||||||
|
}: {
|
||||||
|
baseUrl: string;
|
||||||
|
outDir: string;
|
||||||
|
allCollectedLinks: Record<string, string[]>;
|
||||||
|
}): Promise<Record<string, string[]>> {
|
||||||
|
// not easy to make this async :'(
|
||||||
|
function linkFileDoesNotExist(link: string): boolean {
|
||||||
|
const filePath = `${outDir}/${removePrefix(link, baseUrl)}`;
|
||||||
|
const exists = fs.existsSync(filePath);
|
||||||
|
return !exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapValues(allCollectedLinks, (links) => {
|
||||||
|
return links.filter(linkFileDoesNotExist);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleBrokenLinks({
|
||||||
allCollectedLinks,
|
allCollectedLinks,
|
||||||
onBrokenLinks,
|
onBrokenLinks,
|
||||||
routes,
|
routes,
|
||||||
|
baseUrl,
|
||||||
|
outDir,
|
||||||
}: {
|
}: {
|
||||||
allCollectedLinks: Record<string, string[]>;
|
allCollectedLinks: Record<string, string[]>;
|
||||||
onBrokenLinks: OnBrokenLinks;
|
onBrokenLinks: OnBrokenLinks;
|
||||||
routes: RouteConfig[];
|
routes: RouteConfig[];
|
||||||
|
baseUrl: string;
|
||||||
|
outDir: string;
|
||||||
}) {
|
}) {
|
||||||
if (onBrokenLinks === 'ignore') {
|
if (onBrokenLinks === 'ignore') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const allBrokenLinks = getAllBrokenLinks({allCollectedLinks, routes});
|
|
||||||
|
// If we link to a file like /myFile.zip, and the file actually exist for the file system
|
||||||
|
// it is not a broken link, it may simply be a link to an existing static file...
|
||||||
|
const allCollectedLinksFiltered = await filterExistingFileLinks({
|
||||||
|
allCollectedLinks,
|
||||||
|
baseUrl,
|
||||||
|
outDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allBrokenLinks = getAllBrokenLinks({
|
||||||
|
allCollectedLinks: allCollectedLinksFiltered,
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
const errorMessage = getBrokenLinksErrorMessage(allBrokenLinks);
|
const errorMessage = getBrokenLinksErrorMessage(allBrokenLinks);
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
|
const finalMessage = `${errorMessage}\nNote: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration.\n\n`;
|
||||||
|
|
||||||
// Useful to ensure the CI fails in case of broken link
|
// Useful to ensure the CI fails in case of broken link
|
||||||
if (onBrokenLinks === 'throw') {
|
if (onBrokenLinks === 'throw') {
|
||||||
throw new Error(
|
throw new Error(finalMessage);
|
||||||
`${errorMessage}\nNote: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration.`,
|
|
||||||
);
|
|
||||||
} else if (onBrokenLinks === 'error') {
|
} else if (onBrokenLinks === 'error') {
|
||||||
console.error(chalk.red(errorMessage));
|
console.error(chalk.red(finalMessage));
|
||||||
} else if (onBrokenLinks === 'log') {
|
} else if (onBrokenLinks === 'log') {
|
||||||
console.log(chalk.blue(errorMessage));
|
console.log(chalk.blue(finalMessage));
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected onBrokenLinks value=${onBrokenLinks}`);
|
throw new Error(`unexpected onBrokenLinks value=${onBrokenLinks}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import webpack, {Configuration, Loader, RuleSetRule, Stats} from 'webpack';
|
||||||
import {TransformOptions} from '@babel/core';
|
import {TransformOptions} from '@babel/core';
|
||||||
import {ConfigureWebpackFn} from '@docusaurus/types';
|
import {ConfigureWebpackFn} from '@docusaurus/types';
|
||||||
import {version as cacheLoaderVersion} from 'cache-loader/package.json';
|
import {version as cacheLoaderVersion} from 'cache-loader/package.json';
|
||||||
|
import {STATIC_ASSETS_DIR_NAME} from '../constants';
|
||||||
|
|
||||||
// Utility method to get style loaders
|
// Utility method to get style loaders
|
||||||
export function getStyleLoaders(
|
export function getStyleLoaders(
|
||||||
|
@ -174,14 +175,12 @@ export function compile(config: Configuration[]): Promise<void> {
|
||||||
|
|
||||||
// Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447
|
// Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447
|
||||||
export function getFileLoaderUtils() {
|
export function getFileLoaderUtils() {
|
||||||
const assetsRelativeRoot = 'assets/';
|
|
||||||
|
|
||||||
const loaders = {
|
const loaders = {
|
||||||
file: (options = {}) => {
|
file: (options = {}) => {
|
||||||
return {
|
return {
|
||||||
loader: require.resolve(`file-loader`),
|
loader: require.resolve(`file-loader`),
|
||||||
options: {
|
options: {
|
||||||
name: `${assetsRelativeRoot}[name]-[hash].[ext]`,
|
name: `${STATIC_ASSETS_DIR_NAME}/[name]-[hash].[ext]`,
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -191,7 +190,7 @@ export function getFileLoaderUtils() {
|
||||||
loader: require.resolve(`url-loader`),
|
loader: require.resolve(`url-loader`),
|
||||||
options: {
|
options: {
|
||||||
limit: 10000,
|
limit: 10000,
|
||||||
name: `${assetsRelativeRoot}[name]-[hash].[ext]`,
|
name: `${STATIC_ASSETS_DIR_NAME}[name]-[hash].[ext]`,
|
||||||
fallback: require.resolve(`file-loader`),
|
fallback: require.resolve(`file-loader`),
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
|
|
|
@ -964,6 +964,7 @@ or
|
||||||
|
|
||||||

|

|
||||||
```
|
```
|
||||||
|
|
||||||
The ES imports syntax also works:
|
The ES imports syntax also works:
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
|
|
|
@ -47,4 +47,4 @@ For themes that supports TypeScript theme components, you can add the `--typescr
|
||||||
npm run swizzle @docusaurus/theme-classic Footer --typescript
|
npm run swizzle @docusaurus/theme-classic Footer --typescript
|
||||||
```
|
```
|
||||||
|
|
||||||
At this moment, the only official Docusaurus theme that supports TypeScript theme components is `@docusaurus/theme-classic`. If you are a Docusaurus theme package author who wants to add TypeScript support, see the [Lifecycle APIs docs](./lifecycle-apis#gettypescriptthemepath).
|
At this moment, the only official Docusaurus theme that supports TypeScript theme components is `@docusaurus/theme-classic`. If you are a Docusaurus theme package author who wants to add TypeScript support, see the [Lifecycle APIs docs](./lifecycle-apis.md#gettypescriptthemepath).
|
||||||
|
|
|
@ -61,7 +61,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number to `versions.json`.
|
- Append the new version number to `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
|
@ -14,12 +14,14 @@ const allDocHomesPaths = [
|
||||||
...versions.slice(1).map((version) => `/docs/${version}/`),
|
...versions.slice(1).map((version) => `/docs/${version}/`),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const baseUrl = process.env.BASE_URL || '/';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
title: 'Docusaurus',
|
title: 'Docusaurus',
|
||||||
tagline: 'Build optimized websites quickly, focus on your content',
|
tagline: 'Build optimized websites quickly, focus on your content',
|
||||||
organizationName: 'facebook',
|
organizationName: 'facebook',
|
||||||
projectName: 'docusaurus',
|
projectName: 'docusaurus',
|
||||||
baseUrl: '/',
|
baseUrl,
|
||||||
url: 'https://v2.docusaurus.io',
|
url: 'https://v2.docusaurus.io',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
favicon: 'img/docusaurus.ico',
|
favicon: 'img/docusaurus.ico',
|
||||||
|
@ -189,7 +191,7 @@ module.exports = {
|
||||||
activeBaseRegex: `docs/next/(support|team|resources)`,
|
activeBaseRegex: `docs/next/(support|team|resources)`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: 'versions',
|
to: '/versions',
|
||||||
label: 'All versions',
|
label: 'All versions',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,7 +63,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists). Saved it as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current sidebar configuration (if it exists). Saved it as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number into `versions.json`.
|
- Append the new version number into `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
|
@ -61,7 +61,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists). Saved it as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists). Saved it as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number into `versions.json`.
|
- Append the new version number into `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
|
@ -61,7 +61,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number to `versions.json`.
|
- Append the new version number to `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
|
@ -61,7 +61,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number to `versions.json`.
|
- Append the new version number to `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
|
@ -61,7 +61,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number to `versions.json`.
|
- Append the new version number to `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
|
@ -61,7 +61,7 @@ npm run docusaurus docs:version 1.1.0
|
||||||
When tagging a new version, the document versioning mechanism will:
|
When tagging a new version, the document versioning mechanism will:
|
||||||
|
|
||||||
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
- Copy the full `docs/` folder contents into a new `versioned_docs/version-<version>/` folder.
|
||||||
- Create a versioned sidebars file based from your current [sidebar](sidebar.md) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number to `versions.json`.
|
- Append the new version number to `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue