vue-ssr-lite 0.0.3 → 0.0.5
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 +25 -0
- package/apollo/SsrApolloAdapter.js +1 -1
- package/apollo/index.d.ts +1 -1
- package/index.d.ts +1 -1
- package/package.json +16 -7
- package/engine/.fuse_hidden0000002d00000001 +0 -166
package/README.md
CHANGED
|
@@ -103,6 +103,12 @@ overrides (migration compatibility), and a per-request CSP `nonce` provider.
|
|
|
103
103
|
`ssrApolloNoExternal` from `vue-ssr-lite/vite` is the SSR dependency-inlining
|
|
104
104
|
preset for the Apollo cluster.
|
|
105
105
|
|
|
106
|
+
The Apollo adapter targets the public Apollo Client 3 core ESM module at
|
|
107
|
+
`@apollo/client/core/index.js`. Consumers must install a compatible
|
|
108
|
+
`@apollo/client` 3.x peer and should use `ssrApolloNoExternal` when Vite needs
|
|
109
|
+
to inline Apollo's dual-format dependency cluster. Do not alias Apollo paths
|
|
110
|
+
in consumer applications.
|
|
111
|
+
|
|
106
112
|
## Embedding the engine (no standalone server)
|
|
107
113
|
|
|
108
114
|
`createSsrRequestHandler` from `vue-ssr-lite/engine` is the framework-agnostic
|
|
@@ -139,6 +145,25 @@ or server file.
|
|
|
139
145
|
build step — the package ships plain ESM with hand-maintained type
|
|
140
146
|
declarations).
|
|
141
147
|
|
|
148
|
+
Apollo and GraphQL are development dependencies so the adapter's ESM,
|
|
149
|
+
request-isolation, and cache-hydration contracts run in a normal development
|
|
150
|
+
or prepublish install. Apollo remains an optional peer for consumers that do
|
|
151
|
+
not enable the Apollo integration. In a deliberately dependency-free source
|
|
152
|
+
checkout, only the Apollo-specific contracts are skipped.
|
|
153
|
+
|
|
154
|
+
The package publication allowlist contains only runtime JavaScript, type
|
|
155
|
+
declarations, and the CLI. Hidden filesystem artifacts, caches, coverage, and
|
|
156
|
+
test output are intentionally excluded even when they appear below a runtime
|
|
157
|
+
directory.
|
|
158
|
+
|
|
159
|
+
## 0.0.4
|
|
160
|
+
|
|
161
|
+
- Corrects Node ESM loading of the Apollo adapter by replacing the unsupported
|
|
162
|
+
`@apollo/client/core` directory import with its concrete ESM entry.
|
|
163
|
+
- Adds Apollo request-isolation, hydration-cache, export-map, and package
|
|
164
|
+
content regression contracts.
|
|
165
|
+
- Tightens published-file selection so hidden FUSE files cannot be included.
|
|
166
|
+
|
|
142
167
|
## Publishing
|
|
143
168
|
|
|
144
169
|
Choose the release type and run the corresponding command:
|
package/apollo/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
DefaultOptions,
|
|
5
5
|
InMemoryCacheConfig,
|
|
6
6
|
NormalizedCacheObject,
|
|
7
|
-
} from '@apollo/client/core'
|
|
7
|
+
} from '@apollo/client/core/index.js'
|
|
8
8
|
import type { SsrApolloDefinition, SsrRequestContext } from '../index'
|
|
9
9
|
|
|
10
10
|
export interface SsrApolloClientOptions {
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { App, Component, InjectionKey, Ref } from 'vue'
|
|
2
2
|
import type { Router, RouteRecordRaw, RouterScrollBehavior } from 'vue-router'
|
|
3
|
-
import type { ApolloClient, ApolloLink, DefaultOptions, InMemoryCacheConfig, NormalizedCacheObject } from '@apollo/client/core'
|
|
3
|
+
import type { ApolloClient, ApolloLink, DefaultOptions, InMemoryCacheConfig, NormalizedCacheObject } from '@apollo/client/core/index.js'
|
|
4
4
|
|
|
5
5
|
export interface SsrRequestContext<
|
|
6
6
|
TConfig extends Record<string, unknown> = Record<string, unknown>,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue-ssr-lite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
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
5
|
"author": "safdar-azeem",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
"vue-ssr-lite": "./bin/vue-ssr-lite.mjs"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"bin",
|
|
16
|
-
"engine",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
15
|
+
"bin/*.mjs",
|
|
16
|
+
"engine/*.js",
|
|
17
|
+
"engine/*.d.ts",
|
|
18
|
+
"server/*.js",
|
|
19
|
+
"server/*.d.ts",
|
|
20
|
+
"runtime/*.js",
|
|
21
|
+
"runtime/*.d.ts",
|
|
22
|
+
"apollo/*.js",
|
|
23
|
+
"apollo/*.d.ts",
|
|
24
|
+
"vite/*.js",
|
|
25
|
+
"vite/*.d.ts",
|
|
21
26
|
"index.js",
|
|
22
27
|
"index.d.ts"
|
|
23
28
|
],
|
|
@@ -69,6 +74,10 @@
|
|
|
69
74
|
"optional": true
|
|
70
75
|
}
|
|
71
76
|
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@apollo/client": "^3.13.9",
|
|
79
|
+
"graphql": "^16.12.0"
|
|
80
|
+
},
|
|
72
81
|
"scripts": {
|
|
73
82
|
"test": "node --test tests/*.test.mjs",
|
|
74
83
|
"preversion": "npm test",
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
// SsrEngineHtml — package-owned HTML document primitives.
|
|
2
|
-
//
|
|
3
|
-
// These are invariants, not configuration: escaping rules, XSS-safe state
|
|
4
|
-
// serialization, marker names, and head-tag rendering order are identical in
|
|
5
|
-
// development and production and identical across consumers. The head payload
|
|
6
|
-
// contract and tag order are byte-compatible with the audited ERP renderer so
|
|
7
|
-
// the ERP migration cannot change its SEO output.
|
|
8
|
-
|
|
9
|
-
export const SSR_MARKERS = Object.freeze({
|
|
10
|
-
head: '<!--app-head-->',
|
|
11
|
-
html: '<!--app-html-->',
|
|
12
|
-
state: '<!--app-state-->',
|
|
13
|
-
teleports: '<!--app-teleports-->',
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
export const escapeHtml = (value) =>
|
|
17
|
-
String(value ?? '')
|
|
18
|
-
.replaceAll('&', '&')
|
|
19
|
-
.replaceAll('<', '<')
|
|
20
|
-
.replaceAll('>', '>')
|
|
21
|
-
.replaceAll('"', '"')
|
|
22
|
-
.replaceAll("'", ''')
|
|
23
|
-
|
|
24
|
-
export const escapeXml = (value) =>
|
|
25
|
-
String(value ?? '')
|
|
26
|
-
.replaceAll('&', '&')
|
|
27
|
-
.replaceAll('<', '<')
|
|
28
|
-
.replaceAll('>', '>')
|
|
29
|
-
.replaceAll('"', '"')
|
|
30
|
-
.replaceAll("'", ''')
|
|
31
|
-
|
|
32
|
-
/** XSS-safe JSON serialization for inline <script> payloads. */
|
|
33
|
-
export const serializeState = (value) =>
|
|
34
|
-
JSON.stringify(value)
|
|
35
|
-
.replaceAll('<', '\\u003c')
|
|
36
|
-
.replaceAll('
', '\\u2028')
|
|
37
|
-
.replaceAll('
', '\\u2029')
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Render a head payload into escaped tags.
|
|
41
|
-
*
|
|
42
|
-
* Payload contract (all fields optional): title, description, keywords,
|
|
43
|
-
* robots, ogTitle, ogDescription, ogImage, ogImageAlt, ogUrl, ogType,
|
|
44
|
-
* ogSiteName, ogLocale, twitterCard, twitterTitle, twitterDescription,
|
|
45
|
-
* twitterImage, twitterImageAlt, twitterSite, twitterCreator, canonicalUrl,
|
|
46
|
-
* favicon, jsonLd (array of objects).
|
|
47
|
-
*
|
|
48
|
-
* Every rendered tag carries `attribute` so the browser runtime can remove
|
|
49
|
-
* server-rendered tags before the client SEO layer takes over.
|
|
50
|
-
*/
|
|
51
|
-
export const renderHeadTags = (payload, attribute = 'data-ssr-head') => {
|
|
52
|
-
if (!payload) return ''
|
|
53
|
-
const tags = []
|
|
54
|
-
if (payload.title) {
|
|
55
|
-
tags.push(`<title ${attribute}>${escapeHtml(payload.title)}</title>`)
|
|
56
|
-
}
|
|
57
|
-
const meta = [
|
|
58
|
-
['name', 'description', payload.description],
|
|
59
|
-
['name', 'keywords', payload.keywords],
|
|
60
|
-
['name', 'robots', payload.robots],
|
|
61
|
-
['property', 'og:title', payload.ogTitle],
|
|
62
|
-
['property', 'og:description', payload.ogDescription],
|
|
63
|
-
['property', 'og:image', payload.ogImage],
|
|
64
|
-
['property', 'og:image:alt', payload.ogImageAlt],
|
|
65
|
-
['property', 'og:url', payload.ogUrl],
|
|
66
|
-
['property', 'og:type', payload.ogType],
|
|
67
|
-
['property', 'og:site_name', payload.ogSiteName],
|
|
68
|
-
['property', 'og:locale', payload.ogLocale],
|
|
69
|
-
['name', 'twitter:card', payload.twitterCard],
|
|
70
|
-
['name', 'twitter:title', payload.twitterTitle],
|
|
71
|
-
['name', 'twitter:description', payload.twitterDescription],
|
|
72
|
-
['name', 'twitter:image', payload.twitterImage],
|
|
73
|
-
['name', 'twitter:image:alt', payload.twitterImageAlt],
|
|
74
|
-
['name', 'twitter:site', payload.twitterSite],
|
|
75
|
-
['name', 'twitter:creator', payload.twitterCreator],
|
|
76
|
-
]
|
|
77
|
-
for (const [attributeName, name, content] of meta) {
|
|
78
|
-
if (content != null && content !== '') {
|
|
79
|
-
tags.push(
|
|
80
|
-
`<meta ${attribute} ${attributeName}="${escapeHtml(name)}" content="${escapeHtml(content)}">`,
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (payload.canonicalUrl) {
|
|
85
|
-
tags.push(
|
|
86
|
-
`<link ${attribute} rel="canonical" href="${escapeHtml(payload.canonicalUrl)}">`,
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
if (payload.favicon) {
|
|
90
|
-
tags.push(`<link ${attribute} rel="icon" href="${escapeHtml(payload.favicon)}">`)
|
|
91
|
-
}
|
|
92
|
-
for (const block of payload.jsonLd || []) {
|
|
93
|
-
const json = JSON.stringify(block).replaceAll('<', '\\u003c')
|
|
94
|
-
tags.push(`<script ${attribute} type="application/ld+json">${json}</script>`)
|
|
95
|
-
}
|
|
96
|
-
return tags.join('')
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Render the state script exposing serialized public config and hydration
|
|
101
|
-
* state on package-owned (or consumer-overridden) globals. Emits nothing when
|
|
102
|
-
* both parts are empty. `nonce` supports strict Content Security Policies.
|
|
103
|
-
*/
|
|
104
|
-
export const renderStateScript = (
|
|
105
|
-
{ config, state },
|
|
106
|
-
{ configKey = '__SSR_CONFIG__', stateKey = '__SSR_STATE__', nonce } = {},
|
|
107
|
-
) => {
|
|
108
|
-
const parts = []
|
|
109
|
-
if (config != null && Object.keys(config).length > 0) {
|
|
110
|
-
parts.push(`window.${configKey}=${serializeState(config)}`)
|
|
111
|
-
}
|
|
112
|
-
if (state != null) {
|
|
113
|
-
parts.push(`window.${stateKey}=${serializeState(state)}`)
|
|
114
|
-
}
|
|
115
|
-
if (parts.length === 0) return ''
|
|
116
|
-
const nonceAttribute = nonce ? ` nonce="${escapeHtml(nonce)}"` : ''
|
|
117
|
-
return `<script${nonceAttribute}>${parts.join(';')}</script>`
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Inject rendered fragments into a marker-carrying HTML template. */
|
|
121
|
-
export const injectSsrDocument = (template, { head = '', html = '', state = '', teleports = '' }) =>
|
|
122
|
-
template
|
|
123
|
-
.replace(SSR_MARKERS.head, head)
|
|
124
|
-
.replace(SSR_MARKERS.teleports, teleports)
|
|
125
|
-
.replace(SSR_MARKERS.html, html)
|
|
126
|
-
.replace(SSR_MARKERS.state, state)
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Guarantee the four SSR markers exist in a template so consumers never learn
|
|
130
|
-
* marker names. Existing markers are left untouched; missing ones are added:
|
|
131
|
-
* head before </head>, teleports after <body>, html inside the mount element,
|
|
132
|
-
* state before </body>.
|
|
133
|
-
*/
|
|
134
|
-
export const ensureSsrMarkers = (template, { mountSelector = '#app' } = {}) => {
|
|
135
|
-
let html = String(template)
|
|
136
|
-
if (!html.includes(SSR_MARKERS.head)) {
|
|
137
|
-
html = html.replace(/<\/head>/i, `${SSR_MARKERS.head}\n</head>`)
|
|
138
|
-
}
|
|
139
|
-
if (!html.includes(SSR_MARKERS.teleports)) {
|
|
140
|
-
html = html.replace(/<body([^>]*)>/i, `<body$1>${SSR_MARKERS.teleports}`)
|
|
141
|
-
}
|
|
142
|
-
if (!html.includes(SSR_MARKERS.html)) {
|
|
143
|
-
const id = mountSelector.startsWith('#') ? mountSelector.slice(1) : mountSelector
|
|
144
|
-
const mountPattern = new RegExp(`(<div[^>]*id=["']${id}["'][^>]*>)(</div>)`, 'i')
|
|
145
|
-
html = html.replace(mountPattern, `$1${SSR_MARKERS.html}$2`)
|
|
146
|
-
}
|
|
147
|
-
if (!html.includes(SSR_MARKERS.state)) {
|
|
148
|
-
html = html.replace(/<\/body>/i, `${SSR_MARKERS.state}\n</body>`)
|
|
149
|
-
}
|
|
150
|
-
return html
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Generic accessible error markup used by fallback shells (overridable upstream). */
|
|
154
|
-
export const renderErrorMarkup = (title, message) => `
|
|
155
|
-
<main id="main-content" style="min-height:70vh;display:grid;place-items:center;padding:2rem;text-align:center;font-family:system-ui,sans-serif" tabindex="-1">
|
|
156
|
-
<div><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p><p><a href="/">Return home</a></p></div>
|
|
157
|
-
</main>`
|
|
158
|
-
|
|
159
|
-
/** Sitemap primitives shared by consumers that serve SEO XML from the server. */
|
|
160
|
-
export const renderSitemapUrlSet = (urls) => {
|
|
161
|
-
const uniqueUrls = [...new Set(urls)]
|
|
162
|
-
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${uniqueUrls.map((url) => `\n <url><loc>${escapeXml(url)}</loc></url>`).join('')}\n</urlset>\n`
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export const renderSitemapIndex = (urls) =>
|
|
166
|
-
`<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls.map((url) => `\n <sitemap><loc>${escapeXml(url)}</loc></sitemap>`).join('')}\n</sitemapindex>\n`
|