Skip to content

Commit 0700a73

Browse files
authored
refactor: use h3 instead of express (#6)
* dfsdfdsfsfs * fsdfsdfsd * fsdfsdfsd * todo: fix redirect * export useH3Event remove "custom" redirect implementation * docs: update lincense year * refactor: replace cheerio with node-html-parser (#5) * update all packages * remove unused counter, rearrange imports * fdsfsfsdfsd * also update peerDependencies * ci(publish): update actions version, use node 20, add packageManager to package.json * add ci workflow run unit-tests * setup pnpm before node * fix(ci): setup pnpm before node in publish workflow * update README * add h3 playground * fix(playgrounds): add missing build targets this fixes the build error because of top level await * refactor: use @unhead/ssr to render SSR head * remove app.use(router) usage * update deps * update node to 22, update every deps * remove console.log from client build * remove build.target array from h3 vite config
1 parent 0a20253 commit 0700a73

31 files changed

+1511
-1336
lines changed

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
unit-test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: pnpm/action-setup@v4
18+
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: 20
22+
cache: 'pnpm'
23+
24+
- run: pnpm install
25+
26+
- name: Run unit tests
27+
run: pnpm run unit-test

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20
1+
22

README.md

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Vite plugin to develop Vue SSR apps
77
* State management
88
* Teleports
99
* [Unhead](https://unhead.unjs.io) support
10+
* Based on [H3](https://h3.unjs.io)
1011

1112
## Quick Setup
1213

@@ -80,6 +81,8 @@ const routes = [
8081
export default vueSSR(App, { routes })
8182
```
8283

84+
### State management
85+
8386
Pinia/Vuex is supported by using the `app` and `state` property inside the callback.
8487

8588
```typescript
@@ -96,11 +99,21 @@ export default vueSSR(App, { routes }, ({ app, state }) => {
9699
})
97100
```
98101

99-
> The state will be persisted on `window.__INITIAL_STATE__` property and serialized using `@nuxt/devalue`
102+
> The state will be persisted on `window.__INITIAL_STATE__` property and serialized using `devalue`
103+
104+
### Router
100105

101106
It's possible to make changes to the router, use the `router` property in the callback.
102107

103108
```typescript
109+
const routes = [
110+
{
111+
path: '/',
112+
name: 'counter',
113+
component: Counter,
114+
},
115+
]
116+
104117
export default vueSSR(App, { routes }, ({ router }) => {
105118
router.beforeEach(async (to, from) => {
106119
if (
@@ -113,28 +126,67 @@ export default vueSSR(App, { routes }, ({ router }) => {
113126
})
114127
```
115128

116-
The Express request and response objects are accessible from the callback. Make sure to wrap them in `import.meta.env.SSR`.
129+
To customize the router, just return the router instance.
130+
131+
The `routes` parameter is omitted, because we create a fresh router instance in the method.
132+
133+
```typescript
134+
export default vueSSR(App, {}, async ({ app }) => {
135+
const router = createRouter({
136+
history: import.meta.env.SSR ? createMemoryHistory('/') : createWebHistory('/'),
137+
routes: [
138+
{
139+
path: '/',
140+
name: 'counter',
141+
component: Counter,
142+
},
143+
],
144+
})
145+
146+
return {
147+
router,
148+
}
149+
})
150+
```
151+
152+
### H3
153+
154+
H3 is the underlaying server. During development it injects as an middleware.
155+
156+
The `event` param is used to access the H3 composables.
157+
158+
> NOTE: only works in SSR
117159
118160
```typescript
119-
export default vueSSR(App, { routes }, ({ request, response }) => {
161+
import { getRequestURL } from 'h3'
162+
163+
export default vueSSR(App, { routes }, ({ event }) => {
120164
if (import.meta.env.SSR) {
121-
console.log(request?.originalUrl)
165+
console.log(getRequestURL(event)) // "https://example.com/path"
122166
}
167+
168+
console.log(event) // undefined
123169
})
124170
```
125171

126-
Or use `useSSRContext`.
172+
In a Vue component, use the `useH3Event()` composable
127173

128174
```typescript
129-
const { request, response } = useSSRContext()
175+
import { useH3Event } from 'vite-plugin-vue-ssr'
176+
import { getRequestURL } from 'h3'
130177

131178
if (import.meta.env.SSR) {
132-
console.log(request?.originalUrl)
179+
const event = useH3Event()
180+
181+
console.log(getRequestURL(event)) // "https://example.com/path"
133182
}
134183
```
135184

136-
Using Teleport is supported, but requires a little bit of setup. Targeting `body` is not supported, use `#teleports` instead.
185+
See [https://h3.unjs.io/utils](https://h3.unjs.io/utils) for more composables.
186+
187+
### Teleports
137188

189+
Using `Teleport` is supported, but requires a little bit of setup. Targeting `body` is not supported (in SSR), use `#teleports` instead.
138190

139191
```html
140192
<template>

build.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import { defineBuildConfig } from 'unbuild'
33
export default defineBuildConfig({
44
entries: [
55
{
6-
input: './src/index.ts',
7-
outDir: './dist',
6+
input: 'src/index.ts',
7+
outDir: 'dist',
88
},
99
{
1010
input: 'src/plugin/index.ts',
1111
outDir: 'dist/plugin',
12-
}
12+
},
1313
],
14-
externals: ['express', /@unhead/],
14+
externals: [/@unhead/],
1515
clean: true,
1616
declaration: true,
1717
rollup: {

package.json

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vite-plugin-vue-ssr",
33
"version": "0.12.2",
4-
"packageManager": "pnpm@9.6.0",
4+
"packageManager": "pnpm@9.15.0",
55
"type": "module",
66
"license": "MIT",
77
"description": "Vite plugin to develop Vue SSR apps",
@@ -28,41 +28,40 @@
2828
"scripts": {
2929
"dev": "unbuild --stub",
3030
"build": "unbuild",
31-
"test": "vitest"
31+
"unit-test": "vitest"
3232
},
3333
"keywords": [
3434
"vue",
3535
"ssr",
3636
"vite"
3737
],
3838
"dependencies": {
39-
"@babel/generator": "^7.25.0",
40-
"@babel/parser": "^7.25.0",
41-
"@babel/traverse": "^7.25.1",
42-
"@babel/types": "^7.25.0",
43-
"@types/express": "^4.17.21",
44-
"@unhead/ssr": "^1.9.16",
45-
"cookie": "^0.6.0",
46-
"cookie-parser": "^1.4.6",
39+
"@babel/generator": "^7.26.3",
40+
"@babel/parser": "^7.26.3",
41+
"@babel/traverse": "^7.26.4",
42+
"@babel/types": "^7.26.3",
43+
"@unhead/ssr": "^1.11.14",
4744
"node-html-parser": "^6.1.13"
4845
},
4946
"devDependencies": {
50-
"@types/node": "^20.14.13",
51-
"@unhead/vue": "^1.9.16",
52-
"devalue": "^5.0.0",
53-
"typescript": "^5.5.0",
47+
"@types/node": "^22.10.2",
48+
"@unhead/vue": "^1.11.14",
49+
"devalue": "^5.1.1",
50+
"h3": "^1.13.0",
51+
"typescript": "^5.7.2",
5452
"unbuild": "^2.0.0",
55-
"vite": "^5.3.5",
56-
"vitest": "^1.6.0",
57-
"vue": "^3.4.34",
58-
"vue-router": "^4.4.0"
53+
"vite": "^6.0.3",
54+
"vitest": "^2.1.8",
55+
"vue": "^3.5.13",
56+
"vue-router": "^4.5.0"
5957
},
6058
"peerDependencies": {
61-
"@unhead/vue": "^1.9.16",
62-
"devalue": "^5.0.0",
63-
"vite": "^5.3.5",
64-
"vue": "^3.4.34",
65-
"vue-router": "^4.4.0"
59+
"@unhead/vue": "^1.11.14",
60+
"devalue": "^5.1.1",
61+
"h3": "^1.13.0",
62+
"vite": "^6.0.3",
63+
"vue": "^3.5.13",
64+
"vue-router": "^4.5.0"
6665
},
6766
"peerDependenciesMeta": {
6867
"devalue": {

playground/h3/env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

playground/h3/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<link rel="icon" href="/favicon.ico">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Vite App</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

playground/h3/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "h3-example",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "pnpm run build:client && pnpm run build:server",
9+
"build:client": "vite build --ssrManifest --outDir dist/client",
10+
"build:server": "vite build --ssr src/main.ts --outDir dist/server"
11+
},
12+
"dependencies": {
13+
"@unhead/vue": "^1.11.14",
14+
"h3": "^1.13.0",
15+
"serve-static": "^1.16.2",
16+
"vue": "^3.5.13",
17+
"vue-router": "^4.5.0"
18+
},
19+
"devDependencies": {
20+
"@tsconfig/node22": "^22.0.0",
21+
"@types/node": "^22.10.2",
22+
"@vitejs/plugin-vue": "^5.2.1",
23+
"@vue/tsconfig": "^0.7.0",
24+
"typescript": "^5.7.2",
25+
"vite": "^6.0.3",
26+
"vite-plugin-vue-ssr": "workspace:*"
27+
}
28+
}

playground/h3/public/favicon.ico

4.19 KB
Binary file not shown.

playground/h3/server.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { readFileSync } from 'node:fs'
2+
import { resolve, dirname } from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
import { createServer } from 'node:http'
5+
import { createApp, defineEventHandler, fromNodeMiddleware, toNodeListener } from 'h3'
6+
import { generateTemplate } from 'vite-plugin-vue-ssr/plugin'
7+
import serveStatic from 'serve-static'
8+
9+
const __dirname = dirname(fileURLToPath(import.meta.url))
10+
11+
const main = (await import(resolve(__dirname, './dist/server/main.js'))).default
12+
13+
const template = readFileSync(resolve('dist/client/index.html'), 'utf-8')
14+
15+
const manifest = JSON.parse(
16+
readFileSync(resolve('dist/client/.vite/ssr-manifest.json'), 'utf-8')
17+
)
18+
19+
const app = createApp()
20+
21+
app.use(fromNodeMiddleware(serveStatic(resolve('dist/client'), { index: false })))
22+
app.use(defineEventHandler(async (event) => {
23+
const url = event.node.req.originalUrl ?? '/'
24+
25+
const html = await generateTemplate(main, url, template, event, manifest)
26+
27+
return html
28+
}))
29+
30+
createServer(toNodeListener(app)).listen(3000, () => console.log('server listening on 3000'))

0 commit comments

Comments
 (0)