fixes for svelte 5 and hetzner deployment

This commit is contained in:
saiminh 2026-01-07 20:41:01 +13:00
parent 2b48498f72
commit d7c2ad8ea0
11 changed files with 704 additions and 275 deletions

2
.npmrc Normal file
View file

@ -0,0 +1,2 @@
legacy-peer-deps=true

798
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"start": "node build",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .", "lint": "prettier --plugin-search-dir . --check . && eslint .",
@ -22,7 +23,7 @@
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0", "eslint-plugin-svelte": "^3.0.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"prettier-plugin-svelte": "^3.0.0", "prettier-plugin-svelte": "^3.0.0",
"sass": "^1.64.2", "sass": "^1.64.2",

View file

@ -1,41 +1,58 @@
type Post = { type Post = {
metadata: { metadata: {
title: string title: string
date: string date?: string
description: string description: string
tags: string[] tags?: string[]
header_bg_image: string header_bg_image?: string
svg: string svg?: string
video: string video?: string
order: number order: number
} }
default: { default: unknown // Component, but we don't need to render it for the list page
render: () => string
}
path: string
Content: string
} }
export const fetchMarkdownPosts = async () => { export const fetchMarkdownPosts = async () => {
// eslint-disable-next-line no-useless-escape try {
const allPostFiles = import.meta.glob('/src/routes/work/md/\*.md') const allPostFiles = import.meta.glob('/src/routes/work/md/*.md', { eager: false })
const iterablePostFiles = Object.entries(allPostFiles) const iterablePostFiles = Object.entries(allPostFiles)
const allPosts = await Promise.all( if (iterablePostFiles.length === 0) {
iterablePostFiles.map(async ([path, resolver]) => { console.warn('No markdown files found')
const postPath = path.slice(11, -3).replace('work/md/','work/') return []
const data: unknown = await resolver() }
const postData = data as Post
const content = postData.default.render() as unknown as { html: string }
return { const allPosts = await Promise.all(
meta: postData.metadata, iterablePostFiles.map(async ([path, resolver]) => {
path: postPath, try {
Content: content.html, // Path will be like '/src/routes/work/md/adidas.md'
} // We want '/work/adidas'
}) const pathWithoutExt = path.slice(0, -3) // Remove '.md'
) const pathParts = pathWithoutExt.split('/')
const slug = pathParts[pathParts.length - 1] // Get 'adidas'
const postPath = `/work/${slug}`
return allPosts const data: unknown = await resolver()
const postData = data as Post
// Only extract metadata - we don't need to render the content for the list page
// The work page only uses metadata (title, svg, tags) and path
return {
meta: postData.metadata,
path: postPath,
}
} catch (error) {
console.error(`Error loading post from ${path}:`, error)
return null
}
})
)
// Filter out any null results from failed imports
return allPosts.filter((post): post is NonNullable<typeof post> => post !== null)
} catch (error) {
console.error('Error in fetchMarkdownPosts:', error)
return []
}
} }

View file

@ -3,11 +3,21 @@
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import Loader from '$lib/components/Loader.svelte'; import Loader from '$lib/components/Loader.svelte';
import { navigating, page } from '$app/stores'; import { navigating, page } from '$app/stores';
import { onMount } from 'svelte';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
interface LayoutData { interface LayoutData {
pathname: string; pathname: string;
} }
let { data, children }: { data: LayoutData, children: Snippet } = $props(); let { data, children }: { data: LayoutData, children: Snippet } = $props();
// Track pathname changes - if pathname changes but navigating is still true,
// navigation might be stuck (though this shouldn't happen normally)
let lastPathname = $state(data.pathname);
$effect(() => {
if (data.pathname !== lastPathname) {
lastPathname = data.pathname;
}
});
</script> </script>
<svelte:head> <svelte:head>

View file

@ -142,8 +142,10 @@
bulgefilter.center = new PIXI.Point(mouse.x, 0.5); bulgefilter.center = new PIXI.Point(mouse.x, 0.5);
}) })
let scrollTriggerTweens: Array<GSAPTween> = [];
function createScrollTrigger() { function createScrollTrigger() {
gsap.to([textRows[0], textRows[1]], { const tween1 = gsap.to([textRows[0], textRows[1]], {
y: '+=' + -textRows[0][0].height * 0.9, y: '+=' + -textRows[0][0].height * 0.9,
duration: 1, duration: 1,
ease: 'none', ease: 'none',
@ -155,7 +157,7 @@
// markers: true, // markers: true,
} }
}) })
gsap.to([textRows[2], textRows[3]], { const tween2 = gsap.to([textRows[2], textRows[3]], {
y: '+=' + textRows[0][0].height * 0.9, y: '+=' + textRows[0][0].height * 0.9,
duration: 1, duration: 1,
ease: 'none', ease: 'none',
@ -167,11 +169,22 @@
// markers: true, // markers: true,
} }
}) })
scrollTriggerTweens.push(tween1, tween2);
} }
return () => { return () => {
app.destroy(true, { children: true, texture: true, baseTexture: true }); // IMPORTANT: Kill ScrollTrigger and GSAP animations FIRST,
// before destroying PixiJS objects they reference
scrollTriggerTweens.forEach( tween => {
if (tween.scrollTrigger) {
tween.scrollTrigger.kill();
}
tween.kill();
});
// Kill all other tweens
tweens.forEach( tween => tween.kill() ); tweens.forEach( tween => tween.kill() );
// Now safe to destroy PixiJS app
app.destroy(true, { children: true, texture: true, baseTexture: true });
} }
}) })

View file

@ -1,12 +1,28 @@
import { fetchMarkdownPosts } from '../../lib/importMarkdown' import { fetchMarkdownPosts } from '../../lib/importMarkdown'
export async function load() { export async function load() {
const posts = await fetchMarkdownPosts() try {
if (!posts) console.error('No posts found') const posts = await fetchMarkdownPosts()
if (!posts) {
console.error('No posts found')
return {
posts: [],
title: 'Work References',
description: 'A few of the projects I have worked on'
}
}
return { return {
posts, posts,
title: 'Work References', title: 'Work References',
description: 'A few of the projects I have worked on' description: 'A few of the projects I have worked on'
}
} catch (error) {
console.error('Error loading work posts:', error)
return {
posts: [],
title: 'Work References',
description: 'A few of the projects I have worked on'
}
} }
} }

View file

@ -4,9 +4,9 @@
import { SplitText } from 'gsap/SplitText'; import { SplitText } from 'gsap/SplitText';
let { data } = $props(); let { data } = $props();
const orderedPosts = data.posts.sort((a, b) => { const orderedPosts = $derived.by(() => (data.posts || []).sort((a, b) => {
return a.meta.order - b.meta.order; return a.meta.order - b.meta.order;
}); }));
onMount(() => { onMount(() => {
document.querySelectorAll('.workclone')?.forEach(clone => { document.querySelectorAll('.workclone')?.forEach(clone => {

View file

@ -3,7 +3,8 @@ export async function load( { params }: { params: { slug: string }} ){
const post = await import(`../md/${params.slug}.md`) const post = await import(`../md/${params.slug}.md`)
const { title = '', date = '', header_bg_image = '', svg = '', video = '', tags = [], reference = '', referenceName = '', tasks = [], description = [], images = [], agency = '', agencyName = '' } = post.metadata const { title = '', date = '', header_bg_image = '', svg = '', video = '', tags = [], reference = '', referenceName = '', tasks = [], description = [], images = [], agency = '', agencyName = '' } = post.metadata
const Content = post.default.render() // Don't pass the component - it's not serializable
// Import it directly in the page component instead
return { return {
title, title,
@ -12,7 +13,7 @@ export async function load( { params }: { params: { slug: string }} ){
svg, svg,
video, video,
tags, tags,
Content, slug: params.slug,
reference, reference,
referenceName, referenceName,
tasks, tasks,
@ -23,5 +24,6 @@ export async function load( { params }: { params: { slug: string }} ){
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
throw error
} }
} }

View file

@ -5,9 +5,18 @@
import { CldImage } from 'svelte-cloudinary'; import { CldImage } from 'svelte-cloudinary';
let { data } = $props(); let { data } = $props();
let visible = false; let visible = $state(false);
let Content = $state<any>(null);
onMount(() => { // Import the markdown component dynamically and set up animations
onMount(async () => {
// Load the markdown component
try {
const post = await import(`../md/${data.slug}.md`);
Content = post.default;
} catch (error) {
console.error('Error loading markdown component:', error);
}
gsap.registerPlugin(ScrollToPlugin); gsap.registerPlugin(ScrollToPlugin);
@ -136,7 +145,7 @@
{:else} {:else}
<CldImage <CldImage
src={image} src={image}
alt="{data.title}" alt={data.title}
sizes="(min-width: 768px) 67vw, 90vw" sizes="(min-width: 768px) 67vw, 90vw"
width={1400} width={1400}
height={840} height={840}
@ -178,7 +187,9 @@
{/if} {/if}
</div> </div>
<div class="work-content-text"> <div class="work-content-text">
{@html data.Content.html} {#if Content}
<svelte:component this={Content} />
{/if}
</div> </div>
</div> </div>
{/if} {/if}

View file

@ -1,6 +1,6 @@
// import adapter from '@sveltejs/adapter-auto'; // import adapter from '@sveltejs/adapter-auto';
// import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-node';
import adapter from '@sveltejs/adapter-netlify'; // import adapter from '@sveltejs/adapter-netlify';
import preprocess from 'svelte-preprocess'; import preprocess from 'svelte-preprocess';
import { mdsvex } from 'mdsvex' import { mdsvex } from 'mdsvex'
@ -20,13 +20,12 @@ const config = {
// If your environment is not supported or you settled on a specific environment, switch out the adapter. // If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({ adapter: adapter({
// if true, will create a Netlify Edge Function rather // Output directory for the built server
// than using standard Node-based functions out: 'build',
edge: false, // Precompress output files (gzip and brotli)
// if true, will split your app into multiple functions precompress: true,
// instead of creating a single one for the entire app. // Enable polyfills for Node.js built-in modules
// if `edge` is true, this option cannot be used polyfills: true
split: false
}), }),
csp: { csp: {
mode: 'auto', mode: 'auto',