vue-ssr-lite 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/apollo/SsrApolloAdapter.js +184 -0
- package/apollo/index.d.ts +50 -0
- package/bin/vue-ssr-lite.mjs +83 -0
- package/engine/.fuse_hidden0000002d00000001 +166 -0
- package/engine/SsrEngine.js +139 -0
- package/engine/SsrEngineHostPolicy.js +132 -0
- package/engine/SsrEngineHtml.js +166 -0
- package/engine/index.d.ts +86 -0
- package/engine/index.js +26 -0
- package/index.d.ts +104 -0
- package/index.js +40 -0
- package/package.json +91 -0
- package/runtime/SsrRuntimeApp.js +94 -0
- package/runtime/SsrRuntimeClient.js +72 -0
- package/runtime/SsrRuntimeContext.js +44 -0
- package/runtime/SsrRuntimeServer.js +63 -0
- package/runtime/client.d.ts +20 -0
- package/runtime/server.d.ts +26 -0
- package/server/SsrServer.js +600 -0
- package/server/index.d.ts +91 -0
- package/server/index.js +14 -0
- package/vite/SsrVitePlugin.js +218 -0
- package/vite/index.d.ts +37 -0
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-ssr-lite",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Low-configuration Vue 3 + Vite server-side rendering: engine, standalone server, hydration runtime, Apollo SSR adapter, and Vite integration with virtual entries.",
|
|
5
|
+
"author": "safdar-azeem",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.0.0"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"vue-ssr-lite": "./bin/vue-ssr-lite.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"engine",
|
|
17
|
+
"server",
|
|
18
|
+
"runtime",
|
|
19
|
+
"apollo",
|
|
20
|
+
"vite",
|
|
21
|
+
"index.js",
|
|
22
|
+
"index.d.ts"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./index.d.ts",
|
|
27
|
+
"default": "./index.js"
|
|
28
|
+
},
|
|
29
|
+
"./engine": {
|
|
30
|
+
"types": "./engine/index.d.ts",
|
|
31
|
+
"default": "./engine/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./server": {
|
|
34
|
+
"types": "./server/index.d.ts",
|
|
35
|
+
"default": "./server/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./runtime/server": {
|
|
38
|
+
"types": "./runtime/server.d.ts",
|
|
39
|
+
"default": "./runtime/SsrRuntimeServer.js"
|
|
40
|
+
},
|
|
41
|
+
"./runtime/client": {
|
|
42
|
+
"types": "./runtime/client.d.ts",
|
|
43
|
+
"default": "./runtime/SsrRuntimeClient.js"
|
|
44
|
+
},
|
|
45
|
+
"./apollo": {
|
|
46
|
+
"types": "./apollo/index.d.ts",
|
|
47
|
+
"default": "./apollo/SsrApolloAdapter.js"
|
|
48
|
+
},
|
|
49
|
+
"./vite": {
|
|
50
|
+
"types": "./vite/index.d.ts",
|
|
51
|
+
"default": "./vite/SsrVitePlugin.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@apollo/client": ">=3.10.0 <4",
|
|
56
|
+
"@vue/server-renderer": "^3.5.0",
|
|
57
|
+
"vite": ">=5.0.0",
|
|
58
|
+
"vue": "^3.5.0",
|
|
59
|
+
"vue-router": ">=4.4.0 <6"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@apollo/client": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"vite": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"vue-router": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"test": "node --test tests/*.test.mjs",
|
|
74
|
+
"preversion": "npm test",
|
|
75
|
+
"prepublishOnly": "npm test",
|
|
76
|
+
"release:patch": "npm version patch && npm publish",
|
|
77
|
+
"release:minor": "npm version minor && npm publish",
|
|
78
|
+
"release:major": "npm version major && npm publish"
|
|
79
|
+
},
|
|
80
|
+
"repository": {
|
|
81
|
+
"type": "git",
|
|
82
|
+
"url": "git+https://github.com/safdar-azeem/vue-ssr-lite.git"
|
|
83
|
+
},
|
|
84
|
+
"keywords": [
|
|
85
|
+
"vue",
|
|
86
|
+
"vite",
|
|
87
|
+
"ssr",
|
|
88
|
+
"hydration",
|
|
89
|
+
"apollo"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// SsrRuntimeApp — shared application assembly for server render and browser
|
|
2
|
+
// hydration. Both sides run this exact function with the same application
|
|
3
|
+
// definition, so the server and client component trees cannot drift.
|
|
4
|
+
|
|
5
|
+
import { createSSRApp } from 'vue'
|
|
6
|
+
import { SSR_CONTEXT_KEY, createSsrRequestContext } from './SsrRuntimeContext.js'
|
|
7
|
+
|
|
8
|
+
const resolveConfig = (definition, config) => {
|
|
9
|
+
if (config != null) return config
|
|
10
|
+
if (typeof definition.config === 'function') return definition.config()
|
|
11
|
+
return definition.config ?? {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the app, optional router, optional Apollo clients, and request
|
|
16
|
+
* context from one application definition.
|
|
17
|
+
*
|
|
18
|
+
* Integrations are loaded on demand: vue-router only when the definition
|
|
19
|
+
* declares routes or a router factory, the Apollo adapter only when the
|
|
20
|
+
* definition declares an apollo block. Applications without them never load
|
|
21
|
+
* either library through this path.
|
|
22
|
+
*/
|
|
23
|
+
export const createSsrRuntimeApp = async (definition, options) => {
|
|
24
|
+
const { server, url, host, cookie, state } = options
|
|
25
|
+
const config = resolveConfig(definition, options.config)
|
|
26
|
+
const context = createSsrRequestContext({
|
|
27
|
+
url,
|
|
28
|
+
host,
|
|
29
|
+
cookie,
|
|
30
|
+
config,
|
|
31
|
+
state,
|
|
32
|
+
server,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const app = createSSRApp(definition.root)
|
|
36
|
+
app.provide(SSR_CONTEXT_KEY, context)
|
|
37
|
+
|
|
38
|
+
let router = null
|
|
39
|
+
if (typeof definition.router === 'function') {
|
|
40
|
+
router = await definition.router({ server, context })
|
|
41
|
+
app.use(router)
|
|
42
|
+
} else if (Array.isArray(definition.routes)) {
|
|
43
|
+
const { createRouter, createMemoryHistory, createWebHistory } = await import(
|
|
44
|
+
'vue-router'
|
|
45
|
+
)
|
|
46
|
+
router = createRouter({
|
|
47
|
+
history: server ? createMemoryHistory() : createWebHistory(),
|
|
48
|
+
routes: definition.routes,
|
|
49
|
+
...(definition.scrollBehavior
|
|
50
|
+
? { scrollBehavior: definition.scrollBehavior }
|
|
51
|
+
: {}),
|
|
52
|
+
})
|
|
53
|
+
app.use(router)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let apollo = null
|
|
57
|
+
if (definition.apollo) {
|
|
58
|
+
const { createSsrApolloClients } = await import(
|
|
59
|
+
'../apollo/SsrApolloAdapter.js'
|
|
60
|
+
)
|
|
61
|
+
apollo = createSsrApolloClients(definition.apollo, {
|
|
62
|
+
server,
|
|
63
|
+
cookie: context.cookie,
|
|
64
|
+
config,
|
|
65
|
+
state: context.state,
|
|
66
|
+
context,
|
|
67
|
+
})
|
|
68
|
+
context.apollo = apollo.defaultClient
|
|
69
|
+
definition.apollo.install?.(app, apollo.clients, context)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof definition.setup === 'function') {
|
|
73
|
+
await definition.setup({
|
|
74
|
+
app,
|
|
75
|
+
router,
|
|
76
|
+
context,
|
|
77
|
+
apollo: apollo?.defaultClient ?? null,
|
|
78
|
+
apolloClients: apollo?.clients ?? null,
|
|
79
|
+
server,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
app,
|
|
85
|
+
router,
|
|
86
|
+
context,
|
|
87
|
+
config,
|
|
88
|
+
/** Assemble the final hydration state (Apollo extraction included). */
|
|
89
|
+
extractState: () => {
|
|
90
|
+
if (apollo) apollo.extractInto(context.state)
|
|
91
|
+
return context.state
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SsrRuntimeClient — browser hydration bootstrap.
|
|
2
|
+
//
|
|
3
|
+
// Reads the serialized config and state globals, removes server-rendered head
|
|
4
|
+
// tags (the client-side head/SEO layer re-creates them), rebuilds the exact
|
|
5
|
+
// same application from the shared definition, replays the current route,
|
|
6
|
+
// hydrates, and cleans the globals only after a successful mount so async
|
|
7
|
+
// boundaries keep access while they resolve.
|
|
8
|
+
|
|
9
|
+
import { createSsrRuntimeApp } from './SsrRuntimeApp.js'
|
|
10
|
+
|
|
11
|
+
const DEFAULTS = {
|
|
12
|
+
configKey: '__SSR_CONFIG__',
|
|
13
|
+
stateKey: '__SSR_STATE__',
|
|
14
|
+
headAttribute: 'data-ssr-head',
|
|
15
|
+
mountSelector: '#app',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const hydrateSsrApp = async (definition, options = {}) => {
|
|
19
|
+
const { configKey, stateKey, headAttribute, mountSelector } = {
|
|
20
|
+
...DEFAULTS,
|
|
21
|
+
...definition.hydration,
|
|
22
|
+
...options,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const config = window[configKey]
|
|
26
|
+
const state = window[stateKey] ?? {}
|
|
27
|
+
|
|
28
|
+
document.head
|
|
29
|
+
.querySelectorAll(`[${headAttribute}]`)
|
|
30
|
+
.forEach((element) => element.remove())
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const runtime = await createSsrRuntimeApp(definition, {
|
|
34
|
+
server: false,
|
|
35
|
+
url: window.location.href,
|
|
36
|
+
host: window.location.host,
|
|
37
|
+
config,
|
|
38
|
+
state,
|
|
39
|
+
})
|
|
40
|
+
const { app, router } = runtime
|
|
41
|
+
|
|
42
|
+
// Concise, structured runtime error reporting (kept in production).
|
|
43
|
+
app.config.errorHandler = (error, instance, info) => {
|
|
44
|
+
const component =
|
|
45
|
+
instance?.$?.type?.__name ?? instance?.$?.type?.name ?? 'anonymous'
|
|
46
|
+
console.error(`[vue-ssr-lite] error in <${component}> (${info}):`, error)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (router) {
|
|
50
|
+
await router.push(
|
|
51
|
+
`${window.location.pathname}${window.location.search}${window.location.hash}`,
|
|
52
|
+
)
|
|
53
|
+
await router.isReady()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
app.mount(mountSelector)
|
|
57
|
+
|
|
58
|
+
// Clean globals only after a successful mount. The runtime captured the
|
|
59
|
+
// state object by reference, so async setup keeps access while
|
|
60
|
+
// <Suspense> resolves.
|
|
61
|
+
delete window[configKey]
|
|
62
|
+
delete window[stateKey]
|
|
63
|
+
|
|
64
|
+
return runtime
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(
|
|
67
|
+
'[vue-ssr-lite] Hydration bootstrap failed; the server-rendered page may not be interactive:',
|
|
68
|
+
error,
|
|
69
|
+
)
|
|
70
|
+
throw error
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SsrRuntimeContext — the per-request (and per-hydration) application context.
|
|
2
|
+
//
|
|
3
|
+
// One context is created for every server request and once in the browser at
|
|
4
|
+
// hydration. Nothing here is module-scope: concurrent SSR requests can never
|
|
5
|
+
// share URL, state, head, status, or data clients.
|
|
6
|
+
|
|
7
|
+
import { inject, ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
export const SSR_CONTEXT_KEY = Symbol('vue-ssr-lite:context')
|
|
10
|
+
|
|
11
|
+
export const createSsrRequestContext = ({
|
|
12
|
+
url,
|
|
13
|
+
host,
|
|
14
|
+
cookie,
|
|
15
|
+
config,
|
|
16
|
+
state,
|
|
17
|
+
server,
|
|
18
|
+
}) => ({
|
|
19
|
+
/** Full request URL (server) or current location (browser). */
|
|
20
|
+
url: url instanceof URL ? url : new URL(url),
|
|
21
|
+
host,
|
|
22
|
+
/** Allowlist-filtered Cookie header. Server only; undefined in the browser. */
|
|
23
|
+
cookie: server ? cookie : undefined,
|
|
24
|
+
/** Serializable public configuration. Optional; empty object when unused. */
|
|
25
|
+
config: config ?? {},
|
|
26
|
+
/** Hydration state bag. Consumers may write fields; Apollo state is managed. */
|
|
27
|
+
state: state ?? {},
|
|
28
|
+
/** Head payload ref rendered into the HTML document on the server. */
|
|
29
|
+
head: ref(null),
|
|
30
|
+
/** Mutable response controls honored by the engine. */
|
|
31
|
+
response: { statusCode: 200, redirect: null },
|
|
32
|
+
server: Boolean(server),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
/** Typed access to the request context from any component or composable. */
|
|
36
|
+
export const useSsrContext = () => {
|
|
37
|
+
const context = inject(SSR_CONTEXT_KEY, null)
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'vue-ssr-lite: the SSR request context is not installed. Components using useSsrContext must render inside an app created by vue-ssr-lite.',
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
return context
|
|
44
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SsrRuntimeServer — server-side render orchestration.
|
|
2
|
+
//
|
|
3
|
+
// createSsrRender turns an application definition into the render function
|
|
4
|
+
// the engine calls per request: fresh app, route replay and readiness,
|
|
5
|
+
// render-to-string with teleport capture, state extraction, and status
|
|
6
|
+
// resolution. This file is the server half of the runtime and is only ever
|
|
7
|
+
// evaluated inside the SSR bundle.
|
|
8
|
+
|
|
9
|
+
import { renderToString } from '@vue/server-renderer'
|
|
10
|
+
import { createSsrRuntimeApp } from './SsrRuntimeApp.js'
|
|
11
|
+
|
|
12
|
+
export const createSsrRender = (definition) => {
|
|
13
|
+
return async (request) => {
|
|
14
|
+
const url = new URL(request.url)
|
|
15
|
+
const runtime = await createSsrRuntimeApp(definition, {
|
|
16
|
+
server: true,
|
|
17
|
+
url,
|
|
18
|
+
host: request.host,
|
|
19
|
+
cookie: request.cookie,
|
|
20
|
+
config: request.config,
|
|
21
|
+
state: {},
|
|
22
|
+
})
|
|
23
|
+
const { app, router, context } = runtime
|
|
24
|
+
|
|
25
|
+
if (router) {
|
|
26
|
+
await router.push(`${url.pathname}${url.search}${url.hash}`)
|
|
27
|
+
await router.isReady()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ssrContext = {}
|
|
31
|
+
const html = await renderToString(app, ssrContext)
|
|
32
|
+
const state = runtime.extractState()
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
html,
|
|
36
|
+
head: context.head.value ?? definition.head ?? null,
|
|
37
|
+
state,
|
|
38
|
+
config: runtime.config,
|
|
39
|
+
statusCode: context.response.statusCode,
|
|
40
|
+
redirect: context.response.redirect,
|
|
41
|
+
/**
|
|
42
|
+
* SSR markup for content teleported to <body> (closed panels and
|
|
43
|
+
* modals render placeholders). Must be injected into the served
|
|
44
|
+
* <body> or hydration reports a teleport mismatch.
|
|
45
|
+
*/
|
|
46
|
+
teleports: ssrContext.teleports?.body ?? '',
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Startup probe used by server preflight: verifies the definition is loadable
|
|
53
|
+
* and structurally sound before the server accepts traffic.
|
|
54
|
+
*/
|
|
55
|
+
export const createSsrReadyProbe = (definition) => {
|
|
56
|
+
return async () => {
|
|
57
|
+
if (!definition || !definition.root) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'vue-ssr-lite: the application definition is missing its root component.',
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
import type { Router } from 'vue-router'
|
|
3
|
+
import type { SsrAppDefinition, SsrRequestContext } from '../index'
|
|
4
|
+
|
|
5
|
+
export interface SsrHydrateOptions {
|
|
6
|
+
configKey?: string
|
|
7
|
+
stateKey?: string
|
|
8
|
+
headAttribute?: string
|
|
9
|
+
mountSelector?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare const hydrateSsrApp: (
|
|
13
|
+
definition: SsrAppDefinition,
|
|
14
|
+
options?: SsrHydrateOptions,
|
|
15
|
+
) => Promise<{
|
|
16
|
+
app: App
|
|
17
|
+
router: Router | null
|
|
18
|
+
context: SsrRequestContext
|
|
19
|
+
config: Record<string, unknown>
|
|
20
|
+
}>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SsrAppDefinition, SsrHeadPayload } from '../index'
|
|
2
|
+
|
|
3
|
+
export interface SsrRenderRequest {
|
|
4
|
+
url: string
|
|
5
|
+
host: string
|
|
6
|
+
cookie?: string
|
|
7
|
+
config?: Record<string, unknown>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SsrRenderResult {
|
|
11
|
+
html: string
|
|
12
|
+
head: SsrHeadPayload | null
|
|
13
|
+
state: Record<string, unknown>
|
|
14
|
+
config: Record<string, unknown>
|
|
15
|
+
statusCode: number
|
|
16
|
+
redirect: string | null
|
|
17
|
+
teleports: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export declare const createSsrRender: (
|
|
21
|
+
definition: SsrAppDefinition,
|
|
22
|
+
) => (request: SsrRenderRequest) => Promise<SsrRenderResult>
|
|
23
|
+
|
|
24
|
+
export declare const createSsrReadyProbe: (
|
|
25
|
+
definition: SsrAppDefinition,
|
|
26
|
+
) => () => Promise<void>
|