Skip to content

Commit

Permalink
feat(core): allow plugins to self-disable by returning null (#10286)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored and OzakIOne committed Jul 11, 2024
1 parent e8b6de9 commit 9da3448
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 89 deletions.
4 changes: 2 additions & 2 deletions packages/docusaurus-plugin-client-redirects/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ const PluginName = 'docusaurus-plugin-client-redirects';
export default function pluginClientRedirectsPages(
context: LoadContext,
options: PluginOptions,
): Plugin<void> {
): Plugin<void> | null {
const {trailingSlash} = context.siteConfig;
const router = context.siteConfig.future.experimental_router;

if (router === 'hash') {
logger.warn(
`${PluginName} does not support the Hash Router and will be disabled.`,
);
return {name: PluginName};
return null;
}

return {
Expand Down
12 changes: 6 additions & 6 deletions packages/docusaurus-plugin-google-analytics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ import type {PluginOptions, Options} from './options';
export default function pluginGoogleAnalytics(
context: LoadContext,
options: PluginOptions,
): Plugin {
): Plugin | null {
if (process.env.NODE_ENV !== 'production') {
return null;
}

const {trackingID, anonymizeIP} = options;
const isProd = process.env.NODE_ENV === 'production';

return {
name: 'docusaurus-plugin-google-analytics',

getClientModules() {
return isProd ? ['./analytics'] : [];
return ['./analytics'];
},

injectHtmlTags() {
if (!isProd) {
return {};
}
return {
headTags: [
{
Expand Down
11 changes: 5 additions & 6 deletions packages/docusaurus-plugin-google-gtag/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ function createConfigSnippets({
export default function pluginGoogleGtag(
context: LoadContext,
options: PluginOptions,
): Plugin {
const isProd = process.env.NODE_ENV === 'production';
): Plugin | null {
if (process.env.NODE_ENV !== 'production') {
return null;
}

const firstTrackingId = options.trackingID[0];

Expand All @@ -45,13 +47,10 @@ export default function pluginGoogleGtag(
},

getClientModules() {
return isProd ? ['./gtag'] : [];
return ['./gtag'];
},

injectHtmlTags() {
if (!isProd) {
return {};
}
return {
// Gtag includes GA by default, so we also preconnect to
// google-analytics.
Expand Down
11 changes: 5 additions & 6 deletions packages/docusaurus-plugin-google-tag-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import type {PluginOptions, Options} from './options';
export default function pluginGoogleAnalytics(
context: LoadContext,
options: PluginOptions,
): Plugin {
const {containerId} = options;
const isProd = process.env.NODE_ENV === 'production';
): Plugin | null {
if (process.env.NODE_ENV !== 'production') {
return null;
}

const {containerId} = options;
return {
name: 'docusaurus-plugin-google-tag-manager',

Expand All @@ -28,9 +30,6 @@ export default function pluginGoogleAnalytics(
},

injectHtmlTags() {
if (!isProd) {
return {};
}
return {
preBodyTags: [
{
Expand Down
74 changes: 32 additions & 42 deletions packages/docusaurus-plugin-pwa/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import type {PluginOptions} from '@docusaurus/plugin-pwa';

const PluginName = 'docusaurus-plugin-pwa';

const isProd = process.env.NODE_ENV === 'production';

function getSWBabelLoader() {
return {
loader: 'babel-loader',
Expand All @@ -45,12 +43,21 @@ function getSWBabelLoader() {
export default function pluginPWA(
context: LoadContext,
options: PluginOptions,
): Plugin<void> {
): Plugin<void> | null {
if (process.env.NODE_ENV !== 'production') {
return null;
}
if (context.siteConfig.future.experimental_router === 'hash') {
logger.warn(
`${PluginName} does not support the Hash Router and will be disabled.`,
);
return null;
}

const {
outDir,
baseUrl,
i18n: {currentLocale},
siteConfig,
} = context;
const {
debug,
Expand All @@ -61,13 +68,6 @@ export default function pluginPWA(
swRegister,
} = options;

if (siteConfig.future.experimental_router === 'hash') {
logger.warn(
`${PluginName} does not support the Hash Router and will be disabled.`,
);
return {name: PluginName};
}

return {
name: PluginName,

Expand All @@ -79,7 +79,7 @@ export default function pluginPWA(
},

getClientModules() {
return isProd && swRegister ? [swRegister] : [];
return swRegister ? [swRegister] : [];
},

getDefaultCodeTranslationMessages() {
Expand All @@ -90,10 +90,6 @@ export default function pluginPWA(
},

configureWebpack(config) {
if (!isProd) {
return {};
}

return {
plugins: [
new webpack.EnvironmentPlugin({
Expand All @@ -111,37 +107,31 @@ export default function pluginPWA(

injectHtmlTags() {
const headTags: HtmlTags = [];
if (isProd) {
pwaHead.forEach(({tagName, ...attributes}) => {
(['href', 'content'] as const).forEach((attribute) => {
const attributeValue = attributes[attribute];

if (!attributeValue) {
return;
}

const attributePath =
!!path.extname(attributeValue) && attributeValue;

if (attributePath && !attributePath.startsWith(baseUrl)) {
attributes[attribute] = normalizeUrl([baseUrl, attributeValue]);
}
});

return headTags.push({
tagName,
attributes,
});
pwaHead.forEach(({tagName, ...attributes}) => {
(['href', 'content'] as const).forEach((attribute) => {
const attributeValue = attributes[attribute];

if (!attributeValue) {
return;
}

const attributePath =
!!path.extname(attributeValue) && attributeValue;

if (attributePath && !attributePath.startsWith(baseUrl)) {
attributes[attribute] = normalizeUrl([baseUrl, attributeValue]);
}
});

return headTags.push({
tagName,
attributes,
});
}
});
return {headTags};
},

async postBuild(props) {
if (!isProd) {
return;
}

const swSourceFileTest = /\.m?js$/;

const swWebpackConfig: Configuration = {
Expand Down
4 changes: 2 additions & 2 deletions packages/docusaurus-plugin-sitemap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ const PluginName = 'docusaurus-plugin-sitemap';
export default function pluginSitemap(
context: LoadContext,
options: PluginOptions,
): Plugin<void> {
): Plugin<void> | null {
if (context.siteConfig.future.experimental_router === 'hash') {
logger.warn(
`${PluginName} does not support the Hash Router and will be disabled.`,
);
return {name: PluginName};
return null;
}

return {
Expand Down
9 changes: 5 additions & 4 deletions packages/docusaurus-plugin-vercel-analytics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import type {PluginOptions, Options} from './options';
export default function pluginVercelAnalytics(
context: LoadContext,
options: PluginOptions,
): Plugin {
const isProd = process.env.NODE_ENV === 'production';

): Plugin | null {
if (process.env.NODE_ENV !== 'production') {
return null;
}
return {
name: 'docusaurus-plugin-vercel-analytics',

getClientModules() {
return isProd ? ['./analytics'] : [];
return ['./analytics'];
},

contentLoaded({actions}) {
Expand Down
5 changes: 4 additions & 1 deletion packages/docusaurus-types/src/plugin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ export type LoadedPlugin = InitializedPlugin & {
export type PluginModule<Content = unknown> = {
(context: LoadContext, options: unknown):
| Plugin<Content>
| Promise<Plugin<Content>>;
| Promise<Plugin<Content>>
| null
| Promise<null>;

validateOptions?: <T, U>(data: OptionValidationContext<T, U>) => U;
validateThemeConfig?: <T>(data: ThemeConfigValidationContext<T>) => T;

Expand Down
37 changes: 28 additions & 9 deletions packages/docusaurus/src/commands/swizzle/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,41 @@
*/

import {loadContext} from '../../server/site';
import {initPlugins} from '../../server/plugins/init';
import {initPluginsConfigs} from '../../server/plugins/init';
import {loadPluginConfigs} from '../../server/plugins/configs';
import type {SwizzleCLIOptions, SwizzleContext} from './common';
import type {SwizzleCLIOptions, SwizzleContext, SwizzlePlugin} from './common';
import type {LoadContext} from '@docusaurus/types';

async function getSwizzlePlugins(
context: LoadContext,
): Promise<SwizzlePlugin[]> {
const pluginConfigs = await loadPluginConfigs(context);
const pluginConfigInitResults = await initPluginsConfigs(
context,
pluginConfigs,
);

return pluginConfigInitResults.flatMap((initResult) => {
// Ignore self-disabling plugins returning null
if (initResult.plugin === null) {
return [];
}
return [
// TODO this is a bit confusing, need refactor
{
plugin: initResult.config,
instance: initResult.plugin,
},
];
});
}

export async function initSwizzleContext(
siteDir: string,
options: SwizzleCLIOptions,
): Promise<SwizzleContext> {
const context = await loadContext({siteDir, config: options.config});
const plugins = await initPlugins(context);
const pluginConfigs = await loadPluginConfigs(context);

return {
plugins: plugins.map((plugin, pluginIndex) => ({
plugin: pluginConfigs[pluginIndex]!,
instance: plugin,
})),
plugins: await getSwizzlePlugins(context),
};
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion packages/docusaurus/src/server/plugins/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async function loadSite(
describe('initPlugins', () => {
it('parses plugins correctly and loads them in correct order', async () => {
const {context, plugins} = await loadSite('site-with-plugin');
expect(context.siteConfig.plugins).toHaveLength(6);
expect(context.siteConfig.plugins).toHaveLength(7);
expect(plugins).toHaveLength(10);

expect(plugins[0]!.name).toBe('preset-plugin1');
Expand Down Expand Up @@ -85,4 +85,13 @@ describe('initPlugins', () => {
Note that even inline/anonymous plugin functions require a 'name' property."
`);
});

it('throws user-friendly error message for plugins returning undefined', async () => {
await expect(() => loadSite('site-with-undefined-plugin')).rejects
.toThrowErrorMatchingInlineSnapshot(`
"A Docusaurus plugin returned 'undefined', which is forbidden.
A plugin is expected to return an object having at least a 'name' property.
If you want a plugin to self-disable depending on context/options, you can explicitly return 'null' instead of 'undefined'"
`);
});
});
Loading

0 comments on commit 9da3448

Please sign in to comment.