@riebeckite/plugin-seo 0.0.16 → 0.0.18
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 +9 -8
- package/README_ja.md +2 -6
- package/dist/index.js +79 -51
- package/dist/src/metadata.d.ts +3 -4
- package/dist/src/types.d.ts +0 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -20,8 +20,6 @@ export default defineConfig({
|
|
|
20
20
|
// ...
|
|
21
21
|
plugins: [
|
|
22
22
|
seo({
|
|
23
|
-
siteName: "Riebeckite Blog",
|
|
24
|
-
defaultImage: "/ogp.png",
|
|
25
23
|
feed: { rss: true, atom: true, json: true, limit: 30 },
|
|
26
24
|
sitemap: true,
|
|
27
25
|
robots: true,
|
|
@@ -34,8 +32,6 @@ export default defineConfig({
|
|
|
34
32
|
|
|
35
33
|
| Option | Type | Description |
|
|
36
34
|
| ------ | ---- | ----------- |
|
|
37
|
-
| `siteName` | `string` | Site name used in page titles. Defaults to `site.title`. |
|
|
38
|
-
| `defaultImage` | `string` | Default OG image. Falls back to `site.defaultOgImage`. |
|
|
39
35
|
| `feed` | `{ rss?: boolean; atom?: boolean; json?: boolean; limit?: number }` | Feed output settings. `limit` defaults to 30 and applies to every format. |
|
|
40
36
|
| `sitemap` | `boolean` | Sitemap output settings. |
|
|
41
37
|
| `robots` | `boolean` | robots.txt output settings. |
|
|
@@ -44,22 +40,27 @@ export default defineConfig({
|
|
|
44
40
|
|
|
45
41
|
### Articles (`buildArticleSeo`)
|
|
46
42
|
|
|
47
|
-
- `title`: `"{title} | {
|
|
43
|
+
- `title`: `"{title} | {site.title}"`
|
|
48
44
|
- `description`: `frontmatter.description`, otherwise the first 160 characters
|
|
49
45
|
of the post text
|
|
50
46
|
- `canonicalUrl`: `frontmatter.canonical`, otherwise the entry's resolved canonical permalink
|
|
51
47
|
- `imageUrl`: `frontmatter.ogImage` / `frontmatter.image`, otherwise
|
|
52
|
-
`
|
|
48
|
+
`site.defaultOgImage`
|
|
53
49
|
- `noindex`: `frontmatter.noindex === true`
|
|
54
50
|
- `publishedTime`: `published` / `date` / `created`
|
|
55
51
|
- `modifiedTime`: `updated`, falling back to the publish time
|
|
56
52
|
- `tags`, `readingTimeMinutes`
|
|
57
53
|
- JSON-LD: `BlogPosting` and `BreadcrumbList`
|
|
58
54
|
|
|
55
|
+
Pass the page's `headTags` when they already include a BreadcrumbList (for
|
|
56
|
+
example contributed by the breadcrumbs plugin) and this plugin omits its own
|
|
57
|
+
list, so the page keeps a single BreadcrumbList entity.
|
|
58
|
+
|
|
59
59
|
### Websites (`buildWebsiteSeo`)
|
|
60
60
|
|
|
61
61
|
Title, description, canonical URL, default image, and JSON-LD `WebSite` +
|
|
62
|
-
`BreadcrumbList` for index, tag, and other non-article pages.
|
|
62
|
+
`BreadcrumbList` for index, tag, and other non-article pages. The
|
|
63
|
+
BreadcrumbList is omitted under the same `headTags` condition as articles.
|
|
63
64
|
|
|
64
65
|
## Feeds, sitemap, and robots
|
|
65
66
|
|
|
@@ -109,4 +110,4 @@ shared limit for RSS, Atom, and JSON Feed.
|
|
|
109
110
|
|
|
110
111
|
## See also
|
|
111
112
|
|
|
112
|
-
- [Plugin guide](../../../docs/
|
|
113
|
+
- [Plugin guide](../../../docs/docs/reference/plugin-api.en.md)
|
package/README_ja.md
CHANGED
|
@@ -14,8 +14,6 @@ export default defineConfig({
|
|
|
14
14
|
// ...
|
|
15
15
|
plugins: [
|
|
16
16
|
seo({
|
|
17
|
-
siteName: "Riebeckite Blog",
|
|
18
|
-
defaultImage: "/ogp.png",
|
|
19
17
|
feed: { rss: true, atom: true, json: true, limit: 30 },
|
|
20
18
|
sitemap: true,
|
|
21
19
|
robots: true,
|
|
@@ -26,15 +24,13 @@ export default defineConfig({
|
|
|
26
24
|
|
|
27
25
|
| 項目 | 説明 |
|
|
28
26
|
| --- | --- |
|
|
29
|
-
| `siteName` | ページタイトルに使うサイト名。省略時は `site.title` |
|
|
30
|
-
| `defaultImage` | 既定の OGP 画像。省略時は `site.defaultOgImage` |
|
|
31
27
|
| `feed` | RSS、Atom、JSON Feed の出力を個別に有効化する。`limit` は全形式共通の件数上限で、既定値は 30 件 |
|
|
32
28
|
| `sitemap` | サイトマップを出力する |
|
|
33
29
|
| `robots` | robots.txt を出力する |
|
|
34
30
|
|
|
35
31
|
## 記事の frontmatter が出力を決める
|
|
36
32
|
|
|
37
|
-
`buildArticleSeo()` は記事タイトル、説明、canonical URL、OGP 画像、公開・更新日時、タグ、読了時間を組み立て、`BlogPosting` と `BreadcrumbList` の JSON-LD
|
|
33
|
+
`buildArticleSeo()` は記事タイトル、説明、canonical URL、OGP 画像、公開・更新日時、タグ、読了時間を組み立て、`BlogPosting` と `BreadcrumbList` の JSON-LD を作ります。呼び出し側がページの `headTags` を渡し、そこに BreadcrumbList(breadcrumbs プラグインが提供するものなど)が既にある場合、重複を避けるためこのプラグイン側の BreadcrumbList は省略します。値は次の順で補完されます。
|
|
38
34
|
|
|
39
35
|
| フィールド | 用途 |
|
|
40
36
|
| --- | --- |
|
|
@@ -65,4 +61,4 @@ export default defineConfig({
|
|
|
65
61
|
|
|
66
62
|
## 関連資料
|
|
67
63
|
|
|
68
|
-
- [プラグインシステム](../../../docs/
|
|
64
|
+
- [プラグインシステム](../../../docs/docs/reference/plugin-api.md)
|
package/dist/index.js
CHANGED
|
@@ -125,16 +125,16 @@ function renderJsonFeed(config, entries) {
|
|
|
125
125
|
}
|
|
126
126
|
function getResolvedFeedMetadata(config) {
|
|
127
127
|
return {
|
|
128
|
-
title: config.site.feed.title
|
|
129
|
-
description: config.site.feed.description
|
|
130
|
-
language: config.site.feed.language
|
|
128
|
+
title: config.site.feed.title,
|
|
129
|
+
description: config.site.feed.description,
|
|
130
|
+
language: config.site.feed.language
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// src/metadata.ts
|
|
135
135
|
import { calculateReadingTime } from "@riebeckite/core";
|
|
136
|
-
function buildArticleSeo(config,
|
|
137
|
-
const siteName =
|
|
136
|
+
function buildArticleSeo(config, permalink, post, headTags, language) {
|
|
137
|
+
const siteName = config.site.title;
|
|
138
138
|
const title = getArticleTitle(config, permalink, post.frontmatter.title);
|
|
139
139
|
const canonicalUrl = buildCanonicalUrl(
|
|
140
140
|
config,
|
|
@@ -144,7 +144,6 @@ function buildArticleSeo(config, options, permalink, post) {
|
|
|
144
144
|
const description = getDescription(post) || config.site.description;
|
|
145
145
|
const imageUrl = buildImageUrl(
|
|
146
146
|
config,
|
|
147
|
-
options,
|
|
148
147
|
post.frontmatter.ogImage ?? post.frontmatter.image
|
|
149
148
|
);
|
|
150
149
|
const publishedTime = getIsoDate2(
|
|
@@ -153,10 +152,31 @@ function buildArticleSeo(config, options, permalink, post) {
|
|
|
153
152
|
const modifiedTime = getIsoDate2(post.frontmatter.updated) ?? publishedTime;
|
|
154
153
|
const tags = normalizeTags(post.frontmatter.tags);
|
|
155
154
|
const readingTimeMinutes = calculateReadingTime(post.html);
|
|
156
|
-
const
|
|
157
|
-
{
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
const jsonLd = [
|
|
156
|
+
removeUndefined({
|
|
157
|
+
"@context": "https://schema.org",
|
|
158
|
+
"@type": "BlogPosting",
|
|
159
|
+
headline: title,
|
|
160
|
+
description,
|
|
161
|
+
url: canonicalUrl,
|
|
162
|
+
image: imageUrl ? [imageUrl] : void 0,
|
|
163
|
+
datePublished: publishedTime,
|
|
164
|
+
dateModified: modifiedTime,
|
|
165
|
+
author: config.site.author ? { "@type": "Person", name: config.site.author } : void 0,
|
|
166
|
+
publisher: { "@type": "Organization", name: siteName },
|
|
167
|
+
keywords: tags.length > 0 ? tags.join(", ") : void 0,
|
|
168
|
+
timeRequired: readingTimeMinutes ? `PT${readingTimeMinutes}M` : void 0,
|
|
169
|
+
inLanguage: resolveInLanguage(config, language)
|
|
170
|
+
})
|
|
171
|
+
];
|
|
172
|
+
if (!hasBreadcrumbListHeadTag(headTags)) {
|
|
173
|
+
jsonLd.push(
|
|
174
|
+
buildBreadcrumbSchema(config, [
|
|
175
|
+
{ name: siteName, url: buildAbsoluteUrl(config, "/") },
|
|
176
|
+
{ name: title, url: canonicalUrl }
|
|
177
|
+
])
|
|
178
|
+
);
|
|
179
|
+
}
|
|
160
180
|
return {
|
|
161
181
|
title: buildPageTitle(siteName, title),
|
|
162
182
|
description,
|
|
@@ -168,32 +188,32 @@ function buildArticleSeo(config, options, permalink, post) {
|
|
|
168
188
|
modifiedTime,
|
|
169
189
|
tags,
|
|
170
190
|
readingTimeMinutes,
|
|
171
|
-
jsonLd
|
|
172
|
-
removeUndefined({
|
|
173
|
-
"@context": "https://schema.org",
|
|
174
|
-
"@type": "BlogPosting",
|
|
175
|
-
headline: title,
|
|
176
|
-
description,
|
|
177
|
-
url: canonicalUrl,
|
|
178
|
-
image: imageUrl ? [imageUrl] : void 0,
|
|
179
|
-
datePublished: publishedTime,
|
|
180
|
-
dateModified: modifiedTime,
|
|
181
|
-
author: config.site.author ? { "@type": "Person", name: config.site.author } : void 0,
|
|
182
|
-
publisher: { "@type": "Organization", name: siteName },
|
|
183
|
-
keywords: tags.length > 0 ? tags.join(", ") : void 0,
|
|
184
|
-
timeRequired: readingTimeMinutes ? `PT${readingTimeMinutes}M` : void 0,
|
|
185
|
-
inLanguage: getHtmlLanguage(config)
|
|
186
|
-
}),
|
|
187
|
-
breadcrumb
|
|
188
|
-
]
|
|
191
|
+
jsonLd
|
|
189
192
|
};
|
|
190
193
|
}
|
|
191
|
-
function buildWebsiteSeo(config,
|
|
192
|
-
const siteName =
|
|
194
|
+
function buildWebsiteSeo(config, input, headTags) {
|
|
195
|
+
const siteName = config.site.title;
|
|
193
196
|
const canonicalUrl = buildAbsoluteUrl(config, input.path);
|
|
194
197
|
const description = input.description || config.site.description;
|
|
195
|
-
const imageUrl = buildImageUrl(config
|
|
198
|
+
const imageUrl = buildImageUrl(config);
|
|
196
199
|
const title = input.kind === "tag" ? input.title : buildPageTitle(siteName, input.title);
|
|
200
|
+
const jsonLd = [
|
|
201
|
+
removeUndefined({
|
|
202
|
+
"@context": "https://schema.org",
|
|
203
|
+
"@type": "WebSite",
|
|
204
|
+
name: siteName,
|
|
205
|
+
description,
|
|
206
|
+
url: canonicalUrl,
|
|
207
|
+
inLanguage: getHtmlLanguage(config)
|
|
208
|
+
})
|
|
209
|
+
];
|
|
210
|
+
if (!hasBreadcrumbListHeadTag(headTags)) {
|
|
211
|
+
jsonLd.push(
|
|
212
|
+
buildBreadcrumbSchema(config, [
|
|
213
|
+
{ name: siteName, url: buildAbsoluteUrl(config, "/") }
|
|
214
|
+
])
|
|
215
|
+
);
|
|
216
|
+
}
|
|
197
217
|
return {
|
|
198
218
|
title,
|
|
199
219
|
description,
|
|
@@ -202,34 +222,23 @@ function buildWebsiteSeo(config, options, input) {
|
|
|
202
222
|
type: "website",
|
|
203
223
|
noindex: false,
|
|
204
224
|
tags: [],
|
|
205
|
-
jsonLd
|
|
206
|
-
removeUndefined({
|
|
207
|
-
"@context": "https://schema.org",
|
|
208
|
-
"@type": "WebSite",
|
|
209
|
-
name: siteName,
|
|
210
|
-
description,
|
|
211
|
-
url: canonicalUrl,
|
|
212
|
-
inLanguage: getHtmlLanguage(config)
|
|
213
|
-
}),
|
|
214
|
-
buildBreadcrumbSchema(config, [
|
|
215
|
-
{ name: siteName, url: buildAbsoluteUrl(config, "/") }
|
|
216
|
-
])
|
|
217
|
-
]
|
|
225
|
+
jsonLd
|
|
218
226
|
};
|
|
219
227
|
}
|
|
228
|
+
function resolveInLanguage(config, language) {
|
|
229
|
+
const value = language?.trim();
|
|
230
|
+
return value ? value.replace("_", "-") : getHtmlLanguage(config);
|
|
231
|
+
}
|
|
220
232
|
function buildCanonicalUrl(config, canonical, permalink) {
|
|
221
233
|
if (typeof canonical === "string" && canonical.trim()) {
|
|
222
234
|
return buildAbsoluteUrl(config, canonical.trim());
|
|
223
235
|
}
|
|
224
236
|
return buildPostUrl(config, permalink);
|
|
225
237
|
}
|
|
226
|
-
function buildImageUrl(config,
|
|
227
|
-
const image = value ||
|
|
238
|
+
function buildImageUrl(config, value) {
|
|
239
|
+
const image = value || config.site.defaultOgImage;
|
|
228
240
|
return image ? buildAbsoluteUrl(config, image) : "";
|
|
229
241
|
}
|
|
230
|
-
function getSiteName(config, options) {
|
|
231
|
-
return options.siteName || config.site.title;
|
|
232
|
-
}
|
|
233
242
|
function getArticleTitle(config, slug, value) {
|
|
234
243
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
235
244
|
const lastSegment = slug.split("/").filter(Boolean).at(-1);
|
|
@@ -247,6 +256,25 @@ function getIsoDate2(value) {
|
|
|
247
256
|
const date = new Date(value);
|
|
248
257
|
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
249
258
|
}
|
|
259
|
+
function hasBreadcrumbListHeadTag(headTags) {
|
|
260
|
+
return headTags?.some(
|
|
261
|
+
(tag) => tag.tag === "script" && typeof tag.children === "string" && hasBreadcrumbListJsonLd(tag.children)
|
|
262
|
+
) ?? false;
|
|
263
|
+
}
|
|
264
|
+
function hasBreadcrumbListJsonLd(value) {
|
|
265
|
+
try {
|
|
266
|
+
return hasBreadcrumbListType(JSON.parse(value));
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function hasBreadcrumbListType(value) {
|
|
272
|
+
if (Array.isArray(value)) return value.some(hasBreadcrumbListType);
|
|
273
|
+
if (!value || typeof value !== "object") return false;
|
|
274
|
+
const record = value;
|
|
275
|
+
if (record["@type"] === "BreadcrumbList") return true;
|
|
276
|
+
return hasBreadcrumbListType(record["@graph"]);
|
|
277
|
+
}
|
|
250
278
|
|
|
251
279
|
// src/plugin.ts
|
|
252
280
|
import { definePlugin } from "@riebeckite/core";
|
|
@@ -353,8 +381,8 @@ function seo(options = {}) {
|
|
|
353
381
|
name: "seo",
|
|
354
382
|
options,
|
|
355
383
|
seo: {
|
|
356
|
-
buildArticleSeo: (config, permalink, post) => buildArticleSeo(config,
|
|
357
|
-
buildWebsiteSeo: (config, input) => buildWebsiteSeo(config,
|
|
384
|
+
buildArticleSeo: (config, permalink, post, headTags, language) => buildArticleSeo(config, permalink, post, headTags, language),
|
|
385
|
+
buildWebsiteSeo: (config, input, headTags) => buildWebsiteSeo(config, input, headTags),
|
|
358
386
|
buildAbsoluteUrl,
|
|
359
387
|
buildPostUrl,
|
|
360
388
|
getDescription,
|
package/dist/src/metadata.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { PostContent, ResolvedRiebeckiteConfig, SeoMetadata, WebsiteSeoInput } from "@riebeckite/core";
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function buildWebsiteSeo(config: ResolvedRiebeckiteConfig, options: SeoPluginOptions, input: WebsiteSeoInput): SeoMetadata;
|
|
1
|
+
import type { PluginHeadTag, PostContent, ResolvedRiebeckiteConfig, SeoMetadata, WebsiteSeoInput } from "@riebeckite/core";
|
|
2
|
+
export declare function buildArticleSeo(config: ResolvedRiebeckiteConfig, permalink: string, post: PostContent, headTags?: readonly PluginHeadTag[], language?: string): SeoMetadata;
|
|
3
|
+
export declare function buildWebsiteSeo(config: ResolvedRiebeckiteConfig, input: WebsiteSeoInput, headTags?: readonly PluginHeadTag[]): SeoMetadata;
|
package/dist/src/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-seo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@riebeckite/core": "0.0.
|
|
17
|
+
"@riebeckite/core": "0.0.18"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"tsx": "^4.22.4",
|
|
21
|
-
"@riebeckite/test": "0.0.
|
|
21
|
+
"@riebeckite/test": "0.0.18"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|