vike-ripple 0.1.0

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 ADDED
@@ -0,0 +1,92 @@
1
+ # @vike-ripple/vike-ripple
2
+
3
+ [Vike](https://vike.dev) integration for [Ripple TS](https://ripple-ts.com) — SSR rendering, client hydration with mount fallback, streaming, `<head>` management, and `.tsrx` page file support.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @vike-ripple/vike-ripple
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### 1. Run setup (patches Vike + Ripple)
14
+
15
+ ```sh
16
+ npx vike-ripple setup
17
+ ```
18
+
19
+ Or add to your project's `package.json` so it runs automatically after `npm install`:
20
+
21
+ ```json
22
+ "scripts": {
23
+ "postinstall": "vike-ripple setup"
24
+ }
25
+ ```
26
+
27
+ ### 2. Add plugin to `vite.config.ts`
28
+
29
+ ```ts
30
+ import { defineConfig } from 'vite'
31
+ import vike from 'vike/plugin'
32
+ import { ripple } from '@ripple-ts/vite-plugin'
33
+ import vikeRipple from '@vike-ripple/vike-ripple'
34
+
35
+ export default defineConfig({
36
+ optimizeDeps: {
37
+ exclude: ['ripple'],
38
+ },
39
+ plugins: [
40
+ vikeRipple(),
41
+ ripple({ excludeRippleExternalModules: true }),
42
+ vike(),
43
+ ],
44
+ })
45
+ > **Why `optimizeDeps.exclude: ['ripple']`?** Ripple uses module-scoped variables (`first_child_getter`) shared between `hydrate()` and DOM traversal functions. Vite's dependency optimization splits these into separate bundles, breaking the scope sharing and causing `TypeError: Cannot read properties of undefined (reading 'call')` at `get_first_child` during hydration. Excluding `ripple` from optimization ensures all Ripple internals stay in one module scope.
46
+
47
+ ### 3. Add renderer files
48
+
49
+ Copy from `node_modules/@vike-ripple/vike-ripple/src/renderer/` to your project's `renderer/`:
50
+
51
+ ```
52
+ renderer/
53
+ +config.ts
54
+ +onRenderHtml.tsx
55
+ +onRenderClient.tsx
56
+ ```
57
+
58
+ ### 4. Create a page
59
+
60
+ ```tsrx
61
+ // pages/index/+Page.tsrx
62
+ export function Page(props: {}) @{
63
+ <>
64
+ <head>
65
+ <title>Home</title>
66
+ </head>
67
+ <section class="min-h-screen p-8">
68
+ <h1>Hello, Ripple + Vike!</h1>
69
+ </section>
70
+ </>
71
+ }
72
+ ```
73
+
74
+ ## What this does
75
+
76
+ | Patch | Why |
77
+ |---|---|
78
+ | **`.tsrx` extension** | Vike doesn't know `.tsrx` is a valid page extension — adds it to `isScriptFile.js` |
79
+ | **`?direct` CSS loading** | Vite's SSR module loader appends `?direct` to module IDs; Ripple's `load` hook checks cache with the wrong key |
80
+ | **Hydrate → mount fallback** | Ripple's `hydrate` can mismatch when `<title>` or `<head>` content is extracted during SSR but missing from the client DOM; falls back to `mount` gracefully |
81
+
82
+ ## API
83
+
84
+ ### `vikeRipple()`
85
+
86
+ Vite plugin. Must be placed before `ripple()` in the plugins array, with `enforce: 'pre'` behavior.
87
+
88
+ ### Renderer files
89
+
90
+ - **`+onRenderHtml.tsx`** — SSR via `ripple/server`'s `render()`, extracts `<head>`, `<body>`, and CSS, injects them into Vike's HTML template. Supports streaming via `rippleStream` config.
91
+ - **`+onRenderClient.tsx`** — Hydrates with `hydrate()` from `ripple`, falls back to `mount()` on error. Imports `tailwind.css` if present.
92
+ - **`+config.ts`** — Disables prerender by default, registers `rippleStream` meta config.
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "vike-ripple",
3
+ "version": "0.1.0",
4
+ "description": "Vike (vike.dev) integration for Ripple TS — SSR, CSR, streaming, head management",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./setup": "./src/setup.js"
10
+ },
11
+ "bin": {
12
+ "vike-ripple": "src/setup.js"
13
+ },
14
+ "files": [
15
+ "src"
16
+ ],
17
+ "keywords": [
18
+ "vike",
19
+ "ripple",
20
+ "ripplets",
21
+ "ssr",
22
+ "vite"
23
+ ],
24
+ "license": "MIT",
25
+ "peerDependencies": {
26
+ "vike": ">=0.4.259",
27
+ "@ripple-ts/vite-plugin": ">=0.3.0",
28
+ "ripple": ">=0.1.0"
29
+ }
30
+ }
package/src/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @vike-ripple/vike-ripple — Vike integration for Ripple TS.
3
+ *
4
+ * ## Setup
5
+ * Run once: npx vike-ripple setup
6
+ * Or add to package.json: "postinstall": "vike-ripple setup"
7
+ *
8
+ * Then add the Vite plugin to vite.config.ts:
9
+ * import vikeRipple from '@vike-ripple/vike-ripple'
10
+ * // in plugins: vikeRipple(),
11
+ */
12
+ export default function vikeRipple() {
13
+ return {
14
+ name: 'vike-ripple',
15
+ enforce: 'pre',
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ import type { Config } from 'vike/types'
2
+
3
+ export default {
4
+ prerender: false,
5
+ meta: {
6
+ rippleStream: {
7
+ env: { server: true },
8
+ },
9
+ },
10
+ } satisfies Config
@@ -0,0 +1,29 @@
1
+ // https://vike.dev/onRenderClient
2
+ export { onRenderClient }
3
+
4
+ import { hydrate } from 'ripple'
5
+
6
+ let rendered = false
7
+
8
+ const onRenderClient = async (pageContext) => {
9
+ const { Page } = pageContext
10
+ if (!Page) return
11
+
12
+ const container = document.getElementById('root')
13
+ if (!container) return
14
+
15
+ if (pageContext.isHydration && container.innerHTML !== '') {
16
+ try {
17
+ hydrate(Page, { target: container, props: {} })
18
+ rendered = true
19
+ } catch (err) {
20
+ console.warn('[ripple] hydrate failed, falling back to mount:', err)
21
+ }
22
+ }
23
+
24
+ if (!rendered) {
25
+ const { mount } = await import('ripple')
26
+ mount(Page, { target: container, props: {} })
27
+ rendered = true
28
+ }
29
+ }
@@ -0,0 +1,43 @@
1
+ // https://vike.dev/onRenderHtml
2
+ export { onRenderHtml }
3
+
4
+ import { render, create_ssr_stream } from 'ripple/server'
5
+ import { escapeInject, dangerouslySkipEscape } from 'vike/server'
6
+
7
+ const onRenderHtml = async (pageContext) => {
8
+ const { Page } = pageContext
9
+ if (!Page) throw new Error('No Page')
10
+
11
+ const enableStream = !!(pageContext.config.rippleStream ?? pageContext.config.stream)
12
+
13
+ if (enableStream) {
14
+ const rippleStream = create_ssr_stream()
15
+ render(Page, { stream: rippleStream.sink }).catch(e => {
16
+ console.error('[ripple] render err:', e?.message)
17
+ })
18
+ return escapeInject`<!DOCTYPE html>
19
+ <html>
20
+ <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /></head>
21
+ <body><div id="root">${rippleStream.stream}</div></body>
22
+ </html>`
23
+ }
24
+
25
+ const { head, body, css } = await render(Page, {})
26
+
27
+ const cssHtml = css?.size
28
+ ? `<style data-ripple-ssr>${[...css].join('')}<` + `/style>`
29
+ : ''
30
+
31
+ return escapeInject`<!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <meta charset="UTF-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
36
+ ${dangerouslySkipEscape(head)}
37
+ ${dangerouslySkipEscape(cssHtml)}
38
+ </head>
39
+ <body>
40
+ <div id="root">${dangerouslySkipEscape(body)}</div>
41
+ </body>
42
+ </html>`
43
+ }
package/src/setup.js ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * vike-ripple setup — patches Vike and Ripple for .tsrx support.
4
+ *
5
+ * Run once: npx vike-ripple setup
6
+ * Or add to project's package.json: "postinstall": "vike-ripple setup"
7
+ */
8
+ import { createRequire } from 'module'
9
+ import { join, dirname } from 'path'
10
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
11
+ import { fileURLToPath } from 'url'
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url))
14
+ const projectRoot = process.cwd()
15
+ let exitCode = 0
16
+
17
+ function log(msg) { console.log('[vike-ripple]', msg) }
18
+ function warn(msg) { console.warn('[vike-ripple]', msg) }
19
+
20
+ // ── Patch 1: Register .tsrx with Vike ─────────────────────────
21
+ function patchVikeExtensions() {
22
+ const target = resolveVike('dist/utils/isScriptFile.js')
23
+ if (!target) { warn('vike not found — skipping'); return }
24
+
25
+ let src = readFileSync(target, 'utf-8')
26
+ if (src.includes("'tsrx'")) { log('.tsrx already registered with Vike'); return }
27
+
28
+ const patched = src.replace(
29
+ 'const scriptFileExtensionList = [...extJsOrTs, ...extJsxOrTsx, ...extTemplates];',
30
+ "const scriptFileExtensionList = [...extJsOrTs, ...extJsxOrTsx, ...extTemplates, 'tsrx'];",
31
+ )
32
+ if (patched === src) { warn('Could not patch Vike isScriptFile.js'); exitCode = 1; return }
33
+ writeFileSync(target, patched, 'utf-8')
34
+ log('Registered .tsrx extension with Vike')
35
+ }
36
+
37
+ // ── Patch 2: Fix ?direct in Ripple's load hook ────────────────
38
+ function patchRippleDirect() {
39
+ const target = resolveRipple('src/index.js')
40
+ if (!target) { warn('@ripple-ts/vite-plugin not found — skipping'); return }
41
+
42
+ let src = readFileSync(target, 'utf-8')
43
+ if (src.includes('Handle ?direct query param')) { log('?direct fix already applied'); return }
44
+
45
+ const patched = src.replace(
46
+ 'if (cssCache.has(id)) {\n\t\t\t\t\treturn cssCache.get(id);\n\t\t\t\t}',
47
+ `if (cssCache.has(id)) {
48
+ return cssCache.get(id);
49
+ }
50
+
51
+ // Handle ?direct query param added by Vite's SSR module loading
52
+ if (id.includes('?direct')) {
53
+ const baseId = id.replace('?direct', '');
54
+ if (cssCache.has(baseId)) {
55
+ return cssCache.get(baseId);
56
+ }
57
+ }`,
58
+ )
59
+ if (patched === src) { warn('Could not patch Ripple plugin load hook'); exitCode = 1; return }
60
+ writeFileSync(target, patched, 'utf-8')
61
+ log('Patched Ripple plugin for ?direct CSS module loading')
62
+ }
63
+
64
+ // ── Patch 3: @apply support (tailwindcss integration) ──────────
65
+ function patchRippleApply() {
66
+ const target = resolveRipple('src/index.js')
67
+ if (!target) return
68
+
69
+ let src = readFileSync(target, 'utf-8')
70
+
71
+ if (src.includes('TW_PATCH_APPLY')) { log('@apply patch already applied'); return }
72
+
73
+ // Upgrade from old TW_PATCH format
74
+ if (src.includes('TW_PATCH:')) {
75
+ src = src.replace(
76
+ '// TW_PATCH: prepend tailwindcss so @apply works',
77
+ '// TW_PATCH_APPLY: bring tailwindcss into scope for @apply',
78
+ )
79
+ writeFileSync(target, src, 'utf-8')
80
+ log('Upgraded @apply patch format')
81
+ return
82
+ }
83
+
84
+ // Fresh install — original unpatched code
85
+ const orig = (
86
+ '\t\t\t\t\tif (css) {\n' +
87
+ '\t\t\t\t\t\tconst cssId = createVirtualImportId(filename, root, \'style\');\n' +
88
+ '\t\t\t\t\t\tcssCache.set(cssId, css);'
89
+ )
90
+ const patched = (
91
+ '\t\t\t\t\tif (css) {\n' +
92
+ '\t\t\t\t\t\t// TW_PATCH_APPLY: bring tailwindcss into scope for @apply\n' +
93
+ "\t\t\t\t\t\tcss = '@import \"tailwindcss\";\\n' + css;\n" +
94
+ '\t\t\t\t\t\tconst cssId = createVirtualImportId(filename, root, \'style\');\n' +
95
+ '\t\t\t\t\t\tcssCache.set(cssId, css);'
96
+ )
97
+
98
+ const result = src.replace(orig, patched)
99
+ if (result === src) { warn('Could not patch Ripple plugin for @apply'); return }
100
+ writeFileSync(target, result, 'utf-8')
101
+ log('Patched Ripple plugin for @apply support in <style> blocks')
102
+ }
103
+
104
+ // ── Resolve helpers ────────────────────────────────────────────
105
+ function resolveVike(rel) {
106
+ const p = join(projectRoot, 'node_modules', 'vike', rel)
107
+ if (existsSync(p)) return p
108
+ try { return createRequire(join(projectRoot, 'noop.js')).resolve('vike/' + rel) } catch { return null }
109
+ }
110
+
111
+ function resolveRipple(rel) {
112
+ const p = join(projectRoot, 'node_modules', '@ripple-ts', 'vite-plugin', rel)
113
+ if (existsSync(p)) return p
114
+ try { return createRequire(join(projectRoot, 'noop.js')).resolve('@ripple-ts/vite-plugin/' + rel) } catch { return null }
115
+ }
116
+
117
+ // ── Main ──────────────────────────────────────────────────────
118
+ log('Applying patches...')
119
+ patchVikeExtensions()
120
+ patchRippleDirect()
121
+ patchRippleApply()
122
+ log('Done')
123
+ process.exit(exitCode)