vite-plugin-hyperapp 1.3.1 → 2.0.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 CHANGED
@@ -1,84 +1,173 @@
1
+ # Vite Hyperapp Plugin
1
2
 
2
- # Vite Hyperapp Plugin & Starter
3
+ Use Vite 8 to develop and build Hyperapp apps, with support for JSX/TSX, HMR and SSR.
3
4
 
4
- Use vite as a dev-server and bundler for your hyperapp projects. Supports:
5
+ ## Get started
5
6
 
6
- - Views in JSX/TSX
7
- - Hot module reloading (HMR)
8
- - Server-side rendering (SSR)
9
-
10
- ## Get Started
11
-
12
- To get up and running with Hyperapp and Vite in a few seconds, use [create-vite-hyperapp](https://github.com/zaceno/create-vite-hyperapp):
7
+ In a Vite project, install `hyperapp` as a dependency and `vite-plugin-hyperapp` as a development-dependency:
13
8
 
14
9
  ```sh
15
- > npm create vite-hyperapp@latest
10
+ npm install hyperapp
11
+ npm install -D vite vite-plugin-hyperapp
16
12
  ```
17
13
 
18
- > Use `npm create vite-hyperapp -- --help` for some command line options.
19
-
20
- The starter will ask you for a folder in which to scaffold the project, and it will also ask if you would like to use typescript and/or SSR.
21
-
14
+ > If you intend to use SSR, you will want to install `vite-plugin-hyperapp` as a regular dependency since the server needs to `import {renderToString} from 'vite-plugin-hyperapp/ssr'`
22
15
 
23
- ### Manual setup
24
-
25
- In case you want to install manually, you need a project folder with `vite` installed, and a `vite.config.js` ready to go. Then you can install the vite-plugin for hyperapp, and enable it in the config:
26
-
27
- ```sh
28
- > npm install vite-plugin-hyperapp
29
- ```
16
+ Add the plugin to your Vite config:
30
17
 
31
18
  ```js
32
19
  // vite.config.js
33
- import { defineConfig } from "vite"
34
- import hyperapp from "vite-plugin-hyperapp"
20
+ import { defineConfig } from 'vite'
21
+ import { hyperapp } from 'vite-plugin-hyperapp'
35
22
 
36
23
  export default defineConfig({
37
24
  plugins: [hyperapp()],
38
25
  })
39
26
  ```
40
- > Options are passed as an object to `hyperapp({...})`. Available options described below.
41
27
 
42
- In order to enable build TSX files to build, please add the following options to `compilerOptions` in your `tsconfig.json`:
28
+ Here is a small app to try it with:
43
29
 
44
- ```json
45
- /* Necessary for tsx to work with vite-plugin-hyperapp */
46
- "jsx": "preserve",
47
- "jsxFactory": "jsx",
48
- "jsxFragmentFactory": "jsxFragment"
30
+ ```html
31
+ <!-- index.html -->
32
+ <main id="app"></main>
33
+ <script type="module" src="/src/main.jsx"></script>
49
34
  ```
50
35
 
51
- and to make sure your editor's TS language server picks up the right types for JSX/TSX,
52
- add this line in your `src/vite-env.d.ts`:
36
+ ```jsx
37
+ // src/main.jsx
38
+ import { app } from 'hyperapp'
39
+
40
+ const Increment = (state) => ({ count: state.count + 1 })
41
+
42
+ const view = (state) => (
43
+ <main id="app">
44
+ <button onclick={Increment}>Count: {state.count}</button>
45
+ </main>
46
+ )
47
+
48
+ const node = document.getElementById('app')
49
+ if (!node) throw new Error('Mount node not found')
53
50
 
51
+ app({
52
+ init: { count: 0 },
53
+ view,
54
+ node,
55
+ })
54
56
  ```
55
- /// <reference types="vite-plugin-hyperapp/jsx" />
57
+
58
+ ## Views in JSX or TSX
59
+
60
+ You are free to use hyperapp's built-in `h()` and `text()`, or `@hyperapp/html` or any other view authoring system you prefer.
61
+
62
+ But if you want to use JSX, you can do so with no extra config. Just make sure to name every file where you use JSX with the extension `.jsx` (or `.tsx` if you are using Typescript).
63
+
64
+ ### Component children
65
+
66
+ Unlike the old standard, the JSX support of this plugin now follows the modern convention of putting `children` as a property of components, rather than a second argument.
67
+
68
+ ```jsx
69
+ function Panel({ title, children }) {
70
+ return (
71
+ <section>
72
+ <h2>{title}</h2>
73
+ {children}
74
+ </section>
75
+ )
76
+ }
77
+
78
+ const view = (state) => (
79
+ <main id="app">
80
+ <Panel title="Welcome"><p>{state.message}</p></Panel>
81
+ </main>
82
+ )
56
83
  ```
84
+ ### Fragments
57
85
 
58
- ## Hot-module-reloading (HMR)
86
+ The JSX support comes with support for fragments (`<>...</>`) - allowing components to return an array of nodes.
59
87
 
60
- > The following is taken care of already, when you scaffold your app with `npm create vite-hyperapp`, but it is useful to be aware of
88
+ ```jsx
61
89
 
62
- For hot-module-reloading to work, the plugin injects some code in the module where you initiate the app. In order for this to work, the app-initiation module must follow these two rules:
90
+ function Items({items}) {
91
+ return (
92
+ <>
93
+ {items.map(txt => <li>{txt}</li>)}
94
+ </>
95
+ )
96
+ }
63
97
 
64
- 1. It must import `app` from `"hyperapp"` using named imports (i.e. with `{..app...}`, not `*`
65
- 2. The dispatch function returned from the app-call must be exported as a _named_ (not default) export.
98
+ function List({items}) {
99
+ return (
100
+ <ul>
101
+ <li>Always First</li>
102
+ <Items items={items} />
103
+ <li>Always Last</li>
104
+ </ul>
105
+ )
106
+ }
66
107
 
67
- By default, HMR expects the exported dispatch function to be named `dispatch`. If you choose to name it something else, let the plugin know by providing it to the plugin options:
108
+ <List items={['a', 'b', 'c']} />
109
+ /* ==>
110
+ <ul>
111
+ <li>Always First</li>
112
+ <li>a</li>
113
+ <li>b</li>
114
+ <li>c</li>
115
+ <li>Always Last</li>
116
+ </ul>
117
+ */
118
+ ```
68
119
 
69
- ```js
70
- //vite.config.js
71
- export default defineConfig({
72
- plugins: [hyperapp({
73
- dispatchExport: "myDispatchAlias"
74
- })],
75
- })
120
+ > Note: Hyperapp requires the top node of the view to ***not*** be a fragment.
121
+
122
+ ### Using TypeScript (TSX)
123
+
124
+ If you use TypeScript, use the file extension `.tsx` rather than `.jsx`.
125
+
126
+ You will also need to configure TypeScript to recognize the JSX runtime in your `tsconfig.json`, by adding the following to your `compilerOptions`:
127
+
128
+ ```jsonc
129
+ // tsconfig.json
130
+ {
131
+ "compilerOptions": {
132
+ "jsx": "react-jsx",
133
+ "jsxImportSource": "vite-plugin-hyperapp/jsx"
134
+ }
135
+ }
136
+ ```
137
+
138
+ For components that accept children, type the children as `Child` – a convenience type exported from the runtime:
139
+
140
+ ```tsx
141
+ import type { Child } from 'vite-plugin-hyperapp/jsx/jsx-runtime'
142
+
143
+ function Panel({ title, children }: { title: string; children?: Child }) {
144
+ return (
145
+ <section>
146
+ <h2>{title}</h2>
147
+ {children}
148
+ </section>
149
+ )
150
+ }
76
151
  ```
77
152
 
78
- And if you'd rather use Vite's HMR api directly, or forego HMR entirely, you can use set that as an option in the plugin as well:
153
+ ## Hot module reloading
154
+
155
+ Hot Module Reloading is on by default in the dev server, when using this plugin. It means that any changes to your app will be reloaded *without clearing the state*. That lets you navigate to a particular view and develop it with live updates – and because the state does not change, you don't have to navigate back to the view to see your changes – you are already there!
156
+
157
+ For HMR to work, some simple rules need to be observed:
158
+
159
+ - In your main client side entry, import `app` from `hyperapp` *without renaming it* – e.g. do *not* `import {app as hyperApp} ...`
160
+ - Use an id to identify the app mountpoint in the html entrypoint, and make sure to use the exact same tag ***and id*** for the top vnode of your view.
161
+ - Any kind of logic you put outside of `app()` risks being rerun every HMR so keep your app logic contained to the properties you pass to app (`init`, `subscriptions`, `dispatch`).
162
+
163
+ If HMR is giving you grief, simply disable it by setting the `hmr` option to `false` in the plugin options:
164
+
79
165
 
80
166
  ```js
81
- //vite.config.js
167
+ // vite.config.js
168
+ import { defineConfig } from 'vite'
169
+ import { hyperapp } from 'vite-plugin-hyperapp'
170
+
82
171
  export default defineConfig({
83
172
  plugins: [hyperapp({
84
173
  hmr: false
@@ -86,116 +175,179 @@ export default defineConfig({
86
175
  })
87
176
  ```
88
177
 
89
- ## Server-side Rendering (SSR)
90
178
 
91
- Server side rendering with Vite in general is described here: https://vitejs.dev/guide/ssr.html#server-side-rendering Below are some important details specific to Hyperapp and this plugin.
179
+ ## Server-side rendering
180
+
181
+ Server-side rendering is when the server sends your app's initial view already fully rendered as HTML. Since Hyperapp recycles existing nodes (as long as they match the initial view), your initial view will be rendered even while your app is still loading, improving perceived performance.
182
+
183
+ See [Vite's SSR guide](https://vite.dev/guide/ssr) for setting up a server and
184
+ client entry.
92
185
 
93
- > When you use `npm create vite-hyperapp` to scaffold a project with SSR, these details are set up for you. But they're useful to be aware of as you build out your app.
186
+ This plugin exports `renderToString` from
187
+ `vite-plugin-hyperapp/ssr`. Import it in your server entry to render the
188
+ initial view to an HTML string:
94
189
 
95
- ### Redering your initial app to raw html
190
+ ```js
191
+ // src/entry-server.js
192
+ import { renderToString } from 'vite-plugin-hyperapp/ssr'
193
+ import { init, view } from './app.jsx'
194
+
195
+ //...
196
+
197
+ let html = readFileSync('./index.html.template', 'utf-8')
198
+ html = html.replace(
199
+ '<!--ssr-outlet-->,
200
+ () => renderToString({init, view })
201
+ )
202
+
203
+ //... respond with html
204
+
205
+ ```
96
206
 
97
- This plugin provides a function exported from `vite-plugin-hyperapp/ssr`, which can render your initial app as static html.
207
+ ### How to avoid calling browser APIs server-side
98
208
 
99
- For example:
209
+ Since you pass the same `init` to your server rendering as you pass to your client side app (in order to get the same initial view), there is a danger if your init runs effects that depend on browser-only APIs.
210
+
211
+ To avoid this, use the `import.meta.env.SSR` environment variable in your init action, to opt out of browser-only effects when in SSR:
100
212
 
101
213
  ```js
102
- import { init, view } from "./main"
103
- import renderApp from "vite-plugin-hyperapp/ssr"
104
- export async function render() {
105
- return await renderApp({ init, view })
106
- }
214
+ // src/app.jsx
215
+ const focusInput = () => document.querySelector('#new-item')?.focus()
216
+
217
+ export const init = () => [
218
+ {items: []},
219
+ !import.meta.env.SSR && focusInput //effect won't run in SSR
220
+ ]
107
221
  ```
108
222
 
109
- `renderApp` is an async function that takes the same options object as `hyperapp.app` - except the `node` option - and returns a promise which resolves to the html-string.
223
+ > Note: this only matters for the init action. renderToString does not run subscriptions, nor other actions - unless an init effect dispatches them.
110
224
 
111
- > It is important that the server-entry module has `import ... from 'vite-plugin-hyperapp/ssr'` somewhere in it, in order for HMR to continue working on SSR enabled apps.
112
225
 
113
- ### Mountpoint defined by the view
226
+ ### Initializing your app with data
114
227
 
115
- Unlike many other framework, Hyperapp _replaces_ the given mount-node with the root node rended from the view.
228
+ A common requirement is to initialize one's app with some preloaded data. Let's say, for example, you have a shopping cart and want to start the app with the cart items for the logged in user.
116
229
 
117
- This means that if your non-ssr `index.html` has this:
230
+ ```js
231
+ //src/entry-server.js
232
+
233
+ // check auth header and get the logged in user
234
+ const user = await auth.checkLogin()
235
+
236
+ // get the users existing cart items:
237
+ const cartItems = await db.getCartItemsForUser(user)
238
+
239
+ // ... now, how do we send this data, along with the app
240
+ // to the client?
118
241
 
119
- ```html
120
- <p>Static html here</p>
121
- <div id="app"></div>
122
- <p>More static html</p>
123
242
  ```
124
243
 
125
- and you start your app like this:
244
+ In order to ensure that the server and client both produce the same initial DOM structure, we must ensure they are initialized the same way with the same data. Here is a simple pattern for that:
245
+
246
+
247
+ #### Step 1 – Make the init action accept initialization data.
126
248
 
127
249
  ```js
128
- app({
129
- ...,
130
- node: document.querySelector('#app')
250
+ //app.jsx
251
+ export const init = (
252
+ _,
253
+ {user, cartItems}={user:null, cartItems:[]}
254
+ ) => ({
255
+ //...a bunch of other initial state,
256
+ cartItems, user
131
257
  })
132
258
  ```
133
259
 
134
- Then you _DON'T_ add your ssr outlet like this:
260
+ #### Step 2 - Pass the data as payload to init on the server
135
261
 
136
- ```html
137
- <p>Static html here</p>
138
- <div id="app">
139
- <!--ssr-outlet-->
140
- </div>
141
- <p>More static html</p>
142
- ```
262
+ ```js
263
+ // src/entry-server.js
264
+ // ...
143
265
 
144
- ...but rather like this:
266
+ let html = readFileSync('./index.html.template', 'utf-8')
145
267
 
146
- ```html
147
- <p>Static html here</p>
148
- <!--ssr-outlet-->
149
- <p>More static html</p>
268
+ html = html.replace(
269
+ '<!--ssr-outlet-->',
270
+ () => renderToString({init: [init, {user, cartItems}], view })
271
+ )
150
272
  ```
151
273
 
152
- And you make sure that the root-node of your view has `id="app"`.
153
-
274
+ #### Step 3 - Serialize the data into the html response
154
275
 
155
- ## Important Notes
276
+ This way the same data will be available in the client when the app starts up.
156
277
 
157
- ### Main view with TSX
158
-
159
- TSX-expressions necessarily return the hyperapp type `MaybeVNode<State> | MaybeVNode<State>[]`. However, that is too loose for the `view` you pass to hyperapp's `app({...})` call. Therefore, you need to cast the return of your main view to `VNode<State>` (and of course make sure this is true, also).
278
+ ```js
279
+ // src/entry-server.js
280
+
281
+ //...
282
+
283
+ //html-safe json serialization
284
+ const json = JSON
285
+ .stringify({user, cartItems})
286
+ .replace(
287
+ /[<>&\u2028\u2029]/g,
288
+ character => ({
289
+ '<': '\\u003c',
290
+ '>': '\\u003e',
291
+ '&': '\\u0026',
292
+ '\u2028': '\\u2028',
293
+ '\u2029': '\\u2029',
294
+ })[character]
295
+ )
296
+
297
+ html = html.replace(
298
+ '<!--initial-data-->',
299
+ () => `<script>window.initialData=${json};</script>`
300
+ )
301
+ ```
160
302
 
161
- ```ts
162
- import {type VNode} from 'hyperapp'
163
- ...
303
+ (The HTML template might look like this)
304
+ ```html
305
+ <!doctype html>
306
+ <html lang="en">
307
+ <head>
308
+ <meta charset="UTF-8" />
309
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
310
+ <title></title>
311
+ </head>
312
+ <body>
313
+ <!--initial-data-->
314
+ <!--ssr-outlet-->
315
+ <script type="module" src="/src/entry-client.js"></script>
316
+ </body>
317
+ </html>
164
318
 
165
- export const view = (state:State) => (
166
- <main id="app">
167
- ...
168
- </main>
169
- ) as VNode<State>
170
319
  ```
171
320
 
172
- ### JSX/TSX children
173
321
 
174
- When you pass children to a component, like:
322
+ #### Step 4 - Use the initial data in client app init
175
323
 
176
- ```jsx
177
- <MyComponent>
178
- <p>foo</p>
179
- <p>bar</p>
180
- </MyComponent>
181
- ```
324
+ Wherever you start up your app in the client (in this example `entry-client.js`), pass along the data embedded in the page.
182
325
 
183
- they will arrive as an array of virtual nodes in the _second_ argument to the component.
326
+ ```js
327
+ // src/entry-client.js
328
+ import {app} from 'hyperapp'
329
+ import {init, view} from './app.jsx'
330
+ app({
331
+ init: [init, window.initialData],
332
+ view,
333
+ node: document.getElementById('app')
334
+ })
184
335
 
185
- ```jsx
186
- const MyComponent = (props, children) => {
187
- //children will be: [<p>foo</p>, <p>bar</p>]
188
- }
189
336
  ```
190
337
 
191
- It's ok to put this array of children among other children, because the jsx transform will flatten the child-list:
338
+ ## Safari TSX source maps
192
339
 
193
- ```jsx
194
- const MyComponent = (props, children) => (
195
- <div>
196
- <h1>Header</h1>
197
- {children} {/* <--- this is fine */}
198
- </div>
199
- )
340
+ If Safari Web Inspector doesn't show your original TSX under the transformed
341
+ module, enable the optional development workaround:
342
+
343
+ ```js
344
+ // vite.config.js
345
+ export default defineConfig({
346
+ plugins: [hyperapp({ safariExternalSourcemaps: true })],
347
+ })
200
348
  ```
201
349
 
350
+ It makes Safari load Vite's external `.tsx.map` rather than an inline map.
351
+ Safari shows the generated JS as the parent source and the original TSX below
352
+ it. Chrome and production output are unchanged. This option is off by default;
353
+ ordinary `.ts` files don't need it.
@@ -0,0 +1,4 @@
1
+ export declare function createHmrApp(hot: any): {
2
+ mount(app: any, props: any): any;
3
+ dispose(data: any): Promise<void>;
4
+ };
@@ -0,0 +1,21 @@
1
+ // Development-only browser runtime. Keep this independent of any particular app.
2
+ const stateKey = '__hyperappHmrState';
3
+ export function createHmrApp(hot) {
4
+ let dispatch;
5
+ return {
6
+ mount(app, props) {
7
+ dispatch = app({
8
+ ...props,
9
+ init: Object.hasOwn(hot.data, stateKey) ? hot.data[stateKey] : props.init,
10
+ });
11
+ return dispatch;
12
+ },
13
+ async dispose(data) {
14
+ // Let any already queued Hyperapp render finish before stopping the app.
15
+ await new Promise(resolve => requestAnimationFrame(resolve));
16
+ dispatch(state => (data[stateKey] = state));
17
+ dispatch();
18
+ },
19
+ };
20
+ }
21
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../hmr/runtime.js"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,QAAQ,GAAG,oBAAoB,CAAA;AAErC,MAAM,UAAU,YAAY,CAAC,GAAG;IAC9B,IAAI,QAAQ,CAAA;IAEZ,OAAO;QACL,KAAK,CAAC,GAAG,EAAE,KAAK;YACd,QAAQ,GAAG,GAAG,CAAC;gBACb,GAAG,KAAK;gBACR,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI;aAC1E,CAAC,CAAA;YACF,OAAO,QAAQ,CAAA;QACjB,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,yEAAyE;YACzE,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAA;YAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,CAAA;YAC3C,QAAQ,EAAE,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["// Development-only browser runtime. Keep this independent of any particular app.\nconst stateKey = '__hyperappHmrState'\n\nexport function createHmrApp(hot) {\n let dispatch\n\n return {\n mount(app, props) {\n dispatch = app({\n ...props,\n init: Object.hasOwn(hot.data, stateKey) ? hot.data[stateKey] : props.init,\n })\n return dispatch\n },\n async dispose(data) {\n // Let any already queued Hyperapp render finish before stopping the app.\n await new Promise(resolve => requestAnimationFrame(resolve))\n dispatch(state => (data[stateKey] = state))\n dispatch()\n },\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { type Plugin } from 'vite';
2
+ export declare function hyperappHmr(): Plugin;
@@ -0,0 +1,71 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import MagicString from 'magic-string';
3
+ import { parseSync } from 'vite';
4
+ const virtualId = 'virtual:hyperapp-hmr';
5
+ const resolvedId = '\0' + virtualId;
6
+ // This plugin only instruments the module that imports and calls Hyperapp's app.
7
+ // It does not need to know the entry point, the app's state type, or its view.
8
+ export function hyperappHmr() {
9
+ return {
10
+ name: 'local:hyperapp-hmr',
11
+ apply: 'serve',
12
+ enforce: 'post', // inspect JavaScript after Vite has transformed TS/TSX
13
+ resolveId(id) {
14
+ if (id === virtualId)
15
+ return resolvedId;
16
+ },
17
+ async load(id) {
18
+ if (id === resolvedId) {
19
+ return readFile(new URL('./runtime.js', import.meta.url), 'utf8');
20
+ }
21
+ },
22
+ transform(code, id, options) {
23
+ if (options?.ssr || id.includes('/node_modules/') || !/\.[cm]?[jt]sx?(?:\?|$)/.test(id))
24
+ return;
25
+ if (!code.includes('hyperapp'))
26
+ return;
27
+ const ast = parseSync(id, code);
28
+ const importsApp = ast.program.body.some(node => node.type === 'ImportDeclaration' &&
29
+ node.source.value === 'hyperapp' &&
30
+ node.specifiers.some(specifier => specifier.type === 'ImportSpecifier' &&
31
+ specifier.imported.type === 'Identifier' &&
32
+ specifier.imported.name === 'app' &&
33
+ specifier.local.name === 'app'));
34
+ if (!importsApp)
35
+ return;
36
+ const calls = [];
37
+ function visit(value) {
38
+ if (!value || typeof value !== 'object')
39
+ return;
40
+ if (Array.isArray(value)) {
41
+ value.forEach(visit);
42
+ return;
43
+ }
44
+ const node = value;
45
+ if (node.type === 'CallExpression') {
46
+ const callee = node.callee;
47
+ if (callee.type === 'Identifier' && callee.name === 'app') {
48
+ calls.push({ start: callee.start, end: callee.end });
49
+ }
50
+ }
51
+ for (const [key, child] of Object.entries(node)) {
52
+ if (key !== 'start' && key !== 'end')
53
+ visit(child);
54
+ }
55
+ }
56
+ visit(ast.program.body);
57
+ if (!calls.length)
58
+ return;
59
+ if (calls.length !== 1) {
60
+ this.error(`Expected exactly one Hyperapp app() call in ${id}; found ${calls.length}`);
61
+ }
62
+ const output = new MagicString(code);
63
+ output.prepend(`import { createHmrApp as __hyperappHmrCreate } from '${virtualId}';\n` +
64
+ `const __hyperappHmr = __hyperappHmrCreate(import.meta.hot);\n`);
65
+ output.overwrite(calls[0].start, code.indexOf('(', calls[0].end) + 1, '__hyperappHmr.mount(app, ');
66
+ output.append('\nimport.meta.hot.accept();\nimport.meta.hot.dispose(__hyperappHmr.dispose);\n');
67
+ return { code: output.toString(), map: output.generateMap({ hires: true }) };
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../hmr/vite.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from 'node:fs/promises'\nimport MagicString from 'magic-string'\nimport { parseSync, type Plugin } from 'vite'\n\nconst virtualId = 'virtual:hyperapp-hmr'\nconst resolvedId = '\\0' + virtualId\n\n// This plugin only instruments the module that imports and calls Hyperapp's app.\n// It does not need to know the entry point, the app's state type, or its view.\nexport function hyperappHmr(): Plugin {\n return {\n name: 'local:hyperapp-hmr',\n apply: 'serve',\n enforce: 'post', // inspect JavaScript after Vite has transformed TS/TSX\n resolveId(id) {\n if (id === virtualId) return resolvedId\n },\n async load(id) {\n if (id === resolvedId) {\n return readFile(new URL('./runtime.js', import.meta.url), 'utf8')\n }\n },\n transform(code, id, options) {\n if (options?.ssr || id.includes('/node_modules/') || !/\\.[cm]?[jt]sx?(?:\\?|$)/.test(id)) return\n if (!code.includes('hyperapp')) return\n\n const ast = parseSync(id, code)\n const importsApp = ast.program.body.some(node =>\n node.type === 'ImportDeclaration' &&\n node.source.value === 'hyperapp' &&\n node.specifiers.some(specifier =>\n specifier.type === 'ImportSpecifier' &&\n specifier.imported.type === 'Identifier' &&\n specifier.imported.name === 'app' &&\n specifier.local.name === 'app',\n ),\n )\n if (!importsApp) return\n\n const calls: { start: number; end: number }[] = []\n function visit(value: unknown): void {\n if (!value || typeof value !== 'object') return\n if (Array.isArray(value)) {\n value.forEach(visit)\n return\n }\n const node = value as Record<string, unknown>\n if (node.type === 'CallExpression') {\n const callee = node.callee as { type: string; name?: string; start: number; end: number }\n if (callee.type === 'Identifier' && callee.name === 'app') {\n calls.push({ start: callee.start, end: callee.end })\n }\n }\n for (const [key, child] of Object.entries(node)) {\n if (key !== 'start' && key !== 'end') visit(child)\n }\n }\n visit(ast.program.body)\n if (!calls.length) return\n if (calls.length !== 1) {\n this.error(`Expected exactly one Hyperapp app() call in ${id}; found ${calls.length}`)\n }\n\n const output = new MagicString(code)\n output.prepend(\n `import { createHmrApp as __hyperappHmrCreate } from '${virtualId}';\\n` +\n `const __hyperappHmr = __hyperappHmrCreate(import.meta.hot);\\n`,\n )\n output.overwrite(\n calls[0].start,\n code.indexOf('(', calls[0].end) + 1,\n '__hyperappHmr.mount(app, ',\n )\n output.append('\\nimport.meta.hot.accept();\\nimport.meta.hot.dispose(__hyperappHmr.dispose);\\n')\n return { code: output.toString(), map: output.generateMap({ hires: true }) }\n },\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from 'vite';
2
+ export type HyperappOptions = {
3
+ hmr?: boolean;
4
+ safariExternalSourcemaps?: boolean;
5
+ };
6
+ export declare function hyperapp(options?: HyperappOptions): Plugin[];
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { hyperappJsx } from './jsx/vite.js';
2
+ import { hyperappHmr } from './hmr/vite.js';
3
+ import { safariExternalSourcemapsPlugin } from './safari-sourcemaps/vite.js';
4
+ export function hyperapp(options = {}) {
5
+ const plugins = [hyperappJsx()];
6
+ if (options.hmr !== false)
7
+ plugins.push(hyperappHmr());
8
+ if (options.safariExternalSourcemaps)
9
+ plugins.push(safariExternalSourcemapsPlugin());
10
+ return plugins;
11
+ }
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAA;AAO5E,MAAM,UAAU,QAAQ,CAAC,OAAO,GAAoB,EAAE;IACpD,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;IAC/B,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;IACtD,IAAI,OAAO,CAAC,wBAAwB;QAAE,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,CAAC,CAAA;IACpF,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import type { Plugin } from 'vite'\nimport { hyperappJsx } from './jsx/vite.ts'\nimport { hyperappHmr } from './hmr/vite.ts'\nimport { safariExternalSourcemapsPlugin } from './safari-sourcemaps/vite.ts'\n\nexport type HyperappOptions = {\n hmr?: boolean\n safariExternalSourcemaps?: boolean\n}\n\nexport function hyperapp(options: HyperappOptions = {}): Plugin[] {\n const plugins = [hyperappJsx()]\n if (options.hmr !== false) plugins.push(hyperappHmr())\n if (options.safariExternalSourcemaps) plugins.push(safariExternalSourcemapsPlugin())\n return plugins\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { Fragment, jsx, jsxs, type JSX } from './jsx-runtime.ts';
2
+ import { jsx } from './jsx-runtime.ts';
3
+ export declare const jsxDEV: typeof jsx;
@@ -0,0 +1,4 @@
1
+ export { Fragment, jsx, jsxs } from './jsx-runtime.js';
2
+ import { jsx } from './jsx-runtime.js';
3
+ export const jsxDEV = jsx;
4
+ //# sourceMappingURL=jsx-dev-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-dev-runtime.js","sourceRoot":"","sources":["../../jsx/jsx-dev-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAY,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAEtC,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA","sourcesContent":["export { Fragment, jsx, jsxs, type JSX } from './jsx-runtime.ts'\nimport { jsx } from './jsx-runtime.ts'\n\nexport const jsxDEV = jsx\n"]}
@@ -0,0 +1,20 @@
1
+ import { type Props, type VNode } from 'hyperapp';
2
+ export declare namespace JSX {
3
+ type Element = VNode<any>;
4
+ interface ElementChildrenAttribute {
5
+ children: {};
6
+ }
7
+ interface IntrinsicElements {
8
+ [tag: string]: Props<any> & {
9
+ children?: Child;
10
+ };
11
+ }
12
+ }
13
+ export type Child = JSX.Element | string | number | boolean | null | undefined | readonly Child[];
14
+ type Component = (props: Record<string, unknown>) => Child;
15
+ export declare const Fragment: (props: {
16
+ children?: Child;
17
+ }) => string | number | boolean | readonly Child[] | JSX.Element;
18
+ export declare function jsx(tag: string | Component, props: Record<string, any> | null, key?: string): JSX.Element;
19
+ export declare const jsxs: typeof jsx;
20
+ export {};
@@ -0,0 +1,28 @@
1
+ import { h, text } from 'hyperapp';
2
+ export const Fragment = (props) => props.children ?? [];
3
+ function normalize(children) {
4
+ const result = [];
5
+ const visit = (child) => {
6
+ if (Array.isArray(child)) {
7
+ for (const item of child)
8
+ visit(item);
9
+ }
10
+ else if (typeof child === 'string' || typeof child === 'number') {
11
+ result.push(text(child));
12
+ }
13
+ else if (child != null && child !== false && child !== true) {
14
+ result.push(child);
15
+ }
16
+ };
17
+ visit(children);
18
+ return result;
19
+ }
20
+ export function jsx(tag, props, key) {
21
+ const attributes = props ?? {};
22
+ if (typeof tag === 'function')
23
+ return tag(attributes);
24
+ const { children, ...rest } = attributes;
25
+ return h(tag, key === undefined ? rest : { ...rest, key }, normalize(children));
26
+ }
27
+ export const jsxs = jsx;
28
+ //# sourceMappingURL=jsx-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-runtime.js","sourceRoot":"","sources":["../../jsx/jsx-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,IAAI,EAA0B,MAAM,UAAU,CAAA;AAgB1D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAA2B,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAA;AAE7E,SAAS,SAAS,CAAC,QAAe;IAChC,MAAM,MAAM,GAAU,EAAE,CAAA;IACxB,MAAM,KAAK,GAAG,CAAC,KAAY,EAAQ,EAAE;QACnC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACvC,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAClE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QAC1B,CAAC;aAAM,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC9D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACH,CAAC,CAAA;IACD,KAAK,CAAC,QAAQ,CAAC,CAAA;IACf,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,GAAG,CACjB,GAAuB,EACvB,KAAiC,EACjC,GAAY;IAEZ,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE,CAAA;IAC9B,IAAI,OAAO,GAAG,KAAK,UAAU;QAAE,OAAO,GAAG,CAAC,UAAU,CAAgB,CAAA;IACpE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,UAAU,CAAA;IACxC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjF,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAA","sourcesContent":["import { h, text, type Props, type VNode } from 'hyperapp'\n\n// JSX does not carry Hyperapp's application state type through the tree.\nexport namespace JSX {\n export type Element = VNode<any>\n export interface ElementChildrenAttribute {\n children: {}\n }\n export interface IntrinsicElements {\n [tag: string]: Props<any> & { children?: Child }\n }\n}\n\nexport type Child = JSX.Element | string | number | boolean | null | undefined | readonly Child[]\ntype Component = (props: Record<string, unknown>) => Child\n\nexport const Fragment = (props: { children?: Child }) => props.children ?? []\n\nfunction normalize(children: Child): any[] {\n const result: any[] = []\n const visit = (child: Child): void => {\n if (Array.isArray(child)) {\n for (const item of child) visit(item)\n } else if (typeof child === 'string' || typeof child === 'number') {\n result.push(text(child))\n } else if (child != null && child !== false && child !== true) {\n result.push(child)\n }\n }\n visit(children)\n return result\n}\n\nexport function jsx(\n tag: string | Component,\n props: Record<string, any> | null,\n key?: string,\n): JSX.Element {\n const attributes = props ?? {}\n if (typeof tag === 'function') return tag(attributes) as JSX.Element\n const { children, ...rest } = attributes\n return h(tag, key === undefined ? rest : { ...rest, key }, normalize(children))\n}\n\nexport const jsxs = jsx\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function hyperappJsx(): Plugin;
@@ -0,0 +1,20 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ // The JSX import source points at this folder's standard automatic runtime files.
3
+ export function hyperappJsx() {
4
+ return {
5
+ name: 'local:hyperapp-jsx',
6
+ config() {
7
+ return {
8
+ oxc: {
9
+ jsx: { runtime: 'automatic', importSource: 'vite-plugin-hyperapp/jsx' },
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ 'vite-plugin-hyperapp/jsx': fileURLToPath(new URL('.', import.meta.url)),
14
+ },
15
+ },
16
+ };
17
+ },
18
+ };
19
+ }
20
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../jsx/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGxC,kFAAkF;AAClF,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,MAAM;YACJ,OAAO;gBACL,GAAG,EAAE;oBACH,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,0BAA0B,EAAE;iBACxE;gBACD,OAAO,EAAE;oBACP,KAAK,EAAE;wBACL,0BAA0B,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;qBACzE;iBACF;aACF,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import { fileURLToPath } from 'node:url'\nimport type { Plugin } from 'vite'\n\n// The JSX import source points at this folder's standard automatic runtime files.\nexport function hyperappJsx(): Plugin {\n return {\n name: 'local:hyperapp-jsx',\n config() {\n return {\n oxc: {\n jsx: { runtime: 'automatic', importSource: 'vite-plugin-hyperapp/jsx' },\n },\n resolve: {\n alias: {\n 'vite-plugin-hyperapp/jsx': fileURLToPath(new URL('.', import.meta.url)),\n },\n },\n }\n },\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function safariExternalSourcemapsPlugin(): Plugin;
@@ -0,0 +1,33 @@
1
+ // Safari Web Inspector needs an external map to show the original TSX source.
2
+ // Vite already serves .tsx.map requests; only the JS response needs changing.
3
+ export function safariExternalSourcemapsPlugin() {
4
+ return {
5
+ name: 'local:safari-external-sourcemaps',
6
+ apply: 'serve',
7
+ configureServer(server) {
8
+ server.middlewares.use((req, res, next) => {
9
+ const agent = req.headers['user-agent'] ?? '';
10
+ if (!/Safari\//.test(agent) || /Chrome\/|Chromium\/|CriOS\/|Edg\/|OPR\//.test(agent)) {
11
+ return next();
12
+ }
13
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
14
+ if (!url.pathname.endsWith('.tsx'))
15
+ return next();
16
+ delete req.headers['if-none-match'];
17
+ const end = res.end.bind(res);
18
+ const finish = (chunk, encoding, callback) => encoding ? end(chunk, encoding, callback) : callback ? end(chunk, callback) : end(chunk);
19
+ res.end = ((chunk, encoding, callback) => {
20
+ if (res.statusCode !== 200 || typeof chunk !== 'string') {
21
+ return finish(chunk, encoding, callback);
22
+ }
23
+ const rewritten = chunk.replace(/\/\/# sourceMappingURL=data:application\/json;base64,[A-Za-z0-9+/=]+\s*$/, `//# sourceMappingURL=${url.pathname}.map${url.search}`);
24
+ if (rewritten !== chunk)
25
+ res.removeHeader('ETag');
26
+ return finish(rewritten, encoding, callback);
27
+ });
28
+ next();
29
+ });
30
+ },
31
+ };
32
+ }
33
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../safari-sourcemaps/vite.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,8EAA8E;AAC9E,MAAM,UAAU,8BAA8B;IAC5C,OAAO;QACL,IAAI,EAAE,kCAAkC;QACxC,KAAK,EAAE,OAAO;QACd,eAAe,CAAC,MAAM;YACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBACxC,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;gBAC7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,yCAAyC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;oBACrF,OAAO,IAAI,EAAE,CAAA;gBACf,CAAC;gBAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC,CAAA;gBAChF,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAAE,OAAO,IAAI,EAAE,CAAA;gBAEjD,OAAO,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;gBACnC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBAC7B,MAAM,MAAM,GAAG,CAAC,KAA2B,EAAE,QAAyB,EAAE,QAAqB,EAAE,EAAE,CAC/F,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;gBAC1F,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,KAA2B,EAAE,QAAyB,EAAE,QAAqB,EAAE,EAAE;oBAC3F,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;wBACxD,OAAO,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;oBAC1C,CAAC;oBACD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAC7B,0EAA0E,EAC1E,wBAAwB,GAAG,CAAC,QAAQ,OAAO,GAAG,CAAC,MAAM,EAAE,CACxD,CAAA;oBACD,IAAI,SAAS,KAAK,KAAK;wBAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;oBACjD,OAAO,MAAM,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;gBAC9C,CAAC,CAAmB,CAAA;gBACpB,IAAI,EAAE,CAAA;YACR,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { Plugin } from 'vite'\n\n// Safari Web Inspector needs an external map to show the original TSX source.\n// Vite already serves .tsx.map requests; only the JS response needs changing.\nexport function safariExternalSourcemapsPlugin(): Plugin {\n return {\n name: 'local:safari-external-sourcemaps',\n apply: 'serve',\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const agent = req.headers['user-agent'] ?? ''\n if (!/Safari\\//.test(agent) || /Chrome\\/|Chromium\\/|CriOS\\/|Edg\\/|OPR\\//.test(agent)) {\n return next()\n }\n\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`)\n if (!url.pathname.endsWith('.tsx')) return next()\n\n delete req.headers['if-none-match']\n const end = res.end.bind(res)\n const finish = (chunk?: string | Uint8Array, encoding?: BufferEncoding, callback?: () => void) =>\n encoding ? end(chunk, encoding, callback) : callback ? end(chunk, callback) : end(chunk)\n res.end = ((chunk?: string | Uint8Array, encoding?: BufferEncoding, callback?: () => void) => {\n if (res.statusCode !== 200 || typeof chunk !== 'string') {\n return finish(chunk, encoding, callback)\n }\n const rewritten = chunk.replace(\n /\\/\\/# sourceMappingURL=data:application\\/json;base64,[A-Za-z0-9+/=]+\\s*$/,\n `//# sourceMappingURL=${url.pathname}.map${url.search}`,\n )\n if (rewritten !== chunk) res.removeHeader('ETag')\n return finish(rewritten, encoding, callback)\n }) as typeof res.end\n next()\n })\n },\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export { renderToString } from './render-to-string.ts';
@@ -0,0 +1,2 @@
1
+ export { renderToString } from './render-to-string.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../ssr/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA","sourcesContent":["export { renderToString } from './render-to-string.ts'\n"]}
@@ -0,0 +1,8 @@
1
+ import { type Dispatchable, type VNode } from 'hyperapp';
2
+ type ServerAppProps<S> = {
3
+ init: Dispatchable<S>;
4
+ view: (state: S) => VNode<S>;
5
+ };
6
+ /** Render the state reached by Hyperapp's synchronous initialization effects. */
7
+ export declare function renderToString<S>({ init, view }: ServerAppProps<S>): string;
8
+ export {};
@@ -0,0 +1,10 @@
1
+ import { app } from 'hyperapp';
2
+ import { vdomToHtml } from './vdom-html.js';
3
+ /** Render the state reached by Hyperapp's synchronous initialization effects. */
4
+ export function renderToString({ init, view }) {
5
+ const dispatch = app({ init });
6
+ let state;
7
+ dispatch(current => (state = current));
8
+ return vdomToHtml(view(state));
9
+ }
10
+ //# sourceMappingURL=render-to-string.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-to-string.js","sourceRoot":"","sources":["../../ssr/render-to-string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAiC,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAO3C,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAI,EAAE,IAAI,EAAE,IAAI,EAAqB;IACjE,MAAM,QAAQ,GAAG,GAAG,CAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACjC,IAAI,KAAS,CAAA;IACb,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,CAAA;IACtC,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AAChC,CAAC","sourcesContent":["import { app, type Dispatchable, type VNode } from 'hyperapp'\nimport { vdomToHtml } from './vdom-html.ts'\n\ntype ServerAppProps<S> = {\n init: Dispatchable<S>\n view: (state: S) => VNode<S>\n}\n\n/** Render the state reached by Hyperapp's synchronous initialization effects. */\nexport function renderToString<S>({ init, view }: ServerAppProps<S>): string {\n const dispatch = app<S>({ init })\n let state!: S\n dispatch(current => (state = current))\n return vdomToHtml(view(state))\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { VNode } from 'hyperapp';
2
+ /** Serialize an existing Hyperapp virtual tree without starting an app or using the DOM. */
3
+ export declare function vdomToHtml(node: VNode<any>): string;
@@ -0,0 +1,131 @@
1
+ const booleanAttributes = new Set([
2
+ 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',
3
+ 'default', 'defer', 'disabled', 'disablepictureinpicture',
4
+ 'disableremoteplayback', 'formnovalidate', 'hidden', 'inert', 'ismap',
5
+ 'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open',
6
+ 'playsinline', 'readonly', 'required', 'reversed', 'selected',
7
+ ]);
8
+ const overloadedBooleanAttributes = new Set(['capture', 'download']);
9
+ const voidElements = new Set([
10
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',
11
+ 'meta', 'param', 'source', 'track', 'wbr',
12
+ ]);
13
+ const propertyOnly = new Set([
14
+ 'children', 'key', 'innerHTML', 'outerHTML', 'textContent', 'innerText',
15
+ 'nodeValue', 'scrollTop', 'scrollLeft', 'offsetWidth', 'offsetHeight',
16
+ 'clientWidth', 'clientHeight', 'files', 'labels',
17
+ 'elements', 'options', 'selectedOptions', 'validity', 'validationMessage',
18
+ 'willValidate', 'selectionStart', 'selectionEnd', 'selectionDirection',
19
+ 'valueAsDate', 'valueAsNumber',
20
+ ]);
21
+ function escapeHtml(value) {
22
+ return String(value).replace(/[&<>"']/g, character => ({
23
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
24
+ })[character]);
25
+ }
26
+ function styleName(name) {
27
+ if (name.startsWith('--'))
28
+ return name;
29
+ if (name === 'cssFloat')
30
+ return 'float';
31
+ return name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
32
+ .replace(/^ms-/, '-ms-');
33
+ }
34
+ function serializeStyle(style) {
35
+ if (!style || typeof style !== 'object')
36
+ return '';
37
+ return Object.entries(style).flatMap(([name, value]) => value == null || value === '' || typeof value === 'boolean'
38
+ ? []
39
+ : [`${styleName(name)}: ${String(value)}`]).join('; ');
40
+ }
41
+ function serializeProps(props, tag, isSvg) {
42
+ let attributes = '';
43
+ for (const [property, value] of Object.entries(props)) {
44
+ if (property.startsWith('on') || propertyOnly.has(property) || value == null)
45
+ continue;
46
+ if (property === 'style') {
47
+ const css = serializeStyle(value);
48
+ if (css)
49
+ attributes += ` style="${escapeHtml(css)}"`;
50
+ continue;
51
+ }
52
+ if (typeof value === 'function' || typeof value === 'object' || typeof value === 'symbol')
53
+ continue;
54
+ // An input's current value is reflected by its value attribute; for a
55
+ // textarea, the initial value lives in its text content instead.
56
+ if ((tag === 'textarea' || tag === 'select') && (property === 'value' || property === 'defaultValue'))
57
+ continue;
58
+ if (property === 'defaultValue' && tag !== 'input')
59
+ continue;
60
+ if (property === 'defaultChecked' && tag !== 'input')
61
+ continue;
62
+ const name = property === 'defaultValue' ? 'value'
63
+ : property === 'defaultChecked' ? 'checked'
64
+ : isSvg ? property : property.toLowerCase();
65
+ if (!/^[^\s"'<>/=]+$/.test(name))
66
+ continue;
67
+ if (!isSvg && booleanAttributes.has(name)) {
68
+ if (value)
69
+ attributes += ` ${name}`;
70
+ }
71
+ else if (!isSvg && overloadedBooleanAttributes.has(name) && value === true) {
72
+ attributes += ` ${name}`;
73
+ }
74
+ else if (typeof value !== 'boolean' || name.startsWith('aria-') || name.startsWith('data-') ||
75
+ (!isSvg && ['contenteditable', 'draggable', 'spellcheck', 'translate'].includes(name))) {
76
+ attributes += ` ${name}="${escapeHtml(value)}"`;
77
+ }
78
+ else if (isSvg && value === true) {
79
+ attributes += ` ${name}="true"`;
80
+ }
81
+ }
82
+ return attributes;
83
+ }
84
+ function render(node, isSvg) {
85
+ if (node == null || typeof node === 'boolean')
86
+ return '';
87
+ if (typeof node.tag === 'function')
88
+ return render(node.tag(node.memo), isSvg);
89
+ if (node.type === 3)
90
+ return escapeHtml(node.tag);
91
+ const tag = node.tag;
92
+ if (!/^[A-Za-z][\w:.-]*$/.test(tag))
93
+ throw new Error(`Invalid element tag: ${tag}`);
94
+ const svg = isSvg || tag === 'svg';
95
+ const props = node.props;
96
+ const attributes = serializeProps(props, tag, svg);
97
+ if (!svg && voidElements.has(tag))
98
+ return `<${tag}${attributes}>`;
99
+ let children;
100
+ if (tag === 'textarea' && !svg && (props.value != null || props.defaultValue != null)) {
101
+ children = escapeHtml(props.value ?? props.defaultValue);
102
+ }
103
+ else if ((tag === 'script' || tag === 'style') && !svg) {
104
+ // Raw-text HTML elements do not decode character references. A closing
105
+ // tag in the content cannot safely be represented as a text child.
106
+ children = node.children.map(child => {
107
+ if (child == null || typeof child === 'boolean')
108
+ return '';
109
+ if (child.type !== 3)
110
+ throw new Error(`${tag} only supports text children`);
111
+ const value = String(child.tag);
112
+ if (new RegExp(`</${tag}(?:[\\s/>])`, 'i').test(value)) {
113
+ throw new Error(`Cannot serialize a closing ${tag} tag inside its text`);
114
+ }
115
+ return value;
116
+ }).join('');
117
+ }
118
+ else {
119
+ children = node.children.map(child => render(child, svg && tag !== 'foreignObject')).join('');
120
+ }
121
+ // The HTML parser discards the first newline after these opening tags.
122
+ if (!svg && ['pre', 'listing', 'textarea'].includes(tag) && children.startsWith('\n')) {
123
+ children = `\n${children}`;
124
+ }
125
+ return `<${tag}${attributes}>${children}</${tag}>`;
126
+ }
127
+ /** Serialize an existing Hyperapp virtual tree without starting an app or using the DOM. */
128
+ export function vdomToHtml(node) {
129
+ return render(node, false);
130
+ }
131
+ //# sourceMappingURL=vdom-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vdom-html.js","sourceRoot":"","sources":["../../ssr/vdom-html.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ElementVNode, VNode } from 'hyperapp'\n\nconst booleanAttributes = new Set([\n 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',\n 'default', 'defer', 'disabled', 'disablepictureinpicture',\n 'disableremoteplayback', 'formnovalidate', 'hidden', 'inert', 'ismap',\n 'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open',\n 'playsinline', 'readonly', 'required', 'reversed', 'selected',\n])\nconst overloadedBooleanAttributes = new Set(['capture', 'download'])\nconst voidElements = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',\n 'meta', 'param', 'source', 'track', 'wbr',\n])\nconst propertyOnly = new Set([\n 'children', 'key', 'innerHTML', 'outerHTML', 'textContent', 'innerText',\n 'nodeValue', 'scrollTop', 'scrollLeft', 'offsetWidth', 'offsetHeight',\n 'clientWidth', 'clientHeight', 'files', 'labels',\n 'elements', 'options', 'selectedOptions', 'validity', 'validationMessage',\n 'willValidate', 'selectionStart', 'selectionEnd', 'selectionDirection',\n 'valueAsDate', 'valueAsNumber',\n])\n\nfunction escapeHtml(value: unknown): string {\n return String(value).replace(/[&<>\"']/g, character => ({\n '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;',\n })[character]!)\n}\n\nfunction styleName(name: string): string {\n if (name.startsWith('--')) return name\n if (name === 'cssFloat') return 'float'\n return name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)\n .replace(/^ms-/, '-ms-')\n}\n\nfunction serializeStyle(style: unknown): string {\n if (!style || typeof style !== 'object') return ''\n return Object.entries(style).flatMap(([name, value]) =>\n value == null || value === '' || typeof value === 'boolean'\n ? []\n : [`${styleName(name)}: ${String(value)}`],\n ).join('; ')\n}\n\nfunction serializeProps(props: Record<string, unknown>, tag: string, isSvg: boolean): string {\n let attributes = ''\n for (const [property, value] of Object.entries(props)) {\n if (property.startsWith('on') || propertyOnly.has(property) || value == null) continue\n if (property === 'style') {\n const css = serializeStyle(value)\n if (css) attributes += ` style=\"${escapeHtml(css)}\"`\n continue\n }\n if (typeof value === 'function' || typeof value === 'object' || typeof value === 'symbol') continue\n\n // An input's current value is reflected by its value attribute; for a\n // textarea, the initial value lives in its text content instead.\n if ((tag === 'textarea' || tag === 'select') && (property === 'value' || property === 'defaultValue')) continue\n if (property === 'defaultValue' && tag !== 'input') continue\n if (property === 'defaultChecked' && tag !== 'input') continue\n\n const name = property === 'defaultValue' ? 'value'\n : property === 'defaultChecked' ? 'checked'\n : isSvg ? property : property.toLowerCase()\n\n if (!/^[^\\s\"'<>/=]+$/.test(name)) continue\n if (!isSvg && booleanAttributes.has(name)) {\n if (value) attributes += ` ${name}`\n } else if (!isSvg && overloadedBooleanAttributes.has(name) && value === true) {\n attributes += ` ${name}`\n } else if (typeof value !== 'boolean' || name.startsWith('aria-') || name.startsWith('data-') ||\n (!isSvg && ['contenteditable', 'draggable', 'spellcheck', 'translate'].includes(name))) {\n attributes += ` ${name}=\"${escapeHtml(value)}\"`\n } else if (isSvg && value === true) {\n attributes += ` ${name}=\"true\"`\n }\n }\n return attributes\n}\n\nfunction render(node: VNode<any> | boolean | null | undefined, isSvg: boolean): string {\n if (node == null || typeof node === 'boolean') return ''\n if (typeof node.tag === 'function') return render(node.tag((node as ElementVNode<any>).memo!), isSvg)\n if (node.type === 3) return escapeHtml(node.tag)\n\n const tag = node.tag\n if (!/^[A-Za-z][\\w:.-]*$/.test(tag)) throw new Error(`Invalid element tag: ${tag}`)\n const svg = isSvg || tag === 'svg'\n const props = node.props as Record<string, unknown>\n const attributes = serializeProps(props, tag, svg)\n if (!svg && voidElements.has(tag)) return `<${tag}${attributes}>`\n\n let children: string\n if (tag === 'textarea' && !svg && (props.value != null || props.defaultValue != null)) {\n children = escapeHtml(props.value ?? props.defaultValue)\n } else if ((tag === 'script' || tag === 'style') && !svg) {\n // Raw-text HTML elements do not decode character references. A closing\n // tag in the content cannot safely be represented as a text child.\n children = node.children.map(child => {\n if (child == null || typeof child === 'boolean') return ''\n if (child.type !== 3) throw new Error(`${tag} only supports text children`)\n const value = String(child.tag)\n if (new RegExp(`</${tag}(?:[\\\\s/>])`, 'i').test(value)) {\n throw new Error(`Cannot serialize a closing ${tag} tag inside its text`)\n }\n return value\n }).join('')\n } else {\n children = node.children.map(child => render(child, svg && tag !== 'foreignObject')).join('')\n }\n // The HTML parser discards the first newline after these opening tags.\n if (!svg && ['pre', 'listing', 'textarea'].includes(tag) && children.startsWith('\\n')) {\n children = `\\n${children}`\n }\n return `<${tag}${attributes}>${children}</${tag}>`\n}\n\n/** Serialize an existing Hyperapp virtual tree without starting an app or using the DOM. */\nexport function vdomToHtml(node: VNode<any>): string {\n return render(node, false)\n}\n"]}
package/package.json CHANGED
@@ -1,51 +1,46 @@
1
1
  {
2
2
  "name": "vite-plugin-hyperapp",
3
- "version": "1.3.1",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "Plugin and helpers for using Hyperapp with Vite",
6
- "main": "./index.js",
7
- "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1"
9
- },
10
6
  "author": "Zacharias Enochsson",
11
7
  "license": "MIT",
12
- "files": [
13
- "*.js",
14
- "*.d.ts"
15
- ],
16
8
  "exports": {
17
9
  ".": {
18
- "types": "./index.d.ts",
19
- "import": "./index.js",
20
- "default": "./index.js"
21
- },
22
- "./jsx": {
23
- "types": "./jsx.d.ts",
24
- "import": "./jsx.js",
25
- "default": "./jsx.js"
26
- },
27
- "./jsx.js": {
28
- "types": "./jsx.d.ts",
29
- "import": "./jsx.js",
30
- "default": "./jsx.js"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
31
12
  },
32
13
  "./ssr": {
33
- "types": "./ssr.d.ts",
34
- "import": "./ssr.js",
35
- "default": "./ssr.js"
14
+ "types": "./dist/ssr/index.d.ts",
15
+ "import": "./dist/ssr/index.js"
36
16
  },
37
- "./ssr.js": {
38
- "types": "./ssr.d.ts",
39
- "import": "./ssr.js",
40
- "default": "./ssr.js"
17
+ "./jsx/jsx-runtime": {
18
+ "types": "./dist/jsx/jsx-runtime.d.ts",
19
+ "import": "./dist/jsx/jsx-runtime.js"
20
+ },
21
+ "./jsx/jsx-dev-runtime": {
22
+ "types": "./dist/jsx/jsx-dev-runtime.d.ts",
23
+ "import": "./dist/jsx/jsx-dev-runtime.js"
41
24
  },
42
25
  "./package.json": "./package.json"
43
26
  },
27
+ "files": ["dist"],
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.build.json",
30
+ "test": "npm run build && vitest run"
31
+ },
44
32
  "dependencies": {
45
- "jsdom": "^24.1.0",
46
- "hyperapp": "^2.0.22"
33
+ "magic-string": "^1.4.3"
47
34
  },
48
35
  "peerDependencies": {
49
- "vite": "^4.5.14"
36
+ "hyperapp": "^2.0.22",
37
+ "vite": "^8.3.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^26.6.5",
41
+ "hyperapp": "^2.0.22",
42
+ "typescript": "^7.0.2",
43
+ "vite": "^8.3.0",
44
+ "vitest": "^5.0.3"
50
45
  }
51
46
  }
package/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import { type PluginOption } from "vite"
2
-
3
- export interface HyperappPluginOptions {
4
- hmr?: boolean
5
- dispatchExport?: string
6
- }
7
-
8
- export default function hyperapp(
9
- options?: HyperappIntegrationOptions,
10
- ): PluginOption
package/index.js DELETED
@@ -1,62 +0,0 @@
1
- const defaultOptions = {
2
- hmr: true,
3
- dispatchExport: "dispatch",
4
- }
5
-
6
- const rxIsEntry = /import {.*app[^}]*} from "hyperapp"/
7
- const rxIsSSREntry = /import .+ from "vite-plugin-hyperapp\/ssr"/
8
-
9
- const HMRAppendage = dispatch => `
10
- if (import.meta.hot) {
11
- import.meta.hot.accept(m => {
12
- if (!${dispatch}) return
13
- let prevState = null
14
- ${dispatch}(s => (prevState = s))
15
- ${dispatch}()
16
- m.${dispatch}(prevState)
17
- })
18
- }
19
- `
20
-
21
- const SSRHMRAppendage = `
22
- if (import.meta.hot) {
23
- import.meta.hot.accept()
24
- }
25
- `
26
-
27
- export default function injectConsoleLog(options = {}) {
28
- options = { ...defaultOptions, ...options }
29
-
30
- return {
31
- name: "vite-plugin-hyperapp",
32
-
33
- config: () => ({
34
- esbuild: {
35
- jsxFactory: "jsx",
36
- jsxFragment: "jsxFragment",
37
- jsxInject:
38
- "import {jsx, jsxFragment} from 'vite-plugin-hyperapp/jsx.js'",
39
- },
40
- }),
41
-
42
- async transform(code, id) {
43
- if (!options.hmr) return null
44
-
45
- if (rxIsEntry.test(code)) {
46
- return {
47
- code: code + HMRAppendage(options.dispatchExport),
48
- map: null,
49
- }
50
- }
51
-
52
- if (rxIsSSREntry.test(code)) {
53
- return {
54
- code: code + SSRHMRAppendage,
55
- map: null,
56
- }
57
- }
58
-
59
- return null
60
- },
61
- }
62
- }
package/jsx.d.ts DELETED
@@ -1,59 +0,0 @@
1
- import type { Props, MaybeVNode, ElementVNode } from "hyperapp"
2
-
3
- type Component<S, P> = (
4
- props: P,
5
- children: MaybeVNode<S>[],
6
- ) => MaybeVNode<S> | MaybeVNode<S>[]
7
-
8
- type _JSXChild<S> = string | number | MaybeVNode<S>
9
- type JSXChild<S> = _JSXChild<S> | _JSXChild<S>[]
10
-
11
- export declare function jsx<S>(
12
- type: string,
13
- props?: Props<S>,
14
- ...children: JSXChild<S>[]
15
- ): ElementVNode<S>
16
-
17
- export declare function jsx<S, P>(
18
- type: Component<S, P>,
19
- props?: P | null,
20
- ...children: JSXChild<S>[]
21
- ): ReturnType<Component<S, P>>
22
-
23
- export declare function jsxFragment<S>(
24
- _: any,
25
- children: JSXChild<S>[],
26
- ): MaybeVNode<S>[]
27
-
28
- declare global {
29
- namespace JSX {
30
- /** Result of a JSX expression. Components may return arrays. */
31
- type Element = MaybeVNode<any> | MaybeVNode<any>[]
32
-
33
- /** What you can put in the `<X />` position */
34
- type ElementType = string | Component<any, any>
35
-
36
- /** Allow `<>...</>` fragments; returns an array */
37
- type Fragment = MaybeVNode<any>[]
38
-
39
- /** Intrinsic (HTML/SVG) elements. */
40
- interface IntrinsicElements {
41
- [elem: string]: Props<any>
42
- }
43
-
44
- /** prevents errors about children missing from props in components */
45
- type LibraryManagedAttributes<P> = P & { children?: JSXChild<any>[] }
46
- }
47
- }
48
-
49
- // make jsx and jsxFragment ambient globals because
50
- // ts server doesn't know I'll add them later in transform
51
- type JsxFn = typeof jsx
52
- type JsxFragmentFn = typeof jsxFragment
53
-
54
- declare global {
55
- const jsx: JsxFn
56
- const jsxFragment: JsxFragmentFn
57
- }
58
-
59
- export {}
package/jsx.js DELETED
@@ -1,13 +0,0 @@
1
- import { h, text } from "hyperapp"
2
-
3
- const cproc = children =>
4
- children
5
- .flat()
6
- .map(x => ((typeof x === "string") | (typeof x === "number") ? text(x) : x))
7
-
8
- export const jsx = (tag, props, ...children) =>
9
- typeof tag === "function"
10
- ? tag(props, cproc(children))
11
- : h(tag, props || {}, cproc(children))
12
-
13
- export const jsxFragment = (_, children) => cproc(children)
package/ssr.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import { Subscription } from "hyperapp"
2
-
3
- type Defs<S = any> = {
4
- init?: Dispatchable<S>
5
- view?: (state: S) => VNode<S>
6
- subscriptions?: (state: S) => Subscription<S>[]
7
- dispatch?: (d: Dispatch<S>) => Dispatch<S>
8
- }
9
-
10
- declare module "vite-plugin-hyperapp/ssr" {
11
- export default function (defs: Defs): Promise<string>
12
- }
package/ssr.js DELETED
@@ -1,17 +0,0 @@
1
- import { JSDOM } from "jsdom"
2
- import { app } from "hyperapp"
3
-
4
- export default function (defs) {
5
- const dom = new JSDOM("")
6
- global.document = dom.window.document
7
- const parent = document.createElement("div")
8
- const node = document.createElement("div")
9
- parent.appendChild(node)
10
- return new Promise(resolve => {
11
- const stop = app({ ...defs, node })
12
- setTimeout(() => {
13
- stop() //prevents any eventual callbacks from calling back
14
- resolve(parent.firstChild.outerHTML)
15
- }, 0)
16
- })
17
- }