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 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:
@@ -14,7 +14,7 @@ import {
14
14
  ApolloLink,
15
15
  HttpLink,
16
16
  InMemoryCache,
17
- } from '@apollo/client/core'
17
+ } from '@apollo/client/core/index.js'
18
18
 
19
19
  const DEFAULT_TIMEOUT_MS = 8000
20
20
  const DEFAULT_SSR_FORCE_FETCH_DELAY_MS = 150
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",
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
- "server",
18
- "runtime",
19
- "apollo",
20
- "vite",
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('&', '&amp;')
19
- .replaceAll('<', '&lt;')
20
- .replaceAll('>', '&gt;')
21
- .replaceAll('"', '&quot;')
22
- .replaceAll("'", '&#39;')
23
-
24
- export const escapeXml = (value) =>
25
- String(value ?? '')
26
- .replaceAll('&', '&amp;')
27
- .replaceAll('<', '&lt;')
28
- .replaceAll('>', '&gt;')
29
- .replaceAll('"', '&quot;')
30
- .replaceAll("'", '&apos;')
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`