mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-17 19:16:58 +02:00
chore(pwa, sitemap, client-redirects, ideal-image): JSDoc for types (#6928)
* chore(pwa, sitemap, client-redirects, ideal-image): JSDoc for types * fix
This commit is contained in:
parent
284649c7c8
commit
bfe7ca6237
11 changed files with 129 additions and 52 deletions
|
@ -7,10 +7,7 @@
|
||||||
|
|
||||||
import {validateOptions, DEFAULT_OPTIONS} from '../options';
|
import {validateOptions, DEFAULT_OPTIONS} from '../options';
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import type {
|
import type {Options} from '@docusaurus/plugin-client-redirects';
|
||||||
CreateRedirectsFnOption,
|
|
||||||
Options,
|
|
||||||
} from '@docusaurus/plugin-client-redirects';
|
|
||||||
|
|
||||||
function testValidate(options: Options) {
|
function testValidate(options: Options) {
|
||||||
return validateOptions({validate: normalizePluginOptions, options});
|
return validateOptions({validate: normalizePluginOptions, options});
|
||||||
|
@ -34,7 +31,9 @@ describe('normalizePluginOptions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('overrides all default options with valid user options', () => {
|
it('overrides all default options with valid user options', () => {
|
||||||
const createRedirects: CreateRedirectsFnOption = (_routePath: string) => [];
|
const createRedirects: Options['createRedirects'] = (
|
||||||
|
_routePath: string,
|
||||||
|
) => [];
|
||||||
expect(
|
expect(
|
||||||
testValidate({
|
testValidate({
|
||||||
fromExtensions: ['exe', 'zip'],
|
fromExtensions: ['exe', 'zip'],
|
||||||
|
@ -54,7 +53,8 @@ describe('normalizePluginOptions', () => {
|
||||||
it('rejects bad fromExtensions user inputs', () => {
|
it('rejects bad fromExtensions user inputs', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
testValidate({
|
testValidate({
|
||||||
fromExtensions: [null, undefined, 123, true] as unknown as string[],
|
// @ts-expect-error: for test
|
||||||
|
fromExtensions: [null, undefined, 123, true],
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -62,7 +62,8 @@ describe('normalizePluginOptions', () => {
|
||||||
it('rejects bad toExtensions user inputs', () => {
|
it('rejects bad toExtensions user inputs', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
testValidate({
|
testValidate({
|
||||||
toExtensions: [null, undefined, 123, true] as unknown as string[],
|
// @ts-expect-error: for test
|
||||||
|
toExtensions: [null, undefined, 123, true],
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -70,7 +71,8 @@ describe('normalizePluginOptions', () => {
|
||||||
it('rejects bad createRedirects user inputs', () => {
|
it('rejects bad createRedirects user inputs', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
testValidate({
|
testValidate({
|
||||||
createRedirects: ['bad', 'value'] as unknown as CreateRedirectsFnOption,
|
// @ts-expect-error: for test
|
||||||
|
createRedirects: ['bad', 'value'],
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,10 +9,6 @@ import * as eta from 'eta';
|
||||||
import redirectPageTemplate from './templates/redirectPage.template.html';
|
import redirectPageTemplate from './templates/redirectPage.template.html';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
type CreateRedirectPageOptions = {
|
|
||||||
toUrl: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCompiledRedirectPageTemplate = _.memoize(() =>
|
const getCompiledRedirectPageTemplate = _.memoize(() =>
|
||||||
eta.compile(redirectPageTemplate.trim()),
|
eta.compile(redirectPageTemplate.trim()),
|
||||||
);
|
);
|
||||||
|
@ -24,7 +20,9 @@ function renderRedirectPageTemplate(data: Record<string, unknown>) {
|
||||||
|
|
||||||
export default function createRedirectPageContent({
|
export default function createRedirectPageContent({
|
||||||
toUrl,
|
toUrl,
|
||||||
}: CreateRedirectPageOptions): string {
|
}: {
|
||||||
|
toUrl: string;
|
||||||
|
}): string {
|
||||||
return renderRedirectPageTemplate({
|
return renderRedirectPageTemplate({
|
||||||
toUrl: encodeURI(toUrl),
|
toUrl: encodeURI(toUrl),
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,18 +10,23 @@ export type RedirectOption = {
|
||||||
from: string | string[];
|
from: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// For a given existing route path,
|
|
||||||
// return all the paths from which we should redirect from
|
|
||||||
export type CreateRedirectsFnOption = (
|
|
||||||
path: string,
|
|
||||||
) => string[] | string | null | undefined;
|
|
||||||
|
|
||||||
export type PluginOptions = {
|
export type PluginOptions = {
|
||||||
|
/** Plugin ID. */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** The extensions to be removed from the route after redirecting. */
|
||||||
fromExtensions: string[];
|
fromExtensions: string[];
|
||||||
|
/** The extensions to be appended to the route after redirecting. */
|
||||||
toExtensions: string[];
|
toExtensions: string[];
|
||||||
|
/** The list of redirect rules, each one with multiple `from`s → one `to`. */
|
||||||
redirects: RedirectOption[];
|
redirects: RedirectOption[];
|
||||||
createRedirects?: CreateRedirectsFnOption;
|
/**
|
||||||
|
* A callback to create a redirect rule.
|
||||||
|
* @returns All the paths from which we should redirect to `path`
|
||||||
|
*/
|
||||||
|
createRedirects?: (
|
||||||
|
/** An existing Docusaurus route path */
|
||||||
|
path: string,
|
||||||
|
) => string[] | string | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Options = Partial<PluginOptions>;
|
export type Options = Partial<PluginOptions>;
|
||||||
|
|
|
@ -8,16 +8,22 @@
|
||||||
import type {Props} from '@docusaurus/types';
|
import type {Props} from '@docusaurus/types';
|
||||||
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
|
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
|
||||||
|
|
||||||
// The minimal infos the plugin needs to work
|
/**
|
||||||
|
* The minimal infos the plugin needs to work
|
||||||
|
*/
|
||||||
export type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
|
export type PluginContext = Pick<Props, 'outDir' | 'baseUrl'> & {
|
||||||
options: PluginOptions;
|
options: PluginOptions;
|
||||||
relativeRoutesPaths: string[];
|
relativeRoutesPaths: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// In-memory representation of redirects we want: easier to test
|
/**
|
||||||
// /!\ easy to be confused: "from" is the new page we should create,
|
* In-memory representation of redirects we want: easier to test
|
||||||
// that redirects to "to": the existing Docusaurus page
|
* /!\ easy to be confused: "from" is the new page we should create,
|
||||||
|
* that redirects to "to": the existing Docusaurus page
|
||||||
|
*/
|
||||||
export type RedirectMetadata = {
|
export type RedirectMetadata = {
|
||||||
from: string; // pathname
|
/** Pathname of the new page we should create */
|
||||||
to: string; // pathname
|
from: string;
|
||||||
|
/** Pathname of an existing Docusaurus page */
|
||||||
|
to: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,8 +14,7 @@ declare module '@docusaurus/plugin-ideal-image' {
|
||||||
/**
|
/**
|
||||||
* Specify all widths you want to use; if a specified size exceeds the
|
* Specify all widths you want to use; if a specified size exceeds the
|
||||||
* original image's width, the latter will be used (i.e. images won't be
|
* original image's width, the latter will be used (i.e. images won't be
|
||||||
* scaled up). You may also declare a default sizes array in the loader
|
* scaled up).
|
||||||
* options in your webpack.config.js.
|
|
||||||
*/
|
*/
|
||||||
sizes?: number[];
|
sizes?: number[];
|
||||||
/**
|
/**
|
||||||
|
@ -25,16 +24,17 @@ declare module '@docusaurus/plugin-ideal-image' {
|
||||||
*/
|
*/
|
||||||
size?: number;
|
size?: number;
|
||||||
/**
|
/**
|
||||||
* As an alternative to manually specifying sizes, you can specify min, max
|
* As an alternative to manually specifying `sizes`, you can specify `min`,
|
||||||
* and steps, and the sizes will be generated for you.
|
* `max` and `steps`, and the `sizes` will be generated for you.
|
||||||
*/
|
*/
|
||||||
min?: number;
|
min?: number;
|
||||||
/**
|
/**
|
||||||
* See min above
|
* @see {@link PluginOptions.min}
|
||||||
*/
|
*/
|
||||||
max?: number;
|
max?: number;
|
||||||
/**
|
/**
|
||||||
* Configure the number of images generated between min and max (inclusive)
|
* Configure the number of images generated between `min` and `max`
|
||||||
|
* (inclusive)
|
||||||
*/
|
*/
|
||||||
steps?: number;
|
steps?: number;
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +42,8 @@ declare module '@docusaurus/plugin-ideal-image' {
|
||||||
*/
|
*/
|
||||||
quality?: number;
|
quality?: number;
|
||||||
/**
|
/**
|
||||||
* Just use regular images in dev mode
|
* You can test ideal image behavior in dev mode by setting this to `false`.
|
||||||
|
* Tip: use network throttling in your browser to simulate slow networks.
|
||||||
*/
|
*/
|
||||||
disableInDev?: boolean;
|
disableInDev?: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {translate} from '@docusaurus/Translate';
|
||||||
import type {Props} from '@theme/IdealImage';
|
import type {Props} from '@theme/IdealImage';
|
||||||
|
|
||||||
// Adopted from https://github.com/endiliey/react-ideal-image/blob/master/src/components/helpers.js#L59-L65
|
// Adopted from https://github.com/endiliey/react-ideal-image/blob/master/src/components/helpers.js#L59-L65
|
||||||
const bytesToSize = (bytes: number) => {
|
function bytesToSize(bytes: number) {
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
if (bytes === 0) {
|
if (bytes === 0) {
|
||||||
return 'n/a';
|
return 'n/a';
|
||||||
|
@ -25,10 +25,10 @@ const bytesToSize = (bytes: number) => {
|
||||||
return `${bytes} ${sizes[scale]}`;
|
return `${bytes} ${sizes[scale]}`;
|
||||||
}
|
}
|
||||||
return `${(bytes / 1024 ** scale).toFixed(1)} ${sizes[scale]}`;
|
return `${(bytes / 1024 ** scale).toFixed(1)} ${sizes[scale]}`;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Adopted from https://github.com/endiliey/react-ideal-image/blob/master/src/components/IdealImage/index.js#L43-L75
|
// Adopted from https://github.com/endiliey/react-ideal-image/blob/master/src/components/IdealImage/index.js#L43-L75
|
||||||
const getMessage = (icon: IconKey, state: State) => {
|
function getMessage(icon: IconKey, state: State) {
|
||||||
switch (icon) {
|
switch (icon) {
|
||||||
case 'noicon':
|
case 'noicon':
|
||||||
case 'loaded':
|
case 'loaded':
|
||||||
|
@ -78,7 +78,7 @@ const getMessage = (icon: IconKey, state: State) => {
|
||||||
default:
|
default:
|
||||||
throw new Error(`Wrong icon: ${icon}`);
|
throw new Error(`Wrong icon: ${icon}`);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function IdealImage(props: Props): JSX.Element {
|
export default function IdealImage(props: Props): JSX.Element {
|
||||||
const {alt, className, img} = props;
|
const {alt, className, img} = props;
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default function pluginPWA(
|
||||||
},
|
},
|
||||||
|
|
||||||
getClientModules() {
|
getClientModules() {
|
||||||
return isProd ? [swRegister] : [];
|
return isProd && swRegister ? [swRegister] : [];
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultCodeTranslationMessages() {
|
getDefaultCodeTranslationMessages() {
|
||||||
|
|
|
@ -6,26 +6,90 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare module '@docusaurus/plugin-pwa' {
|
declare module '@docusaurus/plugin-pwa' {
|
||||||
export type pwaHead = {
|
import type {InjectManifestOptions} from 'workbox-build';
|
||||||
tagName: string;
|
|
||||||
href?: string;
|
|
||||||
content?: string;
|
|
||||||
[attributeName: string]: string | boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PluginOptions = {
|
export type PluginOptions = {
|
||||||
|
/**
|
||||||
|
* Turn debug mode on:
|
||||||
|
*
|
||||||
|
* - Workbox logs
|
||||||
|
* - Additional Docusaurus logs
|
||||||
|
* - Unoptimized SW file output
|
||||||
|
* - Source maps
|
||||||
|
*/
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
offlineModeActivationStrategies;
|
/**
|
||||||
injectManifestConfig;
|
* Strategies used to turn the offline mode on:
|
||||||
reloadPopup;
|
*
|
||||||
pwaHead: pwaHead[];
|
* - `appInstalled`: activates for users having installed the site as an app
|
||||||
swCustom;
|
* (not 100% reliable)
|
||||||
swRegister;
|
* - `standalone`: activates for users running the app as standalone (often
|
||||||
|
* the case once a PWA is installed)
|
||||||
|
* - `queryString`: activates if queryString contains `offlineMode=true`
|
||||||
|
* (convenient for PWA debugging)
|
||||||
|
* - `mobile`: activates for mobile users (width <= 940px)
|
||||||
|
* - `saveData`: activates for users with `navigator.connection.saveData ===
|
||||||
|
* true`
|
||||||
|
* - `always`: activates for all users
|
||||||
|
*/
|
||||||
|
offlineModeActivationStrategies: (
|
||||||
|
| 'appInstalled'
|
||||||
|
| 'queryString'
|
||||||
|
| 'standalone'
|
||||||
|
| 'mobile'
|
||||||
|
| 'saveData'
|
||||||
|
| 'always'
|
||||||
|
)[];
|
||||||
|
/**
|
||||||
|
* Workbox options to pass to `workbox.injectManifest()`. This gives you
|
||||||
|
* control over which assets will be precached, and be available offline.
|
||||||
|
* @see https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.injectManifest
|
||||||
|
*/
|
||||||
|
injectManifestConfig: InjectManifestOptions;
|
||||||
|
/**
|
||||||
|
* Module path to reload popup component. This popup is rendered when a new
|
||||||
|
* service worker is waiting to be installed, and we suggest a reload to
|
||||||
|
* the user.
|
||||||
|
*
|
||||||
|
* Passing `false` will disable the popup, but this is not recommended:
|
||||||
|
* users won't have a way to get up-to-date content.
|
||||||
|
* @see {@link @theme/PwaReloadPopup}
|
||||||
|
*/
|
||||||
|
reloadPopup: string | false;
|
||||||
|
/**
|
||||||
|
* Array of objects containing `tagName` and key-value pairs for attributes
|
||||||
|
* to inject into the `<head>` tag. Technically you can inject any head tag
|
||||||
|
* through this, but it's ideally used for tags to make your site PWA-
|
||||||
|
* compliant.
|
||||||
|
*/
|
||||||
|
pwaHead: {
|
||||||
|
tagName: string;
|
||||||
|
href?: string;
|
||||||
|
content?: string;
|
||||||
|
[attributeName: string]: string | boolean;
|
||||||
|
}[];
|
||||||
|
/**
|
||||||
|
* Useful for additional Workbox rules. You can do whatever a service worker
|
||||||
|
* can do here, and use the full power of workbox libraries. The code is
|
||||||
|
* transpiled, so you can use modern ES6+ syntax here.
|
||||||
|
*/
|
||||||
|
swCustom?: string;
|
||||||
|
/**
|
||||||
|
* Adds an entry before the Docusaurus app so that registration can happen
|
||||||
|
* before the app runs. The default `registerSW.js` file is enough for
|
||||||
|
* simple registration. Passing `false` will disable registration entirely.
|
||||||
|
*/
|
||||||
|
swRegister: string | false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/PwaReloadPopup' {
|
declare module '@theme/PwaReloadPopup' {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
/**
|
||||||
|
* The popup should call this callback when the `reload` button is clicked.
|
||||||
|
* This will tell the service worker to install the waiting service worker
|
||||||
|
* and reload the page.
|
||||||
|
*/
|
||||||
readonly onReload: () => void;
|
readonly onReload: () => void;
|
||||||
}
|
}
|
||||||
export default function PwaReloadPopup(props: Props): JSX.Element;
|
export default function PwaReloadPopup(props: Props): JSX.Element;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import type {
|
||||||
import {PluginOptionSchema} from './pluginOptionSchema';
|
import {PluginOptionSchema} from './pluginOptionSchema';
|
||||||
|
|
||||||
export default function pluginSitemap(
|
export default function pluginSitemap(
|
||||||
_context: LoadContext,
|
context: LoadContext,
|
||||||
options: Options,
|
options: Options,
|
||||||
): Plugin<void> {
|
): Plugin<void> {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import type {EnumChangefreq} from 'sitemap';
|
import type {EnumChangefreq} from 'sitemap';
|
||||||
|
|
||||||
export type Options = {
|
export type Options = {
|
||||||
|
/** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */
|
||||||
changefreq?: EnumChangefreq;
|
changefreq?: EnumChangefreq;
|
||||||
|
/** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */
|
||||||
priority?: number;
|
priority?: number;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,6 @@ export const DEFAULT_OPTIONS: Required<Options> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PluginOptionSchema = Joi.object({
|
export const PluginOptionSchema = Joi.object({
|
||||||
// TODO temporary (@alpha-71)
|
|
||||||
cacheTime: Joi.forbidden().messages({
|
cacheTime: Joi.forbidden().messages({
|
||||||
'any.unknown':
|
'any.unknown':
|
||||||
'Option `cacheTime` in sitemap config is deprecated. Please remove it.',
|
'Option `cacheTime` in sitemap config is deprecated. Please remove it.',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue