Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(v2): fix recent baseurl issues #3093

Merged
merged 11 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@
"packages/docusaurus-init/templates/*"
],
"scripts": {
"testBaseUrl": "yarn build:v2:baseUrl && yarn serve:v2:baseUrl",
"start": "yarn build:packages && yarn start:v2",
"start:v1": "yarn workspace docusaurus-1-website start",
"start:v2": "yarn workspace docusaurus-2-website start",
"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:packages": "lerna run build --no-private",
"build:v1": "yarn workspace docusaurus-1-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: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: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'",
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-plugin-content-blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
},
"dependencies": {
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
"@docusaurus/core": "^2.0.0-alpha.58",
"@docusaurus/types": "^2.0.0-alpha.58",
"@docusaurus/utils": "^2.0.0-alpha.58",
"@docusaurus/core": "2.0.0-alpha.58",
"@hapi/joi": "^17.1.1",
"feed": "^4.1.0",
"fs-extra": "^8.1.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/docusaurus-plugin-content-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
"@docusaurus/core": "^2.0.0-alpha.58",
"@docusaurus/types": "^2.0.0-alpha.58",
"@docusaurus/utils": "^2.0.0-alpha.58",
"execa": "^3.4.0",
Expand All @@ -33,8 +34,7 @@
"lodash.pickby": "^4.6.0",
"lodash.sortby": "^4.6.0",
"remark-admonitions": "^1.2.1",
"shelljs": "^0.8.4",
"@docusaurus/core": "^2.0.0-alpha.58"
"shelljs": "^0.8.4"
},
"peerDependencies": {
"react": "^16.8.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Object {
"pluginName": Object {
"pluginId": Object {
"latestVersionName": null,
"path": "docs",
"path": "/docs",
"versions": Array [
Object {
"docs": Array [
Expand Down Expand Up @@ -469,7 +469,7 @@ Object {
"pluginName": Object {
"pluginId": Object {
"latestVersionName": "1.0.1",
"path": "docs",
"path": "/docs",
"versions": Array [
Object {
"docs": Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ describe('docsClientUtils', () => {
test('getActivePlugin', () => {
const data: Record<string, GlobalPluginData> = {
pluginIosId: {
path: 'ios',
path: '/ios',
latestVersionName: 'xyz',
versions: [],
},
pluginAndroidId: {
path: 'android',
path: '/android',
latestVersionName: 'xyz',
versions: [],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const getActivePlugin = (
const activeEntry = Object.entries(allPluginDatas).find(
([_id, pluginData]) => {
return !!matchPath(pathname, {
path: `/${pluginData.path}`,
path: pluginData.path,
exact: false,
strict: false,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-plugin-content-docs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ Available document ids=
const {addRoute, createData, setGlobalData} = actions;

const pluginInstanceGlobalData: GlobalPluginData = {
path: options.path,
path: normalizeUrl([baseUrl, options.path]),
latestVersionName: versioning.latestVersion,
// Initialized empty, will be mutated
versions: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ export default {
return children;
},
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} />;
},
pre: (props: ComponentProps<'div'>): JSX.Element => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ function NavLink({
activeClassName?: string;
prependBaseUrlToHref?: string;
} & ComponentProps<'a'>) {
const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});

return (
<Link
{...(href
Expand All @@ -43,7 +41,7 @@ function NavLink({
: {
isNavLink: true,
activeClassName,
to: toUrl,
to,
...(activeBasePath || activeBaseRegex
? {
isActive: (_match, location) =>
Expand Down
36 changes: 28 additions & 8 deletions packages/docusaurus/src/client/exports/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {NavLink, Link as RRLink} from 'react-router-dom';
import isInternalUrl from './isInternalUrl';
import ExecutionEnvironment from './ExecutionEnvironment';
import {useLinksCollector} from '../LinksCollector';
import {useBaseUrlUtils} from './useBaseUrl';

declare global {
interface Window {
Expand All @@ -21,15 +22,37 @@ declare global {
interface Props {
readonly isNavLink?: boolean;
readonly to?: string;
readonly activeClassName?: string;
readonly href?: string;
readonly activeClassName?: string;
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 {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 preloaded = useRef(false);
const LinkComponent = isNavLink ? NavLink : RRLink;
Expand Down Expand Up @@ -89,10 +112,7 @@ function Link({isNavLink, activeClassName, ...props}: Props): JSX.Element {
const isAnchorLink = targetLink?.startsWith('#') ?? false;
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;

if (targetLink && isInternal && !isAnchorLink) {
if (targetLink && targetLink.startsWith('/http')) {
console.log('collectLink', props);
}
if (targetLink && isInternal && !isAnchorLink && !noBrokenLinkCheck) {
linksCollector.collectLink(targetLink);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ describe('useBaseUrl', () => {
expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual(
'https://v2.docusaurus.io/docusaurus/hello/byebye',
);
expect(useBaseUrl('/docusaurus/')).toEqual('/docusaurus/');
expect(useBaseUrl('/docusaurus/hello')).toEqual('/docusaurus/hello');
});
});

Expand Down Expand Up @@ -125,5 +127,7 @@ describe('useBaseUrlUtils().withBaseUrl()', () => {
expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual(
'https://v2.docusaurus.io/docusaurus/hello/byebye',
);
expect(withBaseUrl('/docusaurus/')).toEqual('/docusaurus/');
expect(withBaseUrl('/docusaurus/hello')).toEqual('/docusaurus/hello');
});
});
13 changes: 8 additions & 5 deletions packages/docusaurus/src/client/exports/useBaseUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
*/

import useDocusaurusContext from './useDocusaurusContext';
import isInternalUrl from './isInternalUrl';
import {hasProtocol} from './isInternalUrl';

type BaseUrlOptions = Partial<{
// note: if the url has a protocol, we never prepend it
// (it never makes any sense to do so)
forcePrependBaseUrl: boolean;
absolute: boolean;
}>;
Expand All @@ -25,15 +23,20 @@ function addBaseUrl(
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;
}

if (forcePrependBaseUrl) {
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;
}
Expand Down
10 changes: 8 additions & 2 deletions packages/docusaurus/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default async function build(
outDir,
generatedFilesDir,
plugins,
siteConfig: {onBrokenLinks},
siteConfig: {baseUrl, onBrokenLinks},
routes,
} = props;

Expand Down Expand Up @@ -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);
console.log(
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export const CONFIG_FILE_NAME = 'docusaurus.config.js';
export const GENERATED_FILES_DIR_NAME = '.docusaurus';
export const SRC_DIR_NAME = 'src';
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 DEFAULT_PORT = 3000;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ exports[`brokenLinks getBrokenLinksErrorMessage 1`] = `

- Page path = /docs/mySourcePage:
-> link to ./myBrokenLink (resolved as: /docs/myBrokenLink)
-> link to ../otherBrokenLink (resolved as: /otherBrokenLink),
-> link to ../otherBrokenLink (resolved as: /otherBrokenLink)


- Page path = /otherSourcePage:
-> link to /badLink
Expand Down
64 changes: 54 additions & 10 deletions packages/docusaurus/src/server/brokenLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import {matchRoutes, RouteConfig as RRRouteConfig} from 'react-router-config';
import resolvePathname from 'resolve-pathname';
import chalk from 'chalk';
import fs from 'fs-extra';
import {mapValues, pickBy, flatMap} from 'lodash';
import {RouteConfig, OnBrokenLinks} from '@docusaurus/types';
import {removePrefix} from '@docusaurus/utils';

function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] {
// @ts-expect-error: types incompatible???
Expand Down Expand Up @@ -107,37 +109,79 @@ export function getBrokenLinksErrorMessage(

return (
`Broken links found!` +
`${Object.entries(allBrokenLinks).map(([pagePath, brokenLinks]) =>
pageBrokenLinksMessage(pagePath, brokenLinks),
)}
`${Object.entries(allBrokenLinks)
.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,
onBrokenLinks,
routes,
baseUrl,
outDir,
}: {
allCollectedLinks: Record<string, string[]>;
onBrokenLinks: OnBrokenLinks;
routes: RouteConfig[];
baseUrl: string;
outDir: string;
}) {
if (onBrokenLinks === 'ignore') {
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);
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
if (onBrokenLinks === 'throw') {
throw new Error(
`${errorMessage}\nNote: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration.`,
);
throw new Error(finalMessage);
} else if (onBrokenLinks === 'error') {
console.error(chalk.red(errorMessage));
console.error(chalk.red(finalMessage));
} else if (onBrokenLinks === 'log') {
console.log(chalk.blue(errorMessage));
console.log(chalk.blue(finalMessage));
} else {
throw new Error(`unexpected onBrokenLinks value=${onBrokenLinks}`);
}
Expand Down
Loading