waku 0.19.0 → 0.19.1
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 +84 -16
- package/dist/cli.js +3 -7
- package/dist/client.d.ts +11 -2
- package/dist/client.js +36 -34
- package/dist/config.d.ts +6 -0
- package/dist/dev.d.ts +2 -2
- package/dist/dev.js +2 -2
- package/dist/lib/builder/build.d.ts +1 -5
- package/dist/lib/builder/build.js +21 -18
- package/dist/lib/builder/output-cloudflare.d.ts +1 -1
- package/dist/lib/builder/output-cloudflare.js +5 -31
- package/dist/lib/builder/output-vercel.js +2 -33
- package/dist/lib/builder/serve-cloudflare.d.ts +6 -0
- package/dist/lib/builder/serve-cloudflare.js +23 -0
- package/dist/lib/builder/serve-deno.d.ts +1 -0
- package/dist/lib/builder/serve-deno.js +22 -0
- package/dist/lib/builder/serve-vercel.d.ts +3 -0
- package/dist/lib/builder/serve-vercel.js +30 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +1 -0
- package/dist/lib/handlers/dev-worker-api.d.ts +1 -1
- package/dist/lib/handlers/dev-worker-api.js +2 -2
- package/dist/lib/handlers/dev-worker-impl.js +11 -8
- package/dist/lib/handlers/handler-dev.js +1 -1
- package/dist/lib/handlers/handler-prd.d.ts +1 -1
- package/dist/lib/handlers/handler-prd.js +2 -1
- package/dist/lib/handlers/types.d.ts +0 -4
- package/dist/lib/plugins/vite-plugin-rsc-delegate.d.ts +2 -2
- package/dist/lib/plugins/vite-plugin-rsc-delegate.js +11 -8
- package/dist/lib/plugins/vite-plugin-rsc-hmr.d.ts +9 -3
- package/dist/lib/plugins/vite-plugin-rsc-hmr.js +50 -9
- package/dist/lib/plugins/vite-plugin-rsc-serve.d.ts +10 -0
- package/dist/lib/plugins/vite-plugin-rsc-serve.js +19 -0
- package/dist/prd.d.ts +2 -2
- package/dist/prd.js +2 -2
- package/dist/router/client.d.ts +1 -0
- package/package.json +8 -8
- package/src/cli.ts +16 -12
- package/src/client.ts +93 -79
- package/src/config.ts +6 -0
- package/src/dev.ts +2 -2
- package/src/lib/builder/build.ts +34 -24
- package/src/lib/builder/output-cloudflare.ts +4 -42
- package/src/lib/builder/output-vercel.ts +2 -40
- package/src/lib/builder/serve-cloudflare.ts +24 -0
- package/src/lib/builder/serve-deno.ts +22 -0
- package/src/lib/builder/serve-vercel.ts +35 -0
- package/src/lib/config.ts +1 -0
- package/src/lib/handlers/dev-worker-api.ts +3 -3
- package/src/lib/handlers/dev-worker-impl.ts +13 -9
- package/src/lib/handlers/handler-dev.ts +1 -1
- package/src/lib/handlers/handler-prd.ts +4 -2
- package/src/lib/handlers/types.ts +0 -6
- package/src/lib/plugins/vite-plugin-rsc-delegate.ts +10 -5
- package/src/lib/plugins/vite-plugin-rsc-hmr.ts +63 -11
- package/src/lib/plugins/vite-plugin-rsc-serve.ts +33 -0
- package/src/lib/renderers/rsc-renderer.ts +3 -3
- package/src/prd.ts +2 -2
- package/src/router/server.ts +6 -5
- package/dist/lib/builder/output-deno.d.ts +0 -2
- package/dist/lib/builder/output-deno.js +0 -25
- package/dist/lib/plugins/vite-plugin-rsc-entries.d.ts +0 -6
- package/dist/lib/plugins/vite-plugin-rsc-entries.js +0 -22
- package/src/lib/builder/output-deno.ts +0 -43
- package/src/lib/plugins/vite-plugin-rsc-entries.ts +0 -28
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ visit [waku.gg](https://waku.gg) or `npm create waku@latest`
|
|
|
15
15
|
|
|
16
16
|
**Waku** _(wah-ku)_ or **わく** means “framework” in Japanese. As the minimal React framework, it aims to accelerate the work of developers at startups and agencies building small to medium-sized React projects. These include marketing websites, light ecommerce, and web applications.
|
|
17
17
|
|
|
18
|
-
We recommend other frameworks for heavy ecommerce or enterprise applications. Waku is a lightweight alternative designed to bring a fun developer experience to the modern React server components era. Yes, let’s make React development fun
|
|
18
|
+
We recommend other frameworks for heavy ecommerce or enterprise applications. Waku is a lightweight alternative designed to bring a fun developer experience to the modern React server components era. Yes, let’s make React development fun!
|
|
19
19
|
|
|
20
20
|
> Waku is in rapid development and some features are currently missing. Please try it on non-production projects and report any issues you may encounter. Expect that there will be some breaking changes on the road towards a stable v1 release. Contributors are welcome.
|
|
21
21
|
|
|
@@ -31,13 +31,13 @@ npm create waku@latest
|
|
|
31
31
|
|
|
32
32
|
Let's face it: React is getting complicated. But not without good reason!
|
|
33
33
|
|
|
34
|
-
While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of composability that are only
|
|
34
|
+
While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of composability that are only possible with the advent of React server components. So stick with us.
|
|
35
35
|
|
|
36
36
|
Future versions of Waku may provide additional APIs to abstract away some of the complexity for an improved developer experience.
|
|
37
37
|
|
|
38
38
|
#### Server components
|
|
39
39
|
|
|
40
|
-
Waku
|
|
40
|
+
Waku supports [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md) and [server actions](https://react.dev/reference/react/use-server). Server components can be made async and can securely perform server-side logic and data fetching. Feel free to use heavy dependencies since they aren't included in the client bundle. They have no interactivity or access to browser APIs since they run exclusively on the server.
|
|
41
41
|
|
|
42
42
|
```tsx
|
|
43
43
|
// server component
|
|
@@ -54,7 +54,7 @@ export const StorePage = async () => {
|
|
|
54
54
|
|
|
55
55
|
#### Client components
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
A `'use client'` directive placed at the top of a file will create a server-client boundary when the module is imported into a server component. All components imported below the boundary will be hydrated to run in the browser as well. They can use all traditional React features such as state, effects, and event handlers.
|
|
58
58
|
|
|
59
59
|
```tsx
|
|
60
60
|
// client component
|
|
@@ -74,13 +74,48 @@ export const Counter = () => {
|
|
|
74
74
|
};
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
#### Shared components
|
|
78
|
+
|
|
79
|
+
Simple React components that [meet all of the rules](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#sharing-code-between-server-and-client) of both server and client components can be imported into either server or client components without affecting the server-client boundary.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// shared component
|
|
83
|
+
export const Headline = ({ children }) => {
|
|
84
|
+
return <h3>{children}</h3>;
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
77
88
|
#### Weaving patterns
|
|
78
89
|
|
|
79
|
-
Server components can import client components and doing so will create a server-client boundary. Client components cannot import server components, but they can accept server components as props such as `children`.
|
|
90
|
+
Server components can import client components and doing so will create a server-client boundary. Client components cannot import server components, but they can accept server components as props such as `children`. For example, you may want to add global context providers this way.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ./src/templates/root-layout.tsx
|
|
94
|
+
import { Providers } from '../components/providers.js';
|
|
95
|
+
|
|
96
|
+
export const RootLayout = async ({ children }) => {
|
|
97
|
+
return (
|
|
98
|
+
<Providers>
|
|
99
|
+
<main>{children}</main>
|
|
100
|
+
</Providers>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// ./src/components/providers.tsx
|
|
107
|
+
'use client';
|
|
108
|
+
|
|
109
|
+
import { Provider } from 'jotai';
|
|
110
|
+
|
|
111
|
+
export const Providers = ({ children }) => {
|
|
112
|
+
return <Provider>{children}</Provider>;
|
|
113
|
+
};
|
|
114
|
+
```
|
|
80
115
|
|
|
81
116
|
#### Server-side rendering
|
|
82
117
|
|
|
83
|
-
Waku provides static prerendering (SSG) or server-side rendering (SSR) options for layouts and pages including their server
|
|
118
|
+
Waku provides static prerendering (SSG) or server-side rendering (SSR) options for layouts and pages including both their server and client components.
|
|
84
119
|
|
|
85
120
|
#### Further reading
|
|
86
121
|
|
|
@@ -90,7 +125,9 @@ To learn more about the modern React architecture, we recommend [Making Sense of
|
|
|
90
125
|
|
|
91
126
|
The entry point for routing in Waku projects is `./src/entries.tsx`. Export the `createPages` function to create your layouts and pages programatically.
|
|
92
127
|
|
|
93
|
-
Both `createLayout` and `createPage` accept a configuration object to specify the route path, React component, and render method
|
|
128
|
+
Both `createLayout` and `createPage` accept a configuration object to specify the route path, React component, and render method. Waku currently supports two options: `'static'` for static prerendering (SSG) or `'dynamic'` for server-side rendering (SSR).
|
|
129
|
+
|
|
130
|
+
For example, you can statically prerender a global header and footer in the root layout at build time, but dynamically render the rest of a home page at request time for personalized user experiences.
|
|
94
131
|
|
|
95
132
|
```tsx
|
|
96
133
|
// ./src/entries.tsx
|
|
@@ -244,7 +281,9 @@ export default createPages(async ({ createPage }) => {
|
|
|
244
281
|
|
|
245
282
|
#### Catch-all routes
|
|
246
283
|
|
|
247
|
-
Catch-all or "wildcard" routes (e.g., `/app/[...catchAll]`) have indefinite segments. Wildcard routes receive a prop with segment values as an ordered array.
|
|
284
|
+
Catch-all or "wildcard" routes (e.g., `/app/[...catchAll]`) have indefinite segments. Wildcard routes receive a prop with segment values as an ordered array.
|
|
285
|
+
|
|
286
|
+
For example, the `/app/profile/settings` route would receive a `catchAll` prop with the value `['profile', 'settings']`. These values can then be used to determine what to render in the component.
|
|
248
287
|
|
|
249
288
|
```tsx
|
|
250
289
|
// ./src/entries.tsx
|
|
@@ -323,7 +362,7 @@ export const Providers = ({ children }) => {
|
|
|
323
362
|
|
|
324
363
|
#### Other layouts
|
|
325
364
|
|
|
326
|
-
Layouts are also helpful further down the tree. For example you could add a layout at `path: '/blog` to add a sidebar to both the blog index and all blog article pages.
|
|
365
|
+
Layouts are also helpful further down the tree. For example, you could add a layout at `path: '/blog` to add a sidebar to both the blog index and all blog article pages.
|
|
327
366
|
|
|
328
367
|
```tsx
|
|
329
368
|
// ./src/entries.tsx
|
|
@@ -347,10 +386,10 @@ import { Sidebar } from '../components/sidebar.js';
|
|
|
347
386
|
|
|
348
387
|
export const BlogLayout = async ({ children }) => {
|
|
349
388
|
return (
|
|
350
|
-
|
|
389
|
+
<div className="flex">
|
|
351
390
|
<div>{children}</div>
|
|
352
391
|
<Sidebar />
|
|
353
|
-
|
|
392
|
+
</div>
|
|
354
393
|
);
|
|
355
394
|
};
|
|
356
395
|
```
|
|
@@ -377,13 +416,13 @@ export const HomePage = async () => {
|
|
|
377
416
|
|
|
378
417
|
Static assets such as images, fonts, stylesheets, and scripts can be placed in a special `./public` folder of the Waku project root directory. The public directory structure is served relative to the `/` base path.
|
|
379
418
|
|
|
380
|
-
For example an image added to `./public/images/logo.svg` can be rendered via `<img src="/images/logo.svg" />`.
|
|
419
|
+
For example, an image added to `./public/images/logo.svg` can be rendered via `<img src="/images/logo.svg" />`.
|
|
381
420
|
|
|
382
421
|
## Data fetching
|
|
383
422
|
|
|
384
423
|
### Server
|
|
385
424
|
|
|
386
|
-
All of the wonderful patterns of React server components are supported. For example you can compile MDX files or perform code syntax highlighting on the server with zero impact on the client bundle size.
|
|
425
|
+
All of the wonderful patterns of React server components are supported. For example, you can compile MDX files or perform code syntax highlighting on the server with zero impact on the client bundle size.
|
|
387
426
|
|
|
388
427
|
```tsx
|
|
389
428
|
// ./src/templates/blog-article-page.tsx
|
|
@@ -395,8 +434,8 @@ export const BlogArticlePage = async ({ slug }) => {
|
|
|
395
434
|
|
|
396
435
|
return (
|
|
397
436
|
<>
|
|
398
|
-
<title>{article.frontmatter.title}</
|
|
399
|
-
<h1>{article.frontmatter.title}</
|
|
437
|
+
<title>{article.frontmatter.title}</title>
|
|
438
|
+
<h1>{article.frontmatter.title}</h1>
|
|
400
439
|
<MDX>{article.content}</MDX>
|
|
401
440
|
</>
|
|
402
441
|
);
|
|
@@ -444,6 +483,35 @@ export const HomePage = async () => {
|
|
|
444
483
|
};
|
|
445
484
|
```
|
|
446
485
|
|
|
486
|
+
Metadata could also be generated programatically.
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
// ./src/templates/home-page.tsx
|
|
490
|
+
export const HomePage = async () => {
|
|
491
|
+
return (
|
|
492
|
+
<>
|
|
493
|
+
<Head />
|
|
494
|
+
<div>{/* ...*/}</div>
|
|
495
|
+
</>
|
|
496
|
+
);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const Head = async () => {
|
|
500
|
+
const metadata = await getMetadata();
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<>
|
|
504
|
+
<title>{metadata.title}</title>
|
|
505
|
+
<meta property="description" content={metadata.description} />
|
|
506
|
+
</>
|
|
507
|
+
);
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const getMetadata = async () => {
|
|
511
|
+
/* ... */
|
|
512
|
+
};
|
|
513
|
+
```
|
|
514
|
+
|
|
447
515
|
## Styling
|
|
448
516
|
|
|
449
517
|
### Global styles
|
|
@@ -578,7 +646,7 @@ npx wrangler dev # or deploy
|
|
|
578
646
|
|
|
579
647
|
```
|
|
580
648
|
npm run build -- --with-deno
|
|
581
|
-
DENO_DEPLOY_TOKEN=... deployctl deploy --project=... --prod serve.
|
|
649
|
+
DENO_DEPLOY_TOKEN=... deployctl deploy --project=... --prod dist/serve.js --exclude node_modules
|
|
582
650
|
```
|
|
583
651
|
|
|
584
652
|
## Community
|
package/dist/cli.js
CHANGED
|
@@ -92,21 +92,17 @@ async function runBuild(options) {
|
|
|
92
92
|
...options,
|
|
93
93
|
config,
|
|
94
94
|
env: process.env,
|
|
95
|
-
|
|
96
|
-
type: values['with-vercel-static'] ? 'static' : 'serverless'
|
|
97
|
-
} : undefined,
|
|
98
|
-
cloudflare: !!values['with-cloudflare'],
|
|
99
|
-
deno: !!values['with-deno']
|
|
95
|
+
deploy: (values['with-vercel'] ?? !!process.env.VERCEL ? values['with-vercel-static'] ? 'vercel-static' : 'vercel-serverless' : undefined) || (values['with-cloudflare'] ? 'cloudflare' : undefined) || (values['with-deno'] ? 'deno' : undefined)
|
|
100
96
|
});
|
|
101
97
|
}
|
|
102
98
|
async function runStart(options) {
|
|
103
99
|
const { distDir, publicDir, entriesJs } = await resolveConfig(config);
|
|
104
|
-
const
|
|
100
|
+
const loadEntries = ()=>import(pathToFileURL(path.resolve(distDir, entriesJs)).toString());
|
|
105
101
|
const app = new Hono();
|
|
106
102
|
app.use('*', honoPrdMiddleware({
|
|
107
103
|
...options,
|
|
108
104
|
config,
|
|
109
|
-
|
|
105
|
+
loadEntries,
|
|
110
106
|
env: process.env
|
|
111
107
|
}));
|
|
112
108
|
app.use('*', serveStatic({
|
package/dist/client.d.ts
CHANGED
|
@@ -5,11 +5,20 @@ declare global {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
type Elements = Promise<Record<string, ReactNode>>;
|
|
8
|
-
|
|
8
|
+
type SetElements = (fn: (prev: Elements) => Elements) => void;
|
|
9
|
+
type CacheEntry = [
|
|
10
|
+
input: string,
|
|
11
|
+
searchParamsString: string,
|
|
12
|
+
setElements: SetElements,
|
|
13
|
+
elements: Elements
|
|
14
|
+
];
|
|
15
|
+
declare const fetchCache: [CacheEntry?];
|
|
16
|
+
export declare const fetchRSC: (input: string, searchParamsString: string, setElements: SetElements, cache?: [CacheEntry?]) => Elements;
|
|
9
17
|
export declare const prefetchRSC: (input: string, searchParamsString: string) => void;
|
|
10
|
-
export declare const Root: ({ initialInput, initialSearchParamsString, children, }: {
|
|
18
|
+
export declare const Root: ({ initialInput, initialSearchParamsString, cache, children, }: {
|
|
11
19
|
initialInput?: string;
|
|
12
20
|
initialSearchParamsString?: string;
|
|
21
|
+
cache?: typeof fetchCache;
|
|
13
22
|
children: ReactNode;
|
|
14
23
|
}) => import("react").FunctionComponentElement<import("react").ProviderProps<(input: string, searchParams?: URLSearchParams) => void>>;
|
|
15
24
|
export declare const useRefetch: () => (input: string, searchParams?: URLSearchParams) => void;
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react/canary" />
|
|
2
2
|
'use client';
|
|
3
|
-
import {
|
|
3
|
+
import { createContext, createElement, memo, use, useCallback, useState, startTransition } from 'react';
|
|
4
4
|
import RSDWClient from 'react-server-dom-webpack/client';
|
|
5
5
|
import { encodeInput } from './lib/renderers/utils.js';
|
|
6
6
|
const { createFromFetch, encodeReply } = RSDWClient;
|
|
@@ -14,15 +14,27 @@ const checkStatus = async (responsePromise)=>{
|
|
|
14
14
|
}
|
|
15
15
|
return response;
|
|
16
16
|
};
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const getCached = (c, m, k)=>(m.has(k) ? m : m.set(k, c())).get(k);
|
|
18
|
+
const cache1 = new WeakMap();
|
|
19
|
+
const mergeElements = (a, b)=>{
|
|
20
|
+
const getResult = async ()=>{
|
|
21
|
+
const nextElements = {
|
|
22
|
+
...await a,
|
|
23
|
+
...await b
|
|
24
|
+
};
|
|
25
|
+
delete nextElements._value;
|
|
26
|
+
return nextElements;
|
|
21
27
|
};
|
|
22
|
-
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
|
|
28
|
+
const cache2 = getCached(()=>new WeakMap(), cache1, a);
|
|
29
|
+
return getCached(getResult, cache2, b);
|
|
30
|
+
};
|
|
31
|
+
const fetchCache = [];
|
|
32
|
+
export const fetchRSC = (input, searchParamsString, setElements, cache = fetchCache)=>{
|
|
33
|
+
let entry = cache[0];
|
|
34
|
+
if (entry && entry[0] === input && entry[1] === searchParamsString) {
|
|
35
|
+
entry[2] = setElements;
|
|
36
|
+
return entry[3];
|
|
37
|
+
}
|
|
26
38
|
const options = {
|
|
27
39
|
async callServer (actionId, args) {
|
|
28
40
|
const response = fetch(BASE_PATH + encodeInput(encodeURIComponent(actionId)), {
|
|
@@ -30,9 +42,10 @@ export const fetchRSC = cache((input, searchParamsString, rerender)=>{
|
|
|
30
42
|
body: await encodeReply(args)
|
|
31
43
|
});
|
|
32
44
|
const data = createFromFetch(checkStatus(response), options);
|
|
45
|
+
const setElements = entry[2];
|
|
33
46
|
startTransition(()=>{
|
|
34
47
|
// FIXME this causes rerenders even if data is empty
|
|
35
|
-
|
|
48
|
+
setElements((prev)=>mergeElements(prev, data));
|
|
36
49
|
});
|
|
37
50
|
return (await data)._value;
|
|
38
51
|
}
|
|
@@ -42,43 +55,32 @@ export const fetchRSC = cache((input, searchParamsString, rerender)=>{
|
|
|
42
55
|
const response = prefetched[url] || fetch(url);
|
|
43
56
|
delete prefetched[url];
|
|
44
57
|
const data = createFromFetch(checkStatus(response), options);
|
|
58
|
+
cache[0] = entry = [
|
|
59
|
+
input,
|
|
60
|
+
searchParamsString,
|
|
61
|
+
setElements,
|
|
62
|
+
data
|
|
63
|
+
];
|
|
45
64
|
return data;
|
|
46
|
-
}
|
|
47
|
-
export const prefetchRSC =
|
|
65
|
+
};
|
|
66
|
+
export const prefetchRSC = (input, searchParamsString)=>{
|
|
48
67
|
const prefetched = globalThis.__WAKU_PREFETCHED__ ||= {};
|
|
49
68
|
const url = BASE_PATH + encodeInput(input) + (searchParamsString ? '?' + searchParamsString : '');
|
|
50
69
|
if (!(url in prefetched)) {
|
|
51
70
|
prefetched[url] = fetch(url);
|
|
52
71
|
}
|
|
53
|
-
}
|
|
72
|
+
};
|
|
54
73
|
const RefetchContext = createContext(()=>{
|
|
55
74
|
throw new Error('Missing Root component');
|
|
56
75
|
});
|
|
57
76
|
const ElementsContext = createContext(null);
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
let rerender;
|
|
61
|
-
const stableRerender = (fn)=>{
|
|
62
|
-
rerender?.(fn);
|
|
63
|
-
};
|
|
64
|
-
const getRerender = ()=>stableRerender;
|
|
65
|
-
const setRerender = (newRerender)=>{
|
|
66
|
-
rerender = newRerender;
|
|
67
|
-
};
|
|
68
|
-
return [
|
|
69
|
-
getRerender,
|
|
70
|
-
setRerender
|
|
71
|
-
];
|
|
72
|
-
});
|
|
73
|
-
export const Root = ({ initialInput, initialSearchParamsString, children })=>{
|
|
74
|
-
const [getRerender, setRerender] = createRerender();
|
|
75
|
-
const [elements, setElements] = useState(()=>fetchRSC(initialInput || '', initialSearchParamsString || '', getRerender()));
|
|
76
|
-
setRerender(setElements);
|
|
77
|
+
export const Root = ({ initialInput, initialSearchParamsString, cache, children })=>{
|
|
78
|
+
const [elements, setElements] = useState(()=>fetchRSC(initialInput || '', initialSearchParamsString || '', (fn)=>setElements(fn), cache));
|
|
77
79
|
const refetch = useCallback((input, searchParams)=>{
|
|
78
|
-
const data = fetchRSC(input, searchParams?.toString() || '',
|
|
80
|
+
const data = fetchRSC(input, searchParams?.toString() || '', setElements, cache);
|
|
79
81
|
setElements((prev)=>mergeElements(prev, data));
|
|
80
82
|
}, [
|
|
81
|
-
|
|
83
|
+
cache
|
|
82
84
|
]);
|
|
83
85
|
return createElement(RefetchContext.Provider, {
|
|
84
86
|
value: refetch
|
package/dist/config.d.ts
CHANGED
|
@@ -44,6 +44,12 @@ export interface Config {
|
|
|
44
44
|
* Defaults to "entries.js".
|
|
45
45
|
*/
|
|
46
46
|
entriesJs?: string;
|
|
47
|
+
/**
|
|
48
|
+
* The serve.js file relative distDir.
|
|
49
|
+
* This file is used for deployment.
|
|
50
|
+
* Defaults to "serve.js".
|
|
51
|
+
*/
|
|
52
|
+
serveJs?: string;
|
|
47
53
|
/**
|
|
48
54
|
* Prefix for HTTP requests to indicate RSC requests.
|
|
49
55
|
* Defaults to "RSC".
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { honoMiddleware } from './lib/middleware/hono-dev.js';
|
|
2
|
-
export { connectMiddleware } from './lib/middleware/connect-dev.js';
|
|
1
|
+
export { honoMiddleware as unstable_honoMiddleware } from './lib/middleware/hono-dev.js';
|
|
2
|
+
export { connectMiddleware as unstable_connectMiddleware } from './lib/middleware/connect-dev.js';
|
|
3
3
|
export { createHandler as unstable_createHandler } from './lib/handlers/handler-dev.js';
|
|
4
4
|
export { build } from './lib/builder/build.js';
|
package/dist/dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { honoMiddleware } from './lib/middleware/hono-dev.js';
|
|
2
|
-
export { connectMiddleware } from './lib/middleware/connect-dev.js';
|
|
1
|
+
export { honoMiddleware as unstable_honoMiddleware } from './lib/middleware/hono-dev.js';
|
|
2
|
+
export { connectMiddleware as unstable_connectMiddleware } from './lib/middleware/connect-dev.js';
|
|
3
3
|
export { createHandler as unstable_createHandler } from './lib/handlers/handler-dev.js';
|
|
4
4
|
export { build } from './lib/builder/build.js';
|
|
@@ -3,9 +3,5 @@ export declare function build(options: {
|
|
|
3
3
|
config?: Config;
|
|
4
4
|
ssr?: boolean;
|
|
5
5
|
env?: Record<string, string>;
|
|
6
|
-
|
|
7
|
-
type: 'static' | 'serverless';
|
|
8
|
-
} | undefined;
|
|
9
|
-
cloudflare?: boolean;
|
|
10
|
-
deno?: boolean;
|
|
6
|
+
deploy?: 'vercel-static' | 'vercel-serverless' | 'cloudflare' | 'deno' | undefined;
|
|
11
7
|
}): Promise<void>;
|
|
@@ -15,11 +15,10 @@ import { rscIndexPlugin } from '../plugins/vite-plugin-rsc-index.js';
|
|
|
15
15
|
import { rscAnalyzePlugin } from '../plugins/vite-plugin-rsc-analyze.js';
|
|
16
16
|
import { nonjsResolvePlugin } from '../plugins/vite-plugin-nonjs-resolve.js';
|
|
17
17
|
import { rscTransformPlugin } from '../plugins/vite-plugin-rsc-transform.js';
|
|
18
|
-
import {
|
|
18
|
+
import { rscServePlugin } from '../plugins/vite-plugin-rsc-serve.js';
|
|
19
19
|
import { rscEnvPlugin } from '../plugins/vite-plugin-rsc-env.js';
|
|
20
20
|
import { emitVercelOutput } from './output-vercel.js';
|
|
21
21
|
import { emitCloudflareOutput } from './output-cloudflare.js';
|
|
22
|
-
import { emitDenoOutput } from './output-deno.js';
|
|
23
22
|
// TODO this file and functions in it are too long. will fix.
|
|
24
23
|
const WAKU_CLIENT = 'waku-client';
|
|
25
24
|
// Upstream issue: https://github.com/rollup/rollup/issues/4699
|
|
@@ -91,7 +90,7 @@ const analyzeEntries = async (entriesFile)=>{
|
|
|
91
90
|
serverEntryFiles
|
|
92
91
|
};
|
|
93
92
|
};
|
|
94
|
-
const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles,
|
|
93
|
+
const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, ssr, serve)=>{
|
|
95
94
|
const serverBuildOutput = await buildVite({
|
|
96
95
|
plugins: [
|
|
97
96
|
nonjsResolvePlugin(),
|
|
@@ -105,14 +104,17 @@ const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile,
|
|
|
105
104
|
},
|
|
106
105
|
serverEntryFiles
|
|
107
106
|
}),
|
|
108
|
-
rscEntriesPlugin({
|
|
109
|
-
entriesFile,
|
|
110
|
-
reExportHonoMiddleware,
|
|
111
|
-
reExportConnectMiddleware
|
|
112
|
-
}),
|
|
113
107
|
rscEnvPlugin({
|
|
114
108
|
config
|
|
115
|
-
})
|
|
109
|
+
}),
|
|
110
|
+
...serve ? [
|
|
111
|
+
rscServePlugin({
|
|
112
|
+
...config,
|
|
113
|
+
entriesFile,
|
|
114
|
+
srcServeFile: decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), `../serve-${serve}.js`)),
|
|
115
|
+
ssr
|
|
116
|
+
})
|
|
117
|
+
] : []
|
|
116
118
|
],
|
|
117
119
|
ssr: {
|
|
118
120
|
resolve: {
|
|
@@ -125,6 +127,10 @@ const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile,
|
|
|
125
127
|
'workerd'
|
|
126
128
|
]
|
|
127
129
|
},
|
|
130
|
+
external: [
|
|
131
|
+
'hono',
|
|
132
|
+
'hono/cloudflare-workers'
|
|
133
|
+
],
|
|
128
134
|
noExternal: /^(?!node:)/
|
|
129
135
|
},
|
|
130
136
|
define: {
|
|
@@ -365,6 +371,7 @@ const emitHtmlFiles = async (rootDir, config, distEntriesFile, buildConfig, getC
|
|
|
365
371
|
}
|
|
366
372
|
return destHtmlFile;
|
|
367
373
|
}));
|
|
374
|
+
htmlFiles.unshift(publicIndexHtmlFile);
|
|
368
375
|
const loadHtmlHeadCode = `
|
|
369
376
|
export function loadHtmlHead(pathname) {
|
|
370
377
|
return ${JSON.stringify(htmlHeadMap)}[pathname] || ${JSON.stringify(publicIndexHtmlHead)};
|
|
@@ -396,17 +403,13 @@ export async function build(options) {
|
|
|
396
403
|
const entriesFile = resolveFileName(joinPath(rootDir, config.srcDir, config.entriesJs));
|
|
397
404
|
const distEntriesFile = resolveFileName(joinPath(rootDir, config.distDir, config.entriesJs));
|
|
398
405
|
const { commonEntryFiles, clientEntryFiles, serverEntryFiles } = await analyzeEntries(entriesFile);
|
|
399
|
-
const serverBuildOutput = await buildServerBundle(rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, !!options.cloudflare ||
|
|
406
|
+
const serverBuildOutput = await buildServerBundle(rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, !!options.ssr, (options.deploy === 'vercel-serverless' ? 'vercel' : false) || (options.deploy === 'cloudflare' ? 'cloudflare' : false) || (options.deploy === 'deno' ? 'deno' : false));
|
|
400
407
|
await buildClientBundle(rootDir, config, commonEntryFiles, clientEntryFiles, serverBuildOutput, !!options.ssr);
|
|
401
408
|
const { buildConfig, getClientModules, rscFiles } = await emitRscFiles(rootDir, config, distEntriesFile);
|
|
402
409
|
const { htmlFiles } = await emitHtmlFiles(rootDir, config, distEntriesFile, buildConfig, getClientModules, !!options.ssr);
|
|
403
|
-
if (options.vercel) {
|
|
404
|
-
await emitVercelOutput(rootDir, config, rscFiles, htmlFiles, !!options.ssr, options.vercel.
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
await emitCloudflareOutput(rootDir, config, !!options.ssr);
|
|
408
|
-
}
|
|
409
|
-
if (options.deno) {
|
|
410
|
-
await emitDenoOutput(rootDir, config, !!options.ssr);
|
|
410
|
+
if (options.deploy?.startsWith('vercel-')) {
|
|
411
|
+
await emitVercelOutput(rootDir, config, rscFiles, htmlFiles, !!options.ssr, options.deploy.slice('vercel-'.length));
|
|
412
|
+
} else if (options.deploy === 'cloudflare') {
|
|
413
|
+
await emitCloudflareOutput(rootDir, config);
|
|
411
414
|
}
|
|
412
415
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ResolvedConfig } from '../config.js';
|
|
2
|
-
export declare const emitCloudflareOutput: (rootDir: string, config: ResolvedConfig
|
|
2
|
+
export declare const emitCloudflareOutput: (rootDir: string, config: ResolvedConfig) => Promise<void>;
|
|
@@ -1,41 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync, writeFileSync } from 'node:fs';
|
|
3
3
|
// XXX this can be very limited. FIXME if anyone has better knowledge.
|
|
4
|
-
export const emitCloudflareOutput = async (rootDir, config
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const entriesFile = path.join(relativeRootDir, config.distDir, config.entriesJs);
|
|
8
|
-
const publicDir = path.join(relativeRootDir, config.distDir, config.publicDir);
|
|
9
|
-
if (!existsSync(path.join(outputDir, 'serve.js'))) {
|
|
10
|
-
writeFileSync(path.join(outputDir, 'serve.js'), `
|
|
11
|
-
import { Hono } from 'hono';
|
|
12
|
-
import { serveStatic } from 'hono/cloudflare-workers';
|
|
13
|
-
|
|
14
|
-
const entries = import('./${entriesFile}');
|
|
15
|
-
const { honoMiddleware } = await entries;
|
|
16
|
-
let serveWaku;
|
|
17
|
-
|
|
18
|
-
const app = new Hono();
|
|
19
|
-
app.use('*', (c, next) => serveWaku(c, next));
|
|
20
|
-
app.use('*', serveStatic({ root: './' }));
|
|
21
|
-
export default {
|
|
22
|
-
async fetch(request, env, ctx) {
|
|
23
|
-
if (!serveWaku) {
|
|
24
|
-
serveWaku = honoMiddleware({ entries, ssr: ${ssr}, env });
|
|
25
|
-
}
|
|
26
|
-
return app.fetch(request, env, ctx);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
`);
|
|
30
|
-
}
|
|
31
|
-
if (!existsSync(path.join(outputDir, 'wrangler.toml'))) {
|
|
32
|
-
writeFileSync(path.join(outputDir, 'wrangler.toml'), `
|
|
4
|
+
export const emitCloudflareOutput = async (rootDir, config)=>{
|
|
5
|
+
if (!existsSync(path.join(rootDir, 'wrangler.toml'))) {
|
|
6
|
+
writeFileSync(path.join(rootDir, 'wrangler.toml'), `
|
|
33
7
|
name = "waku-project"
|
|
34
|
-
main = "
|
|
8
|
+
main = "${config.distDir}/${config.serveJs}"
|
|
35
9
|
compatibility_date = "2023-12-06"
|
|
36
10
|
|
|
37
11
|
[site]
|
|
38
|
-
bucket = "./${publicDir}"
|
|
12
|
+
bucket = "./${config.distDir}/${config.publicDir}"
|
|
39
13
|
`);
|
|
40
14
|
}
|
|
41
15
|
};
|
|
@@ -4,7 +4,7 @@ import { cpSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
4
4
|
export const emitVercelOutput = async (rootDir, config, rscFiles, htmlFiles, ssr, type)=>{
|
|
5
5
|
const publicDir = path.join(rootDir, config.distDir, config.publicDir);
|
|
6
6
|
const outputDir = path.resolve('.vercel', 'output');
|
|
7
|
-
cpSync(
|
|
7
|
+
cpSync(publicDir, path.join(outputDir, 'static'), {
|
|
8
8
|
recursive: true
|
|
9
9
|
});
|
|
10
10
|
if (type === 'serverless') {
|
|
@@ -18,44 +18,13 @@ export const emitVercelOutput = async (rootDir, config, rscFiles, htmlFiles, ssr
|
|
|
18
18
|
});
|
|
19
19
|
const vcConfigJson = {
|
|
20
20
|
runtime: 'nodejs18.x',
|
|
21
|
-
handler:
|
|
21
|
+
handler: `${config.distDir}/${config.serveJs}`,
|
|
22
22
|
launcherType: 'Nodejs'
|
|
23
23
|
};
|
|
24
24
|
writeFileSync(path.join(serverlessDir, '.vc-config.json'), JSON.stringify(vcConfigJson, null, 2));
|
|
25
25
|
writeFileSync(path.join(serverlessDir, 'package.json'), JSON.stringify({
|
|
26
26
|
type: 'module'
|
|
27
27
|
}, null, 2));
|
|
28
|
-
writeFileSync(path.join(serverlessDir, 'serve.js'), `
|
|
29
|
-
import path from 'node:path';
|
|
30
|
-
import fs from 'node:fs';
|
|
31
|
-
|
|
32
|
-
const entries = import(path.resolve('${config.distDir}', '${config.entriesJs}'));
|
|
33
|
-
const { connectMiddleware } = await entries;
|
|
34
|
-
const env = process.env;
|
|
35
|
-
|
|
36
|
-
export default function handler(req, res) {
|
|
37
|
-
connectMiddleware({ entries, ssr: ${ssr}, env })(req, res, () => {
|
|
38
|
-
const { pathname } = new URL(req.url, 'http://localhost');
|
|
39
|
-
const fname = path.join(
|
|
40
|
-
'${config.distDir}',
|
|
41
|
-
'${config.publicDir}',
|
|
42
|
-
pathname,
|
|
43
|
-
path.extname(pathname) ? '' : '${config.indexHtml}',
|
|
44
|
-
);
|
|
45
|
-
if (fs.existsSync(fname)) {
|
|
46
|
-
if (fname.endsWith('.html')) {
|
|
47
|
-
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
48
|
-
} else if (fname.endsWith('.txt')) {
|
|
49
|
-
res.setHeader('content-type', 'text/plain');
|
|
50
|
-
}
|
|
51
|
-
fs.createReadStream(fname).pipe(res);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
res.statusCode = 404;
|
|
55
|
-
res.end();
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
`);
|
|
59
28
|
}
|
|
60
29
|
const overrides = Object.fromEntries(rscFiles.filter((file)=>!path.extname(file)).map((file)=>[
|
|
61
30
|
path.relative(publicDir, file),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { serveStatic } from 'hono/cloudflare-workers';
|
|
3
|
+
import { honoMiddleware } from '../middleware/hono-prd.js';
|
|
4
|
+
const ssr = !!import.meta.env.WAKU_BUILD_SSR;
|
|
5
|
+
const loadEntries = ()=>import(import.meta.env.WAKU_ENTRIES_FILE);
|
|
6
|
+
let serveWaku;
|
|
7
|
+
const app = new Hono();
|
|
8
|
+
app.use('*', (c, next)=>serveWaku(c, next));
|
|
9
|
+
app.use('*', serveStatic({
|
|
10
|
+
root: './'
|
|
11
|
+
}));
|
|
12
|
+
export default {
|
|
13
|
+
async fetch (request, env, ctx) {
|
|
14
|
+
if (!serveWaku) {
|
|
15
|
+
serveWaku = honoMiddleware({
|
|
16
|
+
loadEntries,
|
|
17
|
+
ssr,
|
|
18
|
+
env
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return app.fetch(request, env, ctx);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint import/no-unresolved: off */ // @ts-expect-error no types
|
|
2
|
+
import { Hono } from 'https://deno.land/x/hono/mod.ts';
|
|
3
|
+
// @ts-expect-error no types
|
|
4
|
+
import { serveStatic } from 'https://deno.land/x/hono/middleware.ts';
|
|
5
|
+
import { honoMiddleware } from '../middleware/hono-prd.js';
|
|
6
|
+
const ssr = !!import.meta.env.WAKU_BUILD_SSR;
|
|
7
|
+
const distDir = import.meta.env.WAKU_CONFIG_DIST_DIR;
|
|
8
|
+
const publicDir = import.meta.env.WAKU_CONFIG_PUBLIC_DIR;
|
|
9
|
+
const loadEntries = ()=>import(import.meta.env.WAKU_ENTRIES_FILE);
|
|
10
|
+
// @ts-expect-error no types
|
|
11
|
+
const env = Deno.env.toObject();
|
|
12
|
+
const app = new Hono();
|
|
13
|
+
app.use('*', honoMiddleware({
|
|
14
|
+
loadEntries,
|
|
15
|
+
ssr,
|
|
16
|
+
env
|
|
17
|
+
}));
|
|
18
|
+
app.use('*', serveStatic({
|
|
19
|
+
root: `${distDir}/${publicDir}`
|
|
20
|
+
}));
|
|
21
|
+
// @ts-expect-error no types
|
|
22
|
+
Deno.serve(app.fetch);
|