diff --git a/.gitignore b/.gitignore
index 42d5d2a..083db29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,4 +19,8 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
-package-lock.json
\ No newline at end of file
+package-lock.json
+pnpm-lock.yaml
+
+# tarballs
+*.tgz
\ No newline at end of file
diff --git a/package.json b/package.json
index 9f539ea..d5b6512 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,6 @@
"workspaces": [
"packages/*"
],
- "scripts": {
- },
"author": {
"name": "onWidget",
"email": "contact@onwidget.com",
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
index aae518e..abf2ef8 100644
--- a/packages/analytics/package.json
+++ b/packages/analytics/package.json
@@ -40,9 +40,9 @@
],
"scripts": {},
"devDependencies": {
- "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0"
+ "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
},
"peerDependencies": {
- "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0"
+ "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
}
}
diff --git a/packages/seo/package.json b/packages/seo/package.json
index 45c210e..2ead97e 100644
--- a/packages/seo/package.json
+++ b/packages/seo/package.json
@@ -47,7 +47,7 @@
"devDependencies": {
"@types/html-escaper": "^3.0.0",
"@types/jest": "^29.5.4",
- "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0",
+ "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
"html-escaper": "^3.0.3",
"html-validate": "^8.18.2",
"jest": "^29.6.3",
@@ -55,6 +55,6 @@
"typescript": "^5.1.6"
},
"peerDependencies": {
- "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0"
+ "astro": "^1.2.1 || ^2.0.0 || ^3.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
}
}
diff --git a/packages/seo/src/__test__/buildTags.test.ts b/packages/seo/src/__test__/buildTags.test.ts
index 3321e24..d5c256c 100644
--- a/packages/seo/src/__test__/buildTags.test.ts
+++ b/packages/seo/src/__test__/buildTags.test.ts
@@ -4,438 +4,555 @@ import { HtmlValidate as _HtmlValidate } from "html-validate/node";
const validate = new _HtmlValidate();
describe("buildTags function", () => {
- it("should return an empty string if no config is provided", () => {
- const result = buildTags({});
- expect(result).toBe("");
- });
-
- it("should handle null or undefined values gracefully", () => {
- const config = {
- title: null,
- description: undefined,
- };
- // @ts-ignore
- const result = buildTags(config);
- expect(result).not.toContain("null");
- expect(result).not.toContain("undefined");
- });
-
- it("should escape special characters correctly", async () => {
- const config = {
- title: "Title & Description",
- };
- const result = buildTags(config);
- expect(result).toContain("Title & Description");
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct title tag", async () => {
- const config = {
- title: '',
- };
- const result = buildTags(config);
- expect(result).toContain(
- "
<script>alert("hacked")</script>"
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct description tag", async () => {
- const config = {
- description: '
',
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should escape URLs", async () => {
- const config = {
- openGraph: {
- url: '',
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct robots tag for noindex and nofollow", async () => {
- const config = {
- noindex: true,
- nofollow: true,
- };
- const result = buildTags(config);
- expect(result).toContain('');
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct twitter card tag", async () => {
- const config = {
- twitter: {
- cardType: "summary_large_image",
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct facebook app id tag", async () => {
- const config = {
- facebook: {
- appId: "1234567890",
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct openGraph title tag", async () => {
- const config = {
- openGraph: {
- title: "Test Title",
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct canonical link tag", async () => {
- const config = {
- canonical: "https://example.com/page",
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct alternate link tag for mobile", async () => {
- const config = {
- mobileAlternate: {
- media: "only screen and (max-width: 640px)",
- href: "https://m.example.com/page",
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct openGraph description tag", async () => {
- const config = {
- openGraph: {
- description: "Test Description",
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct openGraph type tag", async () => {
- const config = {
- openGraph: {
- type: "article",
- },
- };
- const result = buildTags(config);
- expect(result).toContain('');
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct openGraph locale tag", async () => {
- const config = {
- openGraph: {
- locale: "en_US",
- },
- };
- const result = buildTags(config);
- expect(result).toContain('');
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct openGraph site_name tag", async () => {
- const config = {
- openGraph: {
- site_name: "Test Site",
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should handle multiple OpenGraph media tags correctly", async () => {
- const config = {
- openGraph: {
- images: [
- { url: "https://example.com/image1.jpg" },
- { url: "https://example.com/image2.jpg" },
- ],
- videos: [
- { url: "https://example.com/video1.mp4" },
- { url: "https://example.com/video2.mp4" },
- ],
- },
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct languageAlternates link tags", async () => {
- const config = {
- languageAlternates: [
- { hreflang: "es", href: "https://example.com/es" },
- { hreflang: "fr", href: "https://example.com/fr" },
- ],
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct twitter site and creator tags", async () => {
- const config = {
- twitter: {
- site: "@testsite",
- handle: "@testhandle",
- },
- };
- const result = buildTags(config);
- expect(result).toContain('');
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct additionalMetaTags", async () => {
- const config = {
- additionalMetaTags: [
- { name: "viewport", content: "width=device-width, initial-scale=1" },
- ],
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate correct additionalLinkTags", async () => {
- const config = {
- additionalLinkTags: [
- { rel: "stylesheet", href: "https://example.com/styles.css" },
- ],
- };
- const result = buildTags(config);
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate og:title and og:description from title and description if not explicitly set", async () => {
- const config = {
- title: "Test Title",
- description: "Test Description",
- openGraph: {},
- };
- const result = buildTags(config);
- expect(result).toContain('');
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should not generate og:title and og:description if openGraph is not defined", async () => {
- const config = {
- title: "Test Title",
- description: "Test Description",
- };
- const result = buildTags(config);
- expect(result).not.toContain(
- ''
- );
- expect(result).not.toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- // Casos de prueba realistas
- it("should generate a complete set of tags for a blog post", async () => {
- const config = {
- title: "My Blog Post",
- description: "A detailed description of my blog post.",
- canonical: "https://example.com/blog/my-blog-post",
- openGraph: {
- type: "article",
- url: "https://example.com/blog/my-blog-post",
- title: "My Blog Post",
- description: "A detailed description of my blog post.",
- images: [{ url: "https://example.com/images/blog-image.jpg" }],
- },
- twitter: {
- cardType: "summary_large_image",
- site: "@example",
- },
- };
- const result = buildTags(config);
- expect(result).toContain("My Blog Post");
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain('');
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain('');
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
-
- it("should generate a complete set of tags for a product page", async () => {
- const config = {
- title: "Product Name",
- description: "Description of the product.",
- canonical: "https://example.com/products/product-name",
- openGraph: {
- type: "product",
- url: "https://example.com/products/product-name",
- title: "Product Name",
- description: "Description of the product.",
- images: [{ url: "https://example.com/images/product-image.jpg" }],
- },
- twitter: {
- cardType: "summary",
- site: "@examplestore",
- },
- };
- const result = buildTags(config);
- expect(result).toContain("Product Name");
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain('');
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain(
- ''
- );
- expect(result).toContain('');
- expect(result).toContain(
- ''
- );
-
- const htmlResult = await validate.validateString(result);
- expect(htmlResult.valid).toBe(true);
- });
+ it("should return an empty string if no config is provided", () => {
+ const result = buildTags({});
+ expect(result).toBe("");
+ });
+
+ it("should handle null or undefined values gracefully", () => {
+ const config = {
+ title: null,
+ description: undefined,
+ };
+ // @ts-ignore
+ const result = buildTags(config);
+ expect(result).not.toContain("null");
+ expect(result).not.toContain("undefined");
+ });
+
+ it("should escape special characters correctly", async () => {
+ const config = {
+ title: "Title & Description",
+ };
+ const result = buildTags(config);
+ expect(result).toContain("Title & Description");
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct title tag", async () => {
+ const config = {
+ title: '',
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ "<script>alert("hacked")</script>"
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct description tag", async () => {
+ const config = {
+ description: '
',
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should escape URLs", async () => {
+ const config = {
+ openGraph: {
+ url: '',
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct robots tag for noindex and nofollow", async () => {
+ const config = {
+ noindex: true,
+ nofollow: true,
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct twitter card tag", async () => {
+ const config = {
+ twitter: {
+ cardType: "summary_large_image",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct facebook app id tag", async () => {
+ const config = {
+ facebook: {
+ appId: "1234567890",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct openGraph title tag", async () => {
+ const config = {
+ openGraph: {
+ title: "Test Title",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct canonical link tag", async () => {
+ const config = {
+ canonical: "https://example.com/page",
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct alternate link tag for mobile", async () => {
+ const config = {
+ mobileAlternate: {
+ media: "only screen and (max-width: 640px)",
+ href: "https://m.example.com/page",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct openGraph description tag", async () => {
+ const config = {
+ openGraph: {
+ description: "Test Description",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct openGraph type tag", async () => {
+ const config = {
+ openGraph: {
+ type: "article",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain('');
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct openGraph locale tag", async () => {
+ const config = {
+ openGraph: {
+ locale: "en_US",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain('');
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct openGraph site_name tag", async () => {
+ const config = {
+ openGraph: {
+ site_name: "Test Site",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should handle multiple OpenGraph media tags correctly", async () => {
+ const config = {
+ openGraph: {
+ images: [
+ { url: "https://example.com/image1.jpg" },
+ { url: "https://example.com/image2.jpg" },
+ ],
+ videos: [
+ { url: "https://example.com/video1.mp4" },
+ { url: "https://example.com/video2.mp4" },
+ ],
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct OpenGraph profile tags", async () => {
+ const config = {
+ openGraph: {
+ profile: {
+ firstName: "John",
+ lastName: "Doe",
+ gender: "male",
+ username: "johndoe",
+ },
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct OpenGraph book tags", async () => {
+ const config = {
+ openGraph: {
+ book: {
+ authors: [
+ "http://walterisaacson.com",
+ "http://author2.com",
+ ],
+ isbn: "978-1451648539",
+ releaseDate: "2011-10-24",
+ tags: ["Steve Jobs", "tag2"],
+ },
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain('');
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct languageAlternates link tags", async () => {
+ const config = {
+ languageAlternates: [
+ { hreflang: "es", href: "https://example.com/es" },
+ { hreflang: "fr", href: "https://example.com/fr" },
+ ],
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct twitter site and creator tags", async () => {
+ const config = {
+ twitter: {
+ site: "@testsite",
+ handle: "@testhandle",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct additionalMetaTags", async () => {
+ const config = {
+ additionalMetaTags: [
+ {
+ name: "viewport",
+ content: "width=device-width, initial-scale=1",
+ },
+ ],
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate correct additionalLinkTags", async () => {
+ const config = {
+ additionalLinkTags: [
+ { rel: "stylesheet", href: "https://example.com/styles.css" },
+ ],
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate og:title and og:description from title and description if not explicitly set", async () => {
+ const config = {
+ title: "Test Title",
+ description: "Test Description",
+ openGraph: {},
+ };
+ const result = buildTags(config);
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should not generate og:title and og:description if openGraph is not defined", async () => {
+ const config = {
+ title: "Test Title",
+ description: "Test Description",
+ };
+ const result = buildTags(config);
+ expect(result).not.toContain(
+ ''
+ );
+ expect(result).not.toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ // Casos de prueba realistas
+ it("should generate a complete set of tags for a blog post", async () => {
+ const config = {
+ title: "My Blog Post",
+ description: "A detailed description of my blog post.",
+ canonical: "https://example.com/blog/my-blog-post",
+ openGraph: {
+ type: "article",
+ url: "https://example.com/blog/my-blog-post",
+ title: "My Blog Post",
+ description: "A detailed description of my blog post.",
+ images: [{ url: "https://example.com/images/blog-image.jpg" }],
+ article: {
+ publishedTime: "2022-01-01T12:00:00Z",
+ modifiedTime: "2022-01-02T12:00:00Z",
+ expirationTime: "2022-01-03T12:00:00Z",
+ section: "Technology",
+ tags: ["tag1", "tag2"],
+ authors: [
+ "https://www.facebook.com/john-doe",
+ "https://www.facebook.com/jane-doe",
+ ],
+ },
+ },
+ twitter: {
+ cardType: "summary_large_image",
+ site: "@example",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain("My Blog Post");
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain('');
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ // OpenGraph article meta tags
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
+
+ it("should generate a complete set of tags for a product page", async () => {
+ const config = {
+ title: "Product Name",
+ description: "Description of the product.",
+ canonical: "https://example.com/products/product-name",
+ openGraph: {
+ type: "product",
+ url: "https://example.com/products/product-name",
+ title: "Product Name",
+ description: "Description of the product.",
+ images: [
+ { url: "https://example.com/images/product-image.jpg" },
+ ],
+ },
+ twitter: {
+ cardType: "summary",
+ site: "@examplestore",
+ },
+ };
+ const result = buildTags(config);
+ expect(result).toContain("Product Name");
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain('');
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+ expect(result).toContain(
+ ''
+ );
+
+ const htmlResult = await validate.validateString(result);
+ expect(htmlResult.valid).toBe(true);
+ });
});
diff --git a/packages/seo/src/utils/buildTags.ts b/packages/seo/src/utils/buildTags.ts
index b4256bc..e3cd46e 100644
--- a/packages/seo/src/utils/buildTags.ts
+++ b/packages/seo/src/utils/buildTags.ts
@@ -2,456 +2,513 @@ import { escape } from "html-escaper";
import type { AstroSeoProps, OpenGraphMedia } from "../types";
const createMetaTag = (attributes: Record): string => {
- const attrs = Object.entries(attributes)
- .map(([key, value]) => `${key}="${escape(value)}"`)
- .join(" ");
- return ``;
+ const attrs = Object.entries(attributes)
+ .map(([key, value]) => `${key}="${escape(value)}"`)
+ .join(" ");
+ return ``;
};
const createLinkTag = (attributes: Record): string => {
- const attrs = Object.entries(attributes)
- .map(([key, value]) => `${key}="${escape(value)}"`)
- .join(" ");
- return ``;
+ const attrs = Object.entries(attributes)
+ .map(([key, value]) => `${key}="${escape(value)}"`)
+ .join(" ");
+ return ``;
};
-const createOpenGraphTag = (property: string, content: string): string => {
- return createMetaTag({ property: `og:${property}`, content });
+const createOpenGraphTag = (
+ property: string,
+ content: string,
+ ogPrefix: boolean = true
+): string => {
+ return createMetaTag({
+ property: ogPrefix ? `og:${property}` : `${property}`,
+ content,
+ });
};
const buildOpenGraphMediaTags = (
- mediaType: "image" | "video",
- media: ReadonlyArray
+ mediaType: "image" | "video",
+ media: ReadonlyArray
): string => {
- let tags = "";
-
- const addTag = (tag: string) => {
- tags += tag + "\n";
- };
-
- media.forEach((medium) => {
- addTag(createOpenGraphTag(mediaType, medium.url));
-
- if (medium.alt) {
- addTag(createOpenGraphTag(`${mediaType}:alt`, medium.alt));
- }
-
- if (medium.secureUrl) {
- addTag(createOpenGraphTag(`${mediaType}:secure_url`, medium.secureUrl));
- }
-
- if (medium.type) {
- addTag(createOpenGraphTag(`${mediaType}:type`, medium.type));
- }
-
- if (medium.width) {
- addTag(createOpenGraphTag(`${mediaType}:width`, medium.width.toString()));
- }
-
- if (medium.height) {
- addTag(
- createOpenGraphTag(`${mediaType}:height`, medium.height.toString())
- );
- }
- });
- return tags;
+ let tags = "";
+
+ const addTag = (tag: string) => {
+ tags += tag + "\n";
+ };
+
+ media.forEach((medium) => {
+ addTag(createOpenGraphTag(mediaType, medium.url));
+
+ if (medium.alt) {
+ addTag(createOpenGraphTag(`${mediaType}:alt`, medium.alt));
+ }
+
+ if (medium.secureUrl) {
+ addTag(
+ createOpenGraphTag(`${mediaType}:secure_url`, medium.secureUrl)
+ );
+ }
+
+ if (medium.type) {
+ addTag(createOpenGraphTag(`${mediaType}:type`, medium.type));
+ }
+
+ if (medium.width) {
+ addTag(
+ createOpenGraphTag(
+ `${mediaType}:width`,
+ medium.width.toString()
+ )
+ );
+ }
+
+ if (medium.height) {
+ addTag(
+ createOpenGraphTag(
+ `${mediaType}:height`,
+ medium.height.toString()
+ )
+ );
+ }
+ });
+ return tags;
};
export const buildTags = (config: AstroSeoProps): string => {
- let tagsToRender = "";
-
- const addTag = (tag: string) => {
- tagsToRender += tag + "\n";
- };
-
- const addMetaTag = (attributes: Record) => {
- addTag(
- ` `${key}="${escape(value)}"`)
- .join(" ")} />`
- );
- };
+ let tagsToRender = "";
- const addLinkTag = (attributes: Record) => {
- addTag(
- ` `${key}="${escape(value)}"`)
- .join(" ")} />`
- );
- };
-
- const addOpenGraphTag = (property: string, content: string) => {
- addMetaTag({ property: `og:${property}`, content });
- };
-
- // Title
- if (config.title) {
- const formattedTitle = config.titleTemplate
- ? config.titleTemplate.replace("%s", config.title)
- : config.title;
- addTag(`${escape(formattedTitle)}`);
- }
-
- // Description
- if (config.description) {
- addTag(createMetaTag({ name: "description", content: config.description }));
- }
-
- // Robots: noindex, nofollow, and other robotsProps
- let robotsContent: string[] = [];
- if (typeof config.noindex !== "undefined") {
- robotsContent.push(config.noindex ? "noindex" : "index");
- }
-
- if (typeof config.nofollow !== "undefined") {
- robotsContent.push(config.nofollow ? "nofollow" : "follow");
- }
-
- if (config.robotsProps) {
- const {
- nosnippet,
- maxSnippet,
- maxImagePreview,
- noarchive,
- unavailableAfter,
- noimageindex,
- notranslate,
- } = config.robotsProps;
-
- if (nosnippet) robotsContent.push("nosnippet");
- if (typeof maxSnippet === 'number') robotsContent.push(`max-snippet:${maxSnippet}`);
- if (maxImagePreview)
- robotsContent.push(`max-image-preview:${maxImagePreview}`);
- if (noarchive) robotsContent.push("noarchive");
- if (unavailableAfter)
- robotsContent.push(`unavailable_after:${unavailableAfter}`);
- if (noimageindex) robotsContent.push("noimageindex");
- if (notranslate) robotsContent.push("notranslate");
- }
-
- if (robotsContent.length > 0) {
- addTag(createMetaTag({ name: "robots", content: robotsContent.join(",") }));
- }
-
- // Canonical
- if (config.canonical) {
- addTag(createLinkTag({ rel: "canonical", href: config.canonical }));
- }
-
- // Mobile Alternate
- if (config.mobileAlternate) {
- addTag(
- createLinkTag({
- rel: "alternate",
- media: config.mobileAlternate.media,
- href: config.mobileAlternate.href,
- })
- );
- }
-
- // Language Alternates
- if (config.languageAlternates && config.languageAlternates.length > 0) {
- config.languageAlternates.forEach((languageAlternate) => {
- addTag(
- createLinkTag({
- rel: "alternate",
- hreflang: languageAlternate.hreflang,
- href: languageAlternate.href,
- })
- );
- });
- }
+ const addTag = (tag: string) => {
+ tagsToRender += tag + "\n";
+ };
- // OpenGraph
- if (config.openGraph) {
- const title = config.openGraph?.title || config.title;
- if (title) {
- addTag(createOpenGraphTag("title", title));
- }
+ const addMetaTag = (attributes: Record) => {
+ addTag(
+ ` `${key}="${escape(value)}"`)
+ .join(" ")} />`
+ );
+ };
- const description = config.openGraph?.description || config.description;
- if (description) {
- addTag(createOpenGraphTag("description", description));
+ const addLinkTag = (attributes: Record) => {
+ addTag(
+ ` `${key}="${escape(value)}"`)
+ .join(" ")} />`
+ );
+ };
+
+ const addOpenGraphTag = (property: string, content: string) => {
+ addMetaTag({ property: `og:${property}`, content });
+ };
+
+ // Title
+ if (config.title) {
+ const formattedTitle = config.titleTemplate
+ ? config.titleTemplate.replace("%s", config.title)
+ : config.title;
+ addTag(`${escape(formattedTitle)}`);
}
- if (config.openGraph.url) {
- addTag(createOpenGraphTag("url", config.openGraph.url));
+ // Description
+ if (config.description) {
+ addTag(
+ createMetaTag({ name: "description", content: config.description })
+ );
}
- if (config.openGraph.type) {
- addTag(createOpenGraphTag("type", config.openGraph.type));
+ // Robots: noindex, nofollow, and other robotsProps
+ let robotsContent: string[] = [];
+ if (typeof config.noindex !== "undefined") {
+ robotsContent.push(config.noindex ? "noindex" : "index");
}
- if (config.openGraph.images && config.openGraph.images.length) {
- addTag(buildOpenGraphMediaTags("image", config.openGraph.images));
+ if (typeof config.nofollow !== "undefined") {
+ robotsContent.push(config.nofollow ? "nofollow" : "follow");
}
- if (config.openGraph.videos && config.openGraph.videos.length) {
- addTag(buildOpenGraphMediaTags("video", config.openGraph.videos));
+ if (config.robotsProps) {
+ const {
+ nosnippet,
+ maxSnippet,
+ maxImagePreview,
+ noarchive,
+ unavailableAfter,
+ noimageindex,
+ notranslate,
+ } = config.robotsProps;
+
+ if (nosnippet) robotsContent.push("nosnippet");
+ if (typeof maxSnippet === "number")
+ robotsContent.push(`max-snippet:${maxSnippet}`);
+ if (maxImagePreview)
+ robotsContent.push(`max-image-preview:${maxImagePreview}`);
+ if (noarchive) robotsContent.push("noarchive");
+ if (unavailableAfter)
+ robotsContent.push(`unavailable_after:${unavailableAfter}`);
+ if (noimageindex) robotsContent.push("noimageindex");
+ if (notranslate) robotsContent.push("notranslate");
}
- if (config.openGraph.locale) {
- addTag(createOpenGraphTag("locale", config.openGraph.locale));
+ if (robotsContent.length > 0) {
+ addTag(
+ createMetaTag({ name: "robots", content: robotsContent.join(",") })
+ );
}
- if (config.openGraph.site_name) {
- addTag(createOpenGraphTag("site_name", config.openGraph.site_name));
+ // Canonical
+ if (config.canonical) {
+ addTag(createLinkTag({ rel: "canonical", href: config.canonical }));
}
- // Open Graph Profile
- if (config.openGraph.profile) {
- if (config.openGraph.profile.firstName) {
+ // Mobile Alternate
+ if (config.mobileAlternate) {
addTag(
- createOpenGraphTag(
- "profile:first_name",
- config.openGraph.profile.firstName
- )
+ createLinkTag({
+ rel: "alternate",
+ media: config.mobileAlternate.media,
+ href: config.mobileAlternate.href,
+ })
);
- }
- if (config.openGraph.profile.lastName) {
- addTag(
- createOpenGraphTag(
- "profile:last_name",
- config.openGraph.profile.lastName
- )
- );
- }
- if (config.openGraph.profile.username) {
- addTag(
- createOpenGraphTag(
- "profile:username",
- config.openGraph.profile.username
- )
- );
- }
- if (config.openGraph.profile.gender) {
- addTag(
- createOpenGraphTag("profile:gender", config.openGraph.profile.gender)
- );
- }
}
- // Open Graph Book
- if (config.openGraph.book) {
- if (
- config.openGraph.book.authors &&
- config.openGraph.book.authors.length
- ) {
- config.openGraph.book.authors.forEach((author) => {
- addTag(createOpenGraphTag("book:author", author));
- });
- }
- if (config.openGraph.book.isbn) {
- addTag(createOpenGraphTag("book:isbn", config.openGraph.book.isbn));
- }
- if (config.openGraph.book.releaseDate) {
- addTag(
- createOpenGraphTag(
- "book:release_date",
- config.openGraph.book.releaseDate
- )
- );
- }
- if (config.openGraph.book.tags && config.openGraph.book.tags.length) {
- config.openGraph.book.tags.forEach((tag) => {
- addTag(createOpenGraphTag("book:tag", tag));
+ // Language Alternates
+ if (config.languageAlternates && config.languageAlternates.length > 0) {
+ config.languageAlternates.forEach((languageAlternate) => {
+ addTag(
+ createLinkTag({
+ rel: "alternate",
+ hreflang: languageAlternate.hreflang,
+ href: languageAlternate.href,
+ })
+ );
});
- }
}
- // Open Graph Article
- if (config.openGraph.article) {
- if (config.openGraph.article.publishedTime) {
- addTag(
- createOpenGraphTag(
- "article:published_time",
- config.openGraph.article.publishedTime
- )
- );
- }
- if (config.openGraph.article.modifiedTime) {
- addTag(
- createOpenGraphTag(
- "article:modified_time",
- config.openGraph.article.modifiedTime
- )
- );
- }
- if (config.openGraph.article.expirationTime) {
- addTag(
- createOpenGraphTag(
- "article:expiration_time",
- config.openGraph.article.expirationTime
- )
- );
- }
- if (
- config.openGraph.article.authors &&
- config.openGraph.article.authors.length
- ) {
- config.openGraph.article.authors.forEach((author) => {
- addTag(createOpenGraphTag("article:author", author));
- });
- }
- if (config.openGraph.article.section) {
- addTag(
- createOpenGraphTag(
- "article:section",
- config.openGraph.article.section
- )
- );
- }
- if (
- config.openGraph.article.tags &&
- config.openGraph.article.tags.length
- ) {
- config.openGraph.article.tags.forEach((tag) => {
- addTag(createOpenGraphTag("article:tag", tag));
- });
- }
+ // OpenGraph
+ if (config.openGraph) {
+ const title = config.openGraph?.title || config.title;
+ if (title) {
+ addTag(createOpenGraphTag("title", title));
+ }
+
+ const description = config.openGraph?.description || config.description;
+ if (description) {
+ addTag(createOpenGraphTag("description", description));
+ }
+
+ if (config.openGraph.url) {
+ addTag(createOpenGraphTag("url", config.openGraph.url));
+ }
+
+ if (config.openGraph.type) {
+ addTag(createOpenGraphTag("type", config.openGraph.type));
+ }
+
+ if (config.openGraph.images && config.openGraph.images.length) {
+ addTag(buildOpenGraphMediaTags("image", config.openGraph.images));
+ }
+
+ if (config.openGraph.videos && config.openGraph.videos.length) {
+ addTag(buildOpenGraphMediaTags("video", config.openGraph.videos));
+ }
+
+ if (config.openGraph.locale) {
+ addTag(createOpenGraphTag("locale", config.openGraph.locale));
+ }
+
+ if (config.openGraph.site_name) {
+ addTag(createOpenGraphTag("site_name", config.openGraph.site_name));
+ }
+
+ // Open Graph Profile
+ if (config.openGraph.profile) {
+ if (config.openGraph.profile.firstName) {
+ addTag(
+ createOpenGraphTag(
+ "profile:first_name",
+ config.openGraph.profile.firstName,
+ false
+ )
+ );
+ }
+ if (config.openGraph.profile.lastName) {
+ addTag(
+ createOpenGraphTag(
+ "profile:last_name",
+ config.openGraph.profile.lastName,
+ false
+ )
+ );
+ }
+ if (config.openGraph.profile.username) {
+ addTag(
+ createOpenGraphTag(
+ "profile:username",
+ config.openGraph.profile.username,
+ false
+ )
+ );
+ }
+ if (config.openGraph.profile.gender) {
+ addTag(
+ createOpenGraphTag(
+ "profile:gender",
+ config.openGraph.profile.gender,
+ false
+ )
+ );
+ }
+ }
+
+ // Open Graph Book
+ if (config.openGraph.book) {
+ if (
+ config.openGraph.book.authors &&
+ config.openGraph.book.authors.length
+ ) {
+ config.openGraph.book.authors.forEach((author) => {
+ addTag(createOpenGraphTag("book:author", author, false));
+ });
+ }
+ if (config.openGraph.book.isbn) {
+ addTag(
+ createOpenGraphTag(
+ "book:isbn",
+ config.openGraph.book.isbn,
+ false
+ )
+ );
+ }
+ if (config.openGraph.book.releaseDate) {
+ addTag(
+ createOpenGraphTag(
+ "book:release_date",
+ config.openGraph.book.releaseDate,
+ false
+ )
+ );
+ }
+ if (
+ config.openGraph.book.tags &&
+ config.openGraph.book.tags.length
+ ) {
+ config.openGraph.book.tags.forEach((tag) => {
+ addTag(createOpenGraphTag("book:tag", tag, false));
+ });
+ }
+ }
+
+ // Open Graph Article
+ if (config.openGraph.article) {
+ if (config.openGraph.article.publishedTime) {
+ addTag(
+ createOpenGraphTag(
+ "article:published_time",
+ config.openGraph.article.publishedTime,
+ false
+ )
+ );
+ }
+ if (config.openGraph.article.modifiedTime) {
+ addTag(
+ createOpenGraphTag(
+ "article:modified_time",
+ config.openGraph.article.modifiedTime,
+ false
+ )
+ );
+ }
+ if (config.openGraph.article.expirationTime) {
+ addTag(
+ createOpenGraphTag(
+ "article:expiration_time",
+ config.openGraph.article.expirationTime,
+ false
+ )
+ );
+ }
+ if (
+ config.openGraph.article.authors &&
+ config.openGraph.article.authors.length
+ ) {
+ config.openGraph.article.authors.forEach((author) => {
+ addTag(createOpenGraphTag("article:author", author, false));
+ });
+ }
+ if (config.openGraph.article.section) {
+ addTag(
+ createOpenGraphTag(
+ "article:section",
+ config.openGraph.article.section,
+ false
+ )
+ );
+ }
+ if (
+ config.openGraph.article.tags &&
+ config.openGraph.article.tags.length
+ ) {
+ config.openGraph.article.tags.forEach((tag) => {
+ addTag(createOpenGraphTag("article:tag", tag, false));
+ });
+ }
+ }
+
+ // Open Graph Video
+ if (config.openGraph.video) {
+ if (
+ config.openGraph.video.actors &&
+ config.openGraph.video.actors.length
+ ) {
+ config.openGraph.video.actors.forEach((actor) => {
+ addTag(createOpenGraphTag("video:actor", actor.profile));
+ if (actor.role) {
+ addTag(
+ createOpenGraphTag("video:actor:role", actor.role)
+ );
+ }
+ });
+ }
+ if (
+ config.openGraph.video.directors &&
+ config.openGraph.video.directors.length
+ ) {
+ config.openGraph.video.directors.forEach((director) => {
+ addTag(createOpenGraphTag("video:director", director));
+ });
+ }
+ if (
+ config.openGraph.video.writers &&
+ config.openGraph.video.writers.length
+ ) {
+ config.openGraph.video.writers.forEach((writer) => {
+ addTag(createOpenGraphTag("video:writer", writer));
+ });
+ }
+ if (config.openGraph.video.duration) {
+ addTag(
+ createOpenGraphTag(
+ "video:duration",
+ config.openGraph.video.duration.toString()
+ )
+ );
+ }
+ if (config.openGraph.video.releaseDate) {
+ addTag(
+ createOpenGraphTag(
+ "video:release_date",
+ config.openGraph.video.releaseDate
+ )
+ );
+ }
+ if (
+ config.openGraph.video.tags &&
+ config.openGraph.video.tags.length
+ ) {
+ config.openGraph.video.tags.forEach((tag) => {
+ addTag(createOpenGraphTag("video:tag", tag));
+ });
+ }
+ if (config.openGraph.video.series) {
+ addTag(
+ createOpenGraphTag(
+ "video:series",
+ config.openGraph.video.series
+ )
+ );
+ }
+ }
}
- // Open Graph Video
- if (config.openGraph.video) {
- if (
- config.openGraph.video.actors &&
- config.openGraph.video.actors.length
- ) {
- config.openGraph.video.actors.forEach((actor) => {
- addTag(createOpenGraphTag("video:actor", actor.profile));
- if (actor.role) {
- addTag(createOpenGraphTag("video:actor:role", actor.role));
- }
- });
- }
- if (
- config.openGraph.video.directors &&
- config.openGraph.video.directors.length
- ) {
- config.openGraph.video.directors.forEach((director) => {
- addTag(createOpenGraphTag("video:director", director));
- });
- }
- if (
- config.openGraph.video.writers &&
- config.openGraph.video.writers.length
- ) {
- config.openGraph.video.writers.forEach((writer) => {
- addTag(createOpenGraphTag("video:writer", writer));
- });
- }
- if (config.openGraph.video.duration) {
- addTag(
- createOpenGraphTag(
- "video:duration",
- config.openGraph.video.duration.toString()
- )
- );
- }
- if (config.openGraph.video.releaseDate) {
+ // Facebook
+ if (config.facebook && config.facebook.appId) {
addTag(
- createOpenGraphTag(
- "video:release_date",
- config.openGraph.video.releaseDate
- )
+ createMetaTag({
+ property: "fb:app_id",
+ content: config.facebook.appId,
+ })
);
- }
- if (config.openGraph.video.tags && config.openGraph.video.tags.length) {
- config.openGraph.video.tags.forEach((tag) => {
- addTag(createOpenGraphTag("video:tag", tag));
- });
- }
- if (config.openGraph.video.series) {
- addTag(
- createOpenGraphTag("video:series", config.openGraph.video.series)
- );
- }
}
- }
-
- // Facebook
- if (config.facebook && config.facebook.appId) {
- addTag(
- createMetaTag({ property: "fb:app_id", content: config.facebook.appId })
- );
- }
-
- // Twitter
- if (config.twitter) {
- if (config.twitter.cardType) {
- addTag(
- createMetaTag({
- name: "twitter:card",
- content: config.twitter.cardType,
- })
- );
+
+ // Twitter
+ if (config.twitter) {
+ if (config.twitter.cardType) {
+ addTag(
+ createMetaTag({
+ name: "twitter:card",
+ content: config.twitter.cardType,
+ })
+ );
+ }
+
+ if (config.twitter.site) {
+ addTag(
+ createMetaTag({
+ name: "twitter:site",
+ content: config.twitter.site,
+ })
+ );
+ }
+
+ if (config.twitter.handle) {
+ addTag(
+ createMetaTag({
+ name: "twitter:creator",
+ content: config.twitter.handle,
+ })
+ );
+ }
}
- if (config.twitter.site) {
- addTag(
- createMetaTag({ name: "twitter:site", content: config.twitter.site })
- );
+ // Additional Meta Tags
+ if (config.additionalMetaTags && config.additionalMetaTags.length > 0) {
+ config.additionalMetaTags.forEach((metaTag) => {
+ const attributes: Record = {
+ content: metaTag.content,
+ };
+
+ if ("name" in metaTag && metaTag.name) {
+ attributes.name = metaTag.name;
+ } else if ("property" in metaTag && metaTag.property) {
+ attributes.property = metaTag.property;
+ } else if ("httpEquiv" in metaTag && metaTag.httpEquiv) {
+ attributes["http-equiv"] = metaTag.httpEquiv;
+ }
+
+ addTag(createMetaTag(attributes));
+ });
}
- if (config.twitter.handle) {
- addTag(
- createMetaTag({
- name: "twitter:creator",
- content: config.twitter.handle,
- })
- );
+ // Additional Link Tags
+ if (config.additionalLinkTags && config.additionalLinkTags.length > 0) {
+ config.additionalLinkTags.forEach((linkTag) => {
+ const attributes: Record = {
+ rel: linkTag.rel,
+ href: linkTag.href,
+ };
+
+ if (linkTag.sizes) {
+ attributes.sizes = linkTag.sizes;
+ }
+ if (linkTag.media) {
+ attributes.media = linkTag.media;
+ }
+ if (linkTag.type) {
+ attributes.type = linkTag.type;
+ }
+ if (linkTag.color) {
+ attributes.color = linkTag.color;
+ }
+ if (linkTag.as) {
+ attributes.as = linkTag.as;
+ }
+ if (linkTag.crossOrigin) {
+ attributes.crossorigin = linkTag.crossOrigin;
+ }
+
+ addTag(createLinkTag(attributes));
+ });
}
- }
-
- // Additional Meta Tags
- if (config.additionalMetaTags && config.additionalMetaTags.length > 0) {
- config.additionalMetaTags.forEach((metaTag) => {
- const attributes: Record = {
- content: metaTag.content,
- };
-
- if ("name" in metaTag && metaTag.name) {
- attributes.name = metaTag.name;
- } else if ("property" in metaTag && metaTag.property) {
- attributes.property = metaTag.property;
- } else if ("httpEquiv" in metaTag && metaTag.httpEquiv) {
- attributes["http-equiv"] = metaTag.httpEquiv;
- }
-
- addTag(createMetaTag(attributes));
- });
- }
-
- // Additional Link Tags
- if (config.additionalLinkTags && config.additionalLinkTags.length > 0) {
- config.additionalLinkTags.forEach((linkTag) => {
- const attributes: Record = {
- rel: linkTag.rel,
- href: linkTag.href,
- };
-
- if (linkTag.sizes) {
- attributes.sizes = linkTag.sizes;
- }
- if (linkTag.media) {
- attributes.media = linkTag.media;
- }
- if (linkTag.type) {
- attributes.type = linkTag.type;
- }
- if (linkTag.color) {
- attributes.color = linkTag.color;
- }
- if (linkTag.as) {
- attributes.as = linkTag.as;
- }
- if (linkTag.crossOrigin) {
- attributes.crossorigin = linkTag.crossOrigin;
- }
-
- addTag(createLinkTag(attributes));
- });
- }
- return tagsToRender.trim();
+ return tagsToRender.trim();
};