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.
Files changed (65) hide show
  1. package/README.md +84 -16
  2. package/dist/cli.js +3 -7
  3. package/dist/client.d.ts +11 -2
  4. package/dist/client.js +36 -34
  5. package/dist/config.d.ts +6 -0
  6. package/dist/dev.d.ts +2 -2
  7. package/dist/dev.js +2 -2
  8. package/dist/lib/builder/build.d.ts +1 -5
  9. package/dist/lib/builder/build.js +21 -18
  10. package/dist/lib/builder/output-cloudflare.d.ts +1 -1
  11. package/dist/lib/builder/output-cloudflare.js +5 -31
  12. package/dist/lib/builder/output-vercel.js +2 -33
  13. package/dist/lib/builder/serve-cloudflare.d.ts +6 -0
  14. package/dist/lib/builder/serve-cloudflare.js +23 -0
  15. package/dist/lib/builder/serve-deno.d.ts +1 -0
  16. package/dist/lib/builder/serve-deno.js +22 -0
  17. package/dist/lib/builder/serve-vercel.d.ts +3 -0
  18. package/dist/lib/builder/serve-vercel.js +30 -0
  19. package/dist/lib/config.d.ts +1 -0
  20. package/dist/lib/config.js +1 -0
  21. package/dist/lib/handlers/dev-worker-api.d.ts +1 -1
  22. package/dist/lib/handlers/dev-worker-api.js +2 -2
  23. package/dist/lib/handlers/dev-worker-impl.js +11 -8
  24. package/dist/lib/handlers/handler-dev.js +1 -1
  25. package/dist/lib/handlers/handler-prd.d.ts +1 -1
  26. package/dist/lib/handlers/handler-prd.js +2 -1
  27. package/dist/lib/handlers/types.d.ts +0 -4
  28. package/dist/lib/plugins/vite-plugin-rsc-delegate.d.ts +2 -2
  29. package/dist/lib/plugins/vite-plugin-rsc-delegate.js +11 -8
  30. package/dist/lib/plugins/vite-plugin-rsc-hmr.d.ts +9 -3
  31. package/dist/lib/plugins/vite-plugin-rsc-hmr.js +50 -9
  32. package/dist/lib/plugins/vite-plugin-rsc-serve.d.ts +10 -0
  33. package/dist/lib/plugins/vite-plugin-rsc-serve.js +19 -0
  34. package/dist/prd.d.ts +2 -2
  35. package/dist/prd.js +2 -2
  36. package/dist/router/client.d.ts +1 -0
  37. package/package.json +8 -8
  38. package/src/cli.ts +16 -12
  39. package/src/client.ts +93 -79
  40. package/src/config.ts +6 -0
  41. package/src/dev.ts +2 -2
  42. package/src/lib/builder/build.ts +34 -24
  43. package/src/lib/builder/output-cloudflare.ts +4 -42
  44. package/src/lib/builder/output-vercel.ts +2 -40
  45. package/src/lib/builder/serve-cloudflare.ts +24 -0
  46. package/src/lib/builder/serve-deno.ts +22 -0
  47. package/src/lib/builder/serve-vercel.ts +35 -0
  48. package/src/lib/config.ts +1 -0
  49. package/src/lib/handlers/dev-worker-api.ts +3 -3
  50. package/src/lib/handlers/dev-worker-impl.ts +13 -9
  51. package/src/lib/handlers/handler-dev.ts +1 -1
  52. package/src/lib/handlers/handler-prd.ts +4 -2
  53. package/src/lib/handlers/types.ts +0 -6
  54. package/src/lib/plugins/vite-plugin-rsc-delegate.ts +10 -5
  55. package/src/lib/plugins/vite-plugin-rsc-hmr.ts +63 -11
  56. package/src/lib/plugins/vite-plugin-rsc-serve.ts +33 -0
  57. package/src/lib/renderers/rsc-renderer.ts +3 -3
  58. package/src/prd.ts +2 -2
  59. package/src/router/server.ts +6 -5
  60. package/dist/lib/builder/output-deno.d.ts +0 -2
  61. package/dist/lib/builder/output-deno.js +0 -25
  62. package/dist/lib/plugins/vite-plugin-rsc-entries.d.ts +0 -6
  63. package/dist/lib/plugins/vite-plugin-rsc-entries.js +0 -22
  64. package/src/lib/builder/output-deno.ts +0 -43
  65. 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 again!
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 made possible with the advent of React server components. So stick with us.
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 follows React conventions including support for [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 to securely perform server-side logic and data fetching, but have no interactivity.
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
- Client components are specified with the `'use client'` directive at the top of the file. They can use all traditional React features such as state, effects, and event handlers.
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 _and_ client components. Client components are then hydrated in the browser to support events, effects, and so on.
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 (`'static'` for SSG or `'dynamic'` for SSR). Layout components must accept a `children` prop.
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. 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.
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}</h3>
399
- <h1>{article.frontmatter.title}</h3>
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.ts --exclude node_modules
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
- vercel: values['with-vercel'] ?? !!process.env.VERCEL ? {
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 entries = import(pathToFileURL(path.resolve(distDir, entriesJs)).toString());
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
- entries,
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
- export declare const fetchRSC: (input: string, searchParamsString: string, rerender: (fn: (prev: Elements) => Elements) => void) => Elements;
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 { cache, createContext, createElement, memo, use, useCallback, useState, startTransition } from 'react';
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 mergeElements = cache(async (a, b)=>{
18
- const nextElements = {
19
- ...await a,
20
- ...await b
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
- delete nextElements._value;
23
- return nextElements;
24
- });
25
- export const fetchRSC = cache((input, searchParamsString, rerender)=>{
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
- rerender((prev)=>mergeElements(prev, data));
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 = cache((input, searchParamsString)=>{
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
- // HACK there should be a better way...
59
- const createRerender = cache(()=>{
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() || '', getRerender());
80
+ const data = fetchRSC(input, searchParams?.toString() || '', setElements, cache);
79
81
  setElements((prev)=>mergeElements(prev, data));
80
82
  }, [
81
- getRerender
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
- vercel?: {
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 { rscEntriesPlugin } from '../plugins/vite-plugin-rsc-entries.js';
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, reExportHonoMiddleware, reExportConnectMiddleware)=>{
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 || !!options.deno, !!options.vercel);
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.type);
405
- }
406
- if (options.cloudflare) {
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, ssr: boolean) => Promise<void>;
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, ssr)=>{
5
- const outputDir = path.resolve('.');
6
- const relativeRootDir = path.relative(outputDir, rootDir);
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 = "serve.js"
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(path.join(rootDir, config.distDir, config.publicDir), path.join(outputDir, 'static'), {
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: 'serve.js',
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,6 @@
1
+ import { Hono } from 'hono';
2
+ declare const app: Hono<import("hono").Env, {}, "/">;
3
+ declare const _default: {
4
+ fetch(request: Request, env: Record<string, string>, ctx: Parameters<typeof app.fetch>[2]): Promise<Response>;
5
+ };
6
+ export default _default;
@@ -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);