Upgrading to Svelte 5 and dependencies that need it. Using enhanced img for local files.

This commit is contained in:
Bradley Shellnut 2024-12-01 19:14:16 -08:00
parent 4b3a1280f6
commit e6a2f8fcb6
16 changed files with 1653 additions and 1779 deletions

11
.gitignore vendored
View file

@ -5,11 +5,14 @@ node_modules
/package
.env
.env.*
!.env.example
*.xdp*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.fleet
.idea
.vercel
.output
.idea
.fleet
# Sentry Config File
.sentryclirc

1
.node-version Normal file
View file

@ -0,0 +1 @@
22

2
.nvmrc
View file

@ -1 +1 @@
v22
22

65
biome.json Normal file
View file

@ -0,0 +1,65 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": { "ignoreUnknown": false, "ignore": [] },
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 150,
"attributePosition": "auto",
"ignore": [
"**/.DS_Store",
"**/node_modules",
"./build",
"./.svelte-kit",
"./package",
"**/.env",
"**/.env.*",
"**/pnpm-lock.yaml",
"**/package-lock.json",
"**/yarn.lock",
"**/paraglide/**"
]
},
"organizeImports": { "enabled": true },
"linter": { "enabled": true, "rules": { "recommended": true } },
"javascript": {
"formatter": {
"jsxQuoteStyle": "single",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"indentStyle": "space",
"lineEnding": "lf",
"lineWidth": 150,
"semicolons": "always",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto"
},
"parser": {
"unsafeParameterDecoratorsEnabled": true
}
},
"overrides": [
{
"include": ["*.svelte"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
}
}
}
}
]
}

11
docker-compose.yaml Normal file
View file

@ -0,0 +1,11 @@
version: "3.8"
services:
redis:
image: redis:latest
container_name: personal_website_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:

View file

@ -1,6 +1,6 @@
{
"name": "personal-website-sveltekit",
"version": "0.0.1",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
@ -10,70 +10,64 @@
"test:ui": "svelte-kit sync && playwright test --ui",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .",
"lint": "biome lint --error-on-warnings .",
"format": "biome format --write .",
"test:integration": "playwright test",
"test:unit": "vitest"
},
"packageManager": "pnpm@9.11.0",
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@iconify-icons/material-symbols": "^1.2.58",
"@iconify-icons/mdi": "^1.2.48",
"@iconify-icons/radix-icons": "^1.2.9",
"@iconify-icons/simple-icons": "^1.2.74",
"@melt-ui/pp": "^0.3.2",
"@playwright/test": "^1.47.2",
"@sveltejs/adapter-auto": "^3.2.5",
"@sveltejs/adapter-node": "^5.2.5",
"@sveltejs/adapter-static": "^3.0.5",
"@sveltejs/enhanced-img": "^0.2.1",
"@sveltejs/kit": "^2.6.1",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@melt-ui/svelte": "^0.86.2",
"@playwright/test": "^1.49.0",
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/enhanced-img": "^0.4.1",
"@sveltejs/kit": "^2.9.0",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@zerodevx/svelte-img": "^2.1.2",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.44.1",
"iconify-icon": "^2.1.0",
"just-intersect": "^4.3.0",
"mdsvex": "^0.11.2",
"mdsvex-relative-images": "^1.0.3",
"postcss": "^8.4.47",
"postcss": "^8.4.49",
"postcss-import": "^16.1.0",
"postcss-load-config": "^5.1.0",
"postcss-preset-env": "^9.6.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"sass": "^1.79.4",
"sass": "^1.81.0",
"satori": "^0.10.14",
"satori-html": "^0.3.2",
"scrape-it": "^6.1.2",
"scrape-it": "^6.1.3",
"sharp": "^0.33.5",
"svelte": "^4.2.19",
"svelte-check": "^3.8.6",
"svelte-meta-tags": "^3.1.4",
"svelte-preprocess": "^5.1.4",
"svelte-sequential-preprocessor": "^2.0.1",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"svelte": "^5.3.1",
"svelte-check": "^4.1.0",
"svelte-meta-tags": "^4.0.4",
"svelte-preprocess": "^6.0.3",
"svelte-sequential-preprocessor": "^2.0.2",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"vanilla-lazyload": "^19.1.3",
"vite": "^5.4.8",
"vite-imagetools": "^7.0.4",
"vite": "^6.0.1",
"vite-imagetools": "^7.0.5",
"vitest": "^1.6.0"
},
"type": "module",
"dependencies": {
"@melt-ui/svelte": "^0.76.3",
"@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-vercel": "^5.4.4",
"@sveltejs/adapter-vercel": "^5.5.0",
"@types/nprogress": "^0.2.3",
"@vercel/og": "^0.6.3",
"bits-ui": "^0.21.15",
"@vercel/og": "^0.6.4",
"bits-ui": "^0.21.16",
"flexsearch": "^0.7.43",
"ioredis": "^5.4.1",
"lucide-svelte": "^0.378.0",
"lucide-svelte": "^0.462.0",
"nprogress": "^0.2.0",
"svelte-local-storage-store": "^0.6.4"
}

File diff suppressed because it is too large Load diff

View file

@ -5,14 +5,18 @@ import {
WALLABAG_PASSWORD,
WALLABAG_URL,
PAGE_SIZE,
USE_REDIS_CACHE
} from '$env/static/private';
import intersect from 'just-intersect';
import type { Article, ArticlePageLoad, WallabagArticle } from '$lib/types/article';
import { ArticleTag } from '$lib/types/articleTag';
import type { PageQuery } from '$lib/types/pageQuery';
import { URLSearchParams } from 'url';
import { redis } from '$lib/server/redis';
USE_REDIS_CACHE,
} from "$env/static/private";
import intersect from "just-intersect";
import type {
Article,
ArticlePageLoad,
WallabagArticle,
} from "$lib/types/article";
import { ArticleTag } from "$lib/types/articleTag";
import type { PageQuery } from "$lib/types/pageQuery";
import { URLSearchParams } from "url";
import { redis } from "$lib/server/redis";
const base: string = WALLABAG_URL;
@ -29,18 +33,18 @@ export async function fetchArticlesApi(
}
const pageQuery: PageQuery = {
sort: 'updated',
sort: "updated",
perPage,
since: 0,
page: Number(queryParams?.page) || 1,
tags: 'programming',
content: 'metadata'
tags: "programming",
content: "metadata",
};
const entriesQueryParams = new URLSearchParams({
...pageQuery,
perPage: `${pageQuery.perPage}`,
since: `${pageQuery.since}`,
page: `${pageQuery.page}`
page: `${pageQuery.page}`,
});
if (USE_REDIS_CACHE) {
@ -55,35 +59,44 @@ export async function fetchArticlesApi(
}
const authBody = {
grant_type: 'password',
grant_type: "password",
client_id: WALLABAG_CLIENT_ID,
client_secret: WALLABAG_CLIENT_SECRET,
username: WALLABAG_USERNAME,
password: WALLABAG_PASSWORD
password: WALLABAG_PASSWORD,
};
const authResponse = await fetch(`${base}/oauth/v2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(authBody)
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(authBody),
});
const auth = await authResponse.json();
const pageResponse = await fetch(`${WALLABAG_URL}/api/entries.json?${entriesQueryParams}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${auth.access_token}`
const pageResponse = await fetch(
`${WALLABAG_URL}/api/entries.json?${entriesQueryParams}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${auth.access_token}`,
},
}
});
);
if (!pageResponse.ok) {
throw new Error(pageResponse.statusText);
}
const cacheControl = pageResponse.headers.get('cache-control') || 'no-cache';
const cacheControl = pageResponse.headers.get("cache-control") || "no-cache";
const { _embedded: favoriteArticles, page, pages, total, limit } = await pageResponse.json();
const {
_embedded: favoriteArticles,
page,
pages,
total,
limit,
} = await pageResponse.json();
const articles: Article[] = [];
favoriteArticles.items.forEach((article: WallabagArticle) => {
@ -94,13 +107,13 @@ export async function fetchArticlesApi(
tags,
title: article.title,
url: new URL(article.url),
domain_name: article.domain_name?.replace('www.', '') ?? '',
domain_name: article.domain_name?.replace("www.", "") ?? "",
hashed_url: article.hashed_url,
reading_time: article.reading_time,
preview_picture: article.preview_picture,
created_at: new Date(article.created_at),
updated_at: new Date(article.updated_at),
archived_at: article.archived_at ? new Date(article.archived_at) : null
archived_at: article.archived_at ? new Date(article.archived_at) : null,
});
}
});
@ -111,11 +124,16 @@ export async function fetchArticlesApi(
totalPages: pages,
limit,
totalArticles: total,
cacheControl
cacheControl,
};
if (USE_REDIS_CACHE) {
redis.set(entriesQueryParams.toString(), JSON.stringify(responseData), 'EX', 43200);
redis.set(
entriesQueryParams.toString(),
JSON.stringify(responseData),
"EX",
43200
);
}
return responseData;

View file

@ -1,11 +1,15 @@
<script lang="ts">
import type { Article } from "$lib/types/article";
import ExternalLink from './ExternalLink.svelte';
import type { Article } from '$lib/types/article';
import ExternalLink from './ExternalLink.svelte';
export let articles: Article[];
export let totalArticles: number;
export let compact: boolean = false;
export let classes: string[] = [];
const {
articles,
totalArticles,
compact = false,
classes = [],
}: { articles: Article[]; totalArticles: number; compact: boolean; classes: string[] } = $props();
console.log('articles', articles);
</script>
<div>

View file

@ -2,7 +2,7 @@
import type { Album } from "$lib/types/album";
import LazyImage from './LazyImage.svelte';
export let albums: Album[];
const { albums }: { albums: Album[] } = $props();
const displayAlbums =
albums?.length > 6 ? albums.slice(0, 6) : albums;
@ -10,15 +10,9 @@
album.src = {
img: { src: `${album.artwork}`, w: 230, h: 230 },
sources: {
avif: [
{ src: `${album.artwork}`, w: 230, h: 230 },
],
webp: [
{ src: `${album.artwork}`, w: 230, h: 230 },
],
jpg: [
{ src: `${album.artwork}`, w: 230, h: 230 },
]
avif: `${album.artwork}`,
webp: `${album.artwork}`,
jpg: `${album.artwork}`
}
}
}

View file

@ -1,9 +1,9 @@
import type { MetaTagsProps } from 'svelte-meta-tags';
import { PUBLIC_SITE_URL } from '$env/static/public';
import type { PageServerLoad } from './$types';
import { fetchBandcampAlbums } from '$lib/util/fetchBandcampAlbums';
import type { Album } from '$lib/types/album';
import type { ArticlePageLoad } from '$lib/types/article';
import { fetchBandcampAlbums } from '$lib/util/fetchBandcampAlbums';
import type { MetaTagsProps } from 'svelte-meta-tags';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => {
let baseUrl;
@ -45,7 +45,7 @@ export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => {
const [albums, articles]: [Album[], ArticlePageLoad] = await Promise.all([
await fetchBandcampAlbums(),
(await fetch(`/api/articles?page=1&limit=3`)).json()
(await fetch('/api/articles?page=1&limit=3')).json()
]);
setHeaders({

View file

@ -1,31 +1,21 @@
<script lang="ts">
import type { PageData } from './$types';
import Bandcamp from '$lib/components/Bandcamp.svelte';
import Articles from '$lib/components/Articles.svelte';
import type { Album } from '$lib/types/album';
import type { Article, ArticlePageLoad } from '$lib/types/article';
import Articles from '$lib/components/Articles.svelte';
import Bandcamp from '$lib/components/Bandcamp.svelte';
import type { Album } from '$lib/types/album';
import type { Article, ArticlePageLoad } from '$lib/types/article';
import type { PageData } from './$types';
export let data: PageData;
let albums: Album[];
let articlesData: ArticlePageLoad;
let articles: Article[];
let totalArticles: number;
const { data } = $props();
let albums: Album[] = $derived(data.albums);
let articlesData: ArticlePageLoad = $derived(data.articlesData);
let articles: Article[] = $derived(articlesData.articles);
let totalArticles: number = $derived(articlesData.totalArticles);
$: if (data) {
albums = data.albums;
articlesData = data.articlesData;
}
$: if (articlesData) {
articles = articlesData.articles;
totalArticles = articlesData.totalArticles;
}
const userNames = {
github: 'BradNut',
linkedIn: 'bradley-shellnut',
email: 'bradleyshellnut@pm.me',
};
const userNames = {
github: 'BradNut',
linkedIn: 'bradley-shellnut',
email: 'bradleyshellnut@pm.me',
};
</script>
<div class="home">

View file

@ -9,9 +9,9 @@
import Svelte from '@iconify-icons/simple-icons/svelte';
import TypeScript from '@iconify-icons/simple-icons/typescript';
import LazyImage from '$lib/components/LazyImage.svelte';
import cruise from '$lib/assets/images/cruise.png?as=run:0';
import tortie_derp from '$lib/assets/images/tortie_derp.jpg?as=run';
import orange_derp from '$lib/assets/images/orange_derp.jpg?as=run';
import cruise from '$lib/assets/images/cruise.png?enhanced';
import tortie_derp from '$lib/assets/images/tortie_derp.jpg?enhanced';
import orange_derp from '$lib/assets/images/orange_derp.jpg?enhanced';
import turnip from '$lib/assets/images/turnip.svg';
import CourseCard from './CourseCard.svelte';
import courseData from './course.json';
@ -140,7 +140,8 @@
justify-content: center;
"
>
<LazyImage src={cruise} alt="Clip art of a cruise ship. Cruise icons created by C-mo Box - Flaticon" />
<enhanced:img src={cruise} alt="Clip art of a cruise ship. Cruise icons created by C-mo Box - Flaticon" />
<!-- <LazyImage src={cruise} alt="Clip art of a cruise ship. Cruise icons created by C-mo Box - Flaticon" /> -->
<p class="center">Crusin'</p>
</div>
</div>
@ -148,11 +149,13 @@
<p>Hanging out with these two cats, Turnip and Taco.</p>
<div class="cat-pics">
<figure>
<LazyImage src={tortie_derp} alt="Turnip Cat" />
<enhanced:img src={tortie_derp} alt="Tortie Cat" />
<!-- <LazyImage src={tortie_derp} alt="Turnip Cat" /> -->
<p class="center">Turnip <img class="icon" src={String(turnip)} width="25px" height="25px" alt="Turnip" /></p>
</figure>
<figure>
<LazyImage src={orange_derp} alt="Taco Cat" />
<enhanced:img src={orange_derp} alt="Tortie Cat" />
<!-- <LazyImage src={orange_derp} alt="Taco Cat" /> -->
<p class="center">Taco 🌮</p>
</figure>
</div>

View file

@ -1,5 +1,5 @@
import SocialImageCard from '$lib/components/socialImageCard.svelte';
import { componentToPng } from '$root/lib/renderImage';
import SocialImageCard from "$lib/components/socialImageCard.svelte";
import { componentToPng } from "$root/lib/renderImage";
const height = 630;
const width = 1200;
@ -7,22 +7,27 @@ const width = 1200;
/** @type {import('./$types').RequestHandler} */
export async function GET({ url }) {
try {
const faviconImageName = 'b_shell_nut_favicon.png';
const faviconImageName = "b_shell_nut_favicon.png";
const image = `${new URL(url.origin).href}${faviconImageName}`;
const header = url.searchParams.get('header') ?? undefined;
const page = url.searchParams.get('page') ?? undefined;
const content = url.searchParams.get('content') ?? '';
const header = url.searchParams.get("header") ?? undefined;
const page = url.searchParams.get("page") ?? undefined;
const content = url.searchParams.get("content") ?? "";
// @ts-expect-error: Argument of type 'typeof SocialImageCard__SvelteComponent_' is not assignable to parameter of type 'SvelteComponent<any, any, any>'
return componentToPng(SocialImageCard, {
header,
page,
content,
image,
width: `${width}`,
height: `${height}`,
url: new URL(url.origin).href
}, height, width);
return componentToPng(
SocialImageCard,
{
header,
page,
content,
image,
width: `${width}`,
height: `${height}`,
url: new URL(url.origin).href,
},
height,
width
);
} catch (e) {
console.error(e);
}

View file

@ -3,8 +3,6 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { preprocessMeltUI } from '@melt-ui/pp';
import { mdsvex } from 'mdsvex';
import mdsvexConfig from './mdsvex.config.js';
import relativeImages from 'mdsvex-relative-images';
/** @type {import('@sveltejs/kit').Config} */
const config = {

View file

@ -1,21 +1,45 @@
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
import { imagetools } from '@zerodevx/svelte-img/vite';
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { enhancedImages } from "@sveltejs/enhanced-img";
import { imagetools } from "@zerodevx/svelte-img/vite";
const config: UserConfig = {
export default defineConfig({
plugins: [
enhancedImages(),
sveltekit(),
imagetools({
// By default, directives are `?width=480;1024;1920&format=avif;webp;jpg`
// Now we change it to generate 5 variants instead - `avif/jpg` formats at `640/1280` + LQIP (Now as:run)
profiles: {
run: new URLSearchParams('?w=300;480;640;1024;1920&format=avif;webp;jpg&as=run:64')
}
})
run: new URLSearchParams(
"?w=300;480;640;1024;1920&format=avif;webp;jpg&as=run:64"
),
},
}),
],
esbuild: {
target: "es2022",
},
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
};
include: ["src/**/*.{test,spec}.{js,ts}"],
mockReset: true,
},
css: {
devSourcemap: true,
preprocessorOptions: {
postcss: {
additionalData: `
@custom-media --below_small (width < 400px);
@custom-media --below_med (width < 700px);
@custom-media --below_large (width < 900px);
@custom-media --below_xlarge (width < 1200px);
export default config;
@custom-media --above_small (width > 400px);
@custom-media --above_med (width > 700px);
@custom-media --above_large (width > 900px);
@custom-media --above_xlarge (width > 1200px);
`,
},
},
},
});