mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-20 20:46:58 +02:00
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:
parent
4e88ea0a1a
commit
119c6d143e
12 changed files with 112 additions and 62 deletions
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
8
packages/docusaurus-types/src/index.d.ts
vendored
8
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)}`
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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 ?? {};
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue