├── .all-contributorsrc
├── .changeset
└── config.json
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── svead.svg
└── workflows
│ ├── e2e-ci.yml
│ └── unit-test.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
├── extensions.json
└── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── apps
└── web
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── README.md
│ ├── mdsvex.config.js
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.cjs
│ ├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── app.postcss
│ ├── index.test.ts
│ ├── lib
│ │ ├── components
│ │ │ ├── details.svelte
│ │ │ └── index.ts
│ │ ├── copy
│ │ │ ├── blog-posting-copy.md
│ │ │ ├── breadcrumbs-copy.md
│ │ │ ├── index-copy.md
│ │ │ ├── multiple-ld-json-sections-copy.md
│ │ │ ├── news-article-copy.md
│ │ │ └── web-page-copy.md
│ │ ├── icons
│ │ │ ├── github.svelte
│ │ │ ├── index.ts
│ │ │ ├── twitter.svelte
│ │ │ └── youtube.svelte
│ │ └── index.ts
│ ├── prism.css
│ └── routes
│ │ ├── +layout.svelte
│ │ ├── +page.svelte
│ │ ├── +page.ts
│ │ ├── article
│ │ └── +page.svelte
│ │ ├── blog-posting
│ │ ├── +page.svelte
│ │ └── +page.ts
│ │ ├── breadcrumbs
│ │ ├── +page.svelte
│ │ └── +page.ts
│ │ ├── multiple-ld-json-sections
│ │ ├── +page.svelte
│ │ └── +page.ts
│ │ ├── news-article
│ │ ├── +page.svelte
│ │ └── +page.ts
│ │ └── web-page
│ │ ├── +page.svelte
│ │ └── +page.ts
│ ├── static
│ ├── favicon.png
│ └── spencee.png
│ ├── svelte.config.js
│ ├── tailwind.config.cjs
│ ├── tests
│ └── index.test.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── vitest.config.ts
├── package.json
├── packages
└── svead
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── playwright.config.ts
│ ├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── index.test.ts
│ ├── lib
│ │ ├── components
│ │ │ ├── head.svelte
│ │ │ ├── head.test.ts
│ │ │ ├── schema-org.svelte
│ │ │ └── schema-org.test.ts
│ │ ├── index.ts
│ │ └── types
│ │ │ ├── index.ts
│ │ │ ├── schema-org.ts
│ │ │ └── seo-config.ts
│ └── routes
│ │ └── +page.svelte
│ ├── static
│ └── favicon.png
│ ├── svelte.config.js
│ ├── tests
│ └── test.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── vitest.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── renovate.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "svead",
3 | "projectOwner": "spences10",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "none",
12 | "contributors": [
13 | {
14 | "login": "spences10",
15 | "name": "Scott Spence",
16 | "avatar_url": "https://avatars.githubusercontent.com/u/234708?v=4",
17 | "profile": "https://scottspence.com/",
18 | "contributions": [
19 | "code",
20 | "doc",
21 | "example",
22 | "maintenance",
23 | "test"
24 | ]
25 | }
26 | ],
27 | "contributorsPerLine": 7,
28 | "linkToUsage": true
29 | }
30 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 |
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 |
15 | Steps to reproduce the behavior:
16 |
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | **Expected behavior**
23 |
24 | A clear and concise description of what you expected to happen.
25 |
26 | **Screenshots**
27 |
28 | If applicable, add screenshots to help explain your problem.
29 |
30 | **Desktop (please complete the following information):**
31 |
32 | - OS: [e.g. iOS]
33 | - Browser [e.g. chrome, safari]
34 | - Version [e.g. 22]
35 |
36 | **Smartphone (please complete the following information):**
37 |
38 | - Device: [e.g. iPhone6]
39 | - OS: [e.g. iOS8.1]
40 | - Browser [e.g. stock browser, safari]
41 | - Version [e.g. 22]
42 |
43 | **Additional context**
44 |
45 | Add any other context about the problem here.
46 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 |
11 | A clear and concise description of what the problem is. Ex. I'm always
12 | frustrated when [...]
13 |
14 | **Describe the solution you'd like**
15 |
16 | A clear and concise description of what you want to happen.
17 |
18 | **Describe alternatives you've considered**
19 |
20 | A clear and concise description of any alternative solutions or
21 | features you've considered.
22 |
23 | **Additional context**
24 |
25 | Add any other context or screenshots about the feature request here.
26 |
--------------------------------------------------------------------------------
/.github/workflows/e2e-ci.yml:
--------------------------------------------------------------------------------
1 | name: 'Tests: E2E'
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | branches: [main]
7 | types: [opened, synchronize]
8 |
9 | jobs:
10 | tests_e2e:
11 | name: Run end-to-end tests
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: 18.x
18 | - uses: actions/cache@v4
19 | with:
20 | path: ~/.pnpm-store
21 | # prettier-ignore
22 | key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
23 | restore-keys: ${{ runner.os }}-pnpm-
24 | - uses: pnpm/action-setup@v4.1.0
25 | with:
26 | version: '^8.0.0'
27 | - name: Install dependencies
28 | run: pnpm i
29 | - name: Build svead package
30 | run: pnpm run build
31 | working-directory: packages/svead
32 | - name: Install playwright browsers
33 | run: npx playwright install --with-deps
34 | working-directory: apps/web
35 | - name: Test
36 | run: pnpm run test
37 | working-directory: apps/web
38 | env:
39 | PUBLIC_FATHOM_ID: ${{ secrets.PUBLIC_FATHOM_ID }}
40 | PUBLIC_FATHOM_URL: ${{ secrets.PUBLIC_FATHOM_URL }}
41 |
--------------------------------------------------------------------------------
/.github/workflows/unit-test.yml:
--------------------------------------------------------------------------------
1 | name: 'Tests: Unit'
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 | types: [opened, synchronize]
9 |
10 | jobs:
11 | unit_tests:
12 | name: Run unit tests for Package
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | - uses: actions/setup-node@v4
18 | with:
19 | node-version: 18.x
20 | - uses: actions/cache@v4
21 | with:
22 | path: ~/.pnpm-store
23 | # prettier-ignore
24 | key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
25 | restore-keys: ${{ runner.os }}-pnpm-
26 | - uses: pnpm/action-setup@v4.1.0
27 | with:
28 | version: '^8.0.0'
29 | - name: Install dependencies
30 | run: pnpm recursive install
31 | - name: Run unit tests
32 | run: pnpm run test:ci
33 | working-directory: packages/svead
34 | env:
35 | PUBLIC_FATHOM_ID: ${{ secrets.PUBLIC_FATHOM_ID }}
36 | PUBLIC_FATHOM_URL: ${{ secrets.PUBLIC_FATHOM_URL }}
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /dist
5 | /.svelte-kit
6 | /package
7 | .env
8 | .env.*
9 | !.env.example
10 | .vercel
11 | vite.config.js.timestamp-*
12 | vite.config.ts.timestamp-*
13 | coverage/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore files for PNPM, NPM and YARN
2 | pnpm-lock.yaml
3 | package-lock.json
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "printWidth": 70,
6 | "arrowParens": "avoid",
7 | "proseWrap": "always",
8 | "plugins": ["prettier-plugin-svelte"],
9 | "overrides": [
10 | { "files": "*.svelte", "options": { "parser": "svelte" } }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "streetsidesoftware.code-spell-checker",
4 | "svelte.svelte-vscode"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.enableSmartCommit": true,
3 | "git.postCommitCommand": "sync",
4 | "cSpell.words": [
5 | "Ahrefs",
6 | "daisyui",
7 | "Kazuma",
8 | "mdsvex",
9 | "noopener",
10 | "noreferrer",
11 | "oekazuma",
12 | "pnpm",
13 | "spencee",
14 | "svead",
15 | "sveltejs",
16 | "vite"
17 | ],
18 | "css.validate": false
19 | }
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Svelte Code of Conduct
2 |
3 | This project is a Svelte Community project and therefore uses the same
4 | [Code of Conduct](https://github.com/sveltejs/community/blob/main/CODE_OF_CONDUCT.md)
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Scott Spence
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Svead 🍺 - Svelte Head Component
2 |
3 |
4 |
5 | [](#contributors-)
6 |
7 |
8 |
9 | [](https://madewithsvelte.com/p/svead/shield-link)
10 |
11 | [](https://github.com/spences10/svead/actions/workflows/e2e-ci.yml)
12 |
13 | [](https://github.com/spences10/svead/actions/workflows/unit-test.yml)
14 |
15 | Svead, a component that allows you to set head meta information,
16 | canonical, title, Twitter and Facebook Open Graph tags.
17 |
18 | Also supports JSON-LD for SEO with the `SchemaOrg` component.
19 |
20 | 
21 |
22 | ## Name
23 |
24 | The name was meant to be Svelte + Head, but I like Puru's suggestion
25 | of Svelte + Mead
26 |
27 | ## v0.0.4 vs v1
28 |
29 | v1 is currently available via `pnpm i -D svead@next` and will be that
30 | way until Svelte 5 comes out of RC phase.
31 |
32 | v1 has changed compared to v0.0.4. The main change is that the there's
33 | one config object with `SeoConfig`.
34 |
35 | Separated out the `SchemaOrg` component from the `Head` component
36 | which can be optionally used to add structured data to your web pages.
37 |
38 | ```svelte
39 |
56 |
57 |
13 | %sveltekit.body%
14 |
15 |
16 |
--------------------------------------------------------------------------------
/packages/svead/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 |
3 | describe('sum test', () => {
4 | it('adds 1 + 2 to equal 3', () => {
5 | expect(1 + 2).toBe(3);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/components/head.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 | {seo_config.title}
12 |
13 |
14 |
15 | {#if seo_config.author_name}
16 |
17 | {/if}
18 |
19 |
20 |
21 |
22 |
23 |
24 | {#if seo_config.open_graph_image}
25 |
26 |
27 | {/if}
28 | {#if seo_config.site_name}
29 |
30 | {/if}
31 |
32 |
33 |
34 |
35 |
36 | {#if seo_config.open_graph_image}
37 |
38 | {/if}
39 | {#if seo_config.twitter_handle}
40 |
41 | {/if}
42 | {#if seo_config.website}
43 |
44 |
45 | {/if}
46 |
47 |
48 |
49 |
50 | {#if seo_config.open_graph_image}
51 |
52 | {/if}
53 |
54 |
55 | {#if seo_config.payment_pointer}
56 |
57 | {/if}
58 |
59 |
60 | {#if seo_config.language}
61 |
62 | {/if}
63 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/components/head.test.ts:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/svelte/svelte5';
2 | import { afterEach, describe, expect, it } from 'vitest';
3 | import Head from './head.svelte';
4 |
5 | const clean_html_content = (content: string): string => {
6 | return content.replace(//g, '');
7 | };
8 |
9 | describe('Head component tests', () => {
10 | afterEach(() => {
11 | document.head.innerHTML = '';
12 | });
13 |
14 | describe('Basic Meta Tags', () => {
15 | it('renders canonical link correctly', async () => {
16 | const url = 'https://example.com';
17 | render(Head, {
18 | seo_config: {
19 | url,
20 | title: 'Test',
21 | description: 'Test description',
22 | },
23 | });
24 | const canonical = document.querySelector(
25 | 'link[rel="canonical"]',
26 | );
27 | expect(canonical?.getAttribute('href')).toBe(url);
28 | });
29 |
30 | it('renders the correct title, description, and author', async () => {
31 | const config = {
32 | title: 'Test Title',
33 | description: 'Test Description',
34 | author_name: 'Test Author',
35 | url: 'https://example.com',
36 | };
37 | render(Head, { seo_config: config });
38 |
39 | expect(document.title).toBe(config.title);
40 | expect(
41 | document
42 | .querySelector('meta[name="description"]')
43 | ?.getAttribute('content'),
44 | ).toBe(config.description);
45 | expect(
46 | document
47 | .querySelector('meta[name="author"]')
48 | ?.getAttribute('content'),
49 | ).toBe(config.author_name);
50 | });
51 |
52 | it('renders the monetization tag when a payment pointer is provided', async () => {
53 | const payment_pointer = '$wallet.example.com/alice';
54 | render(Head, {
55 | seo_config: {
56 | title: 'Test',
57 | description: 'Test',
58 | url: 'https://example.com',
59 | payment_pointer,
60 | },
61 | });
62 | const monetization = document.querySelector(
63 | 'meta[name="monetization"]',
64 | );
65 | expect(monetization?.getAttribute('content')).toBe(
66 | payment_pointer,
67 | );
68 | });
69 | });
70 |
71 | describe('Open Graph Protocol', () => {
72 | it('renders Open Graph tags correctly', async () => {
73 | const config = {
74 | title: 'OG Title',
75 | description: 'OG Description',
76 | url: 'https://example.com',
77 | open_graph_image: 'https://example.com/image.jpg',
78 | site_name: 'Example Site',
79 | language: 'en-US',
80 | };
81 | render(Head, { seo_config: config });
82 |
83 | expect(
84 | document
85 | .querySelector('meta[property="og:title"]')
86 | ?.getAttribute('content'),
87 | ).toBe(config.title);
88 | expect(
89 | document
90 | .querySelector('meta[property="og:description"]')
91 | ?.getAttribute('content'),
92 | ).toBe(config.description);
93 | expect(
94 | document
95 | .querySelector('meta[property="og:url"]')
96 | ?.getAttribute('content'),
97 | ).toBe(config.url);
98 | expect(
99 | document
100 | .querySelector('meta[property="og:image"]')
101 | ?.getAttribute('content'),
102 | ).toBe(config.open_graph_image);
103 | expect(
104 | document
105 | .querySelector('meta[property="og:site_name"]')
106 | ?.getAttribute('content'),
107 | ).toBe(config.site_name);
108 | expect(
109 | document
110 | .querySelector('meta[property="og:locale"]')
111 | ?.getAttribute('content'),
112 | ).toBe(config.language);
113 | });
114 | });
115 |
116 | describe('Twitter Card', () => {
117 | it('renders Twitter Card tags correctly', async () => {
118 | const config = {
119 | title: 'Twitter Title',
120 | description: 'Twitter Description',
121 | url: 'https://example.com',
122 | open_graph_image: 'https://example.com/image.jpg',
123 | website: 'example.com',
124 | twitter_handle: '@example',
125 | twitter_card_type: 'summary_large_image' as const,
126 | };
127 | render(Head, { seo_config: config });
128 |
129 | expect(
130 | document
131 | .querySelector('meta[name="twitter:title"]')
132 | ?.getAttribute('content'),
133 | ).toBe(config.title);
134 | expect(
135 | document
136 | .querySelector('meta[name="twitter:description"]')
137 | ?.getAttribute('content'),
138 | ).toBe(config.description);
139 | expect(
140 | document
141 | .querySelector('meta[name="twitter:image"]')
142 | ?.getAttribute('content'),
143 | ).toBe(config.open_graph_image);
144 | expect(
145 | document
146 | .querySelector('meta[property="twitter:domain"]')
147 | ?.getAttribute('content'),
148 | ).toBe(config.website);
149 | expect(
150 | document
151 | .querySelector('meta[name="twitter:creator"]')
152 | ?.getAttribute('content'),
153 | ).toBe(config.twitter_handle);
154 | expect(
155 | document
156 | .querySelector('meta[name="twitter:card"]')
157 | ?.getAttribute('content'),
158 | ).toBe(config.twitter_card_type);
159 | });
160 | });
161 | });
162 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/components/schema-org.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
29 |
30 |
31 | {@html json_ld_data}
32 |
33 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/components/schema-org.test.ts:
--------------------------------------------------------------------------------
1 | import { afterEach, describe, it } from 'vitest';
2 |
3 | describe.skip('SchemaOrg', () => {
4 | afterEach(() => {
5 | document.head.innerHTML = '';
6 | });
7 |
8 | it.skip('head loads correctly', () => {});
9 | });
10 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Head } from './components/head.svelte';
2 | export { default as SchemaOrg } from './components/schema-org.svelte';
3 | export type { SchemaOrgProps, SeoConfig } from './types/index.js';
4 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './schema-org.js';
2 | export * from './seo-config.js';
3 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/types/schema-org.ts:
--------------------------------------------------------------------------------
1 | import type { Thing, WithContext } from 'schema-dts';
2 |
3 | export type SchemaOrgType = Thing | WithContext;
4 |
5 | export interface SchemaOrgProps {
6 | schema: SchemaOrgType | SchemaOrgType[];
7 | }
8 |
--------------------------------------------------------------------------------
/packages/svead/src/lib/types/seo-config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SEO configuration options for a web page.
3 | * Covers basic SEO, Open Graph, and Twitter Cards requirements.
4 | * Use this interface to define the SEO configuration for a webpage,
5 | * including meta tags and social media properties.
6 | * The properties in this interface can be used to generate the
7 | * necessary HTML meta tags.
8 | */
9 | export interface SeoConfig {
10 | /**
11 | * The title of the web page.
12 | * Used in the tag, og:title, and twitter:title properties.
13 | *
14 | * @type {string}
15 | */
16 | title: string;
17 |
18 | /**
19 | * The description of the web page.
20 | * Used in the description meta tag, og:description, and
21 | * twitter:description properties.
22 | *
23 | * Best practices suggest keeping the description between 50-160 characters.
24 | * Search engines may truncate descriptions longer than 155-160 characters.
25 | *
26 | * Note: The Head component does not enforce these limits,
27 | * it's up to the developer to ensure appropriate length.
28 | *
29 | * @type {string}
30 | */
31 | description: string;
32 |
33 | /**
34 | * The URL of the web page.
35 | * Used as the og:url property and twitter:url.
36 | *
37 | * @type {string}
38 | */
39 | url: string;
40 |
41 | /**
42 | * The website to which the web page belongs.
43 | * Used as twitter:domain.
44 | *
45 | * @type {string}
46 | */
47 | website?: string;
48 |
49 | /**
50 | * The language of the web page.
51 | * Used as the og:locale property.
52 | * Defaults to 'en'.
53 | *
54 | * @type {string}
55 | * @default 'en'
56 | */
57 | language?: string;
58 |
59 | /**
60 | * The URL of the Open Graph image for the web page.
61 | * Used as the og:image and twitter:image properties.
62 | *
63 | * @type {string}
64 | */
65 | open_graph_image?: string;
66 |
67 | /**
68 | * The payment pointer for Web Monetization.
69 | * Used in the monetization meta tag.
70 | *
71 | * @type {string}
72 | */
73 | payment_pointer?: string;
74 |
75 | /**
76 | * The name of the author of the web page.
77 | * Used in the author meta tag.
78 | *
79 | * @type {string}
80 | */
81 | author_name?: string;
82 |
83 | /**
84 | * The name of the site.
85 | * Used as the og:site_name property.
86 | *
87 | * @type {string}
88 | */
89 | site_name?: string;
90 |
91 | /**
92 | * The Twitter handle of the content creator or site.
93 | * Used as the twitter:creator property.
94 | * Should include the @ symbol.
95 | *
96 | * @type {string}
97 | */
98 | twitter_handle?: string;
99 |
100 | /**
101 | * The type of Twitter card to use.
102 | * Used as the twitter:card property.
103 | * Defaults to 'summary_large_image'.
104 | *
105 | * @type {'summary' | 'summary_large_image' | 'app' | 'player'}
106 | * @default 'summary_large_image'
107 | */
108 | twitter_card_type?:
109 | | 'summary'
110 | | 'summary_large_image'
111 | | 'app'
112 | | 'player';
113 |
114 | /**
115 | * Alternative text for the Open Graph image.
116 | * Used as the og:image:alt property.
117 | *
118 | * @type {string}
119 | */
120 | open_graph_image_alt?: string;
121 | }
122 |
--------------------------------------------------------------------------------
/packages/svead/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 | Welcome to your library project
2 |
3 | Create your package using @sveltejs/package and preview/showcase
4 | your work with SvelteKit
5 |
6 |
7 | Visit kit.svelte.dev to read the
8 | documentation
9 |
10 |
--------------------------------------------------------------------------------
/packages/svead/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spences10/svead/90e440d196068f7731af6107eebffb8825f06e10/packages/svead/static/favicon.png
--------------------------------------------------------------------------------
/packages/svead/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter(),
15 | },
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/packages/svead/tests/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | test('index page has expected h1', async ({ page }) => {
4 | await page.goto('/');
5 | await expect(
6 | page.getByRole('heading', {
7 | name: 'Welcome to your library project',
8 | }),
9 | ).toBeVisible();
10 | });
--------------------------------------------------------------------------------
/packages/svead/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "module": "NodeNext",
13 | "moduleResolution": "NodeNext"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/svead/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { svelteTesting } from '@testing-library/svelte/vite';
3 | import { defineConfig } from 'vitest/config';
4 |
5 | export default defineConfig({
6 | plugins: [sveltekit(), svelteTesting()],
7 | test: {
8 | environment: 'jsdom',
9 | include: ['src/**/*.{test,spec}.{js,ts}'],
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/packages/svead/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, mergeConfig } from 'vitest/config';
2 | import viteConfig from './vite.config';
3 |
4 | export default mergeConfig(
5 | viteConfig,
6 | defineConfig({
7 | test: {
8 | include: ['src/**/*.test.{js,ts,svelte}'],
9 | globals: true,
10 | environment: 'jsdom',
11 | coverage: {
12 | all: false,
13 | thresholds: {
14 | statements: 75,
15 | branches: 84,
16 | functions: 68,
17 | lines: 75,
18 | },
19 | },
20 | },
21 | }),
22 | );
23 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'apps/*'
3 | - 'packages/*'
4 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"]
3 | }
4 |
--------------------------------------------------------------------------------
58 |
59 |
60 |
Welcome to My Site
61 |
This is a simple web page example.
62 | ```
63 |
64 | ## Props
65 |
66 | It takes the following props:
67 |
68 | ### `SeoConfig` Props
69 |
70 | | Property | Type | Description | Required |
71 | | :------------------ | :----------------- | :----------------------------------------------------------- | :------- |
72 | | `title` | `string` | The title of the web page. | Yes |
73 | | `description` | `string` | A description of the web page. | Yes |
74 | | `url` | `string` | The URL of the web page. | Yes |
75 | | `website` | `string` | The website the web page belongs to. | No |
76 | | `language` | `string` \| `'en'` | The language of the web page. Defaults to 'en'. | No |
77 | | `open_graph_image` | `string` | The URL of an image to use for Open Graph meta tags. | No |
78 | | `payment_pointer` | `string` | A payment pointer for Web Monetization. | No |
79 | | `author_name` | `string` | The name of the author. | No |
80 | | `site_name` | `string` | The name of the site for og:site_name. | No |
81 | | `twitter_handle` | `string` | The Twitter handle of the content creator or site. | No |
82 | | `twitter_card_type` | `string` | The type of Twitter card. Defaults to 'summary_large_image'. | No |
83 |
84 | ## SchemaOrg Component
85 |
86 | The SchemaOrg component allows you to add structured data to your web
87 | pages using JSON-LD format. This helps search engines better
88 | understand your content and can potentially improve your site's
89 | appearance in search results.
90 |
91 | ### Usage
92 |
93 | ```svelte
94 |
108 |
109 |
110 | ```
111 |
112 | ### `SchemaOrgProps` Props
113 |
114 | | Property | Type | Description | Required |
115 | | :------- | :-------------- | :---------------------------------------------------------- | :------- |
116 | | `schema` | `SchemaOrgType` | The structured data object following schema.org vocabulary. | Yes |
117 |
118 | ### `SchemaOrgType`
119 |
120 | `SchemaOrgType` is a union type that includes:
121 |
122 | - `Thing`: Represents the most generic type of item in schema.org.
123 | - `WithContext`: A Thing with an added `@context` property.
124 |
125 | You can use any valid schema.org type as defined in the
126 | [schema.org documentation](https://schema.org).
127 |
128 | ### Additional Notes:
129 |
130 | - The `@context` property is automatically added by the component if
131 | not provided.
132 | - You can include multiple schema types by nesting them within the
133 | main schema object.
134 | - Always validate your structured data using tools like
135 | [Google's Rich Results Test](https://search.google.com/test/rich-results)
136 | to ensure it's correctly formatted.
137 |
138 | ### Example with Multiple Schema Types
139 |
140 | ```svelte
141 |
159 |
160 |
161 | ```
162 |
163 | ## Packaging for NPM
164 |
165 | Scott, this is here for you to remember how to do this 🙃
166 |
167 | Although I detailed this in
168 | [Making npm Packages with SvelteKit](https://scottspence.com/posts/making-npm-packages-with-sveltekit)
169 | I think it's best to put it here as I always come to the README and
170 | the instructions are never there! 😅
171 |
172 | **Publish the project to NPM**
173 |
174 | ```bash
175 | # change to package directory
176 | cd packages/svead
177 | # authenticate with npm
178 | npm login
179 | # bump version with npm
180 | npm version 0.0.8
181 | # package with sveltekit
182 | pnpm run package
183 | # publish
184 | npm publish
185 | # push tags to github
186 | git push --tags
187 | ```
188 |
189 | **Publish @next package**
190 |
191 | Same procedure except use the `--tag` flag:
192 |
193 | ```bash
194 | # change to package directory
195 | cd packages/svead
196 | # authenticate with npm
197 | npm login
198 | # bump version with npm
199 | npm version 0.0.13
200 | # package with sveltekit
201 | pnpm run package
202 | # publish with tag
203 | npm publish --tag next
204 | # push tags to github
205 | git push --tags
206 | ```
207 |
208 | **Move @next package to latest**
209 |
210 | ```bash
211 | # authenticate with npm
212 | npm login
213 | # move @next to latest
214 | npm dist-tag add svead@0.0.13 latest
215 | ```
216 |
217 | ## pnpm workspaces
218 |
219 | To add the `svead` package to the `web` workspace:
220 |
221 | ```bash
222 | pnpm add -D svead --filter web
223 | ```
224 |
225 | ## Contributors ✨
226 |
227 | Thanks goes to these wonderful people
228 | ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
229 |
230 |
231 |
232 |
233 |
249 |
250 |
251 |
252 |
253 |
254 |
255 | This project follows the
256 | [all-contributors](https://github.com/all-contributors/all-contributors)
257 | specification. Contributions of any kind welcome!
258 |
--------------------------------------------------------------------------------
/apps/web/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/apps/web/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type { import("eslint").Linter.Config } */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:svelte/recommended',
8 | 'prettier',
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte'],
16 | },
17 | env: {
18 | browser: true,
19 | es2017: true,
20 | node: true,
21 | },
22 | overrides: [
23 | {
24 | files: ['*.svelte'],
25 | parser: 'svelte-eslint-parser',
26 | parserOptions: {
27 | parser: '@typescript-eslint/parser',
28 | },
29 | },
30 | ],
31 | };
32 |
--------------------------------------------------------------------------------
/apps/web/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 | .vercel
12 | screenshots
--------------------------------------------------------------------------------
/apps/web/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/apps/web/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore files for PNPM, NPM and YARN
2 | pnpm-lock.yaml
3 | package-lock.json
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/apps/web/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "printWidth": 70,
6 | "arrowParens": "avoid",
7 | "proseWrap": "always",
8 | "plugins": [
9 | "prettier-plugin-svelte",
10 | "prettier-plugin-tailwindcss"
11 | ],
12 | "overrides": [
13 | {
14 | "files": "*.svelte",
15 | "options": {
16 | "parser": "svelte"
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/apps/web/README.md:
--------------------------------------------------------------------------------
1 | # Svead 🍺 - Svelte Head Component
2 |
3 | [docs](../../README.md)
--------------------------------------------------------------------------------
/apps/web/mdsvex.config.js:
--------------------------------------------------------------------------------
1 | import { defineMDSveXConfig as defineConfig } from 'mdsvex';
2 | import autolinkHeadings from 'rehype-autolink-headings';
3 | import slugPlugin from 'rehype-slug';
4 |
5 | const config = defineConfig({
6 | extensions: ['.svelte.md', '.md', '.svx'],
7 |
8 | smartypants: {
9 | dashes: 'oldschool',
10 | },
11 |
12 | remarkPlugins: [],
13 | rehypePlugins: [
14 | slugPlugin,
15 | [
16 | autolinkHeadings,
17 | {
18 | behavior: 'wrap',
19 | },
20 | ],
21 | ],
22 | });
23 |
24 | export default config;
25 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "pnpm run build:packages && vite build",
8 | "preview": "vite preview",
9 | "test": "npm run test:integration && npm run test:unit",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
12 | "lint": "prettier --check . && eslint .",
13 | "format": "prettier --write .",
14 | "test:integration": "playwright test",
15 | "test:unit": "vitest",
16 | "coverage": "vitest run --coverage",
17 | "build:packages": "pnpm -r --filter=\"../../packages/*\" run build"
18 | },
19 | "devDependencies": {
20 | "@playwright/test": "^1.50.1",
21 | "@sveltejs/adapter-auto": "^4.0.0",
22 | "@sveltejs/kit": "^2.17.1",
23 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
24 | "@tailwindcss/typography": "^0.5.16",
25 | "@testing-library/svelte": "^5.2.6",
26 | "@types/eslint": "9.6.1",
27 | "@typescript-eslint/eslint-plugin": "^8.23.0",
28 | "@typescript-eslint/parser": "^8.23.0",
29 | "@vitest/coverage-v8": "^3.0.5",
30 | "autoprefixer": "^10.4.20",
31 | "daisyui": "^4.12.23",
32 | "eslint": "^9.19.0",
33 | "eslint-config-prettier": "^10.0.1",
34 | "eslint-plugin-svelte": "^2.46.1",
35 | "fathom-client": "^3.7.2",
36 | "jsdom": "^26.0.0",
37 | "mdsvex": "^0.12.3",
38 | "postcss": "^8.5.1",
39 | "postcss-load-config": "^6.0.1",
40 | "prettier": "^3.4.2",
41 | "prettier-plugin-svelte": "^3.3.3",
42 | "prettier-plugin-tailwindcss": "^0.6.11",
43 | "rehype-autolink-headings": "^7.1.0",
44 | "rehype-slug": "^6.0.0",
45 | "svead": "workspace:*",
46 | "svelte": "5.26.2",
47 | "svelte-check": "^4.1.4",
48 | "tailwindcss": "^3.4.17",
49 | "tslib": "^2.8.1",
50 | "typescript": "^5.7.3",
51 | "vite": "^6.0.11",
52 | "vitest": "^3.0.5"
53 | },
54 | "type": "module"
55 | }
56 |
--------------------------------------------------------------------------------
/apps/web/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from '@playwright/test';
2 |
3 | const config: PlaywrightTestConfig = {
4 | webServer: {
5 | command: 'pnpm run build && pnpm run preview',
6 | port: 4173,
7 | },
8 | testDir: 'tests',
9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/,
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/apps/web/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const tailwindcss = require('tailwindcss');
2 | const autoprefixer = require('autoprefixer');
3 |
4 | const config = {
5 | plugins: [
6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
7 | tailwindcss(),
8 | //But others, like autoprefixer, need to run after,
9 | autoprefixer,
10 | ],
11 | };
12 |
13 | module.exports = config;
14 |
--------------------------------------------------------------------------------
/apps/web/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/apps/web/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | %sveltekit.head%
11 |
12 |
13 | %sveltekit.body%
14 |
15 |
16 |
--------------------------------------------------------------------------------
/apps/web/src/app.postcss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | html {
4 | scroll-behavior: smooth;
5 | /* margin-left: calc(100vw - 100%); */
6 | word-break: break-word;
7 | }
8 |
9 | /* Scrollbar styles */
10 |
11 | /* Firefox */
12 | * {
13 | scrollbar-width: thin;
14 | scrollbar-color: oklch(var(--s)) oklch(var(--p));
15 | }
16 |
17 | /* Chrome, Edge, and Safari */
18 | *::-webkit-scrollbar {
19 | width: 15px;
20 | }
21 |
22 | *::-webkit-scrollbar-track {
23 | background: oklch(var(--p));
24 | border-radius: 5px;
25 | }
26 |
27 | *::-webkit-scrollbar-thumb {
28 | background-color: oklch(var(--s));
29 | border-radius: 14px;
30 | border: 3px solid oklch(var(--p));
31 | }
32 |
33 | *::-webkit-scrollbar-thumb:hover {
34 | background-color: oklch(var(--a));
35 | }
36 |
37 | @tailwind components;
38 | @tailwind utilities;
39 |
--------------------------------------------------------------------------------
/apps/web/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | describe('sum test', () => {
4 | it('adds 1 + 2 to equal 3', () => {
5 | expect(1 + 2).toBe(3);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/apps/web/src/lib/components/details.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
(isOpen = !isOpen)}
23 | data-testid="details-button"
24 | >
25 | {isOpen ? `Close` : buttonText}
26 |
27 | {#if isOpen}
28 |
33 | {@render children?.()}
34 |
35 | {/if}
36 |
37 |
--------------------------------------------------------------------------------
/apps/web/src/lib/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Details } from './details.svelte';
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/copy/blog-posting-copy.md:
--------------------------------------------------------------------------------
1 | Here's the code for this page:
2 |
3 | ```svelte
4 |
102 |
103 |
104 |
105 |
106 |
107 | {seo_config.title}
108 | {seo_config.description}
109 |
110 |
111 |
112 | ```
113 |
--------------------------------------------------------------------------------
/apps/web/src/lib/copy/breadcrumbs-copy.md:
--------------------------------------------------------------------------------
1 | Here's the code for this page:
2 |
3 | ```svelte
4 |
111 |
112 |
113 |
114 |
115 |
{page_title}
116 |
{page_description}
117 |
118 |
119 | ```
120 |
--------------------------------------------------------------------------------
/apps/web/src/lib/copy/index-copy.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | # Welcome to Svead 🍺
6 |
7 | The Svelte Head and Schema.org Component.
8 |
9 | Svead is a dynamic component that enhances your SEO by allowing you to
10 | set head meta information for canonical, title, Twitter, Facebook,
11 | Open Graph tags, and JSON-LD structured data.
12 |
13 | Visit [GitHub](https://github.com/spences10/svead) to contribute to
14 | this project.
15 |
16 | ## Components
17 |
18 | Svead provides two main components:
19 |
20 | 1. `Head`: For setting meta tags and other head information.
21 | 2. `SchemaOrg`: For adding structured data using JSON-LD.
22 |
23 | ## Example Routes
24 |
25 | Explore how Svead works with different content types:
26 |
27 | - [Breadcrumbs](/breadcrumbs)
28 | - [Article](/article)
29 | - [Blog Posting](/blog-posting)
30 | - [News Article](/news-article)
31 | - [Web Page](/web-page)
32 | - [Multiple JSON-LD Sections](/multiple-ld-json-sections)
33 |
34 | ## Head Component
35 |
36 | ### Usage
37 |
38 | ```svelte
39 |
48 |
49 |
50 | ```
51 |
52 | ### `SeoConfig` Props
53 |
54 |
55 |
56 | | Property | Type | Description | Required |
57 | | :------------------ | :----------------- | :----------------------------------------------------------- | :------- |
58 | | `title` | `string` | The title of the web page. | Yes |
59 | | `description` | `string` | A description of the web page. | Yes |
60 | | `url` | `string` | The URL of the web page. | Yes |
61 | | `website` | `string` | The website the web page belongs to. | No |
62 | | `language` | `string` \| `'en'` | The language of the web page. Defaults to 'en'. | No |
63 | | `open_graph_image` | `string` | The URL of an image to use for Open Graph meta tags. | No |
64 | | `payment_pointer` | `string` | A payment pointer for Web Monetization. | No |
65 | | `author_name` | `string` | The name of the author. | No |
66 | | `site_name` | `string` | The name of the site for og:site_name. | No |
67 | | `twitter_handle` | `string` | The Twitter handle of the content creator or site. | No |
68 | | `twitter_card_type` | `string` | The type of Twitter card. Defaults to 'summary_large_image'. | No |
69 |
70 |
71 |
72 | ## SchemaOrg Component
73 |
74 | ### Usage
75 |
76 | ```svelte
77 |
91 |
92 |
93 | ```
94 |
95 | ### `SchemaOrgProps` Props
96 |
97 |
98 |
99 | | Property | Type | Description | Required |
100 | | :------- | :-------------- | :---------------------------------------------------------- | :------- |
101 | | `schema` | `SchemaOrgType` | The structured data object following schema.org vocabulary. | Yes |
102 |
103 |
104 |
105 | ### `SchemaOrgType`
106 |
107 | `SchemaOrgType` is extended from
108 | [schema-dts](https://github.com/google/schema-dts) and is a union type
109 | that includes:
110 |
111 | - `Thing`: Represents the most generic type of item in schema.org.
112 | - `WithContext`: A Thing with an added `@context` property.
113 |
114 | You can use any valid schema.org type as defined in the
115 | [schema.org documentation](https://schema.org).
116 |
117 | ### Additional Notes:
118 |
119 | - The `@context` property is automatically added by the component if
120 | not provided.
121 | - You can include multiple schema types by nesting them within the
122 | main schema object.
123 | - Always validate your structured data using tools like
124 | [Google's Rich Results Test](https://search.google.com/test/rich-results)
125 | to ensure it's correctly formatted.
126 |
127 | ## Example with Both Components
128 |
129 | ```svelte
130 |
158 |
159 |
160 |
161 |
162 |
163 | {seo_config.title}
164 | {seo_config.description}
165 |
166 |
167 | ```
168 |
169 | This example demonstrates how to use both the `Head` and `SchemaOrg`
170 | components together in a Svelte page, providing both meta tags and
171 | structured data for improved SEO.
172 |
173 | For more information and full documentation, visit the
174 | [Svead GitHub repository](https://github.com/spences10/svead).
175 |
--------------------------------------------------------------------------------
/apps/web/src/lib/copy/multiple-ld-json-sections-copy.md:
--------------------------------------------------------------------------------
1 | Here's the code for this page:
2 |
3 | ```svelte
4 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Home
131 | Blog
132 | {seo_config.title}
133 |
134 |
135 |
136 | {seo_config.title}
137 | {seo_config.description}
138 |
139 |
140 |
141 |
142 |
143 | Introduction to Structured Data
144 |
145 | Structured data helps search engines understand the content of
146 | your web pages...
147 |
148 |
149 |
150 |
151 | Implementing JSON-LD in SvelteKit
152 | Here's how you can add JSON-LD to your SvelteKit project...
153 |
154 |
155 |
156 |
157 |
161 |
162 | ```
163 |
--------------------------------------------------------------------------------
/apps/web/src/lib/copy/news-article-copy.md:
--------------------------------------------------------------------------------
1 | Here's the code for this page:
2 |
3 | ```svelte
4 |
117 |
118 |
119 |
120 |
121 |
122 | {seo_config.title}
123 | {seo_config.description}
124 |
125 |
126 |
127 | ```
128 |
--------------------------------------------------------------------------------
/apps/web/src/lib/copy/web-page-copy.md:
--------------------------------------------------------------------------------
1 | Here's the code for this page:
2 |
3 | ```svelte
4 |
86 |
87 |
88 |
89 |
90 |
91 | {seo_config.title}
92 | {seo_config.description}
93 |
94 |
95 |
96 | ```
97 |
--------------------------------------------------------------------------------
/apps/web/src/lib/icons/github.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/apps/web/src/lib/icons/index.ts:
--------------------------------------------------------------------------------
1 | export { default as GitHub } from './github.svelte';
2 | export { default as Twitter } from './twitter.svelte';
3 | export { default as YouTube } from './youtube.svelte';
4 |
--------------------------------------------------------------------------------
/apps/web/src/lib/icons/twitter.svelte:
--------------------------------------------------------------------------------
1 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/web/src/lib/icons/youtube.svelte:
--------------------------------------------------------------------------------
1 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/web/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './icons';
2 | export * from './components';
3 |
--------------------------------------------------------------------------------
/apps/web/src/prism.css:
--------------------------------------------------------------------------------
1 | /**
2 | * MIT License
3 | * Copyright (c) 2018 Sarah Drasner
4 | * Sarah Drasner's[@sdras] Night Owl
5 | * Ported by Sara vieria [@SaraVieira]
6 | * Added by Souvik Mandal [@SimpleIndian]
7 | */
8 |
9 | code[class*='language-'],
10 | pre[class*='language-'] {
11 | color: #d6deeb;
12 | font-family: 'Victor Mono', Consolas, Monaco, 'Andale Mono',
13 | 'Ubuntu Mono', monospace;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 |
20 | -moz-tab-size: 4;
21 | -o-tab-size: 4;
22 | tab-size: 4;
23 |
24 | -webkit-hyphens: none;
25 | -moz-hyphens: none;
26 | -ms-hyphens: none;
27 | hyphens: none;
28 | }
29 |
30 | pre[class*='language-']::-moz-selection,
31 | pre[class*='language-'] ::-moz-selection,
32 | code[class*='language-']::-moz-selection,
33 | code[class*='language-'] ::-moz-selection {
34 | text-shadow: none;
35 | background: rgba(29, 59, 83, 0.99);
36 | }
37 |
38 | pre[class*='language-']::selection,
39 | pre[class*='language-'] ::selection,
40 | code[class*='language-']::selection,
41 | code[class*='language-'] ::selection {
42 | text-shadow: none;
43 | background: rgba(29, 59, 83, 0.99);
44 | }
45 |
46 | @media print {
47 | code[class*='language-'],
48 | pre[class*='language-'] {
49 | text-shadow: none;
50 | }
51 | }
52 |
53 | /* Code blocks */
54 | pre[class*='language-'] {
55 | /* padding: 1em; */
56 | /* margin: 0.5em 0; */
57 | overflow: auto;
58 | }
59 |
60 | :not(pre) > code[class*='language-'],
61 | pre[class*='language-'] {
62 | color: #d6deeb;
63 | background: #19212e;
64 | }
65 |
66 | :not(pre) > code[class*='language-'] {
67 | /* padding: 0.1em; */
68 | border-radius: 0.3em;
69 | white-space: normal;
70 | }
71 |
72 | .token.comment,
73 | .token.prolog,
74 | .token.cdata {
75 | color: rgb(99, 119, 119);
76 | }
77 |
78 | .token.punctuation {
79 | color: rgb(199, 146, 234);
80 | }
81 |
82 | .namespace {
83 | color: rgb(178, 204, 214);
84 | }
85 |
86 | .token.deleted {
87 | color: rgba(239, 83, 80, 0.56);
88 | font-style: italic;
89 | }
90 |
91 | .token.symbol,
92 | .token.property {
93 | color: rgb(128, 203, 196);
94 | }
95 |
96 | .token.tag,
97 | .token.operator,
98 | .token.keyword {
99 | color: rgb(127, 219, 202);
100 | }
101 |
102 | .token.boolean {
103 | color: rgb(255, 88, 116);
104 | }
105 |
106 | .token.number {
107 | color: rgb(247, 140, 108);
108 | }
109 |
110 | .token.constant,
111 | .token.function,
112 | .token.builtin,
113 | .token.char {
114 | color: rgb(130, 170, 255);
115 | }
116 |
117 | .token.selector,
118 | .token.doctype {
119 | color: rgb(199, 146, 234);
120 | font-style: italic;
121 | }
122 |
123 | .token.attr-name,
124 | .token.inserted {
125 | color: rgb(173, 219, 103);
126 | font-style: italic;
127 | }
128 |
129 | .token.string,
130 | .token.url,
131 | .token.entity,
132 | .language-css .token.string,
133 | .style .token.string {
134 | color: rgb(173, 219, 103);
135 | }
136 |
137 | .token.class-name,
138 | .token.atrule,
139 | .token.attr-value {
140 | color: rgb(255, 203, 139);
141 | }
142 |
143 | .token.regex,
144 | .token.important,
145 | .token.variable {
146 | color: rgb(214, 222, 235);
147 | }
148 |
149 | .token.important,
150 | .token.bold {
151 | font-weight: bold;
152 | }
153 |
154 | .token.italic {
155 | font-style: italic;
156 | }
157 |
--------------------------------------------------------------------------------
/apps/web/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
46 |
47 |
50 |
51 |
52 |
53 |
111 |
--------------------------------------------------------------------------------
/apps/web/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/apps/web/src/routes/+page.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | export const load = async () => {
4 | const slug = 'index-copy';
5 | try {
6 | const Copy = await import(`../lib/copy/${slug}.md`);
7 | return {
8 | Copy: Copy.default,
9 | };
10 | } catch (e) {
11 | throw error(404, 'Uh oh!');
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/src/routes/article/+page.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
Article Example
38 |
--------------------------------------------------------------------------------
/apps/web/src/routes/blog-posting/+page.svelte:
--------------------------------------------------------------------------------
1 |
99 |
100 |
101 |
102 |
103 |
104 | {seo_config.title}
105 | {seo_config.description}
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/apps/web/src/routes/blog-posting/+page.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | export const load = async () => {
4 | const slug = 'blog-posting-copy';
5 | try {
6 | const Copy = await import(`../../lib/copy/${slug}.md`);
7 | return {
8 | Copy: Copy.default,
9 | };
10 | } catch (e) {
11 | throw error(404, 'Uh oh!');
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/src/routes/breadcrumbs/+page.svelte:
--------------------------------------------------------------------------------
1 |
108 |
109 |
110 |
111 |
112 |
{page_title}
113 |
{page_description}
114 |
115 |
116 |
--------------------------------------------------------------------------------
/apps/web/src/routes/breadcrumbs/+page.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | export const load = async () => {
4 | const slug = 'breadcrumbs-copy';
5 | try {
6 | const Copy = await import(`../../lib/copy/${slug}.md`);
7 | return {
8 | Copy: Copy.default,
9 | };
10 | } catch (e) {
11 | throw error(404, 'Uh oh!');
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/src/routes/multiple-ld-json-sections/+page.svelte:
--------------------------------------------------------------------------------
1 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | Home
128 | Blog
129 | {seo_config.title}
130 |
131 |
132 |
133 | {seo_config.title}
134 | {seo_config.description}
135 |
136 |
137 |
138 |
139 |
140 | Introduction to Structured Data
141 |
142 | Structured data helps search engines understand the content of
143 | your web pages...
144 |
145 |
146 |
147 |
148 | Implementing JSON-LD in SvelteKit
149 | Here's how you can add JSON-LD to your SvelteKit project...
150 |
151 |
152 |
153 |
154 |
158 |
159 |
--------------------------------------------------------------------------------
/apps/web/src/routes/multiple-ld-json-sections/+page.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | export const load = async () => {
4 | const slug = 'multiple-ld-json-sections-copy';
5 | try {
6 | const Copy = await import(`../../lib/copy/${slug}.md`);
7 | return {
8 | Copy: Copy.default,
9 | };
10 | } catch (e) {
11 | throw error(404, 'Uh oh!');
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/src/routes/news-article/+page.svelte:
--------------------------------------------------------------------------------
1 |
114 |
115 |
116 |
117 |
118 |
119 | {seo_config.title}
120 | {seo_config.description}
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/apps/web/src/routes/news-article/+page.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | export const load = async () => {
4 | const slug = 'news-article-copy';
5 | try {
6 | const Copy = await import(`../../lib/copy/${slug}.md`);
7 | return {
8 | Copy: Copy.default,
9 | };
10 | } catch (e) {
11 | throw error(404, 'Uh oh!');
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/src/routes/web-page/+page.svelte:
--------------------------------------------------------------------------------
1 |
83 |
84 |
85 |
86 |
87 |
88 | {seo_config.title}
89 | {seo_config.description}
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/apps/web/src/routes/web-page/+page.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | export const load = async () => {
4 | const slug = 'web-page-copy';
5 | try {
6 | const Copy = await import(`../../lib/copy/${slug}.md`);
7 | return {
8 | Copy: Copy.default,
9 | };
10 | } catch (e) {
11 | throw error(404, 'Uh oh!');
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spences10/svead/90e440d196068f7731af6107eebffb8825f06e10/apps/web/static/favicon.png
--------------------------------------------------------------------------------
/apps/web/static/spencee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spences10/svead/90e440d196068f7731af6107eebffb8825f06e10/apps/web/static/spencee.png
--------------------------------------------------------------------------------
/apps/web/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 | import { mdsvex } from 'mdsvex';
4 | import mdsvexConfig from './mdsvex.config.js';
5 |
6 | /** @type {import('@sveltejs/kit').Config} */
7 | const config = {
8 | extensions: ['.svelte', ...mdsvexConfig.extensions],
9 |
10 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
11 | // for more information about preprocessors
12 | preprocess: [mdsvex(mdsvexConfig), vitePreprocess({})],
13 |
14 | kit: {
15 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
16 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
17 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
18 | adapter: adapter(),
19 | },
20 | };
21 |
22 | export default config;
23 |
--------------------------------------------------------------------------------
/apps/web/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const daisyui = require('daisyui');
2 | const typography = require('@tailwindcss/typography');
3 |
4 | /** @type {import('tailwindcss').Config}*/
5 | const config = {
6 | content: ['./src/**/*.{html,js,svelte,ts}'],
7 |
8 | theme: {
9 | extend: {},
10 | },
11 |
12 | plugins: [typography, daisyui],
13 |
14 | daisyui: {
15 | themes: ['night', 'winter'],
16 | },
17 | };
18 |
19 | module.exports = config;
20 |
--------------------------------------------------------------------------------
/apps/web/tests/index.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 | let pageURL = 'http://localhost:4173/';
3 |
4 | test.beforeEach(async ({ page }) => {
5 | await page.goto('/');
6 | });
7 |
8 | test.afterEach(async ({ page }, testInfo) => {
9 | if (testInfo.status === 'failed') {
10 | await page.screenshot({
11 | path: `screenshots/${testInfo.title.replace(/\s+/g, '_')}.png`,
12 | });
13 | }
14 | });
15 |
16 | test('index page has h1', async ({ page }) => {
17 | await page.waitForSelector('h1');
18 | expect(await page.textContent('h1')).toBe('Welcome to Svead 🍺');
19 | });
20 |
21 | test.describe('meta tags', () => {
22 | test('head has canonical', async ({ page }) => {
23 | const metaCanonical = page.locator('link[rel="canonical"]');
24 | await expect(metaCanonical.first()).toHaveAttribute(
25 | 'href',
26 | pageURL,
27 | );
28 | });
29 |
30 | test('head has description', async ({ page }) => {
31 | const metaDescription = page.locator('meta[name="description"]');
32 | await expect(metaDescription.first()).toHaveAttribute(
33 | 'content',
34 | 'Svead, a component that allows you to set head meta information, canonical, title, Twitter and Facebook Open Graph tags.',
35 | );
36 | });
37 |
38 | test('has open graph title', async ({ page }) => {
39 | const metaOgTitle = page.locator('meta[property="og:title"]');
40 | await expect(metaOgTitle.first()).toHaveAttribute(
41 | 'content',
42 | 'This is Svead a Svelte Head Component',
43 | );
44 | });
45 |
46 | test('has open graph description', async ({ page }) => {
47 | const metaOgDescription = page.locator(
48 | 'meta[property="og:description"]',
49 | );
50 | await expect(metaOgDescription.first()).toHaveAttribute(
51 | 'content',
52 | 'Svead, a component that allows you to set head meta information, canonical, title, Twitter and Facebook Open Graph tags.',
53 | );
54 | });
55 |
56 | test('has open graph image', async ({ page }) => {
57 | const metaOgImage = page.locator('meta[property="og:image"]');
58 | await expect(metaOgImage.first()).toHaveAttribute(
59 | 'content',
60 | 'https://og.tailgraph.com/og?fontFamily=Roboto&title=This+is+Svead&titleTailwind=text-gray-800+font-bold+text-6xl&text=Set+Head+meta+tag+information&textTailwind=text-gray-700+text-2xl+mt-4&logoTailwind=h-8&bgTailwind=bg-white&footer=svead.pages.dev&footerTailwind=text-teal-600',
61 | );
62 | });
63 |
64 | test('has open graph url', async ({ page }) => {
65 | const metaOgUrl = page.locator('meta[property="og:url"]');
66 | await expect(metaOgUrl.first()).toHaveAttribute(
67 | 'content',
68 | pageURL,
69 | );
70 | });
71 |
72 | test('has twitter card', async ({ page }) => {
73 | const metaTwitterCard = page.locator('meta[name="twitter:card"]');
74 | await expect(metaTwitterCard.first()).toHaveAttribute(
75 | 'content',
76 | 'summary_large_image',
77 | );
78 | });
79 |
80 | test('has twitter title', async ({ page }) => {
81 | const metaTwitterTitle = page.locator(
82 | 'meta[name="twitter:title"]',
83 | );
84 | await expect(metaTwitterTitle.first()).toHaveAttribute(
85 | 'content',
86 | 'This is Svead a Svelte Head Component',
87 | );
88 | });
89 |
90 | test('has twitter description', async ({ page }) => {
91 | const metaTwitterDescription = page.locator(
92 | 'meta[name="twitter:description"]',
93 | );
94 | await expect(metaTwitterDescription.first()).toHaveAttribute(
95 | 'content',
96 | 'Svead, a component that allows you to set head meta information, canonical, title, Twitter and Facebook Open Graph tags.',
97 | );
98 | });
99 |
100 | test('has twitter image', async ({ page }) => {
101 | const metaTwitterImage = page.locator(
102 | 'meta[name="twitter:image"]',
103 | );
104 | await expect(metaTwitterImage.first()).toHaveAttribute(
105 | 'content',
106 | 'https://og.tailgraph.com/og?fontFamily=Roboto&title=This+is+Svead&titleTailwind=text-gray-800+font-bold+text-6xl&text=Set+Head+meta+tag+information&textTailwind=text-gray-700+text-2xl+mt-4&logoTailwind=h-8&bgTailwind=bg-white&footer=svead.pages.dev&footerTailwind=text-teal-600',
107 | );
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | //
16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
17 | // from the referenced tsconfig.json - TypeScript does not merge them in
18 | }
19 |
--------------------------------------------------------------------------------
/apps/web/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vitest/config';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()],
6 | test: {
7 | environment: 'jsdom',
8 | include: ['src/**/*.{test,spec}.{js,ts}'],
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/apps/web/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, mergeConfig } from 'vitest/config';
2 | import viteConfig from './vite.config';
3 |
4 | export default mergeConfig(
5 | viteConfig,
6 | defineConfig({
7 | test: {
8 | include: ['src/**/*.test.{js,ts,svelte}'],
9 | globals: true,
10 | environment: 'jsdom',
11 | coverage: {
12 | all: false,
13 | thresholds: {
14 | statements: 75,
15 | branches: 84,
16 | functions: 68,
17 | lines: 75,
18 | },
19 | },
20 | },
21 | }),
22 | );
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev:web": "pnpm run build:packages && pnpm --filter=web dev",
5 | "build:web": "pnpm run build:packages && pnpm --filter=web build",
6 | "preview:web": "pnpm run build:packages && pnpm --filter=web preview",
7 | "test:unit:web": "pnpm run build:packages && pnpm --filter=web test:unit",
8 | "test:int:web": "pnpm run build:packages && pnpm --filter=web test:integration",
9 | "coverage:web": "pnpm run build:packages && pnpm --filter=web coverage",
10 | "dev:svead": "pnpm run build:packages && pnpm --filter=svead dev",
11 | "build:svead": "pnpm run build:packages && pnpm --filter=svead build",
12 | "preview:svead": "pnpm run build:packages && pnpm --filter=svead preview",
13 | "test:unit:svead": "pnpm run build:packages && pnpm --filter=svead test:unit",
14 | "test:int:svead": "pnpm run build:packages && pnpm --filter=svead test:integration",
15 | "coverage:svead": "pnpm run build:packages && pnpm --filter=svead coverage",
16 | "build:packages": "pnpm -r --filter=\"./packages/*\" run build",
17 | "format": "pnpm -r --filter=\"./apps/*\" --filter=\"./packages/*\" run format",
18 | "lint": "pnpm -r --filter=\"./apps/*\" --filter=\"./packages/*\" run lint",
19 | "changeset": "changeset",
20 | "version": "changeset version",
21 | "release": "pnpm run build:packages && changeset publish"
22 | },
23 | "devDependencies": {
24 | "@changesets/cli": "^2.27.12"
25 | }
26 | }
--------------------------------------------------------------------------------
/packages/svead/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /dist
5 | /.svelte-kit
6 | /package
7 | .env
8 | .env.*
9 | !.env.example
10 | vite.config.js.timestamp-*
11 | vite.config.ts.timestamp-*
12 |
--------------------------------------------------------------------------------
/packages/svead/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/packages/svead/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore files for PNPM, NPM and YARN
2 | pnpm-lock.yaml
3 | package-lock.json
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/packages/svead/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "printWidth": 70,
6 | "arrowParens": "avoid",
7 | "proseWrap": "always",
8 | "plugins": ["prettier-plugin-svelte"],
9 | "overrides": [
10 | { "files": "*.svelte", "options": { "parser": "svelte" } }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/svead/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # svead
2 |
3 | ## 0.0.14
4 |
5 | ### Patch Changes
6 |
7 | - add changeset
8 |
--------------------------------------------------------------------------------
/packages/svead/README.md:
--------------------------------------------------------------------------------
1 | # Svead 🍺 - Svelte Head Component
2 |
3 | [docs](../../README.md)
--------------------------------------------------------------------------------
/packages/svead/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import svelte from 'eslint-plugin-svelte';
4 | import prettier from 'eslint-config-prettier';
5 | import globals from 'globals';
6 |
7 | /** @type {import('eslint').Linter.Config[]} */
8 | export default [
9 | js.configs.recommended,
10 | ...ts.configs.recommended,
11 | ...svelte.configs['flat/recommended'],
12 | prettier,
13 | ...svelte.configs['flat/prettier'],
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.browser,
18 | ...globals.node
19 | }
20 | }
21 | },
22 | {
23 | files: ['**/*.svelte'],
24 | languageOptions: {
25 | parserOptions: {
26 | parser: ts.parser
27 | }
28 | }
29 | },
30 | {
31 | ignores: ['build/', '.svelte-kit/', 'dist/']
32 | }
33 | ];
34 |
--------------------------------------------------------------------------------
/packages/svead/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svead",
3 | "version": "0.0.14",
4 | "author": {
5 | "name": "Scott Spence",
6 | "email": "svead@scottspence.dev",
7 | "url": "https://scottspence.com"
8 | },
9 | "keywords": [
10 | "svelte",
11 | "sveltekit",
12 | "head",
13 | "meta-tags",
14 | "seo",
15 | "social",
16 | "twitter",
17 | "facebook",
18 | "og",
19 | "open-graph",
20 | "schema",
21 | "json-ld",
22 | "jsonld",
23 | "structured-data"
24 | ],
25 | "scripts": {
26 | "dev": "vite dev",
27 | "build": "vite build && npm run package",
28 | "preview": "vite preview",
29 | "package": "svelte-kit sync && svelte-package && publint",
30 | "prepublishOnly": "npm run package",
31 | "test": "npm run test:integration && npm run test:unit",
32 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
33 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
34 | "lint": "prettier --check . && eslint .",
35 | "format": "prettier --write .",
36 | "test:integration": "playwright test",
37 | "test:unit": "vitest",
38 | "test:ci": "vitest run",
39 | "coverage": "vitest run --coverage"
40 | },
41 | "exports": {
42 | ".": {
43 | "types": "./dist/index.d.ts",
44 | "svelte": "./dist/index.js"
45 | }
46 | },
47 | "files": [
48 | "dist",
49 | "!dist/**/*.test.*",
50 | "!dist/**/*.spec.*"
51 | ],
52 | "peerDependencies": {
53 | "svelte": "^4.0.0 || ^5.0.0"
54 | },
55 | "devDependencies": {
56 | "@playwright/test": "^1.50.1",
57 | "@sveltejs/adapter-auto": "^4.0.0",
58 | "@sveltejs/kit": "^2.17.1",
59 | "@sveltejs/package": "^2.3.10",
60 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
61 | "@testing-library/svelte": "^5.2.6",
62 | "@types/eslint": "9.6.1",
63 | "@typescript-eslint/eslint-plugin": "^8.23.0",
64 | "@typescript-eslint/parser": "^8.23.0",
65 | "@vitest/coverage-v8": "^3.0.5",
66 | "eslint": "^9.19.0",
67 | "eslint-config-prettier": "^10.0.1",
68 | "eslint-plugin-svelte": "^2.46.1",
69 | "jsdom": "^26.0.0",
70 | "prettier": "^3.4.2",
71 | "prettier-plugin-svelte": "^3.3.3",
72 | "publint": "^0.3.2",
73 | "schema-dts": "^1.1.2",
74 | "svelte": "5.26.2",
75 | "svelte-check": "^4.1.4",
76 | "tslib": "^2.8.1",
77 | "typescript": "^5.7.3",
78 | "vite": "^6.0.11",
79 | "vitest": "^3.0.5"
80 | },
81 | "svelte": "./dist/index.js",
82 | "types": "./dist/index.d.ts",
83 | "type": "module",
84 | "repository": {
85 | "type": "git",
86 | "url": "git+https://github.com/spences10/svead.git"
87 | },
88 | "license": "MIT",
89 | "bugs": {
90 | "url": "https://github.com/spences10/svead/issues"
91 | },
92 | "homepage": "https://github.com/spences10/svead#readme"
93 | }
94 |
--------------------------------------------------------------------------------
/packages/svead/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from '@playwright/test';
2 |
3 | const config: PlaywrightTestConfig = {
4 | webServer: {
5 | command: 'pnpm run build && pnpm run preview',
6 | port: 4173,
7 | },
8 | testDir: 'tests',
9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/,
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/packages/svead/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/packages/svead/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | %sveltekit.head%
11 |
12 |