Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: @unhead/svelte #452

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/vite-ssr-svelte/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<!--app-head-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/entry-client.ts"></script>
</body>
</html>
31 changes: 31 additions & 0 deletions examples/vite-ssr-svelte/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "vite-svelte-ts-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "node server",
"build": "npm run build:client && npm run build:server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.ts --outDir dist/server",
"preview": "cross-env NODE_ENV=production node server",
"check": "svelte-check"
},
"dependencies": {
"compression": "^1.7.5",
"express": "^5.0.1",
"sirv": "^3.0.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@tsconfig/svelte": "^5.0.4",
"@types/express": "^5.0.0",
"@types/node": "^22.10.0",
"cross-env": "^7.0.3",
"svelte": "^5.2.9",
"svelte-check": "^4.1.0",
"tslib": "^2.8.1",
"typescript": "~5.7.2",
"vite": "^6.0.1"
}
}
91 changes: 91 additions & 0 deletions examples/vite-ssr-svelte/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import fs from 'node:fs/promises'
import express from 'express'
import { createHead, renderSSRHead } from "unhead/server"
import { unheadCtx } from "unhead"

// Constants
const isProduction = process.env.NODE_ENV === 'production'
const port = process.env.PORT || 5173
const base = process.env.BASE || '/'

// Cached production assets
const templateHtml = isProduction
? await fs.readFile('./dist/client/index.html', 'utf-8')
: ''

// Create http server
const app = express()

// Add Vite or respective production middlewares
/** @type {import('vite').ViteDevServer | undefined} */
let vite
if (!isProduction) {
const { createServer } = await import('vite')
vite = await createServer({
server: { middlewareMode: true },
appType: 'custom',
base,
})
app.use(vite.middlewares)
} else {
const compression = (await import('compression')).default
const sirv = (await import('sirv')).default
app.use(compression())
app.use(base, sirv('./dist/client', { extensions: [] }))
}

// Serve HTML
app.use('*all', async (req, res) => {
try {
const url = req.originalUrl.replace(base, '')

/** @type {string} */
let template
/** @type {import('./src/entry-server.ts').render} */
let render
if (!isProduction) {
// Always read fresh template in development
template = await fs.readFile('./index.html', 'utf-8')
template = await vite.transformIndexHtml(url, template)
render = (await vite.ssrLoadModule('/src/entry-server.ts')).render
} else {
template = templateHtml
render = (await import('./dist/server/entry-server.js')).render
}

const unhead = createHead()
unhead.push({
htmlAttrs: { lang: 'en' },
bodyAttrs: { class: 'test' },
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/vite.svg' },
]
})
unheadCtx.set(unhead, true)
const rendered = await render(url)
unheadCtx.unset()
const headTags = await renderSSRHead(unhead)

const html = template
.replace(`<!--app-head-->`, rendered.head ?? '' + '' + headTags.headTags)
.replace(`<!--app-html-->`, rendered.html ?? '')
.replace('<html>', `<html ${headTags.htmlAttrs}>`)
.replace('<body>', `<body ${headTags.bodyAttrs}>${headTags.bodyTagsOpen}`)
.replace('</body>', `${headTags.bodyTags}</body>`)

res.status(200).set({ 'Content-Type': 'text/html' }).send(html)
} catch (e) {
vite?.ssrFixStacktrace(e)
console.log(e.stack)
res.status(500).end(e.stack)
}
})

// Start http server
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`)
})
45 changes: 45 additions & 0 deletions examples/vite-ssr-svelte/src/App.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script lang="ts">
import svelteLogo from './assets/svelte.svg'
import Counter from './lib/Counter.svelte'
</script>

<main>
<div>
<a href="https://vite.dev" target="_blank" rel="noreferrer">
<img src="/vite.svg" class="logo" alt="Vite Logo" />
</a>
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
</a>
</div>
<h1>Vite + Svelte</h1>

<div class="card">
<Counter />
</div>

<p>
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank" rel="noreferrer">SvelteKit</a>, the official Svelte app framework powered by Vite!
</p>

<p class="read-the-docs">
Click on the Vite and Svelte logos to learn more
</p>
</main>

<style>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.svelte:hover {
filter: drop-shadow(0 0 2em #ff3e00aa);
}
.read-the-docs {
color: #888;
}
</style>
80 changes: 80 additions & 0 deletions examples/vite-ssr-svelte/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

.card {
padding: 2em;
}

#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
1 change: 1 addition & 0 deletions examples/vite-ssr-svelte/src/assets/svelte.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions examples/vite-ssr-svelte/src/entry-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import './app.css'
import { hydrate } from 'svelte'
import App from './App.svelte'
import { createHead } from 'unhead/client'

const unhead = createHead()
unhead.push({
title: 'test',
})

const map = new Map()
map.set('unhead', unhead)

hydrate(App, {
target: document.getElementById('app')!,
context: map
})
6 changes: 6 additions & 0 deletions examples/vite-ssr-svelte/src/entry-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { render as _render } from 'svelte/server'
import App from './App.svelte'

export function render(_url: string) {
return _render(App)
}
19 changes: 19 additions & 0 deletions examples/vite-ssr-svelte/src/lib/Counter.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import { useHead } from 'unhead'

let count: number = $state(0)
const increment = () => {
count += 1
}

useHead({
title: () => {
console.log('resolving')
return `${count} - Counter`
}
})
</script>

<button onclick={increment}>
count is {count}
</button>
2 changes: 2 additions & 0 deletions examples/vite-ssr-svelte/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
5 changes: 5 additions & 0 deletions examples/vite-ssr-svelte/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'

export default {
preprocess: vitePreprocess(),
}
21 changes: 21 additions & 0 deletions examples/vite-ssr-svelte/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true,
"moduleDetection": "force"
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}
12 changes: 12 additions & 0 deletions examples/vite-ssr-svelte/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
7 changes: 7 additions & 0 deletions examples/vite-ssr-svelte/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

// https://vite.dev/config/
export default defineConfig({
plugins: [svelte()],
})
Loading
Loading