@azerion/bunosaurus-plugin-sitemap 0.0.0-stage → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/lib/createSitemap.d.ts +17 -0
- package/lib/createSitemap.js +62 -0
- package/lib/createSitemapItem.d.ts +14 -0
- package/lib/createSitemapItem.js +61 -0
- package/lib/head.d.ts +13 -0
- package/lib/head.js +18 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +51 -0
- package/lib/options.d.ts +48 -0
- package/lib/options.js +59 -0
- package/lib/types.d.ts +53 -0
- package/lib/types.js +21 -0
- package/lib/xml.d.ts +10 -0
- package/lib/xml.js +28 -0
- package/package.json +40 -4
- package/src/createSitemap.ts +93 -0
- package/src/createSitemapItem.ts +91 -0
- package/src/head.ts +25 -0
- package/src/index.ts +59 -0
- package/src/options.ts +126 -0
- package/src/types.ts +78 -0
- package/src/xml.ts +34 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@docusaurus/plugin-sitemap`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Simple sitemap generation plugin for Docusaurus.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
See [plugin-sitemap documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-sitemap).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { CreateSitemapItemsParams } from './types';
|
|
8
|
+
import type { RouteBuildMetadata } from '@docusaurus/types';
|
|
9
|
+
import type { PluginOptions } from './options';
|
|
10
|
+
type CreateSitemapParams = CreateSitemapItemsParams & {
|
|
11
|
+
routesBuildMetadata: {
|
|
12
|
+
[location: string]: RouteBuildMetadata;
|
|
13
|
+
};
|
|
14
|
+
options: PluginOptions;
|
|
15
|
+
};
|
|
16
|
+
export default function createSitemap(params: CreateSitemapParams): Promise<string | null>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.default = createSitemap;
|
|
10
|
+
const utils_1 = require("@docusaurus/utils");
|
|
11
|
+
const glob_1 = require("@docusaurus/glob");
|
|
12
|
+
const xml_1 = require("./xml");
|
|
13
|
+
const createSitemapItem_1 = require("./createSitemapItem");
|
|
14
|
+
const head_1 = require("./head");
|
|
15
|
+
// Not all routes should appear in the sitemap, and we should filter:
|
|
16
|
+
// - parent routes, used for layouts
|
|
17
|
+
// - routes matching options.ignorePatterns
|
|
18
|
+
// - routes with no index metadata
|
|
19
|
+
function getSitemapRoutes({ routes, routesBuildMetadata, options, }) {
|
|
20
|
+
const { ignorePatterns } = options;
|
|
21
|
+
const ignoreMatcher = (0, glob_1.createMatcher)(ignorePatterns);
|
|
22
|
+
function isRouteExcluded(route) {
|
|
23
|
+
return (ignoreMatcher(route.path) ||
|
|
24
|
+
(0, head_1.isNoIndexMetaRoute)({ routesBuildMetadata, route: route.path }));
|
|
25
|
+
}
|
|
26
|
+
return (0, utils_1.flattenRoutes)(routes).filter((route) => !isRouteExcluded(route));
|
|
27
|
+
}
|
|
28
|
+
// Our default implementation receives some additional parameters on purpose
|
|
29
|
+
function createDefaultCreateSitemapItems(internalParams) {
|
|
30
|
+
return async (params) => {
|
|
31
|
+
const sitemapRoutes = getSitemapRoutes({ ...params, ...internalParams });
|
|
32
|
+
if (sitemapRoutes.length === 0) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return Promise.all(sitemapRoutes.map((route) => (0, createSitemapItem_1.createSitemapItem)({
|
|
36
|
+
route,
|
|
37
|
+
siteConfig: params.siteConfig,
|
|
38
|
+
options: internalParams.options,
|
|
39
|
+
})));
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function createSitemap(params) {
|
|
43
|
+
const { routesBuildMetadata, options, routes, siteConfig } = params;
|
|
44
|
+
const defaultCreateSitemapItems = createDefaultCreateSitemapItems({ routesBuildMetadata, options });
|
|
45
|
+
const sitemapItems = params.options.createSitemapItems
|
|
46
|
+
? await params.options.createSitemapItems({
|
|
47
|
+
routes,
|
|
48
|
+
siteConfig,
|
|
49
|
+
defaultCreateSitemapItems,
|
|
50
|
+
})
|
|
51
|
+
: await defaultCreateSitemapItems({
|
|
52
|
+
routes,
|
|
53
|
+
siteConfig,
|
|
54
|
+
});
|
|
55
|
+
if (sitemapItems.length === 0) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const xmlString = await (0, xml_1.sitemapItemsToXmlString)(sitemapItems, {
|
|
59
|
+
lastmod: params.options.lastmod,
|
|
60
|
+
});
|
|
61
|
+
return xmlString;
|
|
62
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { SitemapItem } from './types';
|
|
8
|
+
import type { DocusaurusConfig, RouteConfig } from '@docusaurus/types';
|
|
9
|
+
import type { PluginOptions } from './options';
|
|
10
|
+
export declare function createSitemapItem({ route, siteConfig, options, }: {
|
|
11
|
+
route: RouteConfig;
|
|
12
|
+
siteConfig: DocusaurusConfig;
|
|
13
|
+
options: PluginOptions;
|
|
14
|
+
}): Promise<SitemapItem>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createSitemapItem = createSitemapItem;
|
|
10
|
+
const utils_common_1 = require("@docusaurus/utils-common");
|
|
11
|
+
const utils_1 = require("@docusaurus/utils");
|
|
12
|
+
async function getRouteLastUpdatedAt(route, vcs) {
|
|
13
|
+
// Important to bail-out early here
|
|
14
|
+
// This can lead to duplicated VCS calls and performance problems
|
|
15
|
+
// See https://github.com/facebook/docusaurus/pull/11211
|
|
16
|
+
if (route.metadata?.lastUpdatedAt === null) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (route.metadata?.lastUpdatedAt != null) {
|
|
20
|
+
return route.metadata?.lastUpdatedAt;
|
|
21
|
+
}
|
|
22
|
+
if (route.metadata?.sourceFilePath) {
|
|
23
|
+
const lastUpdateInfo = await vcs.getFileLastUpdateInfo(route.metadata?.sourceFilePath);
|
|
24
|
+
return lastUpdateInfo?.timestamp ?? null;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const LastmodFormatters = {
|
|
29
|
+
date: (timestamp) => new Date(timestamp).toISOString().split('T')[0],
|
|
30
|
+
datetime: (timestamp) => new Date(timestamp).toISOString(),
|
|
31
|
+
};
|
|
32
|
+
function formatLastmod(timestamp, lastmodOption) {
|
|
33
|
+
const format = LastmodFormatters[lastmodOption];
|
|
34
|
+
return format(timestamp);
|
|
35
|
+
}
|
|
36
|
+
async function getRouteLastmod({ route, lastmod, vcs, }) {
|
|
37
|
+
if (lastmod === null) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const lastUpdatedAt = (await getRouteLastUpdatedAt(route, vcs)) ?? null;
|
|
41
|
+
return lastUpdatedAt != null ? formatLastmod(lastUpdatedAt, lastmod) : null;
|
|
42
|
+
}
|
|
43
|
+
async function createSitemapItem({ route, siteConfig, options, }) {
|
|
44
|
+
const { changefreq, priority, lastmod } = options;
|
|
45
|
+
return {
|
|
46
|
+
url: (0, utils_1.normalizeUrl)([
|
|
47
|
+
siteConfig.url,
|
|
48
|
+
(0, utils_common_1.applyTrailingSlash)(route.path, {
|
|
49
|
+
trailingSlash: siteConfig.trailingSlash,
|
|
50
|
+
baseUrl: siteConfig.baseUrl,
|
|
51
|
+
}),
|
|
52
|
+
]),
|
|
53
|
+
changefreq,
|
|
54
|
+
priority,
|
|
55
|
+
lastmod: await getRouteLastmod({
|
|
56
|
+
route,
|
|
57
|
+
lastmod,
|
|
58
|
+
vcs: siteConfig.vcs,
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
}
|
package/lib/head.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { RouteBuildMetadata } from '@docusaurus/types';
|
|
8
|
+
export declare function isNoIndexMetaRoute({ routesBuildMetadata, route, }: {
|
|
9
|
+
routesBuildMetadata: {
|
|
10
|
+
[location: string]: RouteBuildMetadata;
|
|
11
|
+
};
|
|
12
|
+
route: string;
|
|
13
|
+
}): boolean;
|
package/lib/head.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.isNoIndexMetaRoute = isNoIndexMetaRoute;
|
|
10
|
+
// Maybe we want to add a routeConfig.metadata.noIndex instead?
|
|
11
|
+
// But using Helmet is more reliable for third-party plugins...
|
|
12
|
+
function isNoIndexMetaRoute({ routesBuildMetadata, route, }) {
|
|
13
|
+
const routeBuildMetadata = routesBuildMetadata[route];
|
|
14
|
+
if (routeBuildMetadata) {
|
|
15
|
+
return routeBuildMetadata.noIndex;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { PluginOptions, Options } from './options';
|
|
8
|
+
import type { LoadContext, Plugin } from '@docusaurus/types';
|
|
9
|
+
export default function pluginSitemap(context: LoadContext, options: PluginOptions): Plugin<void> | null;
|
|
10
|
+
export { validateOptions } from './options';
|
|
11
|
+
export type { PluginOptions, Options };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.validateOptions = void 0;
|
|
10
|
+
exports.default = pluginSitemap;
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
12
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
13
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
14
|
+
const fs_1 = require("@docusaurus/fs");
|
|
15
|
+
const createSitemap_1 = tslib_1.__importDefault(require("./createSitemap"));
|
|
16
|
+
const PluginName = 'docusaurus-plugin-sitemap';
|
|
17
|
+
function pluginSitemap(context, options) {
|
|
18
|
+
if (context.siteConfig.future.experimental_router === 'hash') {
|
|
19
|
+
logger_1.default.warn(`${PluginName} does not support the Hash Router and will be disabled.`);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
name: PluginName,
|
|
24
|
+
async postBuild({ siteConfig, routes, outDir, routesBuildMetadata }) {
|
|
25
|
+
if (siteConfig.noIndex) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Generate sitemap.
|
|
29
|
+
const generatedSitemap = await (0, createSitemap_1.default)({
|
|
30
|
+
siteConfig,
|
|
31
|
+
routes,
|
|
32
|
+
routesBuildMetadata,
|
|
33
|
+
options,
|
|
34
|
+
});
|
|
35
|
+
if (!generatedSitemap) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Write sitemap file.
|
|
39
|
+
const sitemapPath = node_path_1.default.join(outDir, options.filename);
|
|
40
|
+
try {
|
|
41
|
+
await (0, fs_1.outputFile)(sitemapPath, generatedSitemap);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
logger_1.default.error('Writing sitemap failed.');
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var options_1 = require("./options");
|
|
51
|
+
Object.defineProperty(exports, "validateOptions", { enumerable: true, get: function () { return options_1.validateOptions; } });
|
package/lib/options.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { OptionValidationContext } from '@docusaurus/types';
|
|
8
|
+
import type { ChangeFreq, LastModOption, SitemapItem, CreateSitemapItemsFn, CreateSitemapItemsParams } from './types';
|
|
9
|
+
export type PluginOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* The path to the created sitemap file, relative to the output directory.
|
|
12
|
+
* Useful if you have two plugin instances outputting two files.
|
|
13
|
+
*/
|
|
14
|
+
filename: string;
|
|
15
|
+
/**
|
|
16
|
+
* A list of glob patterns; matching route paths will be filtered from the
|
|
17
|
+
* sitemap. Note that you may need to include the base URL in here.
|
|
18
|
+
*/
|
|
19
|
+
ignorePatterns: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Defines the format of the "lastmod" sitemap item entry, between:
|
|
22
|
+
* - null: do not compute/add a "lastmod" sitemap entry
|
|
23
|
+
* - "date": add a "lastmod" sitemap entry without time (YYYY-MM-DD)
|
|
24
|
+
* - "datetime": add a "lastmod" sitemap entry with time (ISO 8601 datetime)
|
|
25
|
+
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
|
26
|
+
* @see https://www.w3.org/TR/NOTE-datetime
|
|
27
|
+
*/
|
|
28
|
+
lastmod: LastModOption | null;
|
|
29
|
+
/**
|
|
30
|
+
* TODO Docusaurus v4 breaking change: remove useless option
|
|
31
|
+
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
|
32
|
+
*/
|
|
33
|
+
changefreq: ChangeFreq | null;
|
|
34
|
+
/**
|
|
35
|
+
* TODO Docusaurus v4 breaking change: remove useless option
|
|
36
|
+
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
|
37
|
+
*/
|
|
38
|
+
priority: number | null;
|
|
39
|
+
/** Allow control over the construction of SitemapItems */
|
|
40
|
+
createSitemapItems?: CreateSitemapItemsOption;
|
|
41
|
+
};
|
|
42
|
+
type CreateSitemapItemsOption = (params: CreateSitemapItemsParams & {
|
|
43
|
+
defaultCreateSitemapItems: CreateSitemapItemsFn;
|
|
44
|
+
}) => Promise<SitemapItem[]>;
|
|
45
|
+
export type Options = Partial<PluginOptions>;
|
|
46
|
+
export declare const DEFAULT_OPTIONS: PluginOptions;
|
|
47
|
+
export declare function validateOptions({ validate, options, }: OptionValidationContext<Options, PluginOptions>): PluginOptions;
|
|
48
|
+
export {};
|
package/lib/options.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.DEFAULT_OPTIONS = void 0;
|
|
10
|
+
exports.validateOptions = validateOptions;
|
|
11
|
+
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
12
|
+
const types_1 = require("./types");
|
|
13
|
+
exports.DEFAULT_OPTIONS = {
|
|
14
|
+
filename: 'sitemap.xml',
|
|
15
|
+
ignorePatterns: [],
|
|
16
|
+
// TODO Docusaurus v4 breaking change
|
|
17
|
+
// change default to "date" if no bug or perf issue reported
|
|
18
|
+
lastmod: null,
|
|
19
|
+
// TODO Docusaurus v4 breaking change
|
|
20
|
+
// those options are useless and should be removed
|
|
21
|
+
changefreq: 'weekly',
|
|
22
|
+
priority: 0.5,
|
|
23
|
+
};
|
|
24
|
+
const PluginOptionSchema = utils_validation_1.Joi.object({
|
|
25
|
+
// @ts-expect-error: forbidden
|
|
26
|
+
cacheTime: utils_validation_1.Joi.forbidden().messages({
|
|
27
|
+
'any.unknown': 'Option `cacheTime` in sitemap config is deprecated. Please remove it.',
|
|
28
|
+
}),
|
|
29
|
+
// TODO remove for Docusaurus v4 breaking changes?
|
|
30
|
+
// This is not even used by Google crawlers
|
|
31
|
+
// See also https://github.com/facebook/docusaurus/issues/2604
|
|
32
|
+
changefreq: utils_validation_1.Joi.string()
|
|
33
|
+
.valid(null, ...types_1.ChangeFreqList)
|
|
34
|
+
.default(exports.DEFAULT_OPTIONS.changefreq),
|
|
35
|
+
// TODO remove for Docusaurus v4 breaking changes?
|
|
36
|
+
// This is not even used by Google crawlers
|
|
37
|
+
// The priority is "relative", and using the same priority for all routes
|
|
38
|
+
// does not make sense according to the spec
|
|
39
|
+
// See also https://github.com/facebook/docusaurus/issues/2604
|
|
40
|
+
// See also https://www.sitemaps.org/protocol.html
|
|
41
|
+
priority: utils_validation_1.Joi.alternatives()
|
|
42
|
+
.try(utils_validation_1.Joi.valid(null), utils_validation_1.Joi.number().min(0).max(1))
|
|
43
|
+
.default(exports.DEFAULT_OPTIONS.priority),
|
|
44
|
+
lastmod: utils_validation_1.Joi.string()
|
|
45
|
+
.valid(null, ...types_1.LastModOptionList)
|
|
46
|
+
.default(exports.DEFAULT_OPTIONS.lastmod),
|
|
47
|
+
createSitemapItems: utils_validation_1.Joi.function(),
|
|
48
|
+
ignorePatterns: utils_validation_1.Joi.array()
|
|
49
|
+
.items(utils_validation_1.Joi.string())
|
|
50
|
+
.default(exports.DEFAULT_OPTIONS.ignorePatterns),
|
|
51
|
+
trailingSlash: utils_validation_1.Joi.forbidden().messages({
|
|
52
|
+
'any.unknown': 'Please use the new Docusaurus global trailingSlash config instead, and the sitemaps plugin will use it.',
|
|
53
|
+
}),
|
|
54
|
+
filename: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.filename),
|
|
55
|
+
});
|
|
56
|
+
function validateOptions({ validate, options, }) {
|
|
57
|
+
const validatedOptions = validate(PluginOptionSchema, options);
|
|
58
|
+
return validatedOptions;
|
|
59
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { DocusaurusConfig, RouteConfig } from '@docusaurus/types';
|
|
8
|
+
export declare const LastModOptionList: readonly ['date', 'datetime'];
|
|
9
|
+
export type LastModOption = (typeof LastModOptionList)[number];
|
|
10
|
+
export declare const ChangeFreqList: readonly ['hourly', 'daily', 'weekly', 'monthly', 'yearly', 'always', 'never'];
|
|
11
|
+
export type ChangeFreq = (typeof ChangeFreqList)[number];
|
|
12
|
+
export type SitemapItem = {
|
|
13
|
+
/**
|
|
14
|
+
* URL of the page.
|
|
15
|
+
* This URL must begin with the protocol (such as http).
|
|
16
|
+
* It should eventually end with a trailing slash.
|
|
17
|
+
* It should be less than 2,048 characters.
|
|
18
|
+
*/
|
|
19
|
+
url: string;
|
|
20
|
+
/**
|
|
21
|
+
* ISO 8601 date string.
|
|
22
|
+
* See also https://www.w3.org/TR/NOTE-datetime
|
|
23
|
+
*
|
|
24
|
+
* It is recommended to use one of:
|
|
25
|
+
* - date.toISOString()
|
|
26
|
+
* - YYYY-MM-DD
|
|
27
|
+
*
|
|
28
|
+
* Note: as of 2024, Google uses this value for crawling priority.
|
|
29
|
+
* See also https://github.com/facebook/docusaurus/issues/2604
|
|
30
|
+
*/
|
|
31
|
+
lastmod?: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* One of the specified enum values
|
|
34
|
+
*
|
|
35
|
+
* Note: as of 2024, Google ignores this value.
|
|
36
|
+
* See also https://github.com/facebook/docusaurus/issues/2604
|
|
37
|
+
*/
|
|
38
|
+
changefreq?: ChangeFreq | null;
|
|
39
|
+
/**
|
|
40
|
+
* The priority of this URL relative to other URLs on your site.
|
|
41
|
+
* Valid values range from 0.0 to 1.0.
|
|
42
|
+
* The default priority of a page is 0.5.
|
|
43
|
+
*
|
|
44
|
+
* Note: as of 2024, Google ignores this value.
|
|
45
|
+
* See also https://github.com/facebook/docusaurus/issues/2604
|
|
46
|
+
*/
|
|
47
|
+
priority?: number | null;
|
|
48
|
+
};
|
|
49
|
+
export type CreateSitemapItemsParams = {
|
|
50
|
+
siteConfig: DocusaurusConfig;
|
|
51
|
+
routes: RouteConfig[];
|
|
52
|
+
};
|
|
53
|
+
export type CreateSitemapItemsFn = (params: CreateSitemapItemsParams) => Promise<SitemapItem[]>;
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ChangeFreqList = exports.LastModOptionList = void 0;
|
|
10
|
+
exports.LastModOptionList = ['date', 'datetime'];
|
|
11
|
+
// types are according to the sitemap spec:
|
|
12
|
+
// see also https://www.sitemaps.org/protocol.html
|
|
13
|
+
exports.ChangeFreqList = [
|
|
14
|
+
'hourly',
|
|
15
|
+
'daily',
|
|
16
|
+
'weekly',
|
|
17
|
+
'monthly',
|
|
18
|
+
'yearly',
|
|
19
|
+
'always',
|
|
20
|
+
'never',
|
|
21
|
+
];
|
package/lib/xml.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { LastModOption, SitemapItem } from './types';
|
|
8
|
+
export declare function sitemapItemsToXmlString(items: SitemapItem[], options: {
|
|
9
|
+
lastmod: LastModOption | null;
|
|
10
|
+
}): Promise<string>;
|
package/lib/xml.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.sitemapItemsToXmlString = sitemapItemsToXmlString;
|
|
10
|
+
const sitemap_1 = require("sitemap");
|
|
11
|
+
async function sitemapItemsToXmlString(items, options) {
|
|
12
|
+
if (items.length === 0) {
|
|
13
|
+
// Fail fast with a clearer error than the lib's EmptySitemap error
|
|
14
|
+
throw new Error("Can't generate a sitemap with no items");
|
|
15
|
+
}
|
|
16
|
+
// We could generate the XML ourselves, but we keep using the lib on purpose
|
|
17
|
+
// Users can return undocumented fields from createSitemapItems()
|
|
18
|
+
// (links, img, video, news...) that the lib serializes
|
|
19
|
+
// See https://github.com/ekalinin/sitemap.js
|
|
20
|
+
const sitemapStream = new sitemap_1.SitemapStream({
|
|
21
|
+
// WTF is this lib reformatting the string YYYY-MM-DD to datetime...
|
|
22
|
+
lastmodDateOnly: options?.lastmod === 'date',
|
|
23
|
+
});
|
|
24
|
+
items.forEach((item) => sitemapStream.write(item));
|
|
25
|
+
sitemapStream.end();
|
|
26
|
+
const buffer = await (0, sitemap_1.streamToPromise)(sitemapStream);
|
|
27
|
+
return buffer.toString();
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azerion/bunosaurus-plugin-sitemap",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Simple sitemap generation plugin for Docusaurus.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"watch": "tsc --watch"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/azerion/bunosaurus.git",
|
|
17
|
+
"directory": "packages/docusaurus-plugin-sitemap"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@docusaurus/core": "npm:@azerion/bunosaurus-core@4.0.0",
|
|
22
|
+
"@docusaurus/fs": "npm:@azerion/bunosaurus-fs@4.0.0",
|
|
23
|
+
"@docusaurus/glob": "npm:@azerion/bunosaurus-glob@4.0.0",
|
|
24
|
+
"@docusaurus/logger": "npm:@azerion/bunosaurus-logger@4.0.0",
|
|
25
|
+
"@docusaurus/types": "npm:@azerion/bunosaurus-types@4.0.0",
|
|
26
|
+
"@docusaurus/utils": "npm:@azerion/bunosaurus-utils@4.0.0",
|
|
27
|
+
"@docusaurus/utils-common": "npm:@azerion/bunosaurus-utils-common@4.0.0",
|
|
28
|
+
"@docusaurus/utils-validation": "npm:@azerion/bunosaurus-utils-validation@4.0.0",
|
|
29
|
+
"sitemap": "^9.0.1",
|
|
30
|
+
"tslib": "^2.8.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@total-typescript/shoehorn": "^0.1.2"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": "^19.3.0",
|
|
37
|
+
"react-dom": "^19.3.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"bun": ">=1.4.1"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {flattenRoutes} from '@docusaurus/utils';
|
|
9
|
+
import {createMatcher} from '@docusaurus/glob';
|
|
10
|
+
import {sitemapItemsToXmlString} from './xml';
|
|
11
|
+
import {createSitemapItem} from './createSitemapItem';
|
|
12
|
+
import {isNoIndexMetaRoute} from './head';
|
|
13
|
+
import type {CreateSitemapItemsFn, CreateSitemapItemsParams} from './types';
|
|
14
|
+
import type {RouteConfig, RouteBuildMetadata} from '@docusaurus/types';
|
|
15
|
+
import type {PluginOptions} from './options';
|
|
16
|
+
|
|
17
|
+
// Not all routes should appear in the sitemap, and we should filter:
|
|
18
|
+
// - parent routes, used for layouts
|
|
19
|
+
// - routes matching options.ignorePatterns
|
|
20
|
+
// - routes with no index metadata
|
|
21
|
+
function getSitemapRoutes({
|
|
22
|
+
routes,
|
|
23
|
+
routesBuildMetadata,
|
|
24
|
+
options,
|
|
25
|
+
}: CreateSitemapParams) {
|
|
26
|
+
const {ignorePatterns} = options;
|
|
27
|
+
|
|
28
|
+
const ignoreMatcher = createMatcher(ignorePatterns);
|
|
29
|
+
|
|
30
|
+
function isRouteExcluded(route: RouteConfig) {
|
|
31
|
+
return (
|
|
32
|
+
ignoreMatcher(route.path) ||
|
|
33
|
+
isNoIndexMetaRoute({routesBuildMetadata, route: route.path})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return flattenRoutes(routes).filter((route) => !isRouteExcluded(route));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Our default implementation receives some additional parameters on purpose
|
|
41
|
+
function createDefaultCreateSitemapItems(
|
|
42
|
+
internalParams: Pick<CreateSitemapParams, 'routesBuildMetadata' | 'options'>,
|
|
43
|
+
): CreateSitemapItemsFn {
|
|
44
|
+
return async (params) => {
|
|
45
|
+
const sitemapRoutes = getSitemapRoutes({...params, ...internalParams});
|
|
46
|
+
if (sitemapRoutes.length === 0) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return Promise.all(
|
|
50
|
+
sitemapRoutes.map((route) =>
|
|
51
|
+
createSitemapItem({
|
|
52
|
+
route,
|
|
53
|
+
siteConfig: params.siteConfig,
|
|
54
|
+
options: internalParams.options,
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type CreateSitemapParams = CreateSitemapItemsParams & {
|
|
62
|
+
routesBuildMetadata: {[location: string]: RouteBuildMetadata};
|
|
63
|
+
options: PluginOptions;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default async function createSitemap(
|
|
67
|
+
params: CreateSitemapParams,
|
|
68
|
+
): Promise<string | null> {
|
|
69
|
+
const {routesBuildMetadata, options, routes, siteConfig} = params;
|
|
70
|
+
|
|
71
|
+
const defaultCreateSitemapItems: CreateSitemapItemsFn =
|
|
72
|
+
createDefaultCreateSitemapItems({routesBuildMetadata, options});
|
|
73
|
+
|
|
74
|
+
const sitemapItems = params.options.createSitemapItems
|
|
75
|
+
? await params.options.createSitemapItems({
|
|
76
|
+
routes,
|
|
77
|
+
siteConfig,
|
|
78
|
+
defaultCreateSitemapItems,
|
|
79
|
+
})
|
|
80
|
+
: await defaultCreateSitemapItems({
|
|
81
|
+
routes,
|
|
82
|
+
siteConfig,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (sitemapItems.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const xmlString = await sitemapItemsToXmlString(sitemapItems, {
|
|
90
|
+
lastmod: params.options.lastmod,
|
|
91
|
+
});
|
|
92
|
+
return xmlString;
|
|
93
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
|
9
|
+
import {normalizeUrl} from '@docusaurus/utils';
|
|
10
|
+
import type {LastModOption, SitemapItem} from './types';
|
|
11
|
+
import type {DocusaurusConfig, RouteConfig, VcsConfig} from '@docusaurus/types';
|
|
12
|
+
import type {PluginOptions} from './options';
|
|
13
|
+
|
|
14
|
+
async function getRouteLastUpdatedAt(
|
|
15
|
+
route: RouteConfig,
|
|
16
|
+
vcs: Pick<VcsConfig, 'getFileLastUpdateInfo'>,
|
|
17
|
+
): Promise<number | null | undefined> {
|
|
18
|
+
// Important to bail-out early here
|
|
19
|
+
// This can lead to duplicated VCS calls and performance problems
|
|
20
|
+
// See https://github.com/facebook/docusaurus/pull/11211
|
|
21
|
+
if (route.metadata?.lastUpdatedAt === null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (route.metadata?.lastUpdatedAt != null) {
|
|
25
|
+
return route.metadata?.lastUpdatedAt;
|
|
26
|
+
}
|
|
27
|
+
if (route.metadata?.sourceFilePath) {
|
|
28
|
+
const lastUpdateInfo = await vcs.getFileLastUpdateInfo(
|
|
29
|
+
route.metadata?.sourceFilePath,
|
|
30
|
+
);
|
|
31
|
+
return lastUpdateInfo?.timestamp ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type LastModFormatter = (timestamp: number) => string;
|
|
38
|
+
|
|
39
|
+
const LastmodFormatters: Record<LastModOption, LastModFormatter> = {
|
|
40
|
+
date: (timestamp) => new Date(timestamp).toISOString().split('T')[0]!,
|
|
41
|
+
datetime: (timestamp) => new Date(timestamp).toISOString(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function formatLastmod(timestamp: number, lastmodOption: LastModOption) {
|
|
45
|
+
const format = LastmodFormatters[lastmodOption];
|
|
46
|
+
return format(timestamp);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function getRouteLastmod({
|
|
50
|
+
route,
|
|
51
|
+
lastmod,
|
|
52
|
+
vcs,
|
|
53
|
+
}: {
|
|
54
|
+
route: RouteConfig;
|
|
55
|
+
lastmod: LastModOption | null;
|
|
56
|
+
vcs: Pick<VcsConfig, 'getFileLastUpdateInfo'>;
|
|
57
|
+
}): Promise<string | null> {
|
|
58
|
+
if (lastmod === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const lastUpdatedAt = (await getRouteLastUpdatedAt(route, vcs)) ?? null;
|
|
62
|
+
return lastUpdatedAt != null ? formatLastmod(lastUpdatedAt, lastmod) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function createSitemapItem({
|
|
66
|
+
route,
|
|
67
|
+
siteConfig,
|
|
68
|
+
options,
|
|
69
|
+
}: {
|
|
70
|
+
route: RouteConfig;
|
|
71
|
+
siteConfig: DocusaurusConfig;
|
|
72
|
+
options: PluginOptions;
|
|
73
|
+
}): Promise<SitemapItem> {
|
|
74
|
+
const {changefreq, priority, lastmod} = options;
|
|
75
|
+
return {
|
|
76
|
+
url: normalizeUrl([
|
|
77
|
+
siteConfig.url,
|
|
78
|
+
applyTrailingSlash(route.path, {
|
|
79
|
+
trailingSlash: siteConfig.trailingSlash,
|
|
80
|
+
baseUrl: siteConfig.baseUrl,
|
|
81
|
+
}),
|
|
82
|
+
]),
|
|
83
|
+
changefreq,
|
|
84
|
+
priority,
|
|
85
|
+
lastmod: await getRouteLastmod({
|
|
86
|
+
route,
|
|
87
|
+
lastmod,
|
|
88
|
+
vcs: siteConfig.vcs,
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
}
|
package/src/head.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {RouteBuildMetadata} from '@docusaurus/types';
|
|
9
|
+
|
|
10
|
+
// Maybe we want to add a routeConfig.metadata.noIndex instead?
|
|
11
|
+
// But using Helmet is more reliable for third-party plugins...
|
|
12
|
+
export function isNoIndexMetaRoute({
|
|
13
|
+
routesBuildMetadata,
|
|
14
|
+
route,
|
|
15
|
+
}: {
|
|
16
|
+
routesBuildMetadata: {[location: string]: RouteBuildMetadata};
|
|
17
|
+
route: string;
|
|
18
|
+
}): boolean {
|
|
19
|
+
const routeBuildMetadata = routesBuildMetadata[route];
|
|
20
|
+
|
|
21
|
+
if (routeBuildMetadata) {
|
|
22
|
+
return routeBuildMetadata.noIndex;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import logger from '@docusaurus/logger';
|
|
10
|
+
import {outputFile} from '@docusaurus/fs';
|
|
11
|
+
import createSitemap from './createSitemap';
|
|
12
|
+
import type {PluginOptions, Options} from './options';
|
|
13
|
+
import type {LoadContext, Plugin} from '@docusaurus/types';
|
|
14
|
+
|
|
15
|
+
const PluginName = 'docusaurus-plugin-sitemap';
|
|
16
|
+
|
|
17
|
+
export default function pluginSitemap(
|
|
18
|
+
context: LoadContext,
|
|
19
|
+
options: PluginOptions,
|
|
20
|
+
): Plugin<void> | null {
|
|
21
|
+
if (context.siteConfig.future.experimental_router === 'hash') {
|
|
22
|
+
logger.warn(
|
|
23
|
+
`${PluginName} does not support the Hash Router and will be disabled.`,
|
|
24
|
+
);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
name: PluginName,
|
|
30
|
+
|
|
31
|
+
async postBuild({siteConfig, routes, outDir, routesBuildMetadata}) {
|
|
32
|
+
if (siteConfig.noIndex) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Generate sitemap.
|
|
36
|
+
const generatedSitemap = await createSitemap({
|
|
37
|
+
siteConfig,
|
|
38
|
+
routes,
|
|
39
|
+
routesBuildMetadata,
|
|
40
|
+
options,
|
|
41
|
+
});
|
|
42
|
+
if (!generatedSitemap) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Write sitemap file.
|
|
47
|
+
const sitemapPath = path.join(outDir, options.filename);
|
|
48
|
+
try {
|
|
49
|
+
await outputFile(sitemapPath, generatedSitemap);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error('Writing sitemap failed.');
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {validateOptions} from './options';
|
|
59
|
+
export type {PluginOptions, Options};
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {Joi} from '@docusaurus/utils-validation';
|
|
9
|
+
import {ChangeFreqList, LastModOptionList} from './types';
|
|
10
|
+
import type {OptionValidationContext} from '@docusaurus/types';
|
|
11
|
+
import type {
|
|
12
|
+
ChangeFreq,
|
|
13
|
+
LastModOption,
|
|
14
|
+
SitemapItem,
|
|
15
|
+
CreateSitemapItemsFn,
|
|
16
|
+
CreateSitemapItemsParams,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export type PluginOptions = {
|
|
20
|
+
/**
|
|
21
|
+
* The path to the created sitemap file, relative to the output directory.
|
|
22
|
+
* Useful if you have two plugin instances outputting two files.
|
|
23
|
+
*/
|
|
24
|
+
filename: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A list of glob patterns; matching route paths will be filtered from the
|
|
28
|
+
* sitemap. Note that you may need to include the base URL in here.
|
|
29
|
+
*/
|
|
30
|
+
ignorePatterns: string[];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Defines the format of the "lastmod" sitemap item entry, between:
|
|
34
|
+
* - null: do not compute/add a "lastmod" sitemap entry
|
|
35
|
+
* - "date": add a "lastmod" sitemap entry without time (YYYY-MM-DD)
|
|
36
|
+
* - "datetime": add a "lastmod" sitemap entry with time (ISO 8601 datetime)
|
|
37
|
+
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
|
38
|
+
* @see https://www.w3.org/TR/NOTE-datetime
|
|
39
|
+
*/
|
|
40
|
+
lastmod: LastModOption | null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* TODO Docusaurus v4 breaking change: remove useless option
|
|
44
|
+
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
|
45
|
+
*/
|
|
46
|
+
changefreq: ChangeFreq | null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* TODO Docusaurus v4 breaking change: remove useless option
|
|
50
|
+
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
|
51
|
+
*/
|
|
52
|
+
priority: number | null;
|
|
53
|
+
|
|
54
|
+
/** Allow control over the construction of SitemapItems */
|
|
55
|
+
createSitemapItems?: CreateSitemapItemsOption;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type CreateSitemapItemsOption = (
|
|
59
|
+
params: CreateSitemapItemsParams & {
|
|
60
|
+
defaultCreateSitemapItems: CreateSitemapItemsFn;
|
|
61
|
+
},
|
|
62
|
+
) => Promise<SitemapItem[]>;
|
|
63
|
+
|
|
64
|
+
export type Options = Partial<PluginOptions>;
|
|
65
|
+
|
|
66
|
+
export const DEFAULT_OPTIONS: PluginOptions = {
|
|
67
|
+
filename: 'sitemap.xml',
|
|
68
|
+
ignorePatterns: [],
|
|
69
|
+
|
|
70
|
+
// TODO Docusaurus v4 breaking change
|
|
71
|
+
// change default to "date" if no bug or perf issue reported
|
|
72
|
+
lastmod: null,
|
|
73
|
+
|
|
74
|
+
// TODO Docusaurus v4 breaking change
|
|
75
|
+
// those options are useless and should be removed
|
|
76
|
+
changefreq: 'weekly',
|
|
77
|
+
priority: 0.5,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
81
|
+
// @ts-expect-error: forbidden
|
|
82
|
+
cacheTime: Joi.forbidden().messages({
|
|
83
|
+
'any.unknown':
|
|
84
|
+
'Option `cacheTime` in sitemap config is deprecated. Please remove it.',
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
// TODO remove for Docusaurus v4 breaking changes?
|
|
88
|
+
// This is not even used by Google crawlers
|
|
89
|
+
// See also https://github.com/facebook/docusaurus/issues/2604
|
|
90
|
+
changefreq: Joi.string()
|
|
91
|
+
.valid(null, ...ChangeFreqList)
|
|
92
|
+
.default(DEFAULT_OPTIONS.changefreq),
|
|
93
|
+
|
|
94
|
+
// TODO remove for Docusaurus v4 breaking changes?
|
|
95
|
+
// This is not even used by Google crawlers
|
|
96
|
+
// The priority is "relative", and using the same priority for all routes
|
|
97
|
+
// does not make sense according to the spec
|
|
98
|
+
// See also https://github.com/facebook/docusaurus/issues/2604
|
|
99
|
+
// See also https://www.sitemaps.org/protocol.html
|
|
100
|
+
priority: Joi.alternatives()
|
|
101
|
+
.try(Joi.valid(null), Joi.number().min(0).max(1))
|
|
102
|
+
.default(DEFAULT_OPTIONS.priority),
|
|
103
|
+
|
|
104
|
+
lastmod: Joi.string()
|
|
105
|
+
.valid(null, ...LastModOptionList)
|
|
106
|
+
.default(DEFAULT_OPTIONS.lastmod),
|
|
107
|
+
|
|
108
|
+
createSitemapItems: Joi.function(),
|
|
109
|
+
|
|
110
|
+
ignorePatterns: Joi.array()
|
|
111
|
+
.items(Joi.string())
|
|
112
|
+
.default(DEFAULT_OPTIONS.ignorePatterns),
|
|
113
|
+
trailingSlash: Joi.forbidden().messages({
|
|
114
|
+
'any.unknown':
|
|
115
|
+
'Please use the new Docusaurus global trailingSlash config instead, and the sitemaps plugin will use it.',
|
|
116
|
+
}),
|
|
117
|
+
filename: Joi.string().default(DEFAULT_OPTIONS.filename),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export function validateOptions({
|
|
121
|
+
validate,
|
|
122
|
+
options,
|
|
123
|
+
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
|
|
124
|
+
const validatedOptions = validate(PluginOptionSchema, options);
|
|
125
|
+
return validatedOptions;
|
|
126
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {DocusaurusConfig, RouteConfig} from '@docusaurus/types';
|
|
9
|
+
|
|
10
|
+
export const LastModOptionList = ['date', 'datetime'] as const;
|
|
11
|
+
|
|
12
|
+
export type LastModOption = (typeof LastModOptionList)[number];
|
|
13
|
+
|
|
14
|
+
// types are according to the sitemap spec:
|
|
15
|
+
// see also https://www.sitemaps.org/protocol.html
|
|
16
|
+
|
|
17
|
+
export const ChangeFreqList = [
|
|
18
|
+
'hourly',
|
|
19
|
+
'daily',
|
|
20
|
+
'weekly',
|
|
21
|
+
'monthly',
|
|
22
|
+
'yearly',
|
|
23
|
+
'always',
|
|
24
|
+
'never',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export type ChangeFreq = (typeof ChangeFreqList)[number];
|
|
28
|
+
|
|
29
|
+
// We re-recreate our own type because the "sitemap" lib types are not good
|
|
30
|
+
export type SitemapItem = {
|
|
31
|
+
/**
|
|
32
|
+
* URL of the page.
|
|
33
|
+
* This URL must begin with the protocol (such as http).
|
|
34
|
+
* It should eventually end with a trailing slash.
|
|
35
|
+
* It should be less than 2,048 characters.
|
|
36
|
+
*/
|
|
37
|
+
url: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* ISO 8601 date string.
|
|
41
|
+
* See also https://www.w3.org/TR/NOTE-datetime
|
|
42
|
+
*
|
|
43
|
+
* It is recommended to use one of:
|
|
44
|
+
* - date.toISOString()
|
|
45
|
+
* - YYYY-MM-DD
|
|
46
|
+
*
|
|
47
|
+
* Note: as of 2024, Google uses this value for crawling priority.
|
|
48
|
+
* See also https://github.com/facebook/docusaurus/issues/2604
|
|
49
|
+
*/
|
|
50
|
+
lastmod?: string | null;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One of the specified enum values
|
|
54
|
+
*
|
|
55
|
+
* Note: as of 2024, Google ignores this value.
|
|
56
|
+
* See also https://github.com/facebook/docusaurus/issues/2604
|
|
57
|
+
*/
|
|
58
|
+
changefreq?: ChangeFreq | null;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The priority of this URL relative to other URLs on your site.
|
|
62
|
+
* Valid values range from 0.0 to 1.0.
|
|
63
|
+
* The default priority of a page is 0.5.
|
|
64
|
+
*
|
|
65
|
+
* Note: as of 2024, Google ignores this value.
|
|
66
|
+
* See also https://github.com/facebook/docusaurus/issues/2604
|
|
67
|
+
*/
|
|
68
|
+
priority?: number | null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type CreateSitemapItemsParams = {
|
|
72
|
+
siteConfig: DocusaurusConfig;
|
|
73
|
+
routes: RouteConfig[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type CreateSitemapItemsFn = (
|
|
77
|
+
params: CreateSitemapItemsParams,
|
|
78
|
+
) => Promise<SitemapItem[]>;
|
package/src/xml.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {SitemapStream, streamToPromise} from 'sitemap';
|
|
9
|
+
import type {LastModOption, SitemapItem} from './types';
|
|
10
|
+
|
|
11
|
+
export async function sitemapItemsToXmlString(
|
|
12
|
+
items: SitemapItem[],
|
|
13
|
+
options: {lastmod: LastModOption | null},
|
|
14
|
+
): Promise<string> {
|
|
15
|
+
if (items.length === 0) {
|
|
16
|
+
// Fail fast with a clearer error than the lib's EmptySitemap error
|
|
17
|
+
throw new Error("Can't generate a sitemap with no items");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// We could generate the XML ourselves, but we keep using the lib on purpose
|
|
21
|
+
// Users can return undocumented fields from createSitemapItems()
|
|
22
|
+
// (links, img, video, news...) that the lib serializes
|
|
23
|
+
// See https://github.com/ekalinin/sitemap.js
|
|
24
|
+
const sitemapStream = new SitemapStream({
|
|
25
|
+
// WTF is this lib reformatting the string YYYY-MM-DD to datetime...
|
|
26
|
+
lastmodDateOnly: options?.lastmod === 'date',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
items.forEach((item) => sitemapStream.write(item));
|
|
30
|
+
sitemapStream.end();
|
|
31
|
+
|
|
32
|
+
const buffer = await streamToPromise(sitemapStream);
|
|
33
|
+
return buffer.toString();
|
|
34
|
+
}
|