Skip to content

Commit 38e65f4

Browse files
johnnyreillyslorber
andcommitted
feat(core): siteConfig.headTags API to render extra tags in document head (#8151)
Co-authored-by: Sébastien Lorber <[email protected]>
1 parent 824a0f3 commit 38e65f4

File tree

7 files changed

+124
-2
lines changed

7 files changed

+124
-2
lines changed

packages/docusaurus-types/src/config.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import type {RuleSetRule} from 'webpack';
99
import type {Required as RequireKeys, DeepPartial} from 'utility-types';
1010
import type {I18nConfig} from './i18n';
11-
import type {PluginConfig, PresetConfig} from './plugin';
11+
import type {PluginConfig, PresetConfig, HtmlTagObject} from './plugin';
1212

1313
export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'throw';
1414

@@ -192,6 +192,13 @@ export type DocusaurusConfig = {
192192
* @default ["static"]
193193
*/
194194
staticDirectories: string[];
195+
/**
196+
* An array of tags that will be inserted in the HTML `<head>`.
197+
*
198+
* @see https://docusaurus.io/docs/api/docusaurus-config#head
199+
* @default []
200+
*/
201+
headTags: HtmlTagObject[];
195202
/**
196203
* An array of scripts to load. The values can be either strings or plain
197204
* objects of attribute-value maps. The `<script>` tags will be inserted in

packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
77
"baseUrlIssueBanner": true,
88
"clientModules": [],
99
"customFields": {},
10+
"headTags": [],
1011
"i18n": {
1112
"defaultLocale": "en",
1213
"localeConfigs": {},
@@ -44,6 +45,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
4445
"baseUrlIssueBanner": true,
4546
"clientModules": [],
4647
"customFields": {},
48+
"headTags": [],
4749
"i18n": {
4850
"defaultLocale": "en",
4951
"localeConfigs": {},
@@ -83,6 +85,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
8385
"baseUrlIssueBanner": true,
8486
"clientModules": [],
8587
"customFields": {},
88+
"headTags": [],
8689
"i18n": {
8790
"defaultLocale": "en",
8891
"localeConfigs": {},
@@ -122,6 +125,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
122125
"baseUrlIssueBanner": true,
123126
"clientModules": [],
124127
"customFields": {},
128+
"headTags": [],
125129
"i18n": {
126130
"defaultLocale": "en",
127131
"localeConfigs": {},
@@ -164,6 +168,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
164168
],
165169
"customFields": {},
166170
"favicon": "img/docusaurus.ico",
171+
"headTags": [],
167172
"i18n": {
168173
"defaultLocale": "en",
169174
"localeConfigs": {},

packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
7171
"baseUrlIssueBanner": true,
7272
"clientModules": [],
7373
"customFields": {},
74+
"headTags": [],
7475
"i18n": {
7576
"defaultLocale": "en",
7677
"localeConfigs": {

packages/docusaurus/src/server/__tests__/configValidation.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,74 @@ describe('normalizeConfig', () => {
351351
`);
352352
});
353353

354+
it('accepts headTags with tagName and attributes', () => {
355+
expect(() => {
356+
normalizeConfig({
357+
headTags: [
358+
{
359+
tagName: 'link',
360+
attributes: {
361+
rel: 'icon',
362+
href: 'img/docusaurus.png',
363+
},
364+
},
365+
],
366+
});
367+
}).not.toThrow();
368+
});
369+
370+
it("throws error if headTags doesn't have tagName", () => {
371+
expect(() => {
372+
normalizeConfig({
373+
headTags: [
374+
{
375+
attributes: {
376+
rel: 'icon',
377+
href: 'img/docusaurus.png',
378+
},
379+
},
380+
],
381+
});
382+
}).toThrowErrorMatchingInlineSnapshot(`
383+
""headTags[0].tagName" is required
384+
"
385+
`);
386+
});
387+
388+
it("throws error if headTags doesn't have attributes", () => {
389+
expect(() => {
390+
normalizeConfig({
391+
headTags: [
392+
{
393+
tagName: 'link',
394+
},
395+
],
396+
});
397+
}).toThrowErrorMatchingInlineSnapshot(`
398+
""headTags[0].attributes" is required
399+
"
400+
`);
401+
});
402+
403+
it("throws error if headTags doesn't have string attributes", () => {
404+
expect(() => {
405+
normalizeConfig({
406+
headTags: [
407+
{
408+
tagName: 'link',
409+
attributes: {
410+
rel: false,
411+
href: 'img/docusaurus.png',
412+
},
413+
},
414+
],
415+
});
416+
}).toThrowErrorMatchingInlineSnapshot(`
417+
""headTags[0].attributes.rel" must be a string
418+
"
419+
`);
420+
});
421+
354422
it("throws error if css doesn't have href", () => {
355423
expect(() => {
356424
normalizeConfig({

packages/docusaurus/src/server/configValidation.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const DEFAULT_CONFIG: Pick<
3333
| 'plugins'
3434
| 'themes'
3535
| 'presets'
36+
| 'headTags'
3637
| 'stylesheets'
3738
| 'scripts'
3839
| 'clientModules'
@@ -51,6 +52,7 @@ export const DEFAULT_CONFIG: Pick<
5152
plugins: [],
5253
themes: [],
5354
presets: [],
55+
headTags: [],
5456
stylesheets: [],
5557
scripts: [],
5658
clientModules: [],
@@ -222,6 +224,20 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
222224
})
223225
.default(DEFAULT_CONFIG.scripts),
224226
ssrTemplate: Joi.string(),
227+
headTags: Joi.array()
228+
.items(
229+
Joi.object({
230+
tagName: Joi.string().required(),
231+
attributes: Joi.object()
232+
.pattern(/[\w-]+/, Joi.string())
233+
.required(),
234+
}).unknown(),
235+
)
236+
.messages({
237+
'array.includes':
238+
'{#label} is invalid. A headTag must be an object with at least a "tagName" and an "attributes" property.',
239+
})
240+
.default(DEFAULT_CONFIG.headTags),
225241
stylesheets: Joi.array()
226242
.items(
227243
Joi.string(),

packages/docusaurus/src/server/plugins/synthetic.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function createBootstrapPlugin({
2121
const {
2222
stylesheets,
2323
scripts,
24+
headTags,
2425
clientModules: siteConfigClientModules,
2526
} = siteConfig;
2627
return {
@@ -58,7 +59,7 @@ export function createBootstrapPlugin({
5859
},
5960
);
6061
return {
61-
headTags: [...stylesheetsTags, ...scriptsTags],
62+
headTags: [...headTags, ...stylesheetsTags, ...scriptsTags],
6263
};
6364
},
6465
};

website/docs/api/docusaurus.config.js.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,30 @@ module.exports = {
429429
};
430430
```
431431

432+
### `headTags` {#headTags}
433+
434+
An array of tags that will be inserted in the HTML `<head>`. The values must be objects that contain two properties; `tagName` and `attributes`. `tagName` must be a string that determines the tag being created; eg `"link"`. `attributes` must be an attribute-value map.
435+
436+
- Type: `{ tagName: string; attributes: Object; }[]`
437+
438+
Example:
439+
440+
```js title="docusaurus.config.js"
441+
module.exports = {
442+
headTags: [
443+
{
444+
tagName: 'link',
445+
attributes: {
446+
rel: 'icon',
447+
href: '/img/docusaurus.png',
448+
},
449+
},
450+
],
451+
};
452+
```
453+
454+
This would become `<link rel="icon" href="img/docusaurus.png" />` in the generated HTML.
455+
432456
### `scripts` {#scripts}
433457

434458
An array of scripts to load. The values can be either strings or plain objects of attribute-value maps. The `<script>` tags will be inserted in the HTML `<head>`. If you use a plain object, the only required attribute is `src`, and any other attributes are permitted (each one should have boolean/string values).

0 commit comments

Comments
 (0)