mirror of
https://github.com/oekazuma/svelte-meta-tags.git
synced 2025-12-11 20:15:14 +01:00
feat: introduce define helpers (#1737)
This commit is contained in:
parent
987e1ba427
commit
3ccac3e0de
5
.changeset/hip-spoons-stare.md
Normal file
5
.changeset/hip-spoons-stare.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte-meta-tags': minor
|
||||
---
|
||||
|
||||
feat: add helper functions to define meta tags objects
|
||||
@ -29,15 +29,15 @@ Use this when you want to override the default values on child pages, as in the
|
||||
|
||||
## +layout.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { defineBaseMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = ({ url }) => {
|
||||
const baseMetaTags = Object.freeze({
|
||||
const baseTags = defineBaseMetaTags({
|
||||
title: 'Default',
|
||||
titleTemplate: '%s | Svelte Meta Tags',
|
||||
description: 'Svelte Meta Tags is a Svelte component for managing meta tags and SEO in your Svelte applications.',
|
||||
canonical: new URL(url.pathname, url.origin).href,
|
||||
canonical: new URL(url.pathname, url.origin).href, // creates a cleaned up URL (without hashes or query params) from your current URL
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
@ -56,31 +56,27 @@ export const load = ({ url }) => {
|
||||
}
|
||||
]
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
baseMetaTags
|
||||
};
|
||||
return { ...baseTags };
|
||||
};
|
||||
```
|
||||
|
||||
## +page.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { definePageMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = () => {
|
||||
const pageMetaTags = Object.freeze({
|
||||
const pageTags = definePageMetaTags({
|
||||
title: 'TOP',
|
||||
description: 'Description TOP',
|
||||
openGraph: {
|
||||
title: 'Open Graph Title TOP',
|
||||
description: 'Open Graph Description TOP'
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
pageMetaTags
|
||||
};
|
||||
return { ...pageTags };
|
||||
};
|
||||
```
|
||||
|
||||
@ -29,15 +29,15 @@ sidebar:
|
||||
|
||||
## +layout.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { defineBaseMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = ({ url }) => {
|
||||
const baseMetaTags = Object.freeze({
|
||||
const baseTags = defineBaseMetaTags({
|
||||
title: 'Default',
|
||||
titleTemplate: '%s | Svelte Meta Tags',
|
||||
description: 'Svelte Meta Tags is a Svelte component for managing meta tags and SEO in your Svelte applications.',
|
||||
canonical: new URL(url.pathname, url.origin).href,
|
||||
canonical: new URL(url.pathname, url.origin).href, // 現在のURLからクリーンなURL(ハッシュやクエリパラメータなし)を作成します
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
@ -56,31 +56,27 @@ export const load = ({ url }) => {
|
||||
}
|
||||
]
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
baseMetaTags
|
||||
};
|
||||
return { ...baseTags };
|
||||
};
|
||||
```
|
||||
|
||||
## +page.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { definePageMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = () => {
|
||||
const pageMetaTags = Object.freeze({
|
||||
const pageTags = definePageMetaTags({
|
||||
title: 'TOP',
|
||||
description: 'Description TOP',
|
||||
openGraph: {
|
||||
title: 'Open Graph Title TOP',
|
||||
description: 'Open Graph Description TOP'
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
pageMetaTags
|
||||
};
|
||||
return { ...pageTags };
|
||||
};
|
||||
```
|
||||
|
||||
@ -67,7 +67,7 @@ import { LinkButton } from '@astrojs/starlight/components';
|
||||
/>
|
||||
```
|
||||
|
||||
## 子ページでデフォルト値を上書きする
|
||||
## 子ページでデフォルト値を上書きする:
|
||||
|
||||
[Example](https://github.com/oekazuma/svelte-meta-tags/tree/main/example)
|
||||
|
||||
@ -90,15 +90,15 @@ import { LinkButton } from '@astrojs/starlight/components';
|
||||
|
||||
### +layout.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { defineBaseMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = ({ url }) => {
|
||||
const baseMetaTags = Object.freeze({
|
||||
const baseTags = defineBaseMetaTags({
|
||||
title: 'Default',
|
||||
titleTemplate: '%s | Svelte Meta Tags',
|
||||
description: 'Svelte Meta Tags is a Svelte component for managing meta tags and SEO in your Svelte applications.',
|
||||
canonical: new URL(url.pathname, url.origin).href,
|
||||
canonical: new URL(url.pathname, url.origin).href, // 現在のURLからクリーンなURL(ハッシュやクエリパラメータなし)を作成します
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
@ -117,31 +117,39 @@ export const load = ({ url }) => {
|
||||
}
|
||||
]
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
baseMetaTags
|
||||
};
|
||||
return { ...baseTags };
|
||||
};
|
||||
```
|
||||
|
||||
> 注意: `defineBaseMetaTags`は、`+layout.(j|t)s`ファイルで使用することを意図したユーティリティです。
|
||||
> このユーティリティは、フリーズされた`MetaTagsProps`オブジェクトを`baseMetaTags`プロパティにラップして返し、ロードデータで直接使用できます。
|
||||
>
|
||||
> このユーティリティを使用せずに、`return { baseMetaTags: Object.freeze<MetaTagsProps>({ ... }) }`のように生のオブジェクトを提供することもできます。
|
||||
> `MetaTagsProps`は、`import type { MetaTagsProps } from 'svelte-meta-tags'`を使用してインポートできる、このパッケージで提供される型です。
|
||||
|
||||
### +page.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { definePageMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = () => {
|
||||
const pageMetaTags = Object.freeze({
|
||||
const pageTags = definePageMetaTags({
|
||||
title: 'TOP',
|
||||
description: 'Description TOP',
|
||||
openGraph: {
|
||||
title: 'Open Graph Title TOP',
|
||||
description: 'Open Graph Description TOP'
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
pageMetaTags
|
||||
};
|
||||
return { ...pageTags };
|
||||
};
|
||||
```
|
||||
|
||||
> 注意: `defineBaseMetaTags`と同様に、`definePageMetaTags`は`+page.(j|t)s`ファイルで使用することを意図したユーティリティです。
|
||||
> このユーティリティは、フリーズされた`MetaTagsProps`オブジェクトを`pageMetaTags`プロパティにラップして返し、ロードデータで直接使用できます。
|
||||
>
|
||||
> このユーティリティを使用せずに、`return { pageMetaTags: Object.freeze<MetaTagsProps>({ ... }) }`のように生のオブジェクトを提供することもできます。
|
||||
> `MetaTagsProps`は、`import type { MetaTagsProps } from 'svelte-meta-tags'`を使用してインポートできる、このパッケージで提供される型です。
|
||||
|
||||
@ -90,15 +90,15 @@ import { LinkButton } from '@astrojs/starlight/components';
|
||||
|
||||
### +layout.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { defineBaseMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = ({ url }) => {
|
||||
const baseMetaTags = Object.freeze({
|
||||
const baseTags = defineBaseMetaTags({
|
||||
title: 'Default',
|
||||
titleTemplate: '%s | Svelte Meta Tags',
|
||||
description: 'Svelte Meta Tags is a Svelte component for managing meta tags and SEO in your Svelte applications.',
|
||||
canonical: new URL(url.pathname, url.origin).href,
|
||||
canonical: new URL(url.pathname, url.origin).href, // creates a cleaned up URL (without hashes or query params) from your current URL
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: new URL(url.pathname, url.origin).href,
|
||||
@ -117,31 +117,39 @@ export const load = ({ url }) => {
|
||||
}
|
||||
]
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
baseMetaTags
|
||||
};
|
||||
return { ...baseTags };
|
||||
};
|
||||
```
|
||||
|
||||
> Note: `defineBaseMetaTags` is a utility meant to be used in a `+layout.(j|t)s` file.
|
||||
> It returns a frozen `MetaTagsProps` object wrapped in a `baseMetaTags` property for direct use in your load data.
|
||||
>
|
||||
> You can also provide a raw object without this utility with `return { baseMetaTags: Object.freeze<MetaTagsProps>({ ... }) }`.
|
||||
> `MetaTagsProps` is a type provided by this package, imported using `import type { MetaTagsProps } from 'svelte-meta-tags'`.
|
||||
|
||||
### +page.ts
|
||||
|
||||
```svelte
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
```ts
|
||||
import { definePageMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = () => {
|
||||
const pageMetaTags = Object.freeze({
|
||||
const pageTags = definePageMetaTags({
|
||||
title: 'TOP',
|
||||
description: 'Description TOP',
|
||||
openGraph: {
|
||||
title: 'Open Graph Title TOP',
|
||||
description: 'Open Graph Description TOP'
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
pageMetaTags
|
||||
};
|
||||
return { ...pageTags };
|
||||
};
|
||||
```
|
||||
|
||||
> Note: like `defineBaseMetaTags`, `definePageMetaTags` is a utility meant to be used in a `+page.(j|t)s` file.
|
||||
> It returns a frozen `MetaTagsProps` object wrapped in a `pageMetaTags` property for direct use in your load data.
|
||||
>
|
||||
> You can also provide a raw object without this utility with `return { pageMetaTags: Object.freeze<MetaTagsProps>({ ... }) }`.
|
||||
> `MetaTagsProps` is a type provided by this package, imported using `import type { MetaTagsProps } from 'svelte-meta-tags'`.
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { LayoutData } from './$types';
|
||||
import { page } from '$app/state';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { MetaTags, deepMerge } from 'svelte-meta-tags';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
interface Props {
|
||||
data: LayoutData;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let { data, children }: Props = $props();
|
||||
let { data, children } = $props();
|
||||
|
||||
let metaTags = $derived(deepMerge(data.baseMetaTags, page.data.pageMetaTags));
|
||||
</script>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import { defineBaseMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = ({ url }) => {
|
||||
const baseMetaTags = Object.freeze({
|
||||
const baseTags = defineBaseMetaTags({
|
||||
title: 'Normal',
|
||||
titleTemplate: '%s | Svelte Meta Tags',
|
||||
description: 'Svelte Meta Tags is a Svelte component for managing meta tags and SEO in your Svelte applications.',
|
||||
@ -40,9 +40,7 @@ export const load = ({ url }) => {
|
||||
}
|
||||
]
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
baseMetaTags
|
||||
};
|
||||
return { ...baseTags };
|
||||
};
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import { definePageMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = () => {
|
||||
const pageMetaTags = Object.freeze({
|
||||
const pageTags = definePageMetaTags({
|
||||
title: 'TOP',
|
||||
description: 'Description TOP',
|
||||
openGraph: {
|
||||
title: 'Open Graph Title TOP',
|
||||
description: 'Open Graph Description TOP'
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
pageMetaTags
|
||||
};
|
||||
return { ...pageTags };
|
||||
};
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import { definePageMetaTags } from 'svelte-meta-tags';
|
||||
|
||||
export const load = () => {
|
||||
const pageMetaTags = Object.freeze({
|
||||
const pageTags = definePageMetaTags({
|
||||
title: 'About',
|
||||
description: 'Description About',
|
||||
openGraph: {
|
||||
title: 'Open Graph Title About',
|
||||
description: 'Open Graph Description About'
|
||||
}
|
||||
}) satisfies MetaTagsProps;
|
||||
});
|
||||
|
||||
return {
|
||||
pageMetaTags
|
||||
};
|
||||
return { ...pageTags };
|
||||
};
|
||||
|
||||
46
packages/svelte-meta-tags/src/lib/define.ts
Normal file
46
packages/svelte-meta-tags/src/lib/define.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { MetaTagsProps } from './types';
|
||||
|
||||
/**
|
||||
* A convenience wrapper for creating a readonly, type-safe
|
||||
* {@link MetaTagsProps} object for `+layout`.
|
||||
*
|
||||
* @param obj the input props
|
||||
* @returns a frozen copy of the input props inside a ready-to-use object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In +layout.ts
|
||||
* export const load = () => {
|
||||
* const baseTags = defineBaseMetaTags({
|
||||
* title: 'My App',
|
||||
* description: 'Welcome to my application'
|
||||
* });
|
||||
* return { ...baseTags };
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export const defineBaseMetaTags = (obj: MetaTagsProps) => ({ baseMetaTags: Object.freeze(obj) });
|
||||
|
||||
/**
|
||||
* A convenience wrapper for creating a readonly, type-safe
|
||||
* {@link MetaTagsProps} object for `+page`.
|
||||
*
|
||||
* @param obj the input props
|
||||
* @returns a frozen copy of the input props inside a ready-to-use object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In +page.ts
|
||||
* export const load = () => {
|
||||
* const pageTags = definePageMetaTags({
|
||||
* title: 'About Us',
|
||||
* description: 'Learn more about our company'
|
||||
* });
|
||||
* return { ...pageTags };
|
||||
* };
|
||||
*
|
||||
* // In +layout.svelte
|
||||
* const metaTags = deepMerge(data.baseMetaTags, page.data.pageMetaTags);
|
||||
* ```
|
||||
*/
|
||||
export const definePageMetaTags = (obj: MetaTagsProps) => ({ pageMetaTags: Object.freeze(obj) });
|
||||
@ -1,6 +1,7 @@
|
||||
export { default as MetaTags } from './MetaTags.svelte';
|
||||
export { default as JsonLd } from './JsonLd.svelte';
|
||||
export { deepMerge } from './deepMerge';
|
||||
export { defineBaseMetaTags, definePageMetaTags } from './define';
|
||||
export type {
|
||||
MetaTagsProps,
|
||||
JsonLdProps,
|
||||
|
||||
129
packages/svelte-meta-tags/tests/define/define.test.ts
Normal file
129
packages/svelte-meta-tags/tests/define/define.test.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { defineBaseMetaTags, definePageMetaTags } from '$lib/define';
|
||||
import type { MetaTagsProps } from '$lib/types';
|
||||
|
||||
describe('defineBaseMetaTags and definePageMetaTags', () => {
|
||||
const sampleMetaTags: MetaTagsProps = {
|
||||
title: 'Test Title',
|
||||
description: 'Test description',
|
||||
canonical: 'https://example.com'
|
||||
};
|
||||
|
||||
test('should create frozen readonly object', () => {
|
||||
const { baseMetaTags } = defineBaseMetaTags(sampleMetaTags);
|
||||
const { pageMetaTags } = definePageMetaTags(sampleMetaTags);
|
||||
|
||||
expect(baseMetaTags).toEqual(sampleMetaTags);
|
||||
expect(pageMetaTags).toEqual(sampleMetaTags);
|
||||
expect(Object.isFrozen(baseMetaTags)).toBe(true);
|
||||
expect(Object.isFrozen(pageMetaTags)).toBe(true);
|
||||
});
|
||||
|
||||
test('should freeze the props to make them readonly', () => {
|
||||
const { baseMetaTags } = defineBaseMetaTags(sampleMetaTags);
|
||||
|
||||
expect(Object.isFrozen(baseMetaTags)).toBe(true);
|
||||
|
||||
// Should throw when trying to modify frozen object
|
||||
expect(() => {
|
||||
// @ts-expect-error - intentionally testing runtime mutation
|
||||
baseMetaTags.title = 'Modified';
|
||||
}).toThrow(TypeError);
|
||||
});
|
||||
|
||||
test('should not modify the original input object', () => {
|
||||
const original = { ...sampleMetaTags };
|
||||
const { baseMetaTags } = defineBaseMetaTags(sampleMetaTags);
|
||||
|
||||
expect(sampleMetaTags).toEqual(original);
|
||||
expect(baseMetaTags).toEqual(original);
|
||||
});
|
||||
|
||||
test('should handle empty object', () => {
|
||||
const { baseMetaTags } = defineBaseMetaTags({});
|
||||
|
||||
expect(baseMetaTags).toEqual({});
|
||||
expect(Object.isFrozen(baseMetaTags)).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle falsy values correctly', () => {
|
||||
const metaTagsWithFalsy: MetaTagsProps = {
|
||||
title: '',
|
||||
robots: false,
|
||||
keywords: []
|
||||
};
|
||||
|
||||
const { pageMetaTags } = definePageMetaTags(metaTagsWithFalsy);
|
||||
|
||||
expect(pageMetaTags.title).toBe('');
|
||||
expect(pageMetaTags.robots).toBe(false);
|
||||
expect(pageMetaTags.keywords).toEqual([]);
|
||||
});
|
||||
|
||||
test('should support spreading for object merging', () => {
|
||||
const { baseMetaTags } = defineBaseMetaTags({
|
||||
title: 'Base Title',
|
||||
description: 'Base Description'
|
||||
});
|
||||
|
||||
const { pageMetaTags } = definePageMetaTags({
|
||||
title: 'Page Title',
|
||||
canonical: 'https://example.com/page'
|
||||
});
|
||||
|
||||
const combined = { ...baseMetaTags, ...pageMetaTags };
|
||||
|
||||
expect(combined.title).toBe('Page Title'); // Page overrides base
|
||||
expect(combined.description).toBe('Base Description'); // From base
|
||||
expect(combined.canonical).toBe('https://example.com/page'); // From page
|
||||
});
|
||||
|
||||
test('should handle complex nested objects', () => {
|
||||
const complexMetaTags: MetaTagsProps = {
|
||||
title: 'Complex Title',
|
||||
openGraph: {
|
||||
title: 'OG Title',
|
||||
images: [{ url: 'https://example.com/image.jpg' }]
|
||||
},
|
||||
additionalMetaTags: [{ name: 'viewport', content: 'width=device-width' }]
|
||||
};
|
||||
|
||||
const { baseMetaTags } = defineBaseMetaTags(complexMetaTags);
|
||||
|
||||
expect(baseMetaTags.openGraph?.images?.[0]?.url).toBe('https://example.com/image.jpg');
|
||||
expect(Object.isFrozen(baseMetaTags)).toBe(true);
|
||||
});
|
||||
|
||||
test('both functions should behave identically', () => {
|
||||
const { baseMetaTags } = defineBaseMetaTags(sampleMetaTags);
|
||||
const { pageMetaTags } = definePageMetaTags(sampleMetaTags);
|
||||
|
||||
expect(baseMetaTags).toEqual(pageMetaTags);
|
||||
expect(Object.isFrozen(baseMetaTags)).toBe(Object.isFrozen(pageMetaTags));
|
||||
});
|
||||
|
||||
test('should clarify that freeze is shallow (nested objects remain mutable)', () => {
|
||||
const complexMetaTags: MetaTagsProps = {
|
||||
title: 'Complex Title',
|
||||
openGraph: {
|
||||
title: 'OG Title',
|
||||
images: [{ url: 'https://example.com/image.jpg' }]
|
||||
}
|
||||
};
|
||||
|
||||
const { baseMetaTags } = defineBaseMetaTags(complexMetaTags);
|
||||
|
||||
// Top level is frozen
|
||||
expect(Object.isFrozen(baseMetaTags)).toBe(true);
|
||||
|
||||
// But nested objects are NOT frozen (limitation of Object.freeze)
|
||||
expect(Object.isFrozen(baseMetaTags.openGraph)).toBe(false);
|
||||
expect(Object.isFrozen(baseMetaTags.openGraph?.images)).toBe(false);
|
||||
|
||||
// Nested objects can still be mutated
|
||||
if (baseMetaTags.openGraph) {
|
||||
baseMetaTags.openGraph.title = 'Modified OG Title'; // This works!
|
||||
expect(baseMetaTags.openGraph.title).toBe('Modified OG Title');
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user