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/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>