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.
Files changed (120) hide show
  1. package/README.md +2 -1
  2. package/dist/adapter-cloudflare.d.ts +1 -0
  3. package/dist/adapter-cloudflare.d.ts.map +1 -0
  4. package/dist/adapter-cloudflare.js +2 -0
  5. package/dist/adapter-node.d.ts +1 -0
  6. package/dist/adapter-node.d.ts.map +1 -0
  7. package/dist/adapter-node.js +2 -0
  8. package/dist/internal.d.ts +1 -1
  9. package/dist/internal.js +1 -1
  10. package/dist/iso/action-result-context.d.ts +22 -0
  11. package/dist/iso/action-result-context.js +2 -0
  12. package/dist/iso/action.d.ts +60 -25
  13. package/dist/iso/action.js +210 -58
  14. package/dist/iso/cache.d.ts +9 -0
  15. package/dist/iso/cache.js +26 -0
  16. package/dist/iso/define-app.d.ts +14 -0
  17. package/dist/iso/define-app.js +3 -0
  18. package/dist/iso/define-loader.d.ts +31 -0
  19. package/dist/iso/define-loader.js +30 -16
  20. package/dist/iso/define-middleware.d.ts +43 -0
  21. package/dist/iso/define-middleware.js +6 -0
  22. package/dist/iso/define-page.d.ts +7 -2
  23. package/dist/iso/define-page.js +1 -1
  24. package/dist/iso/define-routes.d.ts +24 -1
  25. package/dist/iso/define-routes.js +34 -0
  26. package/dist/iso/define-stream-observer.d.ts +20 -0
  27. package/dist/iso/define-stream-observer.js +3 -0
  28. package/dist/iso/form.d.ts +13 -4
  29. package/dist/iso/form.js +115 -33
  30. package/dist/iso/index.d.ts +15 -7
  31. package/dist/iso/index.js +9 -4
  32. package/dist/iso/internal/action-envelope.d.ts +37 -0
  33. package/dist/iso/internal/action-envelope.js +47 -0
  34. package/dist/iso/internal/action-result-store.d.ts +28 -0
  35. package/dist/iso/internal/action-result-store.js +35 -0
  36. package/dist/iso/internal/contexts.d.ts +0 -2
  37. package/dist/iso/internal/contexts.js +0 -1
  38. package/dist/iso/internal/envelope.js +1 -2
  39. package/dist/iso/internal/form-submit-store.d.ts +9 -0
  40. package/dist/iso/internal/form-submit-store.js +32 -0
  41. package/dist/iso/internal/loader-fetch.js +102 -41
  42. package/dist/iso/internal/loader-runner.js +105 -8
  43. package/dist/iso/internal/loader.d.ts +3 -3
  44. package/dist/iso/internal/middleware-runner.d.ts +22 -0
  45. package/dist/iso/internal/middleware-runner.js +79 -0
  46. package/dist/iso/internal/page-middleware-host.d.ts +13 -0
  47. package/dist/iso/internal/page-middleware-host.js +119 -0
  48. package/dist/iso/internal/route-boundary.d.ts +5 -4
  49. package/dist/iso/internal/route-boundary.js +16 -0
  50. package/dist/iso/internal/safe-redirect.d.ts +7 -0
  51. package/dist/iso/internal/safe-redirect.js +27 -0
  52. package/dist/iso/internal/sse-decoder.d.ts +1 -1
  53. package/dist/iso/internal/sse-decoder.js +40 -26
  54. package/dist/iso/internal/stream-observer-runner.d.ts +13 -0
  55. package/dist/iso/internal/stream-observer-runner.js +48 -0
  56. package/dist/iso/internal/use-partitioner.d.ts +9 -0
  57. package/dist/iso/internal/use-partitioner.js +11 -0
  58. package/dist/iso/internal/use-types.d.ts +7 -0
  59. package/dist/iso/internal/use-types.js +1 -0
  60. package/dist/iso/internal.d.ts +12 -5
  61. package/dist/iso/internal.js +16 -7
  62. package/dist/iso/optimistic-action.d.ts +10 -1
  63. package/dist/iso/optimistic-action.js +11 -3
  64. package/dist/iso/optimistic.d.ts +10 -1
  65. package/dist/iso/optimistic.js +45 -5
  66. package/dist/iso/outcomes.d.ts +50 -0
  67. package/dist/iso/outcomes.js +67 -0
  68. package/dist/iso/page-only.d.ts +5 -0
  69. package/dist/iso/page-only.js +20 -0
  70. package/dist/iso/page.d.ts +3 -3
  71. package/dist/iso/page.js +3 -3
  72. package/dist/iso/use-action-result.d.ts +25 -0
  73. package/dist/iso/use-action-result.js +39 -0
  74. package/dist/iso/use-form-status.d.ts +5 -0
  75. package/dist/iso/use-form-status.js +13 -0
  76. package/dist/page.d.ts +1 -0
  77. package/dist/page.d.ts.map +1 -0
  78. package/dist/page.js +8 -0
  79. package/dist/server/actions-handler.d.ts +27 -6
  80. package/dist/server/actions-handler.js +121 -52
  81. package/dist/server/context.js +1 -1
  82. package/dist/server/index.d.ts +3 -2
  83. package/dist/server/index.js +3 -2
  84. package/dist/server/loaders-handler.d.ts +24 -0
  85. package/dist/server/loaders-handler.js +128 -18
  86. package/dist/server/page-action-handler.d.ts +63 -0
  87. package/dist/server/page-action-handler.js +274 -0
  88. package/dist/server/page-action-resolvers.d.ts +28 -0
  89. package/dist/server/page-action-resolvers.js +147 -0
  90. package/dist/server/render.d.ts +2 -0
  91. package/dist/server/render.js +142 -33
  92. package/dist/server/route-server-modules.d.ts +48 -8
  93. package/dist/server/route-server-modules.js +190 -7
  94. package/dist/server/speculation-rules.d.ts +3 -0
  95. package/dist/server/speculation-rules.js +8 -0
  96. package/dist/server/sse.d.ts +50 -12
  97. package/dist/server/sse.js +130 -53
  98. package/dist/vite/adapter-cloudflare.d.ts +2 -0
  99. package/dist/vite/adapter-cloudflare.js +25 -0
  100. package/dist/vite/adapter-node.d.ts +2 -0
  101. package/dist/vite/adapter-node.js +49 -0
  102. package/dist/vite/adapter.d.ts +29 -0
  103. package/dist/vite/adapter.js +1 -0
  104. package/dist/vite/client-shim.js +5 -4
  105. package/dist/vite/guard-strip.js +52 -27
  106. package/dist/vite/hono-preact.d.ts +6 -6
  107. package/dist/vite/hono-preact.js +48 -77
  108. package/dist/vite/index.d.ts +2 -1
  109. package/dist/vite/index.js +1 -1
  110. package/dist/vite/node-dev-server.d.ts +4 -0
  111. package/dist/vite/node-dev-server.js +121 -0
  112. package/dist/vite/server-entry.d.ts +30 -7
  113. package/dist/vite/server-entry.js +170 -79
  114. package/dist/vite/server-exports-contract.d.ts +6 -0
  115. package/dist/vite/server-exports-contract.js +43 -0
  116. package/dist/vite/server-loader-validation.js +36 -9
  117. package/dist/vite/server-loaders-parser.d.ts +17 -1
  118. package/dist/vite/server-loaders-parser.js +41 -0
  119. package/dist/vite/server-only.js +20 -2
  120. package/package.json +33 -5
@@ -1,6 +1,3 @@
1
- import build from '@hono/vite-build/cloudflare-workers';
2
- import devServer, { defaultOptions } from '@hono/vite-dev-server';
3
- import cloudflareAdapter from '@hono/vite-dev-server/cloudflare';
4
1
  import preact from '@preact/preset-vite';
5
2
  import { clientShimPlugin } from './client-shim.js';
6
3
  import { clientEntryPlugin, VIRTUAL_CLIENT_ENTRY_ID } from './client-entry.js';
@@ -8,68 +5,56 @@ import { serverLoaderValidationPlugin } from './server-loader-validation.js';
8
5
  import { moduleKeyPlugin } from './module-key-plugin.js';
9
6
  import { serverOnlyPlugin } from './server-only.js';
10
7
  import { guardStripPlugin } from './guard-strip.js';
11
- import { GENERATED_SERVER_ENTRY_RELATIVE, generatedServerEntryAbsPath, serverEntryPlugin, } from './server-entry.js';
12
- export function honoPreact(options = {}) {
13
- const { layout = 'src/Layout.tsx', routes = 'src/routes.ts', api = 'src/api.ts', clientEntry = VIRTUAL_CLIENT_ENTRY_ID, entry, clientBuild = {}, serverBuild = {}, sharedBuild = {}, } = options;
14
- const useGeneratedEntry = entry === undefined;
15
- const resolvedEntry = entry ?? GENERATED_SERVER_ENTRY_RELATIVE;
8
+ import { generatedCoreAppAbsPath, generatedEntryWrapperAbsPath, serverEntryPlugin, } from './server-entry.js';
9
+ export function honoPreact(options) {
10
+ // `?? {}` is deliberate: TypeScript types `options` as required, but a
11
+ // zero-arg `honoPreact()` call still reaches here at runtime. Without the
12
+ // fallback, destructuring `undefined` throws a cryptic TypeError; with it,
13
+ // the friendly `adapter`-required guard below fires instead.
14
+ const { adapter, layout = 'src/Layout.tsx', routes = 'src/routes.ts', api = 'src/api.ts', appConfig = 'src/app-config.ts', clientEntry = VIRTUAL_CLIENT_ENTRY_ID, } = options ?? {};
15
+ if (!adapter) {
16
+ throw new Error('[hono-preact] honoPreact() requires an `adapter` option. ' +
17
+ "Import one, e.g. `import { cloudflareAdapter } from 'hono-preact/adapter-cloudflare'`, " +
18
+ 'and pass `honoPreact({ adapter: cloudflareAdapter() })`.');
19
+ }
20
+ const coreAppPath = generatedCoreAppAbsPath();
21
+ const entryWrapperPath = generatedEntryWrapperAbsPath();
22
+ const ctx = {
23
+ root: process.cwd(),
24
+ coreAppModuleId: coreAppPath,
25
+ entryWrapperId: entryWrapperPath,
26
+ };
27
+ // Shared config plus the `client` build environment's input. The worker
28
+ // environment is configured by the adapter's plugins; the `client`
29
+ // environment's entry is framework-owned (every adapter needs the same
30
+ // browser bundle) so it lives here. Without it, the client environment has
31
+ // no input and `vite build` emits no client JavaScript. The
32
+ // `static/client.js` entry name is the URL the SSR layer references and
33
+ // must stay stable.
16
34
  const configPlugin = {
17
35
  name: 'hono-preact:config',
18
- config(_, { mode }) {
19
- const shared = {
36
+ config() {
37
+ return {
20
38
  resolve: {
21
39
  dedupe: ['preact', 'preact/compat', 'preact/hooks', 'preact-iso'],
22
40
  },
23
41
  build: {
24
42
  target: 'esnext',
25
43
  assetsDir: 'static',
26
- ssrEmitAssets: true,
27
- minify: true,
28
- ...sharedBuild,
29
44
  },
30
- };
31
- if (mode === 'client') {
32
- const { rollupOptions: userRollup, ...restClientBuild } = clientBuild;
33
- return {
34
- ...shared,
35
- build: {
36
- ...shared.build,
37
- sourcemap: true,
38
- cssCodeSplit: true,
39
- copyPublicDir: false,
40
- ...restClientBuild,
41
- rollupOptions: {
42
- input: userRollup?.input ?? [clientEntry],
43
- output: {
44
- entryFileNames: 'static/client.js',
45
- chunkFileNames: 'static/[name]-[hash].js',
46
- assetFileNames: 'static/[name]-[hash].[ext]',
47
- // Array-form output is not supported; use an OutputOptions object to
48
- // override individual fields (entryFileNames, chunkFileNames, etc.).
49
- ...(userRollup?.output && !Array.isArray(userRollup.output)
50
- ? userRollup.output
51
- : {}),
45
+ environments: {
46
+ client: {
47
+ build: {
48
+ rollupOptions: {
49
+ input: [clientEntry],
50
+ output: {
51
+ entryFileNames: 'static/client.js',
52
+ chunkFileNames: 'static/[name]-[hash].js',
53
+ assetFileNames: 'static/[name]-[hash].[ext]',
54
+ },
52
55
  },
53
56
  },
54
57
  },
55
- };
56
- }
57
- return {
58
- ...shared,
59
- ssr: {
60
- noExternal: ['preact-render-to-string', 'preact-iso', 'hono-preact'],
61
- },
62
- build: {
63
- ...shared.build,
64
- // Inline sourcemaps so SSR error stacks point at user source
65
- // (e.g. `pages/issue.server.ts:42`) instead of the rolled-up
66
- // Worker chunk. We pick `inline` over a separate .map file because
67
- // SSR is a single bundle and Workers tooling will not look at a
68
- // sibling .map. Bundle size grows; for typical app server bundles
69
- // this is a few hundred KB at most and is worth the debuggability.
70
- // Users can override by passing `serverBuild.sourcemap: false`.
71
- sourcemap: 'inline',
72
- ...serverBuild,
73
58
  },
74
59
  };
75
60
  },
@@ -78,34 +63,20 @@ export function honoPreact(options = {}) {
78
63
  configPlugin,
79
64
  clientShimPlugin(clientEntry),
80
65
  clientEntryPlugin({ routes }),
81
- ...(useGeneratedEntry
82
- ? [
83
- serverEntryPlugin({
84
- layout,
85
- routes,
86
- api,
87
- outputPath: generatedServerEntryAbsPath(),
88
- }),
89
- ]
90
- : []),
66
+ serverEntryPlugin({
67
+ layout,
68
+ routes,
69
+ api,
70
+ appConfig,
71
+ adapter,
72
+ coreAppPath,
73
+ entryWrapperPath,
74
+ }),
91
75
  serverLoaderValidationPlugin(),
92
76
  moduleKeyPlugin(),
93
77
  serverOnlyPlugin(),
94
78
  guardStripPlugin(),
95
- Object.assign(build({ entry: resolvedEntry }), {
96
- apply: (_, { command, mode }) => command === 'build' && mode !== 'client',
97
- }),
98
- Object.assign(devServer({
99
- entry: resolvedEntry,
100
- exclude: [
101
- ...defaultOptions.exclude,
102
- /\.scss/,
103
- /\.css/,
104
- /\?url/,
105
- /\?inline/,
106
- ],
107
- adapter: cloudflareAdapter,
108
- }), { apply: 'serve' }),
79
+ ...adapter.vitePlugins(ctx),
109
80
  ...preact(),
110
81
  ];
111
82
  }
@@ -2,6 +2,7 @@ export { honoPreact } from './hono-preact.js';
2
2
  export { serverLoaderValidationPlugin } from './server-loader-validation.js';
3
3
  export { serverOnlyPlugin, VITE_ROOT_ACCESSOR } from './server-only.js';
4
4
  export { moduleKeyPlugin } from './module-key-plugin.js';
5
- export { GENERATED_SERVER_ENTRY_RELATIVE, generatedServerEntryAbsPath, serverEntryPlugin, } from './server-entry.js';
5
+ export { GENERATED_CORE_APP_RELATIVE, GENERATED_ENTRY_WRAPPER_RELATIVE, generatedCoreAppAbsPath, generatedEntryWrapperAbsPath, serverEntryPlugin, } from './server-entry.js';
6
+ export type { HonoPreactAdapter, HonoPreactAdapterContext } from './adapter.js';
6
7
  export { clientEntryPlugin, VIRTUAL_CLIENT_ENTRY_ID } from './client-entry.js';
7
8
  export { guardStripPlugin } from './guard-strip.js';
@@ -2,6 +2,6 @@ export { honoPreact } from './hono-preact.js';
2
2
  export { serverLoaderValidationPlugin } from './server-loader-validation.js';
3
3
  export { serverOnlyPlugin, VITE_ROOT_ACCESSOR } from './server-only.js';
4
4
  export { moduleKeyPlugin } from './module-key-plugin.js';
5
- export { GENERATED_SERVER_ENTRY_RELATIVE, generatedServerEntryAbsPath, serverEntryPlugin, } from './server-entry.js';
5
+ export { GENERATED_CORE_APP_RELATIVE, GENERATED_ENTRY_WRAPPER_RELATIVE, generatedCoreAppAbsPath, generatedEntryWrapperAbsPath, serverEntryPlugin, } from './server-entry.js';
6
6
  export { clientEntryPlugin, VIRTUAL_CLIENT_ENTRY_ID } from './client-entry.js';
7
7
  export { guardStripPlugin } from './guard-strip.js';
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { HonoPreactAdapterContext } from './adapter.js';
3
+ export declare function nodeBuildPlugin(ctx: HonoPreactAdapterContext): Plugin;
4
+ export declare function nodeDevServerPlugin(ctx: HonoPreactAdapterContext): Plugin;
@@ -0,0 +1,121 @@
1
+ import { createServerModuleRunner } from 'vite';
2
+ export function nodeBuildPlugin(ctx) {
3
+ return {
4
+ name: 'hono-preact:node-build',
5
+ config() {
6
+ return {
7
+ environments: {
8
+ // The Node target has no Cloudflare-style plugin to set the client
9
+ // outDir, so set it here. wrapEntry()'s serveStatic expects the
10
+ // client bundle at dist/client.
11
+ client: {
12
+ build: { outDir: 'dist/client' },
13
+ },
14
+ ssr: {
15
+ build: {
16
+ outDir: 'dist/server',
17
+ ssr: true,
18
+ rollupOptions: {
19
+ input: [ctx.entryWrapperId],
20
+ },
21
+ },
22
+ },
23
+ },
24
+ builder: {
25
+ async buildApp(builder) {
26
+ await builder.build(builder.environments.client);
27
+ await builder.build(builder.environments.ssr);
28
+ },
29
+ },
30
+ };
31
+ },
32
+ };
33
+ }
34
+ export function nodeDevServerPlugin(ctx) {
35
+ return {
36
+ name: 'hono-preact:node-dev-server',
37
+ apply: 'serve',
38
+ configureServer(server) {
39
+ const runner = createServerModuleRunner(server.environments.ssr);
40
+ // Wire the WebSocket upgrade. @hono/node-ws's injectWebSocket(target)
41
+ // just calls target.on('upgrade', fn); we pass a shim that captures
42
+ // that handler so we can invoke it with Node's real upgrade args.
43
+ // Multiple 'upgrade' listeners coexist fine with Vite's own HMR one.
44
+ server.httpServer?.on('upgrade', async (req, socket, head) => {
45
+ try {
46
+ const { injectWebSocket } = await runner.import(ctx.entryWrapperId);
47
+ if (!injectWebSocket)
48
+ return;
49
+ let handler;
50
+ injectWebSocket({
51
+ on(event, fn) {
52
+ if (event === 'upgrade')
53
+ handler = fn;
54
+ },
55
+ });
56
+ handler?.(req, socket, head);
57
+ }
58
+ catch (err) {
59
+ console.error('[hono-preact] dev ws upgrade error', err);
60
+ socket.destroy();
61
+ }
62
+ });
63
+ // Register the SSR middleware synchronously (not via the returned post
64
+ // hook). The post hook runs after Vite's spaFallbackMiddleware, which
65
+ // rewrites req.url to /index.html and makes the SSR app 404. Synchronous
66
+ // registration puts this ahead of Vite's HTML/SPA middlewares.
67
+ server.middlewares.use(async (req, res, next) => {
68
+ try {
69
+ // Vite-internal requests (its HMR client, source modules under
70
+ // /@fs and /@id, optimized deps) must reach Vite's later
71
+ // middlewares, or client hydration and HMR break. The SSR app only
72
+ // owns application routes, so pass these through. Same model as
73
+ // @hono/vite-dev-server's `exclude` option.
74
+ const path = (req.url ?? '').split('?')[0];
75
+ if (path.startsWith('/@') || path.startsWith('/node_modules/')) {
76
+ return next();
77
+ }
78
+ const { default: app } = (await runner.import(ctx.entryWrapperId));
79
+ const url = `http://${req.headers.host}${req.url}`;
80
+ const method = req.method ?? 'GET';
81
+ const headers = new Headers();
82
+ for (const [k, v] of Object.entries(req.headers)) {
83
+ if (typeof v === 'string')
84
+ headers.set(k, v);
85
+ else if (Array.isArray(v))
86
+ v.forEach((vv) => headers.append(k, vv));
87
+ }
88
+ let body;
89
+ if (method !== 'GET' && method !== 'HEAD') {
90
+ const chunks = [];
91
+ for await (const c of req)
92
+ chunks.push(c);
93
+ if (chunks.length) {
94
+ const buf = Buffer.concat(chunks);
95
+ // Copy into a fresh ArrayBuffer: BodyInit accepts ArrayBuffer,
96
+ // and this sidesteps Buffer<ArrayBufferLike> typing friction.
97
+ body = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
98
+ }
99
+ }
100
+ const request = new Request(url, { method, headers, body });
101
+ const response = await app.fetch(request);
102
+ res.statusCode = response.status;
103
+ response.headers.forEach((value, key) => res.setHeader(key, value));
104
+ if (response.body) {
105
+ const reader = response.body.getReader();
106
+ for (;;) {
107
+ const { done, value } = await reader.read();
108
+ if (done)
109
+ break;
110
+ res.write(value);
111
+ }
112
+ }
113
+ res.end();
114
+ }
115
+ catch (err) {
116
+ next(err);
117
+ }
118
+ });
119
+ },
120
+ };
121
+ }
@@ -1,26 +1,49 @@
1
1
  import type { Plugin } from 'vite';
2
- export interface GenerateServerEntrySourceOptions {
2
+ import type { HonoPreactAdapter } from './adapter.js';
3
+ export interface GenerateCoreAppModuleOptions {
3
4
  layoutAbsPath: string;
4
5
  routesAbsPath: string;
5
6
  apiAbsPath: string | undefined;
7
+ appConfigAbsPath: string | undefined;
6
8
  }
7
- export declare function generateServerEntrySource(opts: GenerateServerEntrySourceOptions): string;
8
- export type CatchAllWarning = {
9
+ export declare function generateCoreAppModule(opts: GenerateCoreAppModuleOptions): string;
10
+ export type ApiShadowingRoute = {
9
11
  kind: 'wildcard';
10
12
  method: string;
11
13
  pattern: string;
12
14
  line: number | undefined;
15
+ severity: 'error';
16
+ } | {
17
+ kind: 'reserved';
18
+ method: string;
19
+ pattern: string;
20
+ line: number | undefined;
21
+ severity: 'error';
13
22
  } | {
14
23
  kind: 'notFound';
15
24
  line: number | undefined;
25
+ severity: 'warning';
16
26
  };
17
- export declare function findApiCatchAllRoutes(source: string): CatchAllWarning[];
18
- export declare const GENERATED_SERVER_ENTRY_RELATIVE = "node_modules/.vite/hono-preact/server-entry.tsx";
19
- export declare function generatedServerEntryAbsPath(cwd?: string): string;
27
+ export declare function findApiShadowingRoutes(source: string): ApiShadowingRoute[];
28
+ export declare const GENERATED_CORE_APP_RELATIVE = "node_modules/.vite/hono-preact/core-app.tsx";
29
+ export declare const GENERATED_ENTRY_WRAPPER_RELATIVE = "node_modules/.vite/hono-preact/server-entry.tsx";
30
+ export declare function generatedCoreAppAbsPath(cwd?: string): string;
31
+ export declare function generatedEntryWrapperAbsPath(cwd?: string): string;
20
32
  export interface ServerEntryPluginOptions {
21
33
  layout: string;
22
34
  routes: string;
23
35
  api: string;
24
- outputPath: string;
36
+ /**
37
+ * Project-relative or absolute path to the user's app-config file. The
38
+ * `hono-preact` umbrella plugin always supplies a default
39
+ * (`src/app-config.ts`), so this is required here even though it's
40
+ * optional from the user's perspective: missing the file on disk is
41
+ * allowed (an inline `{ use: [] }` falls back into the generated core
42
+ * app), but the option name itself must be supplied.
43
+ */
44
+ appConfig: string;
45
+ adapter: HonoPreactAdapter;
46
+ coreAppPath: string;
47
+ entryWrapperPath: string;
25
48
  }
26
49
  export declare function serverEntryPlugin(opts: ServerEntryPluginOptions): Plugin;