hono-preact 0.1.0 → 0.3.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 +2 -1
- package/dist/adapter-cloudflare.d.ts +1 -0
- package/dist/adapter-cloudflare.d.ts.map +1 -0
- package/dist/adapter-cloudflare.js +2 -0
- package/dist/adapter-node.d.ts +1 -0
- package/dist/adapter-node.d.ts.map +1 -0
- package/dist/adapter-node.js +2 -0
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/dist/iso/action-result-context.d.ts +22 -0
- package/dist/iso/action-result-context.js +2 -0
- package/dist/iso/action.d.ts +60 -25
- package/dist/iso/action.js +210 -58
- package/dist/iso/cache.d.ts +9 -0
- package/dist/iso/cache.js +26 -0
- package/dist/iso/define-app.d.ts +14 -0
- package/dist/iso/define-app.js +3 -0
- package/dist/iso/define-loader.d.ts +31 -0
- package/dist/iso/define-loader.js +30 -16
- package/dist/iso/define-middleware.d.ts +43 -0
- package/dist/iso/define-middleware.js +6 -0
- package/dist/iso/define-page.d.ts +7 -2
- package/dist/iso/define-page.js +1 -1
- package/dist/iso/define-routes.d.ts +24 -1
- package/dist/iso/define-routes.js +34 -0
- package/dist/iso/define-stream-observer.d.ts +20 -0
- package/dist/iso/define-stream-observer.js +3 -0
- package/dist/iso/form.d.ts +13 -4
- package/dist/iso/form.js +115 -33
- package/dist/iso/index.d.ts +15 -7
- package/dist/iso/index.js +9 -4
- package/dist/iso/internal/action-envelope.d.ts +37 -0
- package/dist/iso/internal/action-envelope.js +47 -0
- package/dist/iso/internal/action-result-store.d.ts +28 -0
- package/dist/iso/internal/action-result-store.js +35 -0
- package/dist/iso/internal/contexts.d.ts +0 -2
- package/dist/iso/internal/contexts.js +0 -1
- package/dist/iso/internal/envelope.js +1 -2
- package/dist/iso/internal/form-submit-store.d.ts +9 -0
- package/dist/iso/internal/form-submit-store.js +32 -0
- package/dist/iso/internal/loader-fetch.js +102 -41
- package/dist/iso/internal/loader-runner.js +105 -8
- package/dist/iso/internal/loader.d.ts +3 -3
- package/dist/iso/internal/middleware-runner.d.ts +22 -0
- package/dist/iso/internal/middleware-runner.js +79 -0
- package/dist/iso/internal/page-middleware-host.d.ts +13 -0
- package/dist/iso/internal/page-middleware-host.js +119 -0
- package/dist/iso/internal/route-boundary.d.ts +5 -4
- package/dist/iso/internal/route-boundary.js +16 -0
- package/dist/iso/internal/safe-redirect.d.ts +7 -0
- package/dist/iso/internal/safe-redirect.js +27 -0
- package/dist/iso/internal/sse-decoder.d.ts +1 -1
- package/dist/iso/internal/sse-decoder.js +40 -26
- package/dist/iso/internal/stream-observer-runner.d.ts +13 -0
- package/dist/iso/internal/stream-observer-runner.js +48 -0
- package/dist/iso/internal/use-partitioner.d.ts +9 -0
- package/dist/iso/internal/use-partitioner.js +11 -0
- package/dist/iso/internal/use-types.d.ts +7 -0
- package/dist/iso/internal/use-types.js +1 -0
- package/dist/iso/internal.d.ts +12 -5
- package/dist/iso/internal.js +16 -7
- package/dist/iso/optimistic-action.d.ts +10 -1
- package/dist/iso/optimistic-action.js +11 -3
- package/dist/iso/optimistic.d.ts +10 -1
- package/dist/iso/optimistic.js +45 -5
- package/dist/iso/outcomes.d.ts +50 -0
- package/dist/iso/outcomes.js +67 -0
- package/dist/iso/page-only.d.ts +5 -0
- package/dist/iso/page-only.js +20 -0
- package/dist/iso/page.d.ts +3 -3
- package/dist/iso/page.js +3 -3
- package/dist/iso/use-action-result.d.ts +25 -0
- package/dist/iso/use-action-result.js +39 -0
- package/dist/iso/use-form-status.d.ts +5 -0
- package/dist/iso/use-form-status.js +13 -0
- package/dist/page.d.ts +1 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +8 -0
- package/dist/server/actions-handler.d.ts +27 -6
- package/dist/server/actions-handler.js +121 -52
- package/dist/server/context.js +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +3 -2
- package/dist/server/loaders-handler.d.ts +24 -0
- package/dist/server/loaders-handler.js +128 -18
- package/dist/server/page-action-handler.d.ts +63 -0
- package/dist/server/page-action-handler.js +274 -0
- package/dist/server/page-action-resolvers.d.ts +28 -0
- package/dist/server/page-action-resolvers.js +147 -0
- package/dist/server/render.d.ts +2 -0
- package/dist/server/render.js +142 -33
- package/dist/server/route-server-modules.d.ts +48 -8
- package/dist/server/route-server-modules.js +190 -7
- package/dist/server/speculation-rules.d.ts +3 -0
- package/dist/server/speculation-rules.js +8 -0
- package/dist/server/sse.d.ts +50 -12
- package/dist/server/sse.js +130 -53
- package/dist/vite/adapter-cloudflare.d.ts +2 -0
- package/dist/vite/adapter-cloudflare.js +25 -0
- package/dist/vite/adapter-node.d.ts +2 -0
- package/dist/vite/adapter-node.js +49 -0
- package/dist/vite/adapter.d.ts +29 -0
- package/dist/vite/adapter.js +1 -0
- package/dist/vite/client-shim.js +5 -4
- package/dist/vite/guard-strip.js +52 -27
- package/dist/vite/hono-preact.d.ts +6 -6
- package/dist/vite/hono-preact.js +48 -77
- package/dist/vite/index.d.ts +2 -1
- package/dist/vite/index.js +1 -1
- package/dist/vite/node-dev-server.d.ts +4 -0
- package/dist/vite/node-dev-server.js +121 -0
- package/dist/vite/server-entry.d.ts +30 -7
- package/dist/vite/server-entry.js +170 -79
- package/dist/vite/server-exports-contract.d.ts +6 -0
- package/dist/vite/server-exports-contract.js +43 -0
- package/dist/vite/server-loader-validation.js +36 -9
- package/dist/vite/server-loaders-parser.d.ts +17 -1
- package/dist/vite/server-loaders-parser.js +41 -0
- package/dist/vite/server-only.js +20 -2
- package/package.json +33 -5
package/dist/server/sse.d.ts
CHANGED
|
@@ -1,22 +1,60 @@
|
|
|
1
1
|
import type { Context } from 'hono';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { StreamObserver, ServerStreamCtx } from '../iso/index';
|
|
3
|
+
/**
|
|
4
|
+
* Options shared by both SSE response helpers. Encodes the lifecycle the SSE
|
|
5
|
+
* pump runs through:
|
|
6
|
+
*
|
|
7
|
+
* - Observer fanout: `onStart` fires before the first chunk, `onChunk` per
|
|
8
|
+
* value yielded by the source, `onEnd` on normal completion, `onError` on
|
|
9
|
+
* a thrown error, `onAbort` when the consumer cancels the response stream
|
|
10
|
+
* before the source finishes.
|
|
11
|
+
* - Timeout discrimination: when `signal.aborted` and `signal.reason` is a
|
|
12
|
+
* `TimeoutError` `DOMException`, the catch path emits `event: timeout`
|
|
13
|
+
* with `{ timeoutMs }` instead of the generic `event: error` frame.
|
|
14
|
+
*/
|
|
15
|
+
export type SseResponseOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* When true, the generator's return value (if defined) is emitted as
|
|
18
|
+
* `event: result` before the stream closes. Only meaningful for
|
|
19
|
+
* generator-sourced responses; ignored for `ReadableStream` sources.
|
|
20
|
+
*/
|
|
4
21
|
emitResult?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Stream observers harvested from the loader/action's `use` array (the
|
|
24
|
+
* non-middleware partition). Hooks are isolated: a throwing observer
|
|
25
|
+
* never corrupts the stream.
|
|
26
|
+
*/
|
|
27
|
+
observers?: ReadonlyArray<StreamObserver<unknown, never>>;
|
|
28
|
+
/** Server-stream ctx threaded to each observer hook. */
|
|
29
|
+
observerCtx?: ServerStreamCtx;
|
|
30
|
+
/**
|
|
31
|
+
* The handler's timeout signal (from `AbortSignal.timeout(timeoutMs)`),
|
|
32
|
+
* inspected in the catch path to distinguish a deadline-driven abort
|
|
33
|
+
* from a generic throw.
|
|
34
|
+
*/
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
/** Used only with `signal`; the timeout value reported in the frame. */
|
|
37
|
+
timeoutMs?: number;
|
|
5
38
|
};
|
|
39
|
+
/** Alias retained for source compatibility with earlier code. */
|
|
40
|
+
export type SseGeneratorOptions = SseResponseOptions;
|
|
6
41
|
/**
|
|
7
42
|
* Wrap an async generator as an SSE response.
|
|
8
43
|
*
|
|
9
|
-
* Each yield is JSON-encoded and written as a `data:` event.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
44
|
+
* Each yield is JSON-encoded and written as a `data:` event. If `emitResult`
|
|
45
|
+
* is true and the generator's return value is defined, it is written as
|
|
46
|
+
* `event: result\ndata: <json>` before the stream closes. If the generator
|
|
47
|
+
* throws, an `event: error` or `event: timeout` frame is written and the
|
|
48
|
+
* stream closes cleanly. Observer lifecycle hooks (`onStart` / `onChunk` /
|
|
49
|
+
* `onEnd` / `onError` / `onAbort`) fire from inside the pump.
|
|
15
50
|
*/
|
|
16
|
-
export declare function sseGeneratorResponse(
|
|
51
|
+
export declare function sseGeneratorResponse(_c: Context, gen: AsyncGenerator<unknown, unknown, unknown>, options?: SseResponseOptions): Response;
|
|
17
52
|
/**
|
|
18
|
-
* Wrap a ReadableStream<T
|
|
19
|
-
* Each enqueued chunk is JSON-encoded and written as a `data:`
|
|
53
|
+
* Wrap a `ReadableStream<T>` (with `T` a JSON-encodable value) as an SSE
|
|
54
|
+
* response. Each enqueued chunk is JSON-encoded and written as a `data:`
|
|
55
|
+
* event. Observer lifecycle hooks fire identically to `sseGeneratorResponse`;
|
|
56
|
+
* `emitResult` is not meaningful here (streams have no return value) and is
|
|
57
|
+
* ignored.
|
|
20
58
|
*/
|
|
21
|
-
export declare function sseReadableStreamResponse(
|
|
59
|
+
export declare function sseReadableStreamResponse(_c: Context, source: ReadableStream<unknown>, options?: SseResponseOptions): Response;
|
|
22
60
|
export declare function isAsyncGenerator(value: unknown): value is AsyncGenerator<unknown, unknown, unknown>;
|
package/dist/server/sse.js
CHANGED
|
@@ -1,78 +1,155 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fanStart, fanChunk, fanEnd, fanError, fanAbort, } from '../iso/internal.js';
|
|
2
|
+
function sseEncodeTransform() {
|
|
3
|
+
const encoder = new TextEncoder();
|
|
4
|
+
return new TransformStream({
|
|
5
|
+
transform(frame, controller) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
if (frame.event)
|
|
8
|
+
lines.push(`event: ${frame.event}`);
|
|
9
|
+
if (frame.id)
|
|
10
|
+
lines.push(`id: ${frame.id}`);
|
|
11
|
+
lines.push(`data: ${frame.data}`);
|
|
12
|
+
controller.enqueue(encoder.encode(lines.join('\n') + '\n\n'));
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function isTimeoutAbort(signal) {
|
|
17
|
+
return Boolean(signal?.aborted &&
|
|
18
|
+
signal.reason instanceof DOMException &&
|
|
19
|
+
signal.reason.name === 'TimeoutError');
|
|
20
|
+
}
|
|
2
21
|
function encodeErrorPayload(err) {
|
|
3
22
|
const message = err instanceof Error ? err.message : String(err);
|
|
4
23
|
const name = err instanceof Error ? err.name : 'Error';
|
|
5
24
|
return JSON.stringify({ message, name });
|
|
6
25
|
}
|
|
7
26
|
/**
|
|
8
|
-
*
|
|
27
|
+
* Adapt a `ReadableStream<T>` as an async generator (with no return value).
|
|
28
|
+
* The reader is released in `finally`, which fires either when the consumer
|
|
29
|
+
* stops iterating or when the source is exhausted.
|
|
30
|
+
*/
|
|
31
|
+
async function* iterReadable(stream) {
|
|
32
|
+
const reader = stream.getReader();
|
|
33
|
+
try {
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done)
|
|
37
|
+
return;
|
|
38
|
+
yield value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
reader.cancel().catch(() => undefined);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The shared pump implementation. Iterates `source` (a generator that may
|
|
47
|
+
* return a final value), encodes each yielded value as a JSON `data:` frame,
|
|
48
|
+
* runs the observer lifecycle, and translates errors into `event: error` or
|
|
49
|
+
* `event: timeout` frames.
|
|
9
50
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* If the generator throws, an `event: error\ndata: {"message","name"}` frame
|
|
14
|
-
* is written and the stream closes cleanly (Hono's default error handler is
|
|
15
|
-
* never invoked because we catch inside the callback).
|
|
51
|
+
* Observer state (`chunks`, `started`, `finished`) lives in the outer
|
|
52
|
+
* function scope so the outer ReadableStream's `cancel()` callback can fire
|
|
53
|
+
* `onAbort` when the consumer cancels mid-stream.
|
|
16
54
|
*/
|
|
17
|
-
|
|
18
|
-
const { emitResult = false } = options;
|
|
19
|
-
|
|
55
|
+
function buildSseResponse(source, options) {
|
|
56
|
+
const { emitResult = false, observers, observerCtx, signal, timeoutMs, } = options;
|
|
57
|
+
const obs = observers ?? [];
|
|
58
|
+
let chunks = 0;
|
|
59
|
+
let started = false;
|
|
60
|
+
let finished = false;
|
|
61
|
+
async function* framePump() {
|
|
62
|
+
if (obs.length > 0 && observerCtx) {
|
|
63
|
+
fanStart(obs, observerCtx);
|
|
64
|
+
started = true;
|
|
65
|
+
}
|
|
20
66
|
try {
|
|
21
|
-
while (
|
|
22
|
-
const step = await
|
|
67
|
+
while (true) {
|
|
68
|
+
const step = await source.next();
|
|
23
69
|
if (step.done) {
|
|
24
70
|
if (emitResult && step.value !== undefined) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
71
|
+
yield { event: 'result', data: JSON.stringify(step.value) };
|
|
72
|
+
}
|
|
73
|
+
if (started && observerCtx) {
|
|
74
|
+
fanEnd(obs, observerCtx, { chunks, result: step.value });
|
|
29
75
|
}
|
|
76
|
+
finished = true;
|
|
30
77
|
return;
|
|
31
78
|
}
|
|
32
|
-
|
|
79
|
+
yield { data: JSON.stringify(step.value) };
|
|
80
|
+
if (started && observerCtx) {
|
|
81
|
+
fanChunk(obs, observerCtx, step.value, chunks);
|
|
82
|
+
}
|
|
83
|
+
chunks += 1;
|
|
33
84
|
}
|
|
34
|
-
// Aborted; release the generator cleanly.
|
|
35
|
-
await gen.return(undefined).catch(() => {
|
|
36
|
-
/* swallow */
|
|
37
|
-
});
|
|
38
85
|
}
|
|
39
86
|
catch (err) {
|
|
40
|
-
await
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
data:
|
|
46
|
-
}
|
|
87
|
+
await source.return(undefined).catch(() => undefined);
|
|
88
|
+
if (started && observerCtx) {
|
|
89
|
+
fanError(obs, observerCtx, err, { chunks });
|
|
90
|
+
}
|
|
91
|
+
if (isTimeoutAbort(signal) && typeof timeoutMs === 'number') {
|
|
92
|
+
yield { event: 'timeout', data: JSON.stringify({ timeoutMs }) };
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
yield { event: 'error', data: encodeErrorPayload(err) };
|
|
96
|
+
}
|
|
97
|
+
finished = true;
|
|
47
98
|
}
|
|
99
|
+
}
|
|
100
|
+
const pump = framePump();
|
|
101
|
+
const body = new ReadableStream({
|
|
102
|
+
async pull(controller) {
|
|
103
|
+
const { value, done } = await pump.next();
|
|
104
|
+
if (done) {
|
|
105
|
+
controller.close();
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
controller.enqueue(value);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
cancel() {
|
|
112
|
+
// Consumer cancelled before the pump completed. Notify observers via
|
|
113
|
+
// `onAbort` exactly when we've genuinely been aborted mid-stream
|
|
114
|
+
// (i.e. the source started but didn't finish naturally).
|
|
115
|
+
if (!finished && started && observerCtx) {
|
|
116
|
+
fanAbort(obs, observerCtx, { chunks });
|
|
117
|
+
}
|
|
118
|
+
pump.return(undefined).catch(() => undefined);
|
|
119
|
+
source.return(undefined).catch(() => undefined);
|
|
120
|
+
},
|
|
121
|
+
}).pipeThrough(sseEncodeTransform());
|
|
122
|
+
return new Response(body, {
|
|
123
|
+
headers: {
|
|
124
|
+
'content-type': 'text/event-stream',
|
|
125
|
+
'cache-control': 'no-cache',
|
|
126
|
+
},
|
|
48
127
|
});
|
|
49
128
|
}
|
|
50
129
|
/**
|
|
51
|
-
* Wrap
|
|
52
|
-
*
|
|
130
|
+
* Wrap an async generator as an SSE response.
|
|
131
|
+
*
|
|
132
|
+
* Each yield is JSON-encoded and written as a `data:` event. If `emitResult`
|
|
133
|
+
* is true and the generator's return value is defined, it is written as
|
|
134
|
+
* `event: result\ndata: <json>` before the stream closes. If the generator
|
|
135
|
+
* throws, an `event: error` or `event: timeout` frame is written and the
|
|
136
|
+
* stream closes cleanly. Observer lifecycle hooks (`onStart` / `onChunk` /
|
|
137
|
+
* `onEnd` / `onError` / `onAbort`) fire from inside the pump.
|
|
53
138
|
*/
|
|
54
|
-
export function
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
data: encodeErrorPayload(err),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
reader.cancel().catch(() => {
|
|
73
|
-
/* swallow */
|
|
74
|
-
});
|
|
75
|
-
}
|
|
139
|
+
export function sseGeneratorResponse(_c, gen, options = {}) {
|
|
140
|
+
return buildSseResponse(gen, options);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Wrap a `ReadableStream<T>` (with `T` a JSON-encodable value) as an SSE
|
|
144
|
+
* response. Each enqueued chunk is JSON-encoded and written as a `data:`
|
|
145
|
+
* event. Observer lifecycle hooks fire identically to `sseGeneratorResponse`;
|
|
146
|
+
* `emitResult` is not meaningful here (streams have no return value) and is
|
|
147
|
+
* ignored.
|
|
148
|
+
*/
|
|
149
|
+
export function sseReadableStreamResponse(_c, source, options = {}) {
|
|
150
|
+
return buildSseResponse(iterReadable(source), {
|
|
151
|
+
...options,
|
|
152
|
+
emitResult: false,
|
|
76
153
|
});
|
|
77
154
|
}
|
|
78
155
|
export function isAsyncGenerator(value) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// packages/vite/src/adapter-cloudflare.ts
|
|
2
|
+
//
|
|
3
|
+
// Standalone module. NOT re-exported by index.ts: importing `hono-preact/vite`
|
|
4
|
+
// must never pull in `@cloudflare/vite-plugin`. Only importing
|
|
5
|
+
// `hono-preact/adapter-cloudflare` loads this file.
|
|
6
|
+
import { cloudflare } from '@cloudflare/vite-plugin';
|
|
7
|
+
export function cloudflareAdapter() {
|
|
8
|
+
return {
|
|
9
|
+
name: 'cloudflare',
|
|
10
|
+
vitePlugins() {
|
|
11
|
+
// `@cloudflare/vite-plugin` drives both workerd dev and the build via
|
|
12
|
+
// the Environment API, and reads the worker entry from wrangler.jsonc
|
|
13
|
+
// `main`. It needs no entry argument from the framework.
|
|
14
|
+
// `cloudflare()` may return a single plugin or an array; normalize so
|
|
15
|
+
// the HonoPreactAdapter contract (a flat Plugin[]) holds either way.
|
|
16
|
+
const produced = cloudflare();
|
|
17
|
+
return Array.isArray(produced) ? produced : [produced];
|
|
18
|
+
},
|
|
19
|
+
wrapEntry(ctx) {
|
|
20
|
+
// A Hono app's default export is already a valid Workers fetch handler,
|
|
21
|
+
// so the platform tail is a bare re-export of the core app module.
|
|
22
|
+
return `export { default } from ${JSON.stringify(ctx.coreAppModuleId)};\n`;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { nodeBuildPlugin, nodeDevServerPlugin } from './node-dev-server.js';
|
|
2
|
+
export function nodeAdapter() {
|
|
3
|
+
return {
|
|
4
|
+
name: 'node',
|
|
5
|
+
vitePlugins(ctx) {
|
|
6
|
+
return [nodeBuildPlugin(ctx), nodeDevServerPlugin(ctx)];
|
|
7
|
+
},
|
|
8
|
+
wrapEntry(ctx) {
|
|
9
|
+
const hasApi = ctx.apiModuleId != null;
|
|
10
|
+
const apiImport = hasApi
|
|
11
|
+
? `import * as __api from ${JSON.stringify(ctx.apiModuleId)};\n`
|
|
12
|
+
: '';
|
|
13
|
+
const injectExport = hasApi
|
|
14
|
+
? `export const injectWebSocket = __api.injectWebSocket;\n`
|
|
15
|
+
: '';
|
|
16
|
+
const injectBoot = hasApi
|
|
17
|
+
? ` if (__api.injectWebSocket) __api.injectWebSocket(server);\n`
|
|
18
|
+
: '';
|
|
19
|
+
// The outer app serves built client assets under /static/* and mounts
|
|
20
|
+
// the framework's core Hono app at the root.
|
|
21
|
+
//
|
|
22
|
+
// The serve() boot is guarded by `import.meta.env.PROD`. In `vite dev`
|
|
23
|
+
// the Node dev plugin loads this wrapper through the SSR module runner
|
|
24
|
+
// purely to obtain `app` (and `injectWebSocket`); PROD is false there so
|
|
25
|
+
// no rogue HTTP server starts. In the production build it constant-folds
|
|
26
|
+
// to true and the bundle boots a real server.
|
|
27
|
+
return (`import { serve } from '@hono/node-server';\n` +
|
|
28
|
+
`import { serveStatic } from '@hono/node-server/serve-static';\n` +
|
|
29
|
+
`import { Hono } from 'hono';\n` +
|
|
30
|
+
`import coreApp from ${JSON.stringify(ctx.coreAppModuleId)};\n` +
|
|
31
|
+
apiImport +
|
|
32
|
+
`\n` +
|
|
33
|
+
`const app = new Hono()\n` +
|
|
34
|
+
` .use('/static/*', serveStatic({ root: './dist/client' }))\n` +
|
|
35
|
+
` .route('/', coreApp);\n` +
|
|
36
|
+
`\n` +
|
|
37
|
+
`export { app };\n` +
|
|
38
|
+
`export default app;\n` +
|
|
39
|
+
injectExport +
|
|
40
|
+
`\n` +
|
|
41
|
+
`if (import.meta.env.PROD) {\n` +
|
|
42
|
+
` const port = Number(process.env.PORT) || 3000;\n` +
|
|
43
|
+
` const server = serve({ fetch: app.fetch, port });\n` +
|
|
44
|
+
` console.log(\`hono-preact: listening on http://localhost:\${port}\`);\n` +
|
|
45
|
+
injectBoot +
|
|
46
|
+
`}\n`);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Static context the framework hands an adapter. `command` and `outDir`
|
|
4
|
+
* are intentionally absent: they are not known when honoPreact() builds its
|
|
5
|
+
* plugin array. Adapters that need them read them from their own plugin
|
|
6
|
+
* hooks (config / configResolved).
|
|
7
|
+
*/
|
|
8
|
+
export interface HonoPreactAdapterContext {
|
|
9
|
+
/** Vite project root (process.cwd() when honoPreact() is called). */
|
|
10
|
+
root: string;
|
|
11
|
+
/** Absolute path of the framework-generated core Hono app module. */
|
|
12
|
+
coreAppModuleId: string;
|
|
13
|
+
/** Absolute path where the adapter's wrapEntry() output is written. */
|
|
14
|
+
entryWrapperId: string;
|
|
15
|
+
/** Absolute path of the user's api module, if it exists. Used by adapters
|
|
16
|
+
* that need to reach api-module exports (e.g. the Node adapter's
|
|
17
|
+
* WebSocket `injectWebSocket`). Undefined when the project has no api.ts. */
|
|
18
|
+
apiModuleId?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A deployment target. `vitePlugins()` contributes the terminal build/dev
|
|
22
|
+
* plugins; `wrapEntry()` returns the platform tail that imports the core
|
|
23
|
+
* Hono app module and adapts it to the runtime.
|
|
24
|
+
*/
|
|
25
|
+
export interface HonoPreactAdapter {
|
|
26
|
+
name: string;
|
|
27
|
+
vitePlugins(ctx: HonoPreactAdapterContext): Plugin[];
|
|
28
|
+
wrapEntry(ctx: HonoPreactAdapterContext): string;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/vite/client-shim.js
CHANGED
|
@@ -17,10 +17,11 @@ export function clientShimPlugin(clientEntry) {
|
|
|
17
17
|
return {
|
|
18
18
|
name: 'hono-preact:client-shim',
|
|
19
19
|
enforce: 'pre',
|
|
20
|
-
apply(_, { command
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
20
|
+
apply(_, { command }) {
|
|
21
|
+
// The shim is needed for dev and for the build. The `transform` hook
|
|
22
|
+
// below self-gates to the client entry module, so it never injects
|
|
23
|
+
// into SSR/worker code regardless of build environment.
|
|
24
|
+
return command === 'serve' || command === 'build';
|
|
24
25
|
},
|
|
25
26
|
configResolved(config) {
|
|
26
27
|
resolvedEntry = path.resolve(config.root, clientEntry);
|
package/dist/vite/guard-strip.js
CHANGED
|
@@ -2,10 +2,33 @@ import { parse } from '@babel/parser';
|
|
|
2
2
|
import MagicString from 'magic-string';
|
|
3
3
|
import { BABEL_PARSER_PLUGINS } from './parser-options.js';
|
|
4
4
|
const ISO_PACKAGE_SOURCES = new Set(['../iso/index.js', 'hono-preact']);
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// In the server bundle we strip anything client-only. The replacement
|
|
6
|
+
// `fn` arity matches the documented `(ctx, next) => Promise<void | Outcome>`
|
|
7
|
+
// shape so any user introspecting `mw.fn` sees the right signature; the
|
|
8
|
+
// framework path filters on `runs` before invoking and never executes a
|
|
9
|
+
// wrong-env body.
|
|
10
|
+
const SERVER_BUNDLE_STRIPS = [
|
|
11
|
+
{
|
|
12
|
+
name: 'defineClientMiddleware',
|
|
13
|
+
replacement: `{ __kind: 'middleware', runs: 'client', fn: (_ctx, next) => next() }`,
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
// In the client bundle we strip anything server-only. Stream observers
|
|
17
|
+
// fire on the server-side streaming pipeline (start/chunk/end/error/abort)
|
|
18
|
+
// so they're server-only too.
|
|
19
|
+
const CLIENT_BUNDLE_STRIPS = [
|
|
20
|
+
{
|
|
21
|
+
name: 'defineServerMiddleware',
|
|
22
|
+
replacement: `{ __kind: 'middleware', runs: 'server', fn: (_ctx, next) => next() }`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'defineStreamObserver',
|
|
26
|
+
replacement: `{ __kind: 'observer' }`,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
function collectLocalBindings(ast, strips) {
|
|
8
30
|
const bindings = new Map();
|
|
31
|
+
const byName = new Map(strips.map((s) => [s.name, s]));
|
|
9
32
|
for (const node of ast.program.body) {
|
|
10
33
|
if (node.type !== 'ImportDeclaration')
|
|
11
34
|
continue;
|
|
@@ -17,11 +40,9 @@ function collectLocalBindings(ast, targets) {
|
|
|
17
40
|
continue;
|
|
18
41
|
if (spec.imported.type !== 'Identifier')
|
|
19
42
|
continue;
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
bindings.set(spec.local.name, name);
|
|
24
|
-
}
|
|
43
|
+
const strategy = byName.get(spec.imported.name);
|
|
44
|
+
if (strategy) {
|
|
45
|
+
bindings.set(spec.local.name, strategy);
|
|
25
46
|
}
|
|
26
47
|
}
|
|
27
48
|
}
|
|
@@ -38,18 +59,15 @@ function findCallsByLocalName(node, bindings, hits) {
|
|
|
38
59
|
const n = node;
|
|
39
60
|
if (n.type === 'CallExpression' &&
|
|
40
61
|
n.callee?.type === 'Identifier' &&
|
|
41
|
-
n.callee.name
|
|
42
|
-
bindings.
|
|
43
|
-
n.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
argStart: n.arguments[0].start,
|
|
51
|
-
argEnd: n.arguments[0].end,
|
|
52
|
-
});
|
|
62
|
+
n.callee.name) {
|
|
63
|
+
const strategy = bindings.get(n.callee.name);
|
|
64
|
+
if (strategy && n.start !== undefined && n.end !== undefined) {
|
|
65
|
+
hits.push({
|
|
66
|
+
strategy,
|
|
67
|
+
start: n.start,
|
|
68
|
+
end: n.end,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
53
71
|
}
|
|
54
72
|
for (const key of Object.keys(node)) {
|
|
55
73
|
if (key === 'loc' ||
|
|
@@ -66,19 +84,27 @@ export function guardStripPlugin() {
|
|
|
66
84
|
transform(code, id, options) {
|
|
67
85
|
if (!/\.[jt]sx?$/.test(id))
|
|
68
86
|
return;
|
|
87
|
+
// F7: `.server.*` files are intentionally skipped in both bundles.
|
|
88
|
+
// In the client bundle the server-only stub plugin already rewrites
|
|
89
|
+
// imports of these files; in the server bundle the file's own
|
|
90
|
+
// body stays as-authored. The validation plugin restricts a
|
|
91
|
+
// `.server.*` module's named exports to the allowlist, so a user
|
|
92
|
+
// cannot land a `defineClientMiddleware(...)` value as a recognized
|
|
93
|
+
// export and ship it to the server.
|
|
69
94
|
if (/\.server\.[jt]sx?$/.test(id))
|
|
70
95
|
return;
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
const strips = options?.ssr ? SERVER_BUNDLE_STRIPS : CLIENT_BUNDLE_STRIPS;
|
|
97
|
+
// Cheap pre-filter: only parse files that mention at least one of the
|
|
98
|
+
// symbols we strip. Avoids parsing the entire dep graph just to
|
|
99
|
+
// confirm no strips apply.
|
|
100
|
+
if (!strips.some((s) => code.includes(s.name)))
|
|
75
101
|
return;
|
|
76
102
|
const ast = parse(code, {
|
|
77
103
|
sourceType: 'module',
|
|
78
104
|
plugins: BABEL_PARSER_PLUGINS,
|
|
79
105
|
errorRecovery: true,
|
|
80
106
|
});
|
|
81
|
-
const bindings = collectLocalBindings(ast,
|
|
107
|
+
const bindings = collectLocalBindings(ast, strips);
|
|
82
108
|
if (bindings.size === 0)
|
|
83
109
|
return;
|
|
84
110
|
const hits = [];
|
|
@@ -87,9 +113,8 @@ export function guardStripPlugin() {
|
|
|
87
113
|
return;
|
|
88
114
|
const s = new MagicString(code);
|
|
89
115
|
for (const hit of [...hits].reverse()) {
|
|
90
|
-
s.overwrite(hit.
|
|
116
|
+
s.overwrite(hit.start, hit.end, hit.strategy.replacement);
|
|
91
117
|
}
|
|
92
|
-
s.prepend(`import { ${NOOP_LOCAL_NAME} } from '${NOOP_IMPORT_SOURCE}';\n`);
|
|
93
118
|
return { code: s.toString(), map: s.generateMap({ hires: true }) };
|
|
94
119
|
},
|
|
95
120
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Plugin } from 'vite';
|
|
2
|
+
import type { HonoPreactAdapter } from './adapter.js';
|
|
2
3
|
export interface HonoPreactOptions {
|
|
4
|
+
/** Deployment target. Required. See hono-preact/adapter-cloudflare. */
|
|
5
|
+
adapter: HonoPreactAdapter;
|
|
3
6
|
layout?: string;
|
|
4
7
|
routes?: string;
|
|
5
8
|
api?: string;
|
|
9
|
+
appConfig?: string;
|
|
6
10
|
clientEntry?: string;
|
|
7
|
-
entry?: string;
|
|
8
|
-
clientBuild?: BuildEnvironmentOptions;
|
|
9
|
-
serverBuild?: BuildEnvironmentOptions;
|
|
10
|
-
sharedBuild?: BuildEnvironmentOptions;
|
|
11
11
|
}
|
|
12
|
-
export declare function honoPreact(options
|
|
12
|
+
export declare function honoPreact(options: HonoPreactOptions): Plugin[];
|