diff --git a/packages/docusaurus-plugin-google-analytics/src/analytics.js b/packages/docusaurus-plugin-google-analytics/src/analytics.js index 38d9edf6eaab..139ed18923d3 100644 --- a/packages/docusaurus-plugin-google-analytics/src/analytics.js +++ b/packages/docusaurus-plugin-google-analytics/src/analytics.js @@ -5,8 +5,10 @@ * LICENSE file in the root directory of this source tree. */ +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; + export default (function() { - if (typeof window === 'undefined') { + if (!ExecutionEnvironment.canUseDOM) { return null; } diff --git a/packages/docusaurus-plugin-google-gtag/src/gtag.js b/packages/docusaurus-plugin-google-gtag/src/gtag.js index 9b7e8a3ebf70..959b9f8a1e5a 100644 --- a/packages/docusaurus-plugin-google-gtag/src/gtag.js +++ b/packages/docusaurus-plugin-google-gtag/src/gtag.js @@ -5,10 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import siteConfig from '@generated/docusaurus.config'; export default (function() { - if (typeof window === 'undefined') { + if (!ExecutionEnvironment.canUseDOM) { return null; } diff --git a/packages/docusaurus/src/client/clientEntry.js b/packages/docusaurus/src/client/clientEntry.js index 28fc5408c6a2..3265c9ccf92c 100644 --- a/packages/docusaurus/src/client/clientEntry.js +++ b/packages/docusaurus/src/client/clientEntry.js @@ -10,12 +10,13 @@ import {hydrate, render} from 'react-dom'; import {BrowserRouter} from 'react-router-dom'; import routes from '@generated/routes'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import App from './App'; import preload from './preload'; import docusaurus from './docusaurus'; // Client-side render (e.g: running in browser) to become single-page application (SPA). -if (typeof window !== 'undefined' && typeof document !== 'undefined') { +if (ExecutionEnvironment.canUseDOM) { window.docusaurus = docusaurus; // For production, attempt to hydrate existing markup for performant first-load experience. // For development, there is no existing markup so we had to render it. diff --git a/packages/docusaurus/src/client/exports/ExecutionEnvironment.js b/packages/docusaurus/src/client/exports/ExecutionEnvironment.js new file mode 100644 index 000000000000..be810f723f67 --- /dev/null +++ b/packages/docusaurus/src/client/exports/ExecutionEnvironment.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const canUseDOM = !!( + typeof window !== 'undefined' && + window.document && + window.document.createElement +); + +const ExecutionEnvironment = { + canUseDOM, + + canUseEventListeners: + canUseDOM && !!(window.addEventListener || window.attachEvent), + + canUseIntersectionObserver: canUseDOM && 'IntersectionObserver' in window, + + canUseViewport: canUseDOM && !!window.screen, +}; + +module.exports = ExecutionEnvironment; diff --git a/packages/docusaurus/src/client/exports/Link.js b/packages/docusaurus/src/client/exports/Link.js index 9a21bbc0b469..861b807d5df6 100644 --- a/packages/docusaurus/src/client/exports/Link.js +++ b/packages/docusaurus/src/client/exports/Link.js @@ -8,6 +8,7 @@ import React, {useEffect, useRef} from 'react'; import {NavLink} from 'react-router-dom'; import isInternalUrl from '@docusaurus/isInternalUrl'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; function Link(props) { const {to, href} = props; @@ -15,8 +16,7 @@ function Link(props) { const isInternal = isInternalUrl(targetLink); const preloaded = useRef(false); - const IOSupported = - typeof window !== 'undefined' && 'IntersectionObserver' in window; + const IOSupported = ExecutionEnvironment.canUseIntersectionObserver; let io; const handleIntersection = (el, cb) => { diff --git a/website/docs/docusaurus-core.md b/website/docs/docusaurus-core.md index 80bda8f829aa..1b80fc12d521 100644 --- a/website/docs/docusaurus-core.md +++ b/website/docs/docusaurus-core.md @@ -4,9 +4,11 @@ title: Docusaurus Client API sidebar_label: Client API --- -Docusaurus provides some API on client that can be helpful when building your site. +Docusaurus provides some APIs on the clients that can be helpful to you when building your site. -## `Head` +## Components + +### `` This reusable React component will manage all of your changes to the document head. It takes plain HTML tags and outputs plain HTML tags and is beginner-friendly. It is a wrapper around [React Helmet](https://github.com/nfl/react-helmet). @@ -55,7 +57,7 @@ Outputs ``` -## `Link` +### `` This component enables linking to internal pages as well as a powerful performance feature called preloading. Preloading is used to prefetch resources so that the resources are fetched by the time the user navigates with this component. We use an `IntersectionObserver` to fetch a low-priority request when the `` is in the viewport and then use an `onMouseOver` event to trigger a high-priority request when it is likely that a user will navigate to the requested resource. @@ -78,7 +80,7 @@ const Page = () => ( ); ``` -### `to`: string +#### `to`: string The target location to navigate to. Example: `/docs/introduction`. @@ -86,7 +88,7 @@ The target location to navigate to. Example: `/docs/introduction`. ``` -### `activeClassName`: string +#### `activeClassName`: string The class to give the `` when it is active. The default given class is `active`. This will be joined with the `className` prop. @@ -96,9 +98,26 @@ The class to give the `` when it is active. The default given class is `ac ``` -## `useDocusaurusContext` +### `` + +Rendering a `` will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects (HTTP 3xx) do. You can refer to [React Router's Redirect documentation](https://reacttraining.com/react-router/web/api/Redirect) for more info on available props. + +Example usage: -React Hooks to access Docusaurus Context. Context contains `siteConfig` object from [docusaurus.config.js](docusaurus.config.js.md). +```jsx {2,5} +import React from 'react'; +import {Redirect} from '@docusaurus/router'; + +function Home() { + return ; +} +``` + +## Hooks + +### `useDocusaurusContext` + +React hook to access Docusaurus Context. Context contains `siteConfig` object from [docusaurus.config.js](docusaurus.config.js.md). ```ts interface DocusaurusContext { @@ -121,9 +140,9 @@ const Test = () => { }; ``` -## `useBaseUrl` +### `useBaseUrl` -React Hook to automatically append `baseUrl` to a string automatically. This is particularly useful if you don't want to hardcode your baseUrl. +React hook to automatically append `baseUrl` to a string automatically. This is particularly useful if you don't want to hardcode your config's `baseUrl`. We highly recommend you to use this. Example usage: @@ -145,17 +164,25 @@ function Help() { } ``` -## `Redirect` +## Modules -Rendering a `` will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects (HTTP 3xx) do. You can refer to [React Router's Redirect documentation](https://reacttraining.com/react-router/web/api/Redirect) for more info on available props. +### `ExecutionEnvironment` -Example usage: +A module which exposes a few boolean variables to check the current rendering environment. Useful if you want to only run certain code on client/server or need to write server-side rendering compatible code. -```jsx {2,5} +```jsx {2} import React from 'react'; -import {Redirect} from '@docusaurus/router'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -function Home() { - return ; +function MyPage() { + const location = ExecutionEnvironment.canUseDOM ? window.href.location : null; + return
{location}
; } ``` + +| Field | Description | +| --- | --- | +| `ExecutionEnvironment.canUseDOM` | `true` if on client, `false` if SSR. | +| `ExecutionEnvironment.canUseEventListeners` | `true` if on client and has `window.addEventListener`. | +| `ExecutionEnvironment.canUseIntersectionObserver` | `true` if on client and has `IntersectionObserver`. | +| `ExecutionEnvironment.canUseViewport` | `true` if on client and has `window.screen`. |