@ripple-ts/vite-plugin 0.3.35 → 0.3.37
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/CHANGELOG.md +22 -0
- package/package.json +4 -4
- package/src/server/component-wrappers.js +51 -0
- package/src/server/production.js +1 -32
- package/src/server/render-route.js +1 -41
- package/tests/render-route-props.test.js +163 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @ripple-ts/vite-plugin
|
|
2
2
|
|
|
3
|
+
## 0.3.37
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
[[`c631ab0`](https://github.com/Ripple-TS/ripple/commit/c631ab0076b7e2cb30f4998101b54c3a86e78c61)]:
|
|
9
|
+
- @tsrx/ripple@0.0.19
|
|
10
|
+
- @ripple-ts/adapter@0.3.37
|
|
11
|
+
|
|
12
|
+
## 0.3.36
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#999](https://github.com/Ripple-TS/ripple/pull/999)
|
|
17
|
+
[`aa6628b`](https://github.com/Ripple-TS/ripple/commit/aa6628b3318f1bdad6a6e12286d3002f8d591e2e)
|
|
18
|
+
Thanks [@trueadm](https://github.com/trueadm)! - Pass RenderRoute params to
|
|
19
|
+
SSR-compiled page components.
|
|
20
|
+
|
|
21
|
+
- Updated dependencies []:
|
|
22
|
+
- @tsrx/ripple@0.0.18
|
|
23
|
+
- @ripple-ts/adapter@0.3.36
|
|
24
|
+
|
|
3
25
|
## 0.3.35
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Vite plugin for Ripple",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.37",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/index.js",
|
|
9
9
|
"main": "src/index.js",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
"url": "https://github.com/Ripple-TS/ripple/issues"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ripple
|
|
36
|
-
"@
|
|
35
|
+
"@tsrx/ripple": "0.0.19",
|
|
36
|
+
"@ripple-ts/adapter": "0.3.37"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^24.3.0",
|
|
40
40
|
"type-fest": "^5.6.0",
|
|
41
41
|
"vite": "^8.0.0",
|
|
42
|
-
"ripple": "0.3.
|
|
42
|
+
"ripple": "0.3.37"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const TSRX_ELEMENT = Symbol.for('ripple.element');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a server TSRX element for layout children.
|
|
5
|
+
*
|
|
6
|
+
* @param {Function} render
|
|
7
|
+
* @returns {{ render: Function, [TSRX_ELEMENT]: true }}
|
|
8
|
+
*/
|
|
9
|
+
function createServerTsrxElement(render) {
|
|
10
|
+
return {
|
|
11
|
+
render,
|
|
12
|
+
[TSRX_ELEMENT]: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a wrapper component that injects props into an SSR component.
|
|
18
|
+
*
|
|
19
|
+
* @param {Function} Component
|
|
20
|
+
* @param {Record<string, unknown>} props
|
|
21
|
+
* @returns {Function}
|
|
22
|
+
*/
|
|
23
|
+
export function createPropsWrapper(Component, props) {
|
|
24
|
+
/**
|
|
25
|
+
* @param {Record<string, unknown>} additionalProps
|
|
26
|
+
*/
|
|
27
|
+
return function WrappedComponent(additionalProps = {}) {
|
|
28
|
+
return Component({ ...additionalProps, ...props });
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a wrapper that composes a layout with a page component.
|
|
34
|
+
*
|
|
35
|
+
* @param {Function} Layout
|
|
36
|
+
* @param {Function} Page
|
|
37
|
+
* @param {Record<string, unknown>} pageProps
|
|
38
|
+
* @returns {Function}
|
|
39
|
+
*/
|
|
40
|
+
export function createLayoutWrapper(Layout, Page, pageProps) {
|
|
41
|
+
/**
|
|
42
|
+
* @param {Record<string, unknown>} additionalProps
|
|
43
|
+
*/
|
|
44
|
+
return function LayoutWrapper(additionalProps = {}) {
|
|
45
|
+
const children = createServerTsrxElement((childProps = {}) => {
|
|
46
|
+
return Page({ ...additionalProps, ...childProps, ...pageProps });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return Layout({ ...additionalProps, children });
|
|
50
|
+
};
|
|
51
|
+
}
|
package/src/server/production.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { createRouter } from './router.js';
|
|
13
13
|
import { createContext, runMiddlewareChain } from './middleware.js';
|
|
14
|
+
import { createLayoutWrapper, createPropsWrapper } from './component-wrappers.js';
|
|
14
15
|
import {
|
|
15
16
|
patch_global_fetch,
|
|
16
17
|
build_rpc_lookup,
|
|
@@ -253,38 +254,6 @@ async function handleServerRoute(route, context, globalMiddlewares) {
|
|
|
253
254
|
);
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
// ============================================================================
|
|
257
|
-
// Component wrappers
|
|
258
|
-
// ============================================================================
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Create a wrapper component that injects props
|
|
262
|
-
* @param {Function} Component
|
|
263
|
-
* @param {Record<string, unknown>} props
|
|
264
|
-
* @returns {Function}
|
|
265
|
-
*/
|
|
266
|
-
function createPropsWrapper(Component, props) {
|
|
267
|
-
return function WrappedComponent(/** @type {unknown} */ output, additionalProps = {}) {
|
|
268
|
-
return Component(output, { ...additionalProps, ...props });
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Create a wrapper that composes a layout with a page component
|
|
274
|
-
* @param {Function} Layout
|
|
275
|
-
* @param {Function} Page
|
|
276
|
-
* @param {Record<string, unknown>} pageProps
|
|
277
|
-
* @returns {Function}
|
|
278
|
-
*/
|
|
279
|
-
function createLayoutWrapper(Layout, Page, pageProps) {
|
|
280
|
-
return function LayoutWrapper(/** @type {unknown} */ output, additionalProps = {}) {
|
|
281
|
-
const children = (/** @type {unknown} */ childOutput) => {
|
|
282
|
-
return Page(childOutput, { ...additionalProps, ...pageProps });
|
|
283
|
-
};
|
|
284
|
-
return Layout(output, { ...additionalProps, children });
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
257
|
// ============================================================================
|
|
289
258
|
// Utilities
|
|
290
259
|
// ============================================================================
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="@tsrx/ripple/types/rpc" />
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { createLayoutWrapper, createPropsWrapper } from './component-wrappers.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @typedef {import('@ripple-ts/vite-plugin').Context} Context
|
|
@@ -142,47 +143,6 @@ function getDefaultExport(module) {
|
|
|
142
143
|
return null;
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
/**
|
|
146
|
-
* Create a wrapper component that injects props
|
|
147
|
-
*
|
|
148
|
-
* @param {Function} Component
|
|
149
|
-
* @param {Record<string, unknown>} props
|
|
150
|
-
* @returns {Function}
|
|
151
|
-
*/
|
|
152
|
-
function createPropsWrapper(Component, props) {
|
|
153
|
-
/**
|
|
154
|
-
* @param {unknown} output
|
|
155
|
-
* @param {Record<string, unknown>} additionalProps
|
|
156
|
-
*/
|
|
157
|
-
return function WrappedComponent(output, additionalProps = {}) {
|
|
158
|
-
return Component(output, { ...additionalProps, ...props });
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Create a wrapper that composes a layout with a page component
|
|
164
|
-
* The layout receives the page as its children prop
|
|
165
|
-
*
|
|
166
|
-
* @param {Function} Layout
|
|
167
|
-
* @param {Function} Page
|
|
168
|
-
* @param {Record<string, unknown>} pageProps
|
|
169
|
-
* @returns {Function}
|
|
170
|
-
*/
|
|
171
|
-
function createLayoutWrapper(Layout, Page, pageProps) {
|
|
172
|
-
/**
|
|
173
|
-
* @param {unknown} output
|
|
174
|
-
* @param {Record<string, unknown>} additionalProps
|
|
175
|
-
*/
|
|
176
|
-
return function LayoutWrapper(output, additionalProps = {}) {
|
|
177
|
-
// Children is a component function that renders the page
|
|
178
|
-
const children = (/** @type {unknown} */ childOutput) => {
|
|
179
|
-
return Page(childOutput, { ...additionalProps, ...pageProps });
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
return Layout(output, { ...additionalProps, children });
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
146
|
/**
|
|
187
147
|
* Escape script content to prevent XSS
|
|
188
148
|
* @param {string} str
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createHandler } from '../src/server/production.js';
|
|
6
|
+
import { handleRenderRoute } from '../src/server/render-route.js';
|
|
7
|
+
import { createLayoutWrapper, createPropsWrapper } from '../src/server/component-wrappers.js';
|
|
8
|
+
|
|
9
|
+
function createRuntime() {
|
|
10
|
+
return {
|
|
11
|
+
hash: () => '00000000',
|
|
12
|
+
createAsyncContext: () => ({
|
|
13
|
+
run: (_store, fn) => fn(),
|
|
14
|
+
getStore: () => undefined,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createHandlerOptions() {
|
|
20
|
+
return {
|
|
21
|
+
render: async (Component) => {
|
|
22
|
+
Component({ fromRender: true });
|
|
23
|
+
return { head: '', body: '<div>ok</div>', css: new Set() };
|
|
24
|
+
},
|
|
25
|
+
getCss: () => '',
|
|
26
|
+
htmlTemplate: '<html><head><!--ssr-head--></head><body><!--ssr-body--></body></html>',
|
|
27
|
+
executeServerFunction: async () => '',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('render route SSR props', () => {
|
|
32
|
+
it('passes injected props as the first SSR component argument', () => {
|
|
33
|
+
const Component = vi.fn();
|
|
34
|
+
const Wrapped = createPropsWrapper(Component, {
|
|
35
|
+
params: { slug: 'echo-api' },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
Wrapped({ fromRender: true, params: { slug: 'ignored' } });
|
|
39
|
+
|
|
40
|
+
expect(Component.mock.calls).toEqual([
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
fromRender: true,
|
|
44
|
+
params: { slug: 'echo-api' },
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('passes route params to production render route components', async () => {
|
|
51
|
+
let pageProps;
|
|
52
|
+
function Page(props) {
|
|
53
|
+
pageProps = props;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handler = createHandler(
|
|
57
|
+
{
|
|
58
|
+
routes: [
|
|
59
|
+
{
|
|
60
|
+
type: 'render',
|
|
61
|
+
path: '/tool/:slug',
|
|
62
|
+
entry: '/src/ToolPage.tsrx',
|
|
63
|
+
before: [],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
components: { '/src/ToolPage.tsrx': Page },
|
|
67
|
+
layouts: {},
|
|
68
|
+
middlewares: [],
|
|
69
|
+
runtime: createRuntime(),
|
|
70
|
+
},
|
|
71
|
+
createHandlerOptions(),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await handler(new Request('https://example.test/tool/echo-api'));
|
|
75
|
+
|
|
76
|
+
expect(pageProps).toEqual({
|
|
77
|
+
fromRender: true,
|
|
78
|
+
params: { slug: 'echo-api' },
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('passes route params to dev render route components', async () => {
|
|
83
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ripple-render-route-'));
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
path.join(tmpDir, 'index.html'),
|
|
86
|
+
'<html><head><!--ssr-head--></head><body><div id="root"><!--ssr-body--></div></body></html>',
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let pageProps;
|
|
90
|
+
function Page(props) {
|
|
91
|
+
pageProps = props;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const vite = {
|
|
95
|
+
config: { root: tmpDir },
|
|
96
|
+
transformIndexHtml: vi.fn(async (_pathname, html) => html),
|
|
97
|
+
ssrLoadModule: vi.fn(async (id) => {
|
|
98
|
+
if (id === 'ripple/server') {
|
|
99
|
+
const { render } = createHandlerOptions();
|
|
100
|
+
return {
|
|
101
|
+
render,
|
|
102
|
+
get_css_for_hashes: () => '',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (id === '/src/ToolPage.tsrx') {
|
|
106
|
+
return { default: Page };
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Unexpected module: ${id}`);
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const response = await handleRenderRoute(
|
|
114
|
+
{
|
|
115
|
+
type: 'render',
|
|
116
|
+
path: '/tool/:slug',
|
|
117
|
+
entry: '/src/ToolPage.tsrx',
|
|
118
|
+
before: [],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
request: new Request('https://example.test/tool/echo-api'),
|
|
122
|
+
params: { slug: 'echo-api' },
|
|
123
|
+
url: new URL('https://example.test/tool/echo-api'),
|
|
124
|
+
state: new Map(),
|
|
125
|
+
},
|
|
126
|
+
vite,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(response.status).toBe(200);
|
|
130
|
+
expect(pageProps).toEqual({
|
|
131
|
+
fromRender: true,
|
|
132
|
+
params: { slug: 'echo-api' },
|
|
133
|
+
});
|
|
134
|
+
} finally {
|
|
135
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('wraps layout children as a server TSRX element and passes page params through', () => {
|
|
140
|
+
const Page = vi.fn();
|
|
141
|
+
const Layout = vi.fn(({ children }) => {
|
|
142
|
+
children.render({ fromChild: true });
|
|
143
|
+
});
|
|
144
|
+
const Wrapped = createLayoutWrapper(Layout, Page, {
|
|
145
|
+
params: { slug: 'echo-api' },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
Wrapped({ fromRender: true });
|
|
149
|
+
|
|
150
|
+
const layoutProps = Layout.mock.calls[0][0];
|
|
151
|
+
expect(layoutProps.fromRender).toBe(true);
|
|
152
|
+
expect(layoutProps.children[Symbol.for('ripple.element')]).toBe(true);
|
|
153
|
+
expect(Page.mock.calls).toEqual([
|
|
154
|
+
[
|
|
155
|
+
{
|
|
156
|
+
fromRender: true,
|
|
157
|
+
fromChild: true,
|
|
158
|
+
params: { slug: 'echo-api' },
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
});
|