@ubean/seo 0.1.1 → 0.1.3

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.
@@ -0,0 +1,110 @@
1
+ import { UseSeoMetaInput } from "@unhead/vue";
2
+ //#region src/index.d.ts
3
+ interface RobotsOptions {
4
+ userAgent?: string;
5
+ allow?: string | string[];
6
+ disallow?: string | string[];
7
+ sitemap?: string | string[];
8
+ crawlDelay?: number;
9
+ }
10
+ interface SitemapUrl {
11
+ loc: string;
12
+ lastmod?: string | Date;
13
+ changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
14
+ priority?: number;
15
+ }
16
+ interface MetaTag {
17
+ name?: string;
18
+ property?: string;
19
+ content: string;
20
+ }
21
+ interface LinkTag {
22
+ rel: string;
23
+ href: string;
24
+ hreflang?: string;
25
+ type?: string;
26
+ sizes?: string;
27
+ media?: string;
28
+ as?: string;
29
+ crossorigin?: '' | 'anonymous' | 'use-credentials';
30
+ }
31
+ interface OpenGraphMeta {
32
+ title?: string;
33
+ description?: string;
34
+ type?: 'website' | 'article' | 'book' | 'profile' | 'music.song' | 'music.album' | 'video.movie';
35
+ url?: string;
36
+ image?: string | OGImage;
37
+ siteName?: string;
38
+ locale?: string;
39
+ localeAlternate?: string[];
40
+ }
41
+ interface OGImage {
42
+ url: string;
43
+ width?: number;
44
+ height?: number;
45
+ alt?: string;
46
+ type?: string;
47
+ }
48
+ interface TwitterMeta {
49
+ card?: 'summary' | 'summary_large_image' | 'app' | 'player';
50
+ site?: string;
51
+ creator?: string;
52
+ title?: string;
53
+ description?: string;
54
+ image?: string;
55
+ }
56
+ interface SeoMetadata {
57
+ title?: string;
58
+ titleTemplate?: string | ((title: string) => string);
59
+ description?: string;
60
+ keywords?: string | string[];
61
+ author?: string;
62
+ canonical?: string;
63
+ robots?: string | {
64
+ index?: boolean;
65
+ follow?: boolean;
66
+ };
67
+ openGraph?: OpenGraphMeta;
68
+ twitter?: TwitterMeta;
69
+ meta?: MetaTag[];
70
+ link?: LinkTag[];
71
+ htmlAttrs?: Record<string, string>;
72
+ bodyAttrs?: Record<string, string>;
73
+ }
74
+ interface ManifestIcon {
75
+ src: string;
76
+ sizes: string;
77
+ type: string;
78
+ purpose?: string;
79
+ }
80
+ interface WebAppManifest {
81
+ name?: string;
82
+ short_name?: string;
83
+ description?: string;
84
+ start_url?: string;
85
+ display?: 'fullscreen' | 'standalone' | 'minimal-ui' | 'browser';
86
+ background_color?: string;
87
+ theme_color?: string;
88
+ orientation?: 'portrait' | 'landscape' | 'any';
89
+ icons?: ManifestIcon[];
90
+ scope?: string;
91
+ lang?: string;
92
+ dir?: 'ltr' | 'rtl' | 'auto';
93
+ }
94
+ declare function useSeoMeta<T extends Record<string, any>>(meta: T): T;
95
+ declare function mergeMetadata(...metadatas: (SeoMetadata | undefined | null)[]): SeoMetadata;
96
+ declare function buildMetaTags(meta: SeoMetadata): MetaTag[];
97
+ declare function buildLinkTags(meta: SeoMetadata): LinkTag[];
98
+ declare function buildTitle(meta: SeoMetadata, fallbackTitle?: string): string;
99
+ declare function renderHeadTags(meta: SeoMetadata, fallbackTitle?: string): string;
100
+ declare function createManifestResponse(manifest: WebAppManifest): Response;
101
+ declare function defineManifest(manifest: WebAppManifest): WebAppManifest;
102
+ declare function formatRobotsTxt(options: RobotsOptions[] | RobotsOptions): string;
103
+ declare function escapeXml(str: string): string;
104
+ declare function formatSitemapXml(urls: SitemapUrl[]): string;
105
+ declare function createRobotsResponse(options: RobotsOptions[] | RobotsOptions): Response;
106
+ declare function createSitemapResponse(urls: SitemapUrl[]): Response;
107
+ declare function defineRobotsConfig(config: RobotsOptions[] | RobotsOptions): RobotsOptions[] | RobotsOptions;
108
+ declare function defineSitemapConfig(urls: SitemapUrl[] | (() => SitemapUrl[] | Promise<SitemapUrl[]>)): SitemapUrl[] | (() => SitemapUrl[] | Promise<SitemapUrl[]>);
109
+ //#endregion
110
+ export { LinkTag, ManifestIcon, MetaTag, OGImage, OpenGraphMeta, RobotsOptions, SeoMetadata, SitemapUrl, TwitterMeta, type UseSeoMetaInput, WebAppManifest, buildLinkTags, buildMetaTags, buildTitle, createManifestResponse, createRobotsResponse, createSitemapResponse, defineManifest, defineRobotsConfig, defineSitemapConfig, escapeXml, formatRobotsTxt, formatSitemapXml, mergeMetadata, renderHeadTags, useSeoMeta };
package/dist/index.js ADDED
@@ -0,0 +1,272 @@
1
+ import { useSeoMeta as useSeoMeta$1 } from "@unhead/vue";
2
+ //#region src/index.ts
3
+ function useSeoMeta(meta) {
4
+ try {
5
+ useSeoMeta$1(meta);
6
+ } catch {}
7
+ return meta;
8
+ }
9
+ function mergeMetadata(...metadatas) {
10
+ const result = {};
11
+ for (const meta of metadatas) {
12
+ if (!meta) continue;
13
+ if (meta.title !== void 0) result.title = meta.title;
14
+ if (meta.titleTemplate !== void 0) result.titleTemplate = meta.titleTemplate;
15
+ if (meta.description !== void 0) result.description = meta.description;
16
+ if (meta.keywords !== void 0) result.keywords = meta.keywords;
17
+ if (meta.author !== void 0) result.author = meta.author;
18
+ if (meta.canonical !== void 0) result.canonical = meta.canonical;
19
+ if (meta.robots !== void 0) result.robots = meta.robots;
20
+ if (meta.htmlAttrs) result.htmlAttrs = {
21
+ ...result.htmlAttrs,
22
+ ...meta.htmlAttrs
23
+ };
24
+ if (meta.bodyAttrs) result.bodyAttrs = {
25
+ ...result.bodyAttrs,
26
+ ...meta.bodyAttrs
27
+ };
28
+ if (meta.openGraph) result.openGraph = {
29
+ ...result.openGraph,
30
+ ...meta.openGraph
31
+ };
32
+ if (meta.twitter) result.twitter = {
33
+ ...result.twitter,
34
+ ...meta.twitter
35
+ };
36
+ if (meta.meta) result.meta = [...result.meta || [], ...meta.meta];
37
+ if (meta.link) result.link = [...result.link || [], ...meta.link];
38
+ }
39
+ return result;
40
+ }
41
+ function buildMetaTags(meta) {
42
+ const tags = [];
43
+ if (meta.description) tags.push({
44
+ name: "description",
45
+ content: meta.description
46
+ });
47
+ if (meta.keywords) {
48
+ const kws = Array.isArray(meta.keywords) ? meta.keywords.join(", ") : meta.keywords;
49
+ tags.push({
50
+ name: "keywords",
51
+ content: kws
52
+ });
53
+ }
54
+ if (meta.author) tags.push({
55
+ name: "author",
56
+ content: meta.author
57
+ });
58
+ if (meta.robots) {
59
+ let robotsValue;
60
+ if (typeof meta.robots === "string") robotsValue = meta.robots;
61
+ else {
62
+ const parts = [];
63
+ if (meta.robots.index !== void 0) parts.push(meta.robots.index ? "index" : "noindex");
64
+ if (meta.robots.follow !== void 0) parts.push(meta.robots.follow ? "follow" : "nofollow");
65
+ robotsValue = parts.join(", ");
66
+ }
67
+ if (robotsValue) tags.push({
68
+ name: "robots",
69
+ content: robotsValue
70
+ });
71
+ }
72
+ if (meta.openGraph) {
73
+ const og = meta.openGraph;
74
+ if (og.title) tags.push({
75
+ property: "og:title",
76
+ content: og.title
77
+ });
78
+ if (og.description) tags.push({
79
+ property: "og:description",
80
+ content: og.description
81
+ });
82
+ if (og.type) tags.push({
83
+ property: "og:type",
84
+ content: og.type
85
+ });
86
+ if (og.url) tags.push({
87
+ property: "og:url",
88
+ content: og.url
89
+ });
90
+ if (og.siteName) tags.push({
91
+ property: "og:site_name",
92
+ content: og.siteName
93
+ });
94
+ if (og.locale) tags.push({
95
+ property: "og:locale",
96
+ content: og.locale
97
+ });
98
+ if (og.localeAlternate) for (const loc of og.localeAlternate) tags.push({
99
+ property: "og:locale:alternate",
100
+ content: loc
101
+ });
102
+ if (og.image) if (typeof og.image === "string") tags.push({
103
+ property: "og:image",
104
+ content: og.image
105
+ });
106
+ else {
107
+ tags.push({
108
+ property: "og:image",
109
+ content: og.image.url
110
+ });
111
+ if (og.image.width) tags.push({
112
+ property: "og:image:width",
113
+ content: String(og.image.width)
114
+ });
115
+ if (og.image.height) tags.push({
116
+ property: "og:image:height",
117
+ content: String(og.image.height)
118
+ });
119
+ if (og.image.alt) tags.push({
120
+ property: "og:image:alt",
121
+ content: og.image.alt
122
+ });
123
+ if (og.image.type) tags.push({
124
+ property: "og:image:type",
125
+ content: og.image.type
126
+ });
127
+ }
128
+ }
129
+ if (meta.twitter) {
130
+ const tw = meta.twitter;
131
+ if (tw.card) tags.push({
132
+ name: "twitter:card",
133
+ content: tw.card
134
+ });
135
+ if (tw.site) tags.push({
136
+ name: "twitter:site",
137
+ content: tw.site
138
+ });
139
+ if (tw.creator) tags.push({
140
+ name: "twitter:creator",
141
+ content: tw.creator
142
+ });
143
+ if (tw.title) tags.push({
144
+ name: "twitter:title",
145
+ content: tw.title
146
+ });
147
+ if (tw.description) tags.push({
148
+ name: "twitter:description",
149
+ content: tw.description
150
+ });
151
+ if (tw.image) tags.push({
152
+ name: "twitter:image",
153
+ content: tw.image
154
+ });
155
+ }
156
+ if (meta.meta) tags.push(...meta.meta);
157
+ return tags;
158
+ }
159
+ function buildLinkTags(meta) {
160
+ const links = [];
161
+ if (meta.canonical) links.push({
162
+ rel: "canonical",
163
+ href: meta.canonical
164
+ });
165
+ if (meta.link) links.push(...meta.link);
166
+ return links;
167
+ }
168
+ function buildTitle(meta, fallbackTitle) {
169
+ let title = meta.title || fallbackTitle || "";
170
+ if (meta.titleTemplate && title) if (typeof meta.titleTemplate === "function") title = meta.titleTemplate(title);
171
+ else title = meta.titleTemplate.replace("%s", title);
172
+ return title;
173
+ }
174
+ function renderHeadTags(meta, fallbackTitle) {
175
+ const parts = [];
176
+ const title = buildTitle(meta, fallbackTitle);
177
+ if (title) parts.push(`<title>${escapeHtml(title)}</title>`);
178
+ const metaTags = buildMetaTags(meta);
179
+ for (const tag of metaTags) if (tag.name) parts.push(`<meta name="${escapeHtml(tag.name)}" content="${escapeHtml(tag.content)}">`);
180
+ else if (tag.property) parts.push(`<meta property="${escapeHtml(tag.property)}" content="${escapeHtml(tag.content)}">`);
181
+ const linkTags = buildLinkTags(meta);
182
+ for (const link of linkTags) {
183
+ const attrs = [`rel="${escapeHtml(link.rel)}"`, `href="${escapeHtml(link.href)}"`];
184
+ if (link.hreflang) attrs.push(`hreflang="${escapeHtml(link.hreflang)}"`);
185
+ if (link.type) attrs.push(`type="${escapeHtml(link.type)}"`);
186
+ if (link.sizes) attrs.push(`sizes="${escapeHtml(link.sizes)}"`);
187
+ if (link.media) attrs.push(`media="${escapeHtml(link.media)}"`);
188
+ if (link.as) attrs.push(`as="${escapeHtml(link.as)}"`);
189
+ if (link.crossorigin !== void 0) attrs.push(link.crossorigin ? `crossorigin="${escapeHtml(link.crossorigin)}"` : "crossorigin");
190
+ parts.push(`<link ${attrs.join(" ")}>`);
191
+ }
192
+ return parts.join("\n");
193
+ }
194
+ function createManifestResponse(manifest) {
195
+ return new Response(JSON.stringify(manifest, null, 2), { headers: {
196
+ "Content-Type": "application/manifest+json; charset=utf-8",
197
+ "Cache-Control": "public, max-age=86400"
198
+ } });
199
+ }
200
+ function defineManifest(manifest) {
201
+ return manifest;
202
+ }
203
+ function escapeHtml(str) {
204
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
205
+ }
206
+ function formatRobotsTxt(options) {
207
+ const groups = Array.isArray(options) ? options : [options];
208
+ const lines = [];
209
+ for (const group of groups) {
210
+ lines.push(`User-agent: ${group.userAgent || "*"}`);
211
+ if (group.allow) {
212
+ const allows = Array.isArray(group.allow) ? group.allow : [group.allow];
213
+ for (const path of allows) lines.push(`Allow: ${path}`);
214
+ }
215
+ if (group.disallow) {
216
+ const disallows = Array.isArray(group.disallow) ? group.disallow : [group.disallow];
217
+ for (const path of disallows) lines.push(`Disallow: ${path}`);
218
+ }
219
+ if (group.crawlDelay !== void 0) lines.push(`Crawl-delay: ${group.crawlDelay}`);
220
+ lines.push("");
221
+ }
222
+ const sitemaps = /* @__PURE__ */ new Set();
223
+ for (const group of groups) if (group.sitemap) {
224
+ const maps = Array.isArray(group.sitemap) ? group.sitemap : [group.sitemap];
225
+ for (const url of maps) sitemaps.add(url);
226
+ }
227
+ for (const sitemap of sitemaps) lines.push(`Sitemap: ${sitemap}`);
228
+ return `${lines.join("\n").trim()}\n`;
229
+ }
230
+ function escapeXml(str) {
231
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
232
+ }
233
+ function formatDate(date) {
234
+ if (date instanceof Date) return date.toISOString().split("T")[0];
235
+ return date;
236
+ }
237
+ function formatSitemapXml(urls) {
238
+ const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>"];
239
+ lines.push("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
240
+ for (const url of urls) {
241
+ lines.push(" <url>");
242
+ lines.push(` <loc>${escapeXml(url.loc)}</loc>`);
243
+ if (url.lastmod) lines.push(` <lastmod>${escapeXml(formatDate(url.lastmod))}</lastmod>`);
244
+ if (url.changefreq) lines.push(` <changefreq>${url.changefreq}</changefreq>`);
245
+ if (url.priority !== void 0) lines.push(` <priority>${url.priority.toFixed(1)}</priority>`);
246
+ lines.push(" </url>");
247
+ }
248
+ lines.push("</urlset>");
249
+ return lines.join("\n");
250
+ }
251
+ function createRobotsResponse(options) {
252
+ const body = formatRobotsTxt(options);
253
+ return new Response(body, { headers: {
254
+ "Content-Type": "text/plain; charset=utf-8",
255
+ "Cache-Control": "public, max-age=3600"
256
+ } });
257
+ }
258
+ function createSitemapResponse(urls) {
259
+ const body = formatSitemapXml(urls);
260
+ return new Response(body, { headers: {
261
+ "Content-Type": "application/xml; charset=utf-8",
262
+ "Cache-Control": "public, max-age=3600"
263
+ } });
264
+ }
265
+ function defineRobotsConfig(config) {
266
+ return config;
267
+ }
268
+ function defineSitemapConfig(urls) {
269
+ return urls;
270
+ }
271
+ //#endregion
272
+ export { buildLinkTags, buildMetaTags, buildTitle, createManifestResponse, createRobotsResponse, createSitemapResponse, defineManifest, defineRobotsConfig, defineSitemapConfig, escapeXml, formatRobotsTxt, formatSitemapXml, mergeMetadata, renderHeadTags, useSeoMeta };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/seo",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "SEO utilities for ubean (useSeoMeta, robots, sitemap, manifest)",
5
5
  "files": [
6
6
  "dist"