waku 1.0.0-beta.4 → 1.0.0-beta.6

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 (60) hide show
  1. package/README.md +20 -17
  2. package/dist/lib/global-types.d.ts +1 -1
  3. package/dist/lib/utils/initial-rsc.d.ts +0 -1
  4. package/dist/lib/utils/initial-rsc.js +7 -7
  5. package/dist/lib/utils/initial-rsc.js.map +1 -1
  6. package/dist/lib/utils/stream.d.ts +0 -1
  7. package/dist/lib/utils/stream.js +0 -35
  8. package/dist/lib/utils/stream.js.map +1 -1
  9. package/dist/lib/vite-plugins/fs-router-typegen.js +21 -3
  10. package/dist/lib/vite-plugins/fs-router-typegen.js.map +1 -1
  11. package/dist/lib/vite-rsc/handler.js +2 -2
  12. package/dist/lib/vite-rsc/handler.js.map +1 -1
  13. package/dist/lib/vite-rsc/ssr.js +5 -10
  14. package/dist/lib/vite-rsc/ssr.js.map +1 -1
  15. package/dist/minimal/client-utils/fetch-store.d.ts +24 -0
  16. package/dist/minimal/client-utils/fetch-store.js +11 -0
  17. package/dist/minimal/client-utils/fetch-store.js.map +1 -0
  18. package/dist/minimal/client-utils/prefetch-cache.js.map +1 -0
  19. package/dist/minimal/client.d.ts +19 -35
  20. package/dist/minimal/client.js +53 -50
  21. package/dist/minimal/client.js.map +1 -1
  22. package/dist/router/base-types.d.ts +16 -3
  23. package/dist/router/base-types.js +6 -1
  24. package/dist/router/base-types.js.map +1 -1
  25. package/dist/router/client.d.ts +66 -21
  26. package/dist/router/client.js +243 -72
  27. package/dist/router/client.js.map +1 -1
  28. package/dist/router/create-pages-utils/inferred-path-types.d.ts +44 -3
  29. package/dist/router/create-pages-utils/inferred-path-types.js.map +1 -1
  30. package/dist/router/create-pages.d.ts +13 -4
  31. package/dist/router/create-pages.js +35 -8
  32. package/dist/router/create-pages.js.map +1 -1
  33. package/dist/router/define-router.d.ts +8 -3
  34. package/dist/router/define-router.js +73 -38
  35. package/dist/router/define-router.js.map +1 -1
  36. package/dist/router/fs-router.d.ts +2 -0
  37. package/dist/router/fs-router.js.map +1 -1
  38. package/dist/router/isomorphic-utils/build-route-href.d.ts +29 -0
  39. package/dist/router/{client-utils → isomorphic-utils}/build-route-href.js +12 -24
  40. package/dist/router/isomorphic-utils/build-route-href.js.map +1 -0
  41. package/dist/router/{client-utils → isomorphic-utils}/match-route-params.d.ts +3 -3
  42. package/dist/router/{client-utils → isomorphic-utils}/match-route-params.js +3 -3
  43. package/dist/router/isomorphic-utils/match-route-params.js.map +1 -0
  44. package/dist/router/{common-utils → isomorphic-utils}/route-path.d.ts +1 -0
  45. package/dist/router/{common-utils → isomorphic-utils}/route-path.js +3 -0
  46. package/dist/router/isomorphic-utils/route-path.js.map +1 -0
  47. package/dist/router/isomorphic-utils/search-codec-registry.d.ts +9 -0
  48. package/dist/router/isomorphic-utils/search-codec-registry.js +11 -0
  49. package/dist/router/isomorphic-utils/search-codec-registry.js.map +1 -0
  50. package/package.json +5 -5
  51. package/dist/lib/utils/prefetch-cache.js.map +0 -1
  52. package/dist/lib/utils/rsc-stream.d.ts +0 -1
  53. package/dist/lib/utils/rsc-stream.js +0 -84
  54. package/dist/lib/utils/rsc-stream.js.map +0 -1
  55. package/dist/router/client-utils/build-route-href.d.ts +0 -26
  56. package/dist/router/client-utils/build-route-href.js.map +0 -1
  57. package/dist/router/client-utils/match-route-params.js.map +0 -1
  58. package/dist/router/common-utils/route-path.js.map +0 -1
  59. /package/dist/{lib/utils → minimal/client-utils}/prefetch-cache.d.ts +0 -0
  60. /package/dist/{lib/utils → minimal/client-utils}/prefetch-cache.js +0 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@ visit [waku.gg](https://waku.gg) or `npm create waku@latest`
13
13
 
14
14
  ## Introduction
15
15
 
16
- **Waku** _(wah-ku)_ or **わく** is the minimal React framework. It’s lightweight and designed for a fun developer experience, yet supports all the latest React 19 features like server components and actions. Built for marketing sites, headless commerce, and web apps. For large enterprise applications, you may prefer a heavier framework.
16
+ **Waku** _(wah-ku)_ or **わく** is the minimal React framework. It's lightweight and designed for a fun developer experience, yet supports all the latest React 19 features like server components and actions. Built for marketing sites, headless commerce, and web apps. For large enterprise applications, you may prefer a heavier framework.
17
17
 
18
18
  ## Getting started
19
19
 
@@ -29,21 +29,21 @@ npm create waku@latest
29
29
  - `waku build` to generate a production build
30
30
  - `waku start` to serve the production build locally
31
31
 
32
- **Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.13.0`
32
+ **Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.15.0`
33
33
 
34
34
  ## Rendering
35
35
 
36
- While there’s a bit of a learning curve to modern React rendering, it introduces powerful new patterns of full-stack composability that are only possible with the advent of [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).
36
+ While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of full-stack composability that are only possible with the advent of [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).
37
37
 
38
- So please don’t be intimidated by the `'use client'` directive! Once you get the hang of it, you’ll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time. It’s way simpler than maintaining separate codebases for your backend and frontend.
38
+ So please don't be intimidated by the `'use client'` directive! Once you get the hang of it, you'll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time. It's way simpler than maintaining separate codebases for your backend and frontend.
39
39
 
40
- And please don’t fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are always 100% client components.
40
+ And please don't fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are always 100% client components.
41
41
 
42
42
  > Future versions of Waku may provide additional opt-in APIs to abstract some of the complexity away for an improved developer experience.
43
43
 
44
44
  #### Server components
45
45
 
46
- Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren’t included in the client bundle. They have no state, interactivity, or access to browser APIs since they run _exclusively_ on the server.
46
+ Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren't included in the client bundle. They have no state, interactivity, or access to browser APIs since they run _exclusively_ on the server.
47
47
 
48
48
  ```tsx
49
49
  // server component
@@ -133,9 +133,9 @@ Waku provides static prerendering (SSG) and server-side rendering (SSR) options
133
133
 
134
134
  Each layout and page in Waku is composed of a React component hierarchy.
135
135
 
136
- It begins with a server component at the top of the tree. Then at points down the hierarchy, you’ll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point, all imported components are hydrated and will run in the browser as well.
136
+ It begins with a server component at the top of the tree. Then at points down the hierarchy, you'll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point, all imported components are hydrated and will run in the browser as well.
137
137
 
138
- Server components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new “React server” layer](https://github.com/reactwg/server-components/discussions/4) that runs _before_ the traditional “React client” layer with which you’re already familiar.
138
+ Server components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new "React server" layer](https://github.com/reactwg/server-components/discussions/4) that runs _before_ the traditional "React client" layer with which you're already familiar.
139
139
 
140
140
  Client components are still server-side rendered as SSR is separate from RSC. Please see the [linked diagrams](https://github.com/reactwg/server-components/discussions/4) for a helpful visual.
141
141
 
@@ -145,7 +145,7 @@ To learn more about the modern React architecture, we recommend [Making Sense of
145
145
 
146
146
  ## Routing
147
147
 
148
- Waku provides a minimal file-based “pages router” experience built for the server components era.
148
+ Waku provides a minimal file-based "pages router" experience built for the server components era.
149
149
 
150
150
  Its underlying [low-level API](https://github.com/wakujs/waku/blob/main/docs/create-pages.mdx) is also available for those that prefer programmatic routing. This documentation covers file-based routing since many React developers prefer it, but please feel free to try both and see which you like more!
151
151
 
@@ -395,7 +395,7 @@ export const getConfig = async () => {
395
395
 
396
396
  #### Catch-all routes
397
397
 
398
- Catch-all or “wildcard” segment routes (e.g., `/app/[...catchAll]`) are marked with an ellipsis before the name and have indefinite segments.
398
+ Catch-all or "wildcard" segment routes (e.g., `/app/[...catchAll]`) are marked with an ellipsis before the name and have indefinite segments.
399
399
 
400
400
  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.
401
401
 
@@ -761,6 +761,8 @@ This allows you to have a `dynamic` slice component while keeping the rest of th
761
761
 
762
762
  The `<Link />` component should be used for internal links. It accepts a `to` prop for the destination and handles client-side navigation through Waku's router.
763
763
 
764
+ `to` is either a route string, or a structured `{ to, params, search, hash }` target for typed navigation (the same shape `router.push` accepts; see [Typed Routes](https://github.com/wakujs/waku/blob/main/docs/guides/typed-routes.mdx)).
765
+
764
766
  ```tsx
765
767
  // ./src/pages/index.tsx
766
768
  import { Link } from 'waku';
@@ -770,6 +772,7 @@ export default async function HomePage() {
770
772
  <>
771
773
  <h1>Home</h1>
772
774
  <Link to="/about">About</Link>
775
+ <Link to={{ to: '/search', search: { q: 'waku' } }}>Search</Link>
773
776
  </>
774
777
  );
775
778
  }
@@ -1247,7 +1250,7 @@ export const ContactForm = () => {
1247
1250
 
1248
1251
  #### Configuring API routes
1249
1252
 
1250
- API routes are dynamic by default, but if you’re using them to create a static resource such as an XML document, you can export a `getConfig` function that returns a config object with the render property set to `'static'`.
1253
+ API routes are dynamic by default, but if you're using them to create a static resource such as an XML document, you can export a `getConfig` function that returns a config object with the render property set to `'static'`.
1251
1254
 
1252
1255
  ```ts
1253
1256
  // ./src/pages/_api/blog/rss.xml.ts
@@ -1410,17 +1413,17 @@ export const ContactForm = ({ author = 'guest' }) => {
1410
1413
 
1411
1414
  Server actions integrate with many other React APIs such as the [`useTransition`](https://react.dev/reference/react/useTransition) hook for handling pending states, the [`useActionState`](https://react.dev/reference/react/useActionState) hook for accessing returned values, and the [`useOptimistic`](https://react.dev/reference/react/useOptimistic) hook for performing optimistic UI updates.
1412
1415
 
1413
- See the talk [What’s new in React 19?](https://www.youtube.com/watch?v=AJOGzVygGcY) to learn more.
1416
+ See the talk [What's new in React 19?](https://www.youtube.com/watch?v=AJOGzVygGcY) to learn more.
1414
1417
 
1415
1418
  ## State management
1416
1419
 
1417
- We recommend [Jotai](https://jotai.org) for global React state management based on the atomic model’s performance and scalability, but Waku is compatible with all React state management libraries such as Zustand and Valtio.
1420
+ We recommend [Jotai](https://jotai.org) for global React state management based on the atomic model's performance and scalability, but Waku is compatible with all React state management libraries such as Zustand and Valtio.
1418
1421
 
1419
- > We’re exploring a deeper integration of atomic state management into Waku to achieve the performance and developer experience of signals while preserving React’s declarative programming model.
1422
+ > We're exploring a deeper integration of atomic state management into Waku to achieve the performance and developer experience of signals while preserving React's declarative programming model.
1420
1423
 
1421
1424
  ## Environment variables
1422
1425
 
1423
- It’s important to distinguish environment variables that must be kept secret from those that can be made public.
1426
+ It's important to distinguish environment variables that must be kept secret from those that can be made public.
1424
1427
 
1425
1428
  #### Private
1426
1429
 
@@ -1428,7 +1431,7 @@ By default all environment variables are considered private and are accessible o
1428
1431
 
1429
1432
  #### Public
1430
1433
 
1431
- A special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users’ browsers.
1434
+ A special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users' browsers.
1432
1435
 
1433
1436
  ### Runtime agnostic (recommended)
1434
1437
 
@@ -1613,7 +1616,7 @@ Please join our friendly [GitHub discussions](https://github.com/wakujs/waku/dis
1613
1616
 
1614
1617
  ## Roadmap
1615
1618
 
1616
- Waku is in active development and we’re seeking additional contributors. Check out our [roadmap](https://github.com/wakujs/waku/issues/24) for more information.
1619
+ Waku is in active development and we're seeking additional contributors. Check out our [roadmap](https://github.com/wakujs/waku/issues/24) for more information.
1617
1620
 
1618
1621
  ## Contributing
1619
1622
 
@@ -4,7 +4,7 @@ declare global {
4
4
  var __WAKU_REFETCH_ROUTE__: (() => void) | undefined;
5
5
  var __WAKU_START_PREVIEW_SERVER__: (() => Promise<import('./vite-rsc/preview.js').PreviewServer>) | undefined;
6
6
  var __WAKU_SERVER_ENV__: Readonly<Record<string, string>> | undefined;
7
- var __WAKU_PREFETCHED__: import('./utils/prefetch-cache.js').PrefetchEntry[] | undefined;
7
+ var __WAKU_PREFETCHED__: import('../minimal/client-utils/prefetch-cache.js').PrefetchEntry[] | undefined;
8
8
  var __WAKU_INITIAL_RSC__: import('./utils/initial-rsc.js').InitialRscEntry | undefined;
9
9
  var __WAKU_DEBUG_CHANNELS__: Map<string, {
10
10
  readable: ReadableStream;
@@ -1,6 +1,5 @@
1
1
  export type InitialRscEntry = {
2
2
  response: Promise<Response>;
3
- close: () => void;
4
3
  debugId?: string;
5
4
  };
6
5
  export declare const consumeInitialRscEntry: () => InitialRscEntry | undefined;
@@ -9,6 +9,7 @@ export const consumeInitialRscEntry = ()=>{
9
9
  }
10
10
  return entry;
11
11
  };
12
+ // DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154
12
13
  export const createInitialRscEntryCode = (debugId)=>`
13
14
  (() => {
14
15
  const e = {};
@@ -20,13 +21,12 @@ export const createInitialRscEntryCode = (debugId)=>`
20
21
  d.forEach(f);
21
22
  d.length = 0;
22
23
  d.push = f;
23
- e.close = () => {
24
- if (document.readyState === 'loading') {
25
- document.addEventListener('DOMContentLoaded', () => c.close());
26
- } else {
27
- c.close();
28
- }
29
- };
24
+ const close = ${import.meta.env.DEV ? '() => setTimeout(() => c.close(), 5000)' : '() => c.close()'};
25
+ if (document.readyState === 'loading') {
26
+ document.addEventListener('DOMContentLoaded', close);
27
+ } else {
28
+ close();
29
+ }
30
30
  }
31
31
  })));
32
32
  ${debugId ? `e.debugId = ${JSON.stringify(debugId)};` : ''}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/utils/initial-rsc.ts"],"sourcesContent":["// The initial RSC entry is emitted as an inline script during SSR. It carries\n// the streamed RSC payload of the first render as a `Response`, so the client\n// can hydrate without an extra round trip. It is consumed exactly once.\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type InitialRscEntry = {\n response: Promise<Response>;\n close: () => void;\n debugId?: string;\n};\n\nexport const consumeInitialRscEntry = (): InitialRscEntry | undefined => {\n const entry = globalThis.__WAKU_INITIAL_RSC__;\n if (entry) {\n globalThis.__WAKU_INITIAL_RSC__ = undefined;\n }\n return entry;\n};\n\nexport const createInitialRscEntryCode = (debugId: string | undefined) =>\n `\n (() => {\n const e = {};\n e.response = Promise.resolve(new Response(new ReadableStream({\n start(c) {\n const d = (window.__FLIGHT_DATA ||= []);\n const t = new TextEncoder();\n const f = (s) => c.enqueue(typeof s === 'string' ? t.encode(s) : s);\n d.forEach(f);\n d.length = 0;\n d.push = f;\n e.close = () => {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => c.close());\n } else {\n c.close();\n }\n };\n }\n })));\n ${debugId ? `e.debugId = ${JSON.stringify(debugId)};` : ''}\n return e;\n })()\n`\n .split('\\n')\n .map((line) => line.trim())\n .join('');\n"],"names":["consumeInitialRscEntry","entry","globalThis","__WAKU_INITIAL_RSC__","undefined","createInitialRscEntryCode","debugId","JSON","stringify","split","map","line","trim","join"],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,wEAAwE;AAExE,qEAAqE;AAOrE,OAAO,MAAMA,yBAAyB;IACpC,MAAMC,QAAQC,WAAWC,oBAAoB;IAC7C,IAAIF,OAAO;QACTC,WAAWC,oBAAoB,GAAGC;IACpC;IACA,OAAOH;AACT,EAAE;AAEF,OAAO,MAAMI,4BAA4B,CAACC,UACxC,CAAC;;;;;;;;;;;;;;;;;;;;IAoBC,EAAEA,UAAU,CAAC,YAAY,EAAEC,KAAKC,SAAS,CAACF,SAAS,CAAC,CAAC,GAAG,GAAG;;;AAG/D,CAAC,CACIG,KAAK,CAAC,MACNC,GAAG,CAAC,CAACC,OAASA,KAAKC,IAAI,IACvBC,IAAI,CAAC,IAAI"}
1
+ {"version":3,"sources":["../../../src/lib/utils/initial-rsc.ts"],"sourcesContent":["// The initial RSC entry is emitted as an inline script during SSR. It carries\n// the streamed RSC payload of the first render as a `Response`, so the client\n// can hydrate without an extra round trip. It is consumed exactly once.\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type InitialRscEntry = {\n response: Promise<Response>;\n debugId?: string;\n};\n\nexport const consumeInitialRscEntry = (): InitialRscEntry | undefined => {\n const entry = globalThis.__WAKU_INITIAL_RSC__;\n if (entry) {\n globalThis.__WAKU_INITIAL_RSC__ = undefined;\n }\n return entry;\n};\n\n// DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154\nexport const createInitialRscEntryCode = (debugId: string | undefined) =>\n `\n (() => {\n const e = {};\n e.response = Promise.resolve(new Response(new ReadableStream({\n start(c) {\n const d = (window.__FLIGHT_DATA ||= []);\n const t = new TextEncoder();\n const f = (s) => c.enqueue(typeof s === 'string' ? t.encode(s) : s);\n d.forEach(f);\n d.length = 0;\n d.push = f;\n const close = ${import.meta.env.DEV ? '() => setTimeout(() => c.close(), 5000)' : '() => c.close()'};\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', close);\n } else {\n close();\n }\n }\n })));\n ${debugId ? `e.debugId = ${JSON.stringify(debugId)};` : ''}\n return e;\n })()\n`\n .split('\\n')\n .map((line) => line.trim())\n .join('');\n"],"names":["consumeInitialRscEntry","entry","globalThis","__WAKU_INITIAL_RSC__","undefined","createInitialRscEntryCode","debugId","env","DEV","JSON","stringify","split","map","line","trim","join"],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,wEAAwE;AAExE,qEAAqE;AAMrE,OAAO,MAAMA,yBAAyB;IACpC,MAAMC,QAAQC,WAAWC,oBAAoB;IAC7C,IAAIF,OAAO;QACTC,WAAWC,oBAAoB,GAAGC;IACpC;IACA,OAAOH;AACT,EAAE;AAEF,8HAA8H;AAC9H,OAAO,MAAMI,4BAA4B,CAACC,UACxC,CAAC;;;;;;;;;;;sBAWmB,EAAE,YAAYC,GAAG,CAACC,GAAG,GAAG,4CAA4C,kBAAkB;;;;;;;;IAQxG,EAAEF,UAAU,CAAC,YAAY,EAAEG,KAAKC,SAAS,CAACJ,SAAS,CAAC,CAAC,GAAG,GAAG;;;AAG/D,CAAC,CACIK,KAAK,CAAC,MACNC,GAAG,CAAC,CAACC,OAASA,KAAKC,IAAI,IACvBC,IAAI,CAAC,IAAI"}
@@ -2,6 +2,5 @@ export declare const stringToStream: (str: string) => ReadableStream;
2
2
  export declare const streamToBytes: (stream: ReadableStream) => Promise<Uint8Array>;
3
3
  export declare const bytesToStream: (bytes: Uint8Array) => ReadableStream;
4
4
  export declare function batchReadableStream(input: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
5
- export declare function deferReadableStream<T>(stream: ReadableStream<T>, promise: Promise<void>): ReadableStream<T>;
6
5
  export declare function produceMultiplexedStream(fn: (callback: (key: string, stream: ReadableStream) => Promise<void>) => Promise<void>): ReadableStream<Uint8Array>;
7
6
  export declare function consumeMultiplexedStream(frameStream: ReadableStream<Uint8Array>, callback: (key: string, stream: ReadableStream<Uint8Array>) => Promise<void>): Promise<void>;
@@ -71,41 +71,6 @@ export function batchReadableStream(input) {
71
71
  }
72
72
  }));
73
73
  }
74
- export function deferReadableStream(stream, promise) {
75
- const reader = stream.getReader();
76
- let canceled = false;
77
- return new ReadableStream({
78
- async pull (controller) {
79
- try {
80
- const { done, value } = await reader.read();
81
- if (done) {
82
- await promise;
83
- if (!canceled) {
84
- controller.close();
85
- }
86
- reader.releaseLock();
87
- return;
88
- }
89
- if (!canceled) {
90
- controller.enqueue(value);
91
- }
92
- } catch (error) {
93
- if (!canceled) {
94
- controller.error(error);
95
- }
96
- reader.releaseLock();
97
- }
98
- },
99
- async cancel (reason) {
100
- canceled = true;
101
- try {
102
- await reader.cancel(reason);
103
- } finally{
104
- reader.releaseLock();
105
- }
106
- }
107
- });
108
- }
109
74
  // Stream Multiplexer
110
75
  const FRAME_START = 0x01;
111
76
  const FRAME_CHUNK = 0x02;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/utils/stream.ts"],"sourcesContent":["// Utility functions for web streams (not Node.js streams)\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport const stringToStream = (str: string): ReadableStream => {\n return new ReadableStream({\n start(controller) {\n controller.enqueue(encoder.encode(str));\n controller.close();\n },\n });\n};\n\nexport const streamToBytes = async (\n stream: ReadableStream,\n): Promise<Uint8Array> => {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n chunks.push(value);\n }\n return concatUint8Array(chunks);\n};\n\nexport const bytesToStream = (bytes: Uint8Array): ReadableStream =>\n new ReadableStream({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n\nfunction concatUint8Array(chunks: readonly Uint8Array[]): Uint8Array {\n if (chunks.length === 1) {\n return chunks[0]!;\n }\n const total = chunks.reduce((n, chunk) => n + chunk.byteLength, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function batchReadableStream(\n input: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n const buffer: Uint8Array[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const flushBuffer = (\n controller: TransformStreamDefaultController<Uint8Array>,\n ): void => {\n clearTimeout(timer);\n timer = undefined;\n if (buffer.length) {\n try {\n controller.enqueue(concatUint8Array(buffer));\n } catch {\n // ignore errors\n // ref: https://github.com/wakujs/waku/pull/1863#discussion_r2634546953\n }\n buffer.length = 0;\n }\n };\n\n return input.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n buffer.push(chunk);\n if (!timer) {\n timer = setTimeout(() => flushBuffer(controller));\n }\n },\n flush(controller) {\n flushBuffer(controller);\n },\n }),\n );\n}\n\nexport function deferReadableStream<T>(\n stream: ReadableStream<T>,\n promise: Promise<void>,\n): ReadableStream<T> {\n const reader = stream.getReader();\n let canceled = false;\n return new ReadableStream<T>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n await promise;\n if (!canceled) {\n controller.close();\n }\n reader.releaseLock();\n return;\n }\n if (!canceled) {\n controller.enqueue(value);\n }\n } catch (error) {\n if (!canceled) {\n controller.error(error);\n }\n reader.releaseLock();\n }\n },\n async cancel(reason) {\n canceled = true;\n try {\n await reader.cancel(reason);\n } finally {\n reader.releaseLock();\n }\n },\n });\n}\n\n// Stream Multiplexer\n\nconst FRAME_START = 0x01;\nconst FRAME_CHUNK = 0x02;\nconst FRAME_END = 0x03;\nconst FRAME_ERROR = 0x04;\n\nfunction encodeStart(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_START;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeEnd(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_END;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeChunk(key: string, chunk: Uint8Array): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + chunk.length);\n let offset = 0;\n out[offset++] = FRAME_CHUNK;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, chunk.length);\n offset += 4;\n out.set(chunk, offset);\n return out;\n}\n\nfunction encodeError(key: string, error: unknown): Uint8Array {\n const keyBytes = encoder.encode(key);\n const payload = encoder.encode(String(error));\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + payload.length);\n let offset = 0;\n out[offset++] = FRAME_ERROR;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, payload.length);\n offset += 4;\n out.set(payload, offset);\n return out;\n}\n\nfunction createFrameDispatcher(\n onStart: (key: string) => void,\n onChunk: (key: string, chunk: Uint8Array) => void,\n onEnd: (key: string) => void,\n onError: (key: string, error: string) => void,\n): (data: Uint8Array) => void {\n let buffer: Uint8Array = new Uint8Array(0);\n\n return (data: Uint8Array): void => {\n buffer = concatUint8Array([buffer, data]);\n\n while (buffer.length > 0) {\n const frameType = buffer[0]!;\n // Need at least frameType + 2 byte key length\n if (buffer.length < 3) {\n break;\n }\n const keyLen = new DataView(buffer.buffer, buffer.byteOffset).getUint16(\n 1,\n );\n const headerLen = 1 + 2 + keyLen;\n if (buffer.length < headerLen) {\n break;\n }\n const key = decoder.decode(buffer.slice(3, 3 + keyLen));\n\n if (frameType === FRAME_START) {\n onStart(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n if (frameType === FRAME_END) {\n onEnd(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n // chunk or error: need 4 byte payload length\n if (buffer.length < headerLen + 4) {\n break;\n }\n const payloadLen = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n ).getUint32(headerLen);\n const totalLen = headerLen + 4 + payloadLen;\n if (buffer.length < totalLen) {\n break;\n }\n const payload = buffer.slice(headerLen + 4, totalLen);\n\n if (frameType === FRAME_CHUNK) {\n onChunk(key, payload);\n } else if (frameType === FRAME_ERROR) {\n onError(key, decoder.decode(payload));\n } else {\n throw new Error(`Unknown frame type: ${frameType}`);\n }\n buffer = buffer.slice(totalLen);\n }\n };\n}\n\nexport function produceMultiplexedStream(\n fn: (\n callback: (key: string, stream: ReadableStream) => Promise<void>,\n ) => Promise<void>,\n): ReadableStream<Uint8Array> {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n\n const frameStream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n });\n\n const callback = async (key: string, stream: ReadableStream) => {\n controller.enqueue(encodeStart(key));\n const reader = stream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n controller.enqueue(encodeChunk(key, value));\n }\n controller.enqueue(encodeEnd(key));\n } catch (err) {\n controller.enqueue(encodeError(key, err));\n }\n };\n\n fn(callback).then(\n () => controller.close(),\n (err) => controller.error(err),\n );\n\n return frameStream;\n}\n\nexport async function consumeMultiplexedStream(\n frameStream: ReadableStream<Uint8Array>,\n callback: (key: string, stream: ReadableStream<Uint8Array>) => Promise<void>,\n): Promise<void> {\n const controllers = new Map<\n string,\n ReadableStreamDefaultController<Uint8Array>\n >();\n const promises: Promise<void>[] = [];\n const dispatchFrame = createFrameDispatcher(\n // onStart\n (key) => {\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controllers.set(key, c);\n },\n });\n promises.push(callback(key, stream));\n },\n // onChunk\n (key, chunk) => {\n controllers.get(key)?.enqueue(chunk);\n },\n // onEnd\n (key) => {\n controllers.get(key)?.close();\n controllers.delete(key);\n },\n // onError\n (key, error) => {\n controllers.get(key)?.error(error);\n controllers.delete(key);\n },\n );\n\n const reader = frameStream.getReader();\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n dispatchFrame(value);\n }\n\n await Promise.all(promises);\n}\n"],"names":["encoder","TextEncoder","decoder","TextDecoder","stringToStream","str","ReadableStream","start","controller","enqueue","encode","close","streamToBytes","stream","reader","getReader","chunks","value","done","read","Uint8Array","Error","push","concatUint8Array","bytesToStream","bytes","length","total","reduce","n","chunk","byteLength","out","offset","set","batchReadableStream","input","buffer","timer","flushBuffer","clearTimeout","undefined","pipeThrough","TransformStream","transform","setTimeout","flush","deferReadableStream","promise","canceled","pull","releaseLock","error","cancel","reason","FRAME_START","FRAME_CHUNK","FRAME_END","FRAME_ERROR","encodeStart","key","keyBytes","DataView","setUint16","encodeEnd","encodeChunk","setUint32","encodeError","payload","String","createFrameDispatcher","onStart","onChunk","onEnd","onError","data","frameType","keyLen","byteOffset","getUint16","headerLen","decode","slice","payloadLen","getUint32","totalLen","produceMultiplexedStream","fn","frameStream","c","callback","err","then","consumeMultiplexedStream","controllers","Map","promises","dispatchFrame","get","delete","Promise","all"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/utils/stream.ts"],"sourcesContent":["// Utility functions for web streams (not Node.js streams)\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport const stringToStream = (str: string): ReadableStream => {\n return new ReadableStream({\n start(controller) {\n controller.enqueue(encoder.encode(str));\n controller.close();\n },\n });\n};\n\nexport const streamToBytes = async (\n stream: ReadableStream,\n): Promise<Uint8Array> => {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n chunks.push(value);\n }\n return concatUint8Array(chunks);\n};\n\nexport const bytesToStream = (bytes: Uint8Array): ReadableStream =>\n new ReadableStream({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n\nfunction concatUint8Array(chunks: readonly Uint8Array[]): Uint8Array {\n if (chunks.length === 1) {\n return chunks[0]!;\n }\n const total = chunks.reduce((n, chunk) => n + chunk.byteLength, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function batchReadableStream(\n input: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n const buffer: Uint8Array[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const flushBuffer = (\n controller: TransformStreamDefaultController<Uint8Array>,\n ): void => {\n clearTimeout(timer);\n timer = undefined;\n if (buffer.length) {\n try {\n controller.enqueue(concatUint8Array(buffer));\n } catch {\n // ignore errors\n // ref: https://github.com/wakujs/waku/pull/1863#discussion_r2634546953\n }\n buffer.length = 0;\n }\n };\n\n return input.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n buffer.push(chunk);\n if (!timer) {\n timer = setTimeout(() => flushBuffer(controller));\n }\n },\n flush(controller) {\n flushBuffer(controller);\n },\n }),\n );\n}\n\n// Stream Multiplexer\n\nconst FRAME_START = 0x01;\nconst FRAME_CHUNK = 0x02;\nconst FRAME_END = 0x03;\nconst FRAME_ERROR = 0x04;\n\nfunction encodeStart(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_START;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeEnd(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_END;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeChunk(key: string, chunk: Uint8Array): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + chunk.length);\n let offset = 0;\n out[offset++] = FRAME_CHUNK;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, chunk.length);\n offset += 4;\n out.set(chunk, offset);\n return out;\n}\n\nfunction encodeError(key: string, error: unknown): Uint8Array {\n const keyBytes = encoder.encode(key);\n const payload = encoder.encode(String(error));\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + payload.length);\n let offset = 0;\n out[offset++] = FRAME_ERROR;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, payload.length);\n offset += 4;\n out.set(payload, offset);\n return out;\n}\n\nfunction createFrameDispatcher(\n onStart: (key: string) => void,\n onChunk: (key: string, chunk: Uint8Array) => void,\n onEnd: (key: string) => void,\n onError: (key: string, error: string) => void,\n): (data: Uint8Array) => void {\n let buffer: Uint8Array = new Uint8Array(0);\n\n return (data: Uint8Array): void => {\n buffer = concatUint8Array([buffer, data]);\n\n while (buffer.length > 0) {\n const frameType = buffer[0]!;\n // Need at least frameType + 2 byte key length\n if (buffer.length < 3) {\n break;\n }\n const keyLen = new DataView(buffer.buffer, buffer.byteOffset).getUint16(\n 1,\n );\n const headerLen = 1 + 2 + keyLen;\n if (buffer.length < headerLen) {\n break;\n }\n const key = decoder.decode(buffer.slice(3, 3 + keyLen));\n\n if (frameType === FRAME_START) {\n onStart(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n if (frameType === FRAME_END) {\n onEnd(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n // chunk or error: need 4 byte payload length\n if (buffer.length < headerLen + 4) {\n break;\n }\n const payloadLen = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n ).getUint32(headerLen);\n const totalLen = headerLen + 4 + payloadLen;\n if (buffer.length < totalLen) {\n break;\n }\n const payload = buffer.slice(headerLen + 4, totalLen);\n\n if (frameType === FRAME_CHUNK) {\n onChunk(key, payload);\n } else if (frameType === FRAME_ERROR) {\n onError(key, decoder.decode(payload));\n } else {\n throw new Error(`Unknown frame type: ${frameType}`);\n }\n buffer = buffer.slice(totalLen);\n }\n };\n}\n\nexport function produceMultiplexedStream(\n fn: (\n callback: (key: string, stream: ReadableStream) => Promise<void>,\n ) => Promise<void>,\n): ReadableStream<Uint8Array> {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n\n const frameStream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n });\n\n const callback = async (key: string, stream: ReadableStream) => {\n controller.enqueue(encodeStart(key));\n const reader = stream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n controller.enqueue(encodeChunk(key, value));\n }\n controller.enqueue(encodeEnd(key));\n } catch (err) {\n controller.enqueue(encodeError(key, err));\n }\n };\n\n fn(callback).then(\n () => controller.close(),\n (err) => controller.error(err),\n );\n\n return frameStream;\n}\n\nexport async function consumeMultiplexedStream(\n frameStream: ReadableStream<Uint8Array>,\n callback: (key: string, stream: ReadableStream<Uint8Array>) => Promise<void>,\n): Promise<void> {\n const controllers = new Map<\n string,\n ReadableStreamDefaultController<Uint8Array>\n >();\n const promises: Promise<void>[] = [];\n const dispatchFrame = createFrameDispatcher(\n // onStart\n (key) => {\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controllers.set(key, c);\n },\n });\n promises.push(callback(key, stream));\n },\n // onChunk\n (key, chunk) => {\n controllers.get(key)?.enqueue(chunk);\n },\n // onEnd\n (key) => {\n controllers.get(key)?.close();\n controllers.delete(key);\n },\n // onError\n (key, error) => {\n controllers.get(key)?.error(error);\n controllers.delete(key);\n },\n );\n\n const reader = frameStream.getReader();\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n dispatchFrame(value);\n }\n\n await Promise.all(promises);\n}\n"],"names":["encoder","TextEncoder","decoder","TextDecoder","stringToStream","str","ReadableStream","start","controller","enqueue","encode","close","streamToBytes","stream","reader","getReader","chunks","value","done","read","Uint8Array","Error","push","concatUint8Array","bytesToStream","bytes","length","total","reduce","n","chunk","byteLength","out","offset","set","batchReadableStream","input","buffer","timer","flushBuffer","clearTimeout","undefined","pipeThrough","TransformStream","transform","setTimeout","flush","FRAME_START","FRAME_CHUNK","FRAME_END","FRAME_ERROR","encodeStart","key","keyBytes","DataView","setUint16","encodeEnd","encodeChunk","setUint32","encodeError","error","payload","String","createFrameDispatcher","onStart","onChunk","onEnd","onError","data","frameType","keyLen","byteOffset","getUint16","headerLen","decode","slice","payloadLen","getUint32","totalLen","produceMultiplexedStream","fn","frameStream","c","callback","err","then","consumeMultiplexedStream","controllers","Map","promises","dispatchFrame","get","delete","Promise","all"],"mappings":"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"}
@@ -160,6 +160,7 @@ export async function generateFsRouterTypes(pagesDir) {
160
160
  };
161
161
  const generateFile = async (filePaths)=>{
162
162
  const fileInfo = [];
163
+ const layoutPaths = [];
163
164
  const moduleNames = getImportModuleNames(filePaths);
164
165
  for (const filePath of filePaths){
165
166
  // where to import the component from
@@ -170,8 +171,11 @@ export async function generateFsRouterTypes(pagesDir) {
170
171
  } catch {
171
172
  return null;
172
173
  }
173
- if (filePath.endsWith('/_layout.tsx') || isIgnoredPath(filePath.split('/'))) {
174
+ if (isIgnoredPath(filePath.split('/'))) {
174
175
  continue;
176
+ } else if (filePath.endsWith('/_layout.tsx')) {
177
+ const path = filePath.slice(0, -'/_layout.tsx'.length);
178
+ layoutPaths.push(getGrouplessPath(path) || '/');
175
179
  } else if (filePath.endsWith('/index.tsx')) {
176
180
  const path = filePath.slice(0, -'/index.tsx'.length);
177
181
  fileInfo.push({
@@ -192,7 +196,7 @@ export async function generateFsRouterTypes(pagesDir) {
192
196
  '// deno-fmt-ignore-file',
193
197
  '// biome-ignore format: generated types do not need formatting',
194
198
  '// prettier-ignore',
195
- hasAnyGetConfig ? "import type { PathsForPages, GetConfigResponse } from 'waku/router';" : "import type { PathsForPages } from 'waku/router';"
199
+ hasAnyGetConfig ? "import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';" : "import type { PathsForPages } from 'waku/router';"
196
200
  ];
197
201
  if (hasAnyGetConfig) {
198
202
  lines.push('');
@@ -214,7 +218,21 @@ export async function generateFsRouterTypes(pagesDir) {
214
218
  }
215
219
  }
216
220
  lines[lines.length - 1] += ';';
217
- lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ' }', '}', '');
221
+ const uniqueLayoutPaths = [
222
+ ...new Set(layoutPaths)
223
+ ];
224
+ if (uniqueLayoutPaths.length) {
225
+ lines.push('', '// prettier-ignore', 'type Layout =');
226
+ for (const path of uniqueLayoutPaths){
227
+ lines.push(`| { path: '${path}' }`);
228
+ }
229
+ lines[lines.length - 1] += ';';
230
+ }
231
+ lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ...uniqueLayoutPaths.length ? [
232
+ ' layouts: Layout;'
233
+ ] : [], ' }', ...hasAnyGetConfig ? [
234
+ ' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}'
235
+ ] : [], '}', '');
218
236
  const result = lines.join('\n');
219
237
  return result;
220
238
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/vite-plugins/fs-router-typegen.ts"],"sourcesContent":["// waku/router is technically a separate library from waku core.\n// This file is an exception placed here so that fsRouter users\n// get automatic type generation with better DX.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { readdir, writeFile } from 'node:fs/promises';\nimport { parse, transformWithOxc } from 'vite';\nimport type { ParseResult, Plugin } from 'vite';\nimport { EXTENSIONS, SRC_PAGES, SRC_SERVER_ENTRY } from '../constants.js';\nimport { getGrouplessPath } from '../utils/create-pages.js';\nimport { isIgnoredPath } from '../utils/fs-router.js';\nimport { joinPath } from '../utils/path.js';\n\ntype ProgramNode = ParseResult['program'];\ntype ImportDeclaration = ProgramNode['body'][number] & {\n type: 'ImportDeclaration';\n};\ntype ImportSpecifier = ImportDeclaration['specifiers'][number] & {\n type: 'ImportSpecifier';\n};\ntype ExportNamedDeclaration = ProgramNode['body'][number] & {\n type: 'ExportNamedDeclaration';\n};\ntype ExportSpecifier = ExportNamedDeclaration['specifiers'][number] & {\n type: 'ExportSpecifier';\n};\n\n// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words\nexport function toIdentifier(input: string): string {\n // Strip the file extension\n let identifier = input.includes('.')\n ? input.split('.').slice(0, -1).join('.')\n : input;\n // Replace any characters besides letters, numbers, underscores, and dollar signs with underscores\n identifier = identifier.replace(/[^\\p{L}\\p{N}_$]/gu, '_');\n // Ensure it starts with a letter\n if (/^\\d/.test(identifier)) {\n identifier = '_' + identifier;\n }\n // Turn it into PascalCase\n // Since the first letter is uppercased, it will not be a reserved word\n return (\n 'File_' +\n identifier\n .split('_')\n .map((part) => {\n if (part[0] === undefined) {\n return '';\n }\n return part[0].toUpperCase() + part.slice(1);\n })\n .join('')\n );\n}\n\nexport function getImportModuleNames(filePaths: string[]): {\n [k: string]: string;\n} {\n const moduleNameCount: { [k: string]: number } = {};\n const moduleNames: { [k: string]: string } = {};\n for (const filePath of filePaths) {\n let identifier = toIdentifier(filePath);\n moduleNameCount[identifier] = (moduleNameCount[identifier] ?? -1) + 1;\n if (moduleNameCount[identifier]) {\n identifier = `${identifier}_${moduleNameCount[identifier]}`;\n }\n try {\n moduleNames[filePath.replace(/^\\//, '')] = identifier;\n } catch (e) {\n console.log(e);\n }\n }\n return moduleNames;\n}\n\nconst parseModule = async (filePath: string) => {\n const source = readFileSync(filePath, 'utf8');\n const lang: 'jsx' | 'ts' | 'tsx' = filePath.endsWith('.tsx')\n ? 'tsx'\n : filePath.endsWith('.ts')\n ? 'ts'\n : 'jsx';\n const transformed = await transformWithOxc(source, filePath, {\n lang,\n jsx: 'preserve',\n });\n return (await parse(filePath, transformed.code, { lang } as never)).program;\n};\n\nconst getImportedName = (specifier: ImportSpecifier) =>\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value);\n\nconst getExportedName = (specifier: ExportSpecifier) =>\n specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : String(specifier.exported.value);\n\nexport const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => {\n return {\n name: 'waku:vite-plugins:fs-router-typegen',\n apply: 'serve',\n configureServer(server) {\n const srcDir = joinPath(server.config.root, opts.srcDir);\n const pagesDir = joinPath(srcDir, SRC_PAGES);\n\n const outputFile = joinPath(srcDir, 'pages.gen.ts');\n const updateGeneratedFile = async (file: string | undefined) => {\n // skip when the changed file is the generated file itself\n if (file && outputFile.endsWith(file)) {\n return;\n }\n // skip when the entries file exists or pages dir does not exist\n if (!existsSync(pagesDir) || !(await detectFsRouterUsage(srcDir))) {\n return;\n }\n const generation = await generateFsRouterTypes(pagesDir);\n if (!generation) {\n // skip failures\n return;\n }\n await writeFile(outputFile, generation, 'utf-8');\n };\n\n server.watcher.on('change', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('add', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('unlink', async (file) => {\n await updateGeneratedFile(file);\n });\n void updateGeneratedFile(undefined);\n },\n };\n};\n\nexport async function detectFsRouterUsage(srcDir: string): Promise<boolean> {\n const existingServerEntry = EXTENSIONS.map((ext) =>\n joinPath(srcDir, SRC_SERVER_ENTRY + ext),\n ).find((entriesFile) => existsSync(entriesFile));\n\n // managed mode if no entry\n if (!existingServerEntry) {\n return true;\n }\n\n try {\n const file = await parseModule(existingServerEntry);\n const usesFsRouter = file.body.some((node) => {\n if (node.type !== 'ImportDeclaration') {\n return false;\n }\n if (\n node.source.type !== 'Literal' ||\n typeof node.source.value !== 'string' ||\n !node.source.value.startsWith('waku')\n ) {\n return false;\n }\n return node.specifiers.some((specifier) => {\n if (\n specifier.type !== 'ImportSpecifier' ||\n specifier.local.type !== 'Identifier'\n ) {\n return false;\n }\n return getImportedName(specifier) === 'fsRouter';\n });\n });\n return usesFsRouter;\n } catch {\n return false;\n }\n}\n\nexport async function generateFsRouterTypes(pagesDir: string) {\n // Recursively collect `.tsx` files in the given directory\n const collectFiles = async (\n dir: string,\n files: string[] = [],\n ): Promise<string[]> => {\n // TODO revisit recursive option for readdir once more stable\n // https://nodejs.org/docs/latest-v20.x/api/fs.html#direntparentpath\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = joinPath(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(fullPath, files);\n } else {\n if (entry.name.endsWith('.tsx')) {\n files.push(fullPath.slice(pagesDir.length));\n }\n }\n }\n return files;\n };\n\n const fileExportsGetConfig = async (filePath: string) => {\n const file = await parseModule(pagesDir + filePath);\n return file.body.some((node) => {\n if (node.type !== 'ExportNamedDeclaration') {\n return false;\n }\n if (\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (decl) =>\n decl.id.type === 'Identifier' && decl.id.name === 'getConfig',\n )\n ) {\n return true;\n }\n if (\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'getConfig'\n ) {\n return true;\n }\n return node.specifiers.some(\n (specifier) =>\n specifier.type === 'ExportSpecifier' &&\n getExportedName(specifier) === 'getConfig',\n );\n });\n };\n\n const generateFile = async (filePaths: string[]): Promise<string | null> => {\n const fileInfo: { path: string; src: string; hasGetConfig: boolean }[] = [];\n const moduleNames = getImportModuleNames(filePaths);\n\n for (const filePath of filePaths) {\n // where to import the component from\n const src = filePath.replace(/^\\//, '');\n let hasGetConfig: boolean;\n try {\n hasGetConfig = await fileExportsGetConfig(filePath);\n } catch {\n return null;\n }\n\n if (\n filePath.endsWith('/_layout.tsx') ||\n isIgnoredPath(filePath.split('/'))\n ) {\n continue;\n } else if (filePath.endsWith('/index.tsx')) {\n const path = filePath.slice(0, -'/index.tsx'.length);\n fileInfo.push({\n path: getGrouplessPath(path) || '/',\n src,\n hasGetConfig,\n });\n } else {\n fileInfo.push({\n path: getGrouplessPath(filePath.replace('.tsx', '')),\n src,\n hasGetConfig,\n });\n }\n }\n\n const hasAnyGetConfig = fileInfo.some((file) => file.hasGetConfig);\n const lines: string[] = [\n '// deno-fmt-ignore-file',\n '// biome-ignore format: generated types do not need formatting',\n '// prettier-ignore',\n hasAnyGetConfig\n ? \"import type { PathsForPages, GetConfigResponse } from 'waku/router';\"\n : \"import type { PathsForPages } from 'waku/router';\",\n ];\n\n if (hasAnyGetConfig) {\n lines.push('');\n for (const file of fileInfo) {\n if (!file.hasGetConfig) {\n continue;\n }\n const moduleName = moduleNames[file.src];\n lines.push(\n `// prettier-ignore`,\n `import type { getConfig as ${moduleName}_getConfig } from './${SRC_PAGES}/${file.src.replace('.tsx', '')}';`,\n );\n }\n }\n\n lines.push('', '// prettier-ignore', 'type Page =');\n for (const file of fileInfo) {\n const moduleName = moduleNames[file.src];\n if (file.hasGetConfig) {\n lines.push(\n `| ({ path: '${file.path}' } & GetConfigResponse<typeof ${moduleName}_getConfig>)`,\n );\n } else {\n lines.push(`| { path: '${file.path}'; render: 'static' }`);\n }\n }\n lines[lines.length - 1] += ';';\n\n lines.push(\n '',\n '// prettier-ignore',\n \"declare module 'waku/router' {\",\n ' interface RouteConfig {',\n ' paths: PathsForPages<Page>;',\n ' }',\n ' interface CreatePagesConfig {',\n ' pages: Page;',\n ' }',\n '}',\n '',\n );\n\n const result = lines.join('\\n');\n\n return result;\n };\n\n const files = await collectFiles(pagesDir);\n if (!files.length) {\n return;\n }\n const generation = await generateFile(files);\n return generation;\n}\n"],"names":["existsSync","readFileSync","readdir","writeFile","parse","transformWithOxc","EXTENSIONS","SRC_PAGES","SRC_SERVER_ENTRY","getGrouplessPath","isIgnoredPath","joinPath","toIdentifier","input","identifier","includes","split","slice","join","replace","test","map","part","undefined","toUpperCase","getImportModuleNames","filePaths","moduleNameCount","moduleNames","filePath","e","console","log","parseModule","source","lang","endsWith","transformed","jsx","code","program","getImportedName","specifier","imported","type","name","String","value","getExportedName","exported","fsRouterTypegenPlugin","opts","apply","configureServer","server","srcDir","config","root","pagesDir","outputFile","updateGeneratedFile","file","detectFsRouterUsage","generation","generateFsRouterTypes","watcher","on","existingServerEntry","ext","find","entriesFile","usesFsRouter","body","some","node","startsWith","specifiers","local","collectFiles","dir","files","entries","withFileTypes","entry","fullPath","isDirectory","push","length","fileExportsGetConfig","declaration","declarations","decl","id","generateFile","fileInfo","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","result"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/vite-plugins/fs-router-typegen.ts"],"sourcesContent":["// waku/router is technically a separate library from waku core.\n// This file is an exception placed here so that fsRouter users\n// get automatic type generation with better DX.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { readdir, writeFile } from 'node:fs/promises';\nimport { parse, transformWithOxc } from 'vite';\nimport type { ParseResult, Plugin } from 'vite';\nimport { EXTENSIONS, SRC_PAGES, SRC_SERVER_ENTRY } from '../constants.js';\nimport { getGrouplessPath } from '../utils/create-pages.js';\nimport { isIgnoredPath } from '../utils/fs-router.js';\nimport { joinPath } from '../utils/path.js';\n\ntype ProgramNode = ParseResult['program'];\ntype ImportDeclaration = ProgramNode['body'][number] & {\n type: 'ImportDeclaration';\n};\ntype ImportSpecifier = ImportDeclaration['specifiers'][number] & {\n type: 'ImportSpecifier';\n};\ntype ExportNamedDeclaration = ProgramNode['body'][number] & {\n type: 'ExportNamedDeclaration';\n};\ntype ExportSpecifier = ExportNamedDeclaration['specifiers'][number] & {\n type: 'ExportSpecifier';\n};\n\n// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words\nexport function toIdentifier(input: string): string {\n // Strip the file extension\n let identifier = input.includes('.')\n ? input.split('.').slice(0, -1).join('.')\n : input;\n // Replace any characters besides letters, numbers, underscores, and dollar signs with underscores\n identifier = identifier.replace(/[^\\p{L}\\p{N}_$]/gu, '_');\n // Ensure it starts with a letter\n if (/^\\d/.test(identifier)) {\n identifier = '_' + identifier;\n }\n // Turn it into PascalCase\n // Since the first letter is uppercased, it will not be a reserved word\n return (\n 'File_' +\n identifier\n .split('_')\n .map((part) => {\n if (part[0] === undefined) {\n return '';\n }\n return part[0].toUpperCase() + part.slice(1);\n })\n .join('')\n );\n}\n\nexport function getImportModuleNames(filePaths: string[]): {\n [k: string]: string;\n} {\n const moduleNameCount: { [k: string]: number } = {};\n const moduleNames: { [k: string]: string } = {};\n for (const filePath of filePaths) {\n let identifier = toIdentifier(filePath);\n moduleNameCount[identifier] = (moduleNameCount[identifier] ?? -1) + 1;\n if (moduleNameCount[identifier]) {\n identifier = `${identifier}_${moduleNameCount[identifier]}`;\n }\n try {\n moduleNames[filePath.replace(/^\\//, '')] = identifier;\n } catch (e) {\n console.log(e);\n }\n }\n return moduleNames;\n}\n\nconst parseModule = async (filePath: string) => {\n const source = readFileSync(filePath, 'utf8');\n const lang: 'jsx' | 'ts' | 'tsx' = filePath.endsWith('.tsx')\n ? 'tsx'\n : filePath.endsWith('.ts')\n ? 'ts'\n : 'jsx';\n const transformed = await transformWithOxc(source, filePath, {\n lang,\n jsx: 'preserve',\n });\n return (await parse(filePath, transformed.code, { lang } as never)).program;\n};\n\nconst getImportedName = (specifier: ImportSpecifier) =>\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value);\n\nconst getExportedName = (specifier: ExportSpecifier) =>\n specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : String(specifier.exported.value);\n\nexport const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => {\n return {\n name: 'waku:vite-plugins:fs-router-typegen',\n apply: 'serve',\n configureServer(server) {\n const srcDir = joinPath(server.config.root, opts.srcDir);\n const pagesDir = joinPath(srcDir, SRC_PAGES);\n\n const outputFile = joinPath(srcDir, 'pages.gen.ts');\n const updateGeneratedFile = async (file: string | undefined) => {\n // skip when the changed file is the generated file itself\n if (file && outputFile.endsWith(file)) {\n return;\n }\n // skip when the entries file exists or pages dir does not exist\n if (!existsSync(pagesDir) || !(await detectFsRouterUsage(srcDir))) {\n return;\n }\n const generation = await generateFsRouterTypes(pagesDir);\n if (!generation) {\n // skip failures\n return;\n }\n await writeFile(outputFile, generation, 'utf-8');\n };\n\n server.watcher.on('change', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('add', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('unlink', async (file) => {\n await updateGeneratedFile(file);\n });\n void updateGeneratedFile(undefined);\n },\n };\n};\n\nexport async function detectFsRouterUsage(srcDir: string): Promise<boolean> {\n const existingServerEntry = EXTENSIONS.map((ext) =>\n joinPath(srcDir, SRC_SERVER_ENTRY + ext),\n ).find((entriesFile) => existsSync(entriesFile));\n\n // managed mode if no entry\n if (!existingServerEntry) {\n return true;\n }\n\n try {\n const file = await parseModule(existingServerEntry);\n const usesFsRouter = file.body.some((node) => {\n if (node.type !== 'ImportDeclaration') {\n return false;\n }\n if (\n node.source.type !== 'Literal' ||\n typeof node.source.value !== 'string' ||\n !node.source.value.startsWith('waku')\n ) {\n return false;\n }\n return node.specifiers.some((specifier) => {\n if (\n specifier.type !== 'ImportSpecifier' ||\n specifier.local.type !== 'Identifier'\n ) {\n return false;\n }\n return getImportedName(specifier) === 'fsRouter';\n });\n });\n return usesFsRouter;\n } catch {\n return false;\n }\n}\n\nexport async function generateFsRouterTypes(pagesDir: string) {\n // Recursively collect `.tsx` files in the given directory\n const collectFiles = async (\n dir: string,\n files: string[] = [],\n ): Promise<string[]> => {\n // TODO revisit recursive option for readdir once more stable\n // https://nodejs.org/docs/latest-v20.x/api/fs.html#direntparentpath\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = joinPath(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(fullPath, files);\n } else {\n if (entry.name.endsWith('.tsx')) {\n files.push(fullPath.slice(pagesDir.length));\n }\n }\n }\n return files;\n };\n\n const fileExportsGetConfig = async (filePath: string) => {\n const file = await parseModule(pagesDir + filePath);\n return file.body.some((node) => {\n if (node.type !== 'ExportNamedDeclaration') {\n return false;\n }\n if (\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (decl) =>\n decl.id.type === 'Identifier' && decl.id.name === 'getConfig',\n )\n ) {\n return true;\n }\n if (\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'getConfig'\n ) {\n return true;\n }\n return node.specifiers.some(\n (specifier) =>\n specifier.type === 'ExportSpecifier' &&\n getExportedName(specifier) === 'getConfig',\n );\n });\n };\n\n const generateFile = async (filePaths: string[]): Promise<string | null> => {\n const fileInfo: { path: string; src: string; hasGetConfig: boolean }[] = [];\n const layoutPaths: string[] = [];\n const moduleNames = getImportModuleNames(filePaths);\n\n for (const filePath of filePaths) {\n // where to import the component from\n const src = filePath.replace(/^\\//, '');\n let hasGetConfig: boolean;\n try {\n hasGetConfig = await fileExportsGetConfig(filePath);\n } catch {\n return null;\n }\n\n if (isIgnoredPath(filePath.split('/'))) {\n continue;\n } else if (filePath.endsWith('/_layout.tsx')) {\n const path = filePath.slice(0, -'/_layout.tsx'.length);\n layoutPaths.push(getGrouplessPath(path) || '/');\n } else if (filePath.endsWith('/index.tsx')) {\n const path = filePath.slice(0, -'/index.tsx'.length);\n fileInfo.push({\n path: getGrouplessPath(path) || '/',\n src,\n hasGetConfig,\n });\n } else {\n fileInfo.push({\n path: getGrouplessPath(filePath.replace('.tsx', '')),\n src,\n hasGetConfig,\n });\n }\n }\n\n const hasAnyGetConfig = fileInfo.some((file) => file.hasGetConfig);\n const lines: string[] = [\n '// deno-fmt-ignore-file',\n '// biome-ignore format: generated types do not need formatting',\n '// prettier-ignore',\n hasAnyGetConfig\n ? \"import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';\"\n : \"import type { PathsForPages } from 'waku/router';\",\n ];\n\n if (hasAnyGetConfig) {\n lines.push('');\n for (const file of fileInfo) {\n if (!file.hasGetConfig) {\n continue;\n }\n const moduleName = moduleNames[file.src];\n lines.push(\n `// prettier-ignore`,\n `import type { getConfig as ${moduleName}_getConfig } from './${SRC_PAGES}/${file.src.replace('.tsx', '')}';`,\n );\n }\n }\n\n lines.push('', '// prettier-ignore', 'type Page =');\n for (const file of fileInfo) {\n const moduleName = moduleNames[file.src];\n if (file.hasGetConfig) {\n lines.push(\n `| ({ path: '${file.path}' } & GetConfigResponse<typeof ${moduleName}_getConfig>)`,\n );\n } else {\n lines.push(`| { path: '${file.path}'; render: 'static' }`);\n }\n }\n lines[lines.length - 1] += ';';\n\n const uniqueLayoutPaths = [...new Set(layoutPaths)];\n if (uniqueLayoutPaths.length) {\n lines.push('', '// prettier-ignore', 'type Layout =');\n for (const path of uniqueLayoutPaths) {\n lines.push(`| { path: '${path}' }`);\n }\n lines[lines.length - 1] += ';';\n }\n\n lines.push(\n '',\n '// prettier-ignore',\n \"declare module 'waku/router' {\",\n ' interface RouteConfig {',\n ' paths: PathsForPages<Page>;',\n ' }',\n ' interface CreatePagesConfig {',\n ' pages: Page;',\n ...(uniqueLayoutPaths.length ? [' layouts: Layout;'] : []),\n ' }',\n ...(hasAnyGetConfig\n ? [\n ' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}',\n ]\n : []),\n '}',\n '',\n );\n\n const result = lines.join('\\n');\n\n return result;\n };\n\n const files = await collectFiles(pagesDir);\n if (!files.length) {\n return;\n }\n const generation = await generateFile(files);\n return generation;\n}\n"],"names":["existsSync","readFileSync","readdir","writeFile","parse","transformWithOxc","EXTENSIONS","SRC_PAGES","SRC_SERVER_ENTRY","getGrouplessPath","isIgnoredPath","joinPath","toIdentifier","input","identifier","includes","split","slice","join","replace","test","map","part","undefined","toUpperCase","getImportModuleNames","filePaths","moduleNameCount","moduleNames","filePath","e","console","log","parseModule","source","lang","endsWith","transformed","jsx","code","program","getImportedName","specifier","imported","type","name","String","value","getExportedName","exported","fsRouterTypegenPlugin","opts","apply","configureServer","server","srcDir","config","root","pagesDir","outputFile","updateGeneratedFile","file","detectFsRouterUsage","generation","generateFsRouterTypes","watcher","on","existingServerEntry","ext","find","entriesFile","usesFsRouter","body","some","node","startsWith","specifiers","local","collectFiles","dir","files","entries","withFileTypes","entry","fullPath","isDirectory","push","length","fileExportsGetConfig","declaration","declarations","decl","id","generateFile","fileInfo","layoutPaths","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","uniqueLayoutPaths","Set","result"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { BUILD_METADATA_FILE, DIST_PUBLIC, DIST_SERVER } from '../constants.js';
6
6
  import { setAllEnv } from '../env.js';
7
7
  import { getErrorInfo } from '../utils/custom-errors.js';
8
8
  import { sanitizeLog } from '../utils/log.js';
9
- import { joinPath } from '../utils/path.js';
9
+ import { addBase, joinPath } from '../utils/path.js';
10
10
  import { DEBUG_ID_HEADER } from '../utils/react-debug-channel.js';
11
11
  import { createRenderUtils } from '../utils/render.js';
12
12
  import { getInput } from '../utils/request.js';
@@ -46,7 +46,7 @@ const toProcessRequest = (handleRequest)=>async (req)=>{
46
46
  const body = stringToStream(message);
47
47
  const headers = {};
48
48
  if (info?.location) {
49
- headers.location = info.location;
49
+ headers.location = addBase(info.location, config.basePath);
50
50
  }
51
51
  return new Response(body, {
52
52
  status,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/vite-rsc/handler.ts"],"sourcesContent":["import {\n createFromReadableStream,\n createTemporaryReferenceSet,\n decodeAction,\n decodeFormState,\n decodeReply,\n loadServerAction,\n renderToReadableStream,\n} from '@vitejs/plugin-rsc/rsc';\nimport { buildMetadata } from 'virtual:vite-rsc-waku/build-metadata';\nimport { config, isBuild } from 'virtual:vite-rsc-waku/config';\nimport notFoundHtml from 'virtual:vite-rsc-waku/not-found';\nimport { BUILD_METADATA_FILE, DIST_PUBLIC, DIST_SERVER } from '../constants.js';\nimport { setAllEnv } from '../env.js';\nimport type {\n Unstable_CreateServerEntryAdapter as CreateServerEntryAdapter,\n Unstable_HandleBuild as HandleBuild,\n Unstable_HandleRequest as HandleRequest,\n Unstable_ProcessBuild as ProcessBuild,\n Unstable_ProcessRequest as ProcessRequest,\n} from '../types.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { joinPath } from '../utils/path.js';\nimport { DEBUG_ID_HEADER } from '../utils/react-debug-channel.js';\nimport { createRenderUtils } from '../utils/render.js';\nimport { getInput } from '../utils/request.js';\nimport { encodeRscPath } from '../utils/rsc-path.js';\nimport { stringToStream } from '../utils/stream.js';\n\nfunction loadSsrEntryModule() {\n // This is an API to communicate between two server environments `rsc` and `ssr`.\n // https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc/README.md#importmetaviterscloadmodule\n return import.meta.viteRsc.loadModule<\n typeof import('../vite-entries/entry.ssr.js')\n >('ssr', 'index');\n}\n\nconst toProcessRequest =\n (handleRequest: HandleRequest): ProcessRequest =>\n async (req) => {\n const temporaryReferences = createTemporaryReferenceSet();\n\n const input = await getInput(\n req,\n config,\n temporaryReferences,\n decodeReply,\n decodeAction,\n decodeFormState,\n loadServerAction,\n );\n\n const debugId =\n (import.meta.env.DEV && req.headers.get(DEBUG_ID_HEADER.toLowerCase())) ||\n undefined;\n const debugChannels = globalThis.__WAKU_DEBUG_CHANNELS__;\n const debugChannel = debugId ? debugChannels?.get(debugId) : undefined;\n if (debugId) {\n debugChannels?.delete(debugId);\n }\n\n const renderUtils = createRenderUtils(\n temporaryReferences,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n debugChannel,\n debugId,\n );\n\n let res: Awaited<ReturnType<typeof handleRequest>>;\n try {\n res = await handleRequest(input, {\n ...renderUtils,\n loadBuildMetadata: async (key: string) => buildMetadata.get(key),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n const status = info?.status || 500;\n let message: string;\n if (info) {\n message = (e as { message?: string } | undefined)?.message || String(e);\n } else {\n console.warn(sanitizeLog(e));\n message = 'Internal Server Error';\n }\n const body = stringToStream(message);\n const headers: { location?: string } = {};\n if (info?.location) {\n headers.location = info.location;\n }\n return new Response(body, { status, headers });\n }\n\n if (res instanceof ReadableStream) {\n return new Response(res);\n } else if (res && res !== 'fallback') {\n return res;\n }\n\n // fallback index html like packages/waku/src/lib/plugins/vite-plugin-rsc-index.ts\n const url = new URL(req.url);\n if (res === 'fallback' || (!res && url.pathname === '/')) {\n const { INTERNAL_renderHtmlFallback } = await loadSsrEntryModule();\n const htmlFallbackStream = await INTERNAL_renderHtmlFallback();\n const headers = { 'content-type': 'text/html; charset=utf-8' };\n return new Response(htmlFallbackStream, { headers });\n }\n\n return null;\n };\n\nconst toProcessBuild =\n (handleBuild: HandleBuild): ProcessBuild =>\n async ({ emitFile, unstable_registerPrunableFile }) => {\n const renderUtils = createRenderUtils(\n undefined,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n );\n\n let fallbackHtml: string | undefined;\n const getFallbackHtml = async () => {\n if (!fallbackHtml) {\n const ssrEntryModule = await loadSsrEntryModule();\n fallbackHtml = await ssrEntryModule.INTERNAL_renderHtmlFallback();\n }\n return fallbackHtml;\n };\n\n const getPublicFilePath = (fileName: string) => {\n const filePath = joinPath(DIST_PUBLIC, fileName);\n if (!filePath.startsWith(DIST_PUBLIC + '/')) {\n throw new Error('fileName escapes the public directory: ' + fileName);\n }\n return filePath;\n };\n\n await handleBuild({\n renderRsc: renderUtils.renderRsc,\n parseRsc: renderUtils.parseRsc,\n renderHtml: renderUtils.renderHtml,\n rscPath2pathname: (rscPath) =>\n joinPath(config.rscBase, encodeRscPath(rscPath)),\n saveBuildMetadata: async (key, value) => {\n buildMetadata.set(key, value);\n },\n generateFile: async (fileName, body) => {\n await emitFile(\n getPublicFilePath(fileName),\n typeof body === 'string' ? stringToStream(body) : body,\n );\n },\n generateDefaultHtml: async (fileName) => {\n await emitFile(\n getPublicFilePath(fileName),\n stringToStream(await getFallbackHtml()),\n );\n },\n unstable_registerPrunableFile,\n });\n await emitFile(\n joinPath(DIST_SERVER, BUILD_METADATA_FILE),\n stringToStream(\n `export const buildMetadata = new Map(${JSON.stringify(\n Array.from(buildMetadata),\n )});`,\n ),\n );\n };\n\nexport const createServerEntryAdapter: CreateServerEntryAdapter =\n (fn) => (handlers, options) => {\n const processRequest = toProcessRequest(handlers.handleRequest);\n const processBuild = toProcessBuild(handlers.handleBuild);\n return fn(\n {\n handlers,\n processRequest,\n processBuild,\n setAllEnv,\n config,\n isBuild,\n notFoundHtml,\n },\n options,\n );\n };\n"],"names":["createFromReadableStream","createTemporaryReferenceSet","decodeAction","decodeFormState","decodeReply","loadServerAction","renderToReadableStream","buildMetadata","config","isBuild","notFoundHtml","BUILD_METADATA_FILE","DIST_PUBLIC","DIST_SERVER","setAllEnv","getErrorInfo","sanitizeLog","joinPath","DEBUG_ID_HEADER","createRenderUtils","getInput","encodeRscPath","stringToStream","loadSsrEntryModule","viteRsc","loadModule","toProcessRequest","handleRequest","req","temporaryReferences","input","debugId","env","DEV","headers","get","toLowerCase","undefined","debugChannels","globalThis","__WAKU_DEBUG_CHANNELS__","debugChannel","delete","renderUtils","WAKU_BUILD_ID","res","loadBuildMetadata","key","e","info","status","message","String","console","warn","body","location","Response","ReadableStream","url","URL","pathname","INTERNAL_renderHtmlFallback","htmlFallbackStream","toProcessBuild","handleBuild","emitFile","unstable_registerPrunableFile","fallbackHtml","getFallbackHtml","ssrEntryModule","getPublicFilePath","fileName","filePath","startsWith","Error","renderRsc","parseRsc","renderHtml","rscPath2pathname","rscPath","rscBase","saveBuildMetadata","value","set","generateFile","generateDefaultHtml","JSON","stringify","Array","from","createServerEntryAdapter","fn","handlers","options","processRequest","processBuild"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/vite-rsc/handler.ts"],"sourcesContent":["import {\n createFromReadableStream,\n createTemporaryReferenceSet,\n decodeAction,\n decodeFormState,\n decodeReply,\n loadServerAction,\n renderToReadableStream,\n} from '@vitejs/plugin-rsc/rsc';\nimport { buildMetadata } from 'virtual:vite-rsc-waku/build-metadata';\nimport { config, isBuild } from 'virtual:vite-rsc-waku/config';\nimport notFoundHtml from 'virtual:vite-rsc-waku/not-found';\nimport { BUILD_METADATA_FILE, DIST_PUBLIC, DIST_SERVER } from '../constants.js';\nimport { setAllEnv } from '../env.js';\nimport type {\n Unstable_CreateServerEntryAdapter as CreateServerEntryAdapter,\n Unstable_HandleBuild as HandleBuild,\n Unstable_HandleRequest as HandleRequest,\n Unstable_ProcessBuild as ProcessBuild,\n Unstable_ProcessRequest as ProcessRequest,\n} from '../types.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { addBase, joinPath } from '../utils/path.js';\nimport { DEBUG_ID_HEADER } from '../utils/react-debug-channel.js';\nimport { createRenderUtils } from '../utils/render.js';\nimport { getInput } from '../utils/request.js';\nimport { encodeRscPath } from '../utils/rsc-path.js';\nimport { stringToStream } from '../utils/stream.js';\n\nfunction loadSsrEntryModule() {\n // This is an API to communicate between two server environments `rsc` and `ssr`.\n // https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc/README.md#importmetaviterscloadmodule\n return import.meta.viteRsc.loadModule<\n typeof import('../vite-entries/entry.ssr.js')\n >('ssr', 'index');\n}\n\nconst toProcessRequest =\n (handleRequest: HandleRequest): ProcessRequest =>\n async (req) => {\n const temporaryReferences = createTemporaryReferenceSet();\n\n const input = await getInput(\n req,\n config,\n temporaryReferences,\n decodeReply,\n decodeAction,\n decodeFormState,\n loadServerAction,\n );\n\n const debugId =\n (import.meta.env.DEV && req.headers.get(DEBUG_ID_HEADER.toLowerCase())) ||\n undefined;\n const debugChannels = globalThis.__WAKU_DEBUG_CHANNELS__;\n const debugChannel = debugId ? debugChannels?.get(debugId) : undefined;\n if (debugId) {\n debugChannels?.delete(debugId);\n }\n\n const renderUtils = createRenderUtils(\n temporaryReferences,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n debugChannel,\n debugId,\n );\n\n let res: Awaited<ReturnType<typeof handleRequest>>;\n try {\n res = await handleRequest(input, {\n ...renderUtils,\n loadBuildMetadata: async (key: string) => buildMetadata.get(key),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n const status = info?.status || 500;\n let message: string;\n if (info) {\n message = (e as { message?: string } | undefined)?.message || String(e);\n } else {\n console.warn(sanitizeLog(e));\n message = 'Internal Server Error';\n }\n const body = stringToStream(message);\n const headers: { location?: string } = {};\n if (info?.location) {\n headers.location = addBase(info.location, config.basePath);\n }\n return new Response(body, { status, headers });\n }\n\n if (res instanceof ReadableStream) {\n return new Response(res);\n } else if (res && res !== 'fallback') {\n return res;\n }\n\n // fallback index html like packages/waku/src/lib/plugins/vite-plugin-rsc-index.ts\n const url = new URL(req.url);\n if (res === 'fallback' || (!res && url.pathname === '/')) {\n const { INTERNAL_renderHtmlFallback } = await loadSsrEntryModule();\n const htmlFallbackStream = await INTERNAL_renderHtmlFallback();\n const headers = { 'content-type': 'text/html; charset=utf-8' };\n return new Response(htmlFallbackStream, { headers });\n }\n\n return null;\n };\n\nconst toProcessBuild =\n (handleBuild: HandleBuild): ProcessBuild =>\n async ({ emitFile, unstable_registerPrunableFile }) => {\n const renderUtils = createRenderUtils(\n undefined,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n );\n\n let fallbackHtml: string | undefined;\n const getFallbackHtml = async () => {\n if (!fallbackHtml) {\n const ssrEntryModule = await loadSsrEntryModule();\n fallbackHtml = await ssrEntryModule.INTERNAL_renderHtmlFallback();\n }\n return fallbackHtml;\n };\n\n const getPublicFilePath = (fileName: string) => {\n const filePath = joinPath(DIST_PUBLIC, fileName);\n if (!filePath.startsWith(DIST_PUBLIC + '/')) {\n throw new Error('fileName escapes the public directory: ' + fileName);\n }\n return filePath;\n };\n\n await handleBuild({\n renderRsc: renderUtils.renderRsc,\n parseRsc: renderUtils.parseRsc,\n renderHtml: renderUtils.renderHtml,\n rscPath2pathname: (rscPath) =>\n joinPath(config.rscBase, encodeRscPath(rscPath)),\n saveBuildMetadata: async (key, value) => {\n buildMetadata.set(key, value);\n },\n generateFile: async (fileName, body) => {\n await emitFile(\n getPublicFilePath(fileName),\n typeof body === 'string' ? stringToStream(body) : body,\n );\n },\n generateDefaultHtml: async (fileName) => {\n await emitFile(\n getPublicFilePath(fileName),\n stringToStream(await getFallbackHtml()),\n );\n },\n unstable_registerPrunableFile,\n });\n await emitFile(\n joinPath(DIST_SERVER, BUILD_METADATA_FILE),\n stringToStream(\n `export const buildMetadata = new Map(${JSON.stringify(\n Array.from(buildMetadata),\n )});`,\n ),\n );\n };\n\nexport const createServerEntryAdapter: CreateServerEntryAdapter =\n (fn) => (handlers, options) => {\n const processRequest = toProcessRequest(handlers.handleRequest);\n const processBuild = toProcessBuild(handlers.handleBuild);\n return fn(\n {\n handlers,\n processRequest,\n processBuild,\n setAllEnv,\n config,\n isBuild,\n notFoundHtml,\n },\n options,\n );\n };\n"],"names":["createFromReadableStream","createTemporaryReferenceSet","decodeAction","decodeFormState","decodeReply","loadServerAction","renderToReadableStream","buildMetadata","config","isBuild","notFoundHtml","BUILD_METADATA_FILE","DIST_PUBLIC","DIST_SERVER","setAllEnv","getErrorInfo","sanitizeLog","addBase","joinPath","DEBUG_ID_HEADER","createRenderUtils","getInput","encodeRscPath","stringToStream","loadSsrEntryModule","viteRsc","loadModule","toProcessRequest","handleRequest","req","temporaryReferences","input","debugId","env","DEV","headers","get","toLowerCase","undefined","debugChannels","globalThis","__WAKU_DEBUG_CHANNELS__","debugChannel","delete","renderUtils","WAKU_BUILD_ID","res","loadBuildMetadata","key","e","info","status","message","String","console","warn","body","location","basePath","Response","ReadableStream","url","URL","pathname","INTERNAL_renderHtmlFallback","htmlFallbackStream","toProcessBuild","handleBuild","emitFile","unstable_registerPrunableFile","fallbackHtml","getFallbackHtml","ssrEntryModule","getPublicFilePath","fileName","filePath","startsWith","Error","renderRsc","parseRsc","renderHtml","rscPath2pathname","rscPath","rscBase","saveBuildMetadata","value","set","generateFile","generateDefaultHtml","JSON","stringify","Array","from","createServerEntryAdapter","fn","handlers","options","processRequest","processBuild"],"mappings":"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"}
@@ -7,18 +7,13 @@ import htmlShell from 'virtual:vite-rsc-waku/html-shell';
7
7
  import { INTERNAL_ServerRoot } from '../../minimal/client.js';
8
8
  import { getErrorInfo } from '../utils/custom-errors.js';
9
9
  import { sanitizeLog } from '../utils/log.js';
10
- import { waitForRootPrerequisites } from '../utils/rsc-stream.js';
11
10
  import { getBootstrapPreamble } from '../utils/ssr.js';
12
- import { batchReadableStream, deferReadableStream } from '../utils/stream.js';
11
+ import { batchReadableStream } from '../utils/stream.js';
13
12
  function createFromReadableStream(stream) {
14
- let resolve;
15
- const promise = new Promise((r)=>{
16
- resolve = r;
17
- });
18
- const deferredStream = deferReadableStream(stream, promise);
19
- const root = createFromReadableStreamBase(deferredStream);
20
- waitForRootPrerequisites(root).then(resolve, resolve);
21
- return root;
13
+ // DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154
14
+ return createFromReadableStreamBase(import.meta.env.DEV ? stream.pipeThrough(new TransformStream({
15
+ flush: ()=>new Promise((r)=>setTimeout(r, 5000))
16
+ })) : stream);
22
17
  }
23
18
  // This code runs on `ssr` environment,
24
19
  // i.e. it runs on server but without `react-server` condition.