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

feat(core): allow plugins to self-disable by returning null #10286

Merged
merged 5 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 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
Loading