feat(v2): plugins injectHtmlTags + configureWebpack should receive content loaded (#5037)

* more lifecycles should receive plugin loaded content

* refactor docs/blog plugins to use newly injected loaded plugin content instead of a mutable variable

* update lifecycle docs

* update lifecycle docs

* fix failing tests
This commit is contained in:
Sébastien Lorber 2021-06-22 17:36:51 +02:00 committed by GitHub
parent 4e88ea0a1a
commit 119c6d143e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 112 additions and 62 deletions

View file

@ -55,7 +55,7 @@ import {
export default function pluginContentBlog( export default function pluginContentBlog(
context: LoadContext, context: LoadContext,
options: PluginOptions, options: PluginOptions,
): Plugin<BlogContent | null> { ): Plugin<BlogContent> {
if (options.admonitions) { if (options.admonitions) {
options.remarkPlugins = options.remarkPlugins.concat([ options.remarkPlugins = options.remarkPlugins.concat([
[admonitions, options.admonitions], [admonitions, options.admonitions],
@ -88,8 +88,6 @@ export default function pluginContentBlog(
const aliasedSource = (source: string) => const aliasedSource = (source: string) =>
`~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`; `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
let blogPosts: BlogPost[] = [];
return { return {
name: 'docusaurus-plugin-content-blog', name: 'docusaurus-plugin-content-blog',
@ -116,10 +114,19 @@ export default function pluginContentBlog(
async loadContent() { async loadContent() {
const {postsPerPage, routeBasePath} = options; const {postsPerPage, routeBasePath} = options;
blogPosts = await generateBlogPosts(contentPaths, context, options); const blogPosts: BlogPost[] = await generateBlogPosts(
contentPaths,
context,
options,
);
if (!blogPosts.length) { if (!blogPosts.length) {
return null; return {
blogPosts: [],
blogListPaginated: [],
blogTags: {},
blogTagsListPath: null,
};
} }
// Colocate next and prev metadata. // Colocate next and prev metadata.
@ -242,7 +249,7 @@ export default function pluginContentBlog(
const {addRoute, createData} = actions; const {addRoute, createData} = actions;
const { const {
blogPosts: loadedBlogPosts, blogPosts,
blogListPaginated, blogListPaginated,
blogTags, blogTags,
blogTagsListPath, blogTagsListPath,
@ -275,7 +282,7 @@ export default function pluginContentBlog(
// Create routes for blog entries. // Create routes for blog entries.
await Promise.all( await Promise.all(
loadedBlogPosts.map(async (blogPost) => { blogPosts.map(async (blogPost) => {
const {id, metadata} = blogPost; const {id, metadata} = blogPost;
await createData( await createData(
// Note that this created data path must be in sync with // Note that this created data path must be in sync with
@ -403,6 +410,7 @@ export default function pluginContentBlog(
_config: Configuration, _config: Configuration,
isServer: boolean, isServer: boolean,
{getJSLoader}: ConfigureWebpackUtils, {getJSLoader}: ConfigureWebpackUtils,
content,
) { ) {
const { const {
rehypePlugins, rehypePlugins,
@ -416,7 +424,7 @@ export default function pluginContentBlog(
siteDir, siteDir,
contentPaths, contentPaths,
truncateMarker, truncateMarker,
sourceToPermalink: getSourceToPermalink(blogPosts), sourceToPermalink: getSourceToPermalink(content.blogPosts),
onBrokenMarkdownLink: (brokenMarkdownLink) => { onBrokenMarkdownLink: (brokenMarkdownLink) => {
if (onBrokenMarkdownLinks === 'ignore') { if (onBrokenMarkdownLinks === 'ignore') {
return; return;
@ -506,8 +514,8 @@ export default function pluginContentBlog(
); );
}, },
injectHtmlTags() { injectHtmlTags({content}) {
if (!blogPosts.length) { if (!content.blogPosts.length) {
return {}; return {};
} }

View file

@ -309,6 +309,8 @@ describe('simple website', () => {
test('configureWebpack', async () => { test('configureWebpack', async () => {
const {plugin} = await loadSite(); const {plugin} = await loadSite();
const content = await plugin.loadContent?.();
const config = applyConfigureWebpack( const config = applyConfigureWebpack(
plugin.configureWebpack, plugin.configureWebpack,
{ {
@ -319,6 +321,8 @@ describe('simple website', () => {
}, },
}, },
false, false,
undefined,
content,
); );
const errors = validate(config); const errors = validate(config);
expect(errors).toBeUndefined(); expect(errors).toBeUndefined();

View file

@ -41,7 +41,7 @@ import {PermalinkToSidebar} from '@docusaurus/plugin-content-docs-types';
import {RuleSetRule} from 'webpack'; import {RuleSetRule} from 'webpack';
import {cliDocsVersionCommand} from './cli'; import {cliDocsVersionCommand} from './cli';
import {VERSIONS_JSON_FILE} from './constants'; import {VERSIONS_JSON_FILE} from './constants';
import {flatten, keyBy, compact} from 'lodash'; import {flatten, keyBy, compact, mapValues} from 'lodash';
import {toGlobalDataVersion} from './globalData'; import {toGlobalDataVersion} from './globalData';
import {toVersionMetadataProp} from './props'; import {toVersionMetadataProp} from './props';
import { import {
@ -59,7 +59,6 @@ export default function pluginContentDocs(
const versionsMetadata = readVersionsMetadata({context, options}); const versionsMetadata = readVersionsMetadata({context, options});
const sourceToPermalink: SourceToPermalink = {};
const pluginId = options.id ?? DEFAULT_PLUGIN_ID; const pluginId = options.id ?? DEFAULT_PLUGIN_ID;
const pluginDataDirRoot = path.join( const pluginDataDirRoot = path.join(
@ -225,12 +224,6 @@ export default function pluginContentDocs(
// sort to ensure consistent output for tests // sort to ensure consistent output for tests
docs.sort((a, b) => a.id.localeCompare(b.id)); docs.sort((a, b) => a.id.localeCompare(b.id));
// TODO annoying side effect!
Object.values(docs).forEach((loadedDoc) => {
const {source, permalink} = loadedDoc;
sourceToPermalink[source] = permalink;
});
// TODO really useful? replace with global state logic? // TODO really useful? replace with global state logic?
const permalinkToSidebar: PermalinkToSidebar = {}; const permalinkToSidebar: PermalinkToSidebar = {};
Object.values(docs).forEach((doc) => { Object.values(docs).forEach((doc) => {
@ -369,7 +362,7 @@ export default function pluginContentDocs(
}); });
}, },
configureWebpack(_config, isServer, utils) { configureWebpack(_config, isServer, utils, content) {
const {getJSLoader} = utils; const {getJSLoader} = utils;
const { const {
rehypePlugins, rehypePlugins,
@ -378,9 +371,17 @@ export default function pluginContentDocs(
beforeDefaultRemarkPlugins, beforeDefaultRemarkPlugins,
} = options; } = options;
function getSourceToPermalink(): SourceToPermalink {
const allDocs = flatten(content.loadedVersions.map((v) => v.docs));
return mapValues(
keyBy(allDocs, (d) => d.source),
(d) => d.permalink,
);
}
const docsMarkdownOptions: DocsMarkdownOption = { const docsMarkdownOptions: DocsMarkdownOption = {
siteDir, siteDir,
sourceToPermalink, sourceToPermalink: getSourceToPermalink(),
versionsMetadata, versionsMetadata,
onBrokenMarkdownLink: (brokenMarkdownLink) => { onBrokenMarkdownLink: (brokenMarkdownLink) => {
if (siteConfig.onBrokenMarkdownLinks === 'ignore') { if (siteConfig.onBrokenMarkdownLinks === 'ignore') {

View file

@ -198,7 +198,7 @@ export interface Props extends LoadContext, InjectedHtmlTags {
siteMetadata: DocusaurusSiteMetadata; siteMetadata: DocusaurusSiteMetadata;
routes: RouteConfig[]; routes: RouteConfig[];
routesPaths: string[]; routesPaths: string[];
plugins: Plugin<unknown>[]; plugins: LoadedPlugin<unknown>[];
} }
export interface PluginContentLoadedActions { export interface PluginContentLoadedActions {
@ -233,10 +233,12 @@ export interface Plugin<Content> {
routesLoaded?(routes: RouteConfig[]): void; // TODO remove soon, deprecated (alpha-60) routesLoaded?(routes: RouteConfig[]): void; // TODO remove soon, deprecated (alpha-60)
postBuild?(props: Props): void; postBuild?(props: Props): void;
postStart?(props: Props): void; postStart?(props: Props): void;
// TODO refactor the configureWebpack API surface: use an object instead of multiple params (requires breaking change)
configureWebpack?( configureWebpack?(
config: Configuration, config: Configuration,
isServer: boolean, isServer: boolean,
utils: ConfigureWebpackUtils, utils: ConfigureWebpackUtils,
content: Content,
): Configuration & {mergeStrategy?: ConfigureWebpackFnMergeStrategy}; ): Configuration & {mergeStrategy?: ConfigureWebpackFnMergeStrategy};
configurePostCss?(options: PostCssOptions): PostCssOptions; configurePostCss?(options: PostCssOptions): PostCssOptions;
getThemePath?(): string; getThemePath?(): string;
@ -244,7 +246,9 @@ export interface Plugin<Content> {
getPathsToWatch?(): string[]; getPathsToWatch?(): string[];
getClientModules?(): string[]; getClientModules?(): string[];
extendCli?(cli: Command): void; extendCli?(cli: Command): void;
injectHtmlTags?(): { injectHtmlTags?({
content: Content,
}): {
headTags?: HtmlTags; headTags?: HtmlTags;
preBodyTags?: HtmlTags; preBodyTags?: HtmlTags;
postBodyTags?: HtmlTags; postBodyTags?: HtmlTags;

View file

@ -184,17 +184,19 @@ async function buildLocale({
if (configureWebpack) { if (configureWebpack) {
clientConfig = applyConfigureWebpack( clientConfig = applyConfigureWebpack(
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. // TODO remove this implicit api: inject in callback instead
clientConfig, clientConfig,
false, false,
props.siteConfig.webpack?.jsLoader, props.siteConfig.webpack?.jsLoader,
plugin.content,
); );
serverConfig = applyConfigureWebpack( serverConfig = applyConfigureWebpack(
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. // TODO remove this implicit api: inject in callback instead
serverConfig, serverConfig,
true, true,
props.siteConfig.webpack?.jsLoader, props.siteConfig.webpack?.jsLoader,
plugin.content,
); );
} }
}); });

View file

@ -156,10 +156,11 @@ export default async function start(
if (configureWebpack) { if (configureWebpack) {
config = applyConfigureWebpack( config = applyConfigureWebpack(
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. // TODO remove this implicit api: inject in callback instead
config, config,
false, false,
props.siteConfig.webpack?.jsLoader, props.siteConfig.webpack?.jsLoader,
plugin.content,
); );
} }
}); });

View file

@ -6,12 +6,8 @@
*/ */
import htmlTagObjectToString from './htmlTags'; import htmlTagObjectToString from './htmlTags';
import { import {InjectedHtmlTags, HtmlTagObject, HtmlTags} from '@docusaurus/types';
Plugin, import {LoadedPlugin} from '../plugins';
InjectedHtmlTags,
HtmlTagObject,
HtmlTags,
} from '@docusaurus/types';
function toString(val: string | HtmlTagObject): string { function toString(val: string | HtmlTagObject): string {
return typeof val === 'string' ? val : htmlTagObjectToString(val); return typeof val === 'string' ? val : htmlTagObjectToString(val);
@ -21,14 +17,14 @@ export function createHtmlTagsString(tags: HtmlTags): string {
return Array.isArray(tags) ? tags.map(toString).join('\n') : toString(tags); return Array.isArray(tags) ? tags.map(toString).join('\n') : toString(tags);
} }
export function loadHtmlTags(plugins: Plugin<unknown>[]): InjectedHtmlTags { export function loadHtmlTags(plugins: LoadedPlugin[]): InjectedHtmlTags {
const htmlTags = plugins.reduce( const htmlTags = plugins.reduce(
(acc, plugin) => { (acc, plugin) => {
if (!plugin.injectHtmlTags) { if (!plugin.injectHtmlTags) {
return acc; return acc;
} }
const {headTags, preBodyTags, postBodyTags} = const {headTags, preBodyTags, postBodyTags} =
plugin.injectHtmlTags() || {}; plugin.injectHtmlTags({content: plugin.content}) || {};
return { return {
headTags: headTags headTags: headTags
? `${acc.headTags}\n${createHtmlTagsString(headTags)}` ? `${acc.headTags}\n${createHtmlTagsString(headTags)}`

View file

@ -196,6 +196,7 @@ export async function load(
} = siteConfig; } = siteConfig;
plugins.push({ plugins.push({
name: 'docusaurus-bootstrap-plugin', name: 'docusaurus-bootstrap-plugin',
content: null,
options: {}, options: {},
version: {type: 'synthetic'}, version: {type: 'synthetic'},
getClientModules() { getClientModules() {

View file

@ -53,6 +53,8 @@ export function sortConfig(routeConfigs: RouteConfig[]): void {
}); });
} }
export type LoadedPlugin = InitPlugin & {content: unknown};
export async function loadPlugins({ export async function loadPlugins({
pluginConfigs, pluginConfigs,
context, context,
@ -60,7 +62,7 @@ export async function loadPlugins({
pluginConfigs: PluginConfig[]; pluginConfigs: PluginConfig[];
context: LoadContext; context: LoadContext;
}): Promise<{ }): Promise<{
plugins: InitPlugin[]; plugins: LoadedPlugin[];
pluginsRouteConfigs: RouteConfig[]; pluginsRouteConfigs: RouteConfig[];
globalData: unknown; globalData: unknown;
themeConfigTranslated: ThemeConfig; themeConfigTranslated: ThemeConfig;
@ -75,21 +77,20 @@ export async function loadPlugins({
// Currently plugins run lifecycle methods in parallel and are not order-dependent. // Currently plugins run lifecycle methods in parallel and are not order-dependent.
// We could change this in future if there are plugins which need to // We could change this in future if there are plugins which need to
// run in certain order or depend on others for data. // run in certain order or depend on others for data.
type ContentLoadedPlugin = {plugin: InitPlugin; content: unknown}; const loadedPlugins: LoadedPlugin[] = await Promise.all(
const contentLoadedPlugins: ContentLoadedPlugin[] = await Promise.all(
plugins.map(async (plugin) => { plugins.map(async (plugin) => {
const content = plugin.loadContent ? await plugin.loadContent() : null; const content = plugin.loadContent ? await plugin.loadContent() : null;
return {plugin, content}; return {...plugin, content};
}), }),
); );
type ContentLoadedTranslatedPlugin = ContentLoadedPlugin & { type ContentLoadedTranslatedPlugin = LoadedPlugin & {
translationFiles: TranslationFiles; translationFiles: TranslationFiles;
}; };
const contentLoadedTranslatedPlugins: ContentLoadedTranslatedPlugin[] = await Promise.all( const contentLoadedTranslatedPlugins: ContentLoadedTranslatedPlugin[] = await Promise.all(
contentLoadedPlugins.map(async (contentLoadedPlugin) => { loadedPlugins.map(async (contentLoadedPlugin) => {
const translationFiles = const translationFiles =
(await contentLoadedPlugin.plugin?.getTranslationFiles?.({ (await contentLoadedPlugin?.getTranslationFiles?.({
content: contentLoadedPlugin.content, content: contentLoadedPlugin.content,
})) ?? []; })) ?? [];
const localizedTranslationFiles = await Promise.all( const localizedTranslationFiles = await Promise.all(
@ -98,7 +99,7 @@ export async function loadPlugins({
locale: context.i18n.currentLocale, locale: context.i18n.currentLocale,
siteDir: context.siteDir, siteDir: context.siteDir,
translationFile, translationFile,
plugin: contentLoadedPlugin.plugin, plugin: contentLoadedPlugin,
}), }),
), ),
); );
@ -109,11 +110,11 @@ export async function loadPlugins({
}), }),
); );
const allContent: AllContent = chain(contentLoadedPlugins) const allContent: AllContent = chain(loadedPlugins)
.groupBy((item) => item.plugin.name) .groupBy((item) => item.name)
.mapValues((nameItems) => { .mapValues((nameItems) => {
return chain(nameItems) return chain(nameItems)
.groupBy((item) => item.plugin.options.id ?? DEFAULT_PLUGIN_ID) .groupBy((item) => item.options.id ?? DEFAULT_PLUGIN_ID)
.mapValues((idItems) => idItems[0].content) .mapValues((idItems) => idItems[0].content)
.value(); .value();
}) })
@ -126,7 +127,7 @@ export async function loadPlugins({
await Promise.all( await Promise.all(
contentLoadedTranslatedPlugins.map( contentLoadedTranslatedPlugins.map(
async ({plugin, content, translationFiles}) => { async ({content, translationFiles, ...plugin}) => {
if (!plugin.contentLoaded) { if (!plugin.contentLoaded) {
return; return;
} }
@ -191,7 +192,7 @@ export async function loadPlugins({
// We could change this in future if there are plugins which need to // We could change this in future if there are plugins which need to
// run in certain order or depend on others for data. // run in certain order or depend on others for data.
await Promise.all( await Promise.all(
contentLoadedTranslatedPlugins.map(async ({plugin}) => { contentLoadedTranslatedPlugins.map(async (plugin) => {
if (!plugin.routesLoaded) { if (!plugin.routesLoaded) {
return null; return null;
} }
@ -218,10 +219,10 @@ export async function loadPlugins({
untranslatedThemeConfig: ThemeConfig, untranslatedThemeConfig: ThemeConfig,
): ThemeConfig { ): ThemeConfig {
return contentLoadedTranslatedPlugins.reduce( return contentLoadedTranslatedPlugins.reduce(
(currentThemeConfig, {plugin, translationFiles}) => { (currentThemeConfig, plugin) => {
const translatedThemeConfigSlice = plugin.translateThemeConfig?.({ const translatedThemeConfigSlice = plugin.translateThemeConfig?.({
themeConfig: currentThemeConfig, themeConfig: currentThemeConfig,
translationFiles, translationFiles: plugin.translationFiles,
}); });
return { return {
...currentThemeConfig, ...currentThemeConfig,
@ -233,7 +234,7 @@ export async function loadPlugins({
} }
return { return {
plugins, plugins: loadedPlugins,
pluginsRouteConfigs, pluginsRouteConfigs,
globalData, globalData,
themeConfigTranslated: translateThemeConfig(context.siteConfig.themeConfig), themeConfigTranslated: translateThemeConfig(context.siteConfig.themeConfig),

View file

@ -77,7 +77,9 @@ describe('extending generated webpack config', () => {
return {}; return {};
}; };
config = applyConfigureWebpack(configureWebpack, config, false); config = applyConfigureWebpack(configureWebpack, config, false, undefined, {
content: 42,
});
expect(config).toEqual({ expect(config).toEqual({
entry: 'entry.js', entry: 'entry.js',
output: { output: {
@ -105,7 +107,9 @@ describe('extending generated webpack config', () => {
}, },
}); });
config = applyConfigureWebpack(configureWebpack, config, false); config = applyConfigureWebpack(configureWebpack, config, false, undefined, {
content: 42,
});
expect(config).toEqual({ expect(config).toEqual({
entry: 'entry.js', entry: 'entry.js',
output: { output: {
@ -137,6 +141,8 @@ describe('extending generated webpack config', () => {
createConfigureWebpack(), createConfigureWebpack(),
config, config,
false, false,
undefined,
{content: 42},
); );
expect(defaultStrategyMergeConfig).toEqual({ expect(defaultStrategyMergeConfig).toEqual({
module: { module: {
@ -148,6 +154,8 @@ describe('extending generated webpack config', () => {
createConfigureWebpack({'module.rules': 'prepend'}), createConfigureWebpack({'module.rules': 'prepend'}),
config, config,
false, false,
undefined,
{content: 42},
); );
expect(prependRulesStrategyConfig).toEqual({ expect(prependRulesStrategyConfig).toEqual({
module: { module: {
@ -159,6 +167,8 @@ describe('extending generated webpack config', () => {
createConfigureWebpack({uselessAttributeName: 'append'}), createConfigureWebpack({uselessAttributeName: 'append'}),
config, config,
false, false,
undefined,
{content: 42},
); );
expect(uselessMergeStrategyConfig).toEqual({ expect(uselessMergeStrategyConfig).toEqual({
module: { module: {

View file

@ -198,13 +198,15 @@ function getCacheLoaderDeprecated() {
* @param config initial webpack config * @param config initial webpack config
* @param isServer indicates if this is a server webpack configuration * @param isServer indicates if this is a server webpack configuration
* @param jsLoader custom js loader config * @param jsLoader custom js loader config
* @param content content loaded by the plugin
* @returns final/ modified webpack config * @returns final/ modified webpack config
*/ */
export function applyConfigureWebpack( export function applyConfigureWebpack(
configureWebpack: ConfigureWebpackFn, configureWebpack: ConfigureWebpackFn,
config: Configuration, config: Configuration,
isServer: boolean, isServer: boolean,
jsLoader?: 'babel' | ((isServer: boolean) => RuleSetRule), jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) | undefined,
content: unknown,
): Configuration { ): Configuration {
// Export some utility functions // Export some utility functions
const utils: ConfigureWebpackUtils = { const utils: ConfigureWebpackUtils = {
@ -214,7 +216,12 @@ export function applyConfigureWebpack(
getCacheLoader: getCacheLoaderDeprecated, getCacheLoader: getCacheLoaderDeprecated,
}; };
if (typeof configureWebpack === 'function') { if (typeof configureWebpack === 'function') {
const {mergeStrategy, ...res} = configureWebpack(config, isServer, utils); const {mergeStrategy, ...res} = configureWebpack(
config,
isServer,
utils,
content,
);
if (res && typeof res === 'object') { if (res && typeof res === 'object') {
// @ts-expect-error: annoying error due to enums: https://github.com/survivejs/webpack-merge/issues/179 // @ts-expect-error: annoying error due to enums: https://github.com/survivejs/webpack-merge/issues/179
const customizeRules: Record<string, CustomizeRule> = mergeStrategy ?? {}; const customizeRules: Record<string, CustomizeRule> = mergeStrategy ?? {};

View file

@ -279,10 +279,16 @@ export default function friendsPlugin(context, options) {
} }
``` ```
## `configureWebpack(config, isServer, utils)` {#configurewebpackconfig-isserver-utils} ## `configureWebpack(config, isServer, utils, content)` {#configurewebpackconfig-isserver-utils}
Modifies the internal webpack config. If the return value is a JavaScript object, it will be merged into the final config using [`webpack-merge`](https://github.com/survivejs/webpack-merge). If it is a function, it will be called and receive `config` as the first argument and an `isServer` flag as the argument argument. Modifies the internal webpack config. If the return value is a JavaScript object, it will be merged into the final config using [`webpack-merge`](https://github.com/survivejs/webpack-merge). If it is a function, it will be called and receive `config` as the first argument and an `isServer` flag as the argument argument.
:::caution
The API of `configureWebpack` will be modified in the future to accept an object (`configureWebpack({config, isServer, utils, content})`)
:::
### `config` {#config} ### `config` {#config}
`configureWebpack` is called with `config` generated according to client/server build. You may treat this as the base config to be merged with. `configureWebpack` is called with `config` generated according to client/server build. You may treat this as the base config to be merged with.
@ -293,11 +299,10 @@ Modifies the internal webpack config. If the return value is a JavaScript object
### `utils` {#utils} ### `utils` {#utils}
The initial call to `configureWebpack` also receives a util object consists of three functions: `configureWebpack` also receives an util object:
- `getStyleLoaders(isServer: boolean, cssOptions: {[key: string]: any}): Loader[]` - `getStyleLoaders(isServer: boolean, cssOptions: {[key: string]: any}): Loader[]`
- `getCacheLoader(isServer: boolean, cacheOptions?: {}): Loader | null` - `getJSLoader(isServer: boolean, cacheOptions?: {}): Loader | null`
- `getBabelLoader(isServer: boolean, babelOptions?: {}): Loader`
You may use them to return your webpack configures conditionally. You may use them to return your webpack configures conditionally.
@ -326,6 +331,10 @@ module.exports = function (context, options) {
}; };
``` ```
### `content` {#content}
`configureWebpack` will be called both with the content loaded by the plugin.
### Merge strategy {#merge-strategy} ### Merge strategy {#merge-strategy}
We merge the Webpack configuration parts of plugins into the global Webpack config using [webpack-merge](https://github.com/survivejs/webpack-merge). We merge the Webpack configuration parts of plugins into the global Webpack config using [webpack-merge](https://github.com/survivejs/webpack-merge).
@ -439,10 +448,12 @@ module.exports = function (context, options) {
}; };
``` ```
## `injectHtmlTags()` {#injecthtmltags} ## `injectHtmlTags({content})` {#injecthtmltags}
Inject head and/or body HTML tags to Docusaurus generated HTML. Inject head and/or body HTML tags to Docusaurus generated HTML.
`injectHtmlTags` will be called both with the content loaded by the plugin.
```typescript ```typescript
function injectHtmlTags(): { function injectHtmlTags(): {
headTags?: HtmlTags; headTags?: HtmlTags;
@ -477,8 +488,11 @@ Example:
module.exports = function (context, options) { module.exports = function (context, options) {
return { return {
name: 'docusaurus-plugin', name: 'docusaurus-plugin',
loadContent: async () => {
return {remoteHeadTags: await fetchHeadTagsFromAPI()};
},
// highlight-start // highlight-start
injectHtmlTags() { injectHtmlTags({content}) {
return { return {
headTags: [ headTags: [
{ {
@ -488,6 +502,7 @@ module.exports = function (context, options) {
href: 'https://www.github.com', href: 'https://www.github.com',
}, },
}, },
...content.remoteHeadTags,
], ],
preBodyTags: [ preBodyTags: [
{ {
@ -765,7 +780,7 @@ module.exports = function (context, opts) {
// https://webpack.js.org/configuration/dev-server/#devserverafter // https://webpack.js.org/configuration/dev-server/#devserverafter
}, },
configureWebpack(config, isServer) { configureWebpack(config, isServer, utils, content) {
// Modify internal webpack config. If returned value is an Object, it // Modify internal webpack config. If returned value is an Object, it
// will be merged into the final config using webpack-merge; // will be merged into the final config using webpack-merge;
// If the returned value is a function, it will receive the config as the 1st argument and an isServer flag as the 2nd argument. // If the returned value is a function, it will receive the config as the 1st argument and an isServer flag as the 2nd argument.
@ -790,11 +805,11 @@ module.exports = function (context, opts) {
// Register an extra command to enhance the CLI of Docusaurus // Register an extra command to enhance the CLI of Docusaurus
}, },
injectHtmlTags() { injectHtmlTags({content}) {
// Inject head and/or body HTML tags. // Inject head and/or body HTML tags.
}, },
async getTranslationFiles() { async getTranslationFiles({content}) {
// Return translation files // Return translation files
}, },