waku 1.0.0-beta.4 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/lib/utils/initial-rsc.d.ts +0 -1
- package/dist/lib/utils/initial-rsc.js +7 -7
- package/dist/lib/utils/initial-rsc.js.map +1 -1
- package/dist/lib/utils/stream.d.ts +0 -1
- package/dist/lib/utils/stream.js +0 -35
- package/dist/lib/utils/stream.js.map +1 -1
- package/dist/lib/vite-plugins/fs-router-typegen.js +4 -2
- package/dist/lib/vite-plugins/fs-router-typegen.js.map +1 -1
- package/dist/lib/vite-rsc/ssr.js +5 -10
- package/dist/lib/vite-rsc/ssr.js.map +1 -1
- package/dist/minimal/client-utils/fetch-store.d.ts +24 -0
- package/dist/minimal/client-utils/fetch-store.js +11 -0
- package/dist/minimal/client-utils/fetch-store.js.map +1 -0
- package/dist/minimal/client.d.ts +2 -34
- package/dist/minimal/client.js +7 -36
- package/dist/minimal/client.js.map +1 -1
- package/dist/router/base-types.d.ts +3 -1
- package/dist/router/base-types.js.map +1 -1
- package/dist/router/client-utils/build-route-href.d.ts +13 -15
- package/dist/router/client-utils/build-route-href.js +12 -24
- package/dist/router/client-utils/build-route-href.js.map +1 -1
- package/dist/router/client-utils/match-route-params.d.ts +3 -3
- package/dist/router/client-utils/match-route-params.js +3 -3
- package/dist/router/client-utils/match-route-params.js.map +1 -1
- package/dist/router/client-utils/search-codec-registry.d.ts +9 -0
- package/dist/router/client-utils/search-codec-registry.js +11 -0
- package/dist/router/client-utils/search-codec-registry.js.map +1 -0
- package/dist/router/client.d.ts +45 -10
- package/dist/router/client.js +118 -6
- package/dist/router/client.js.map +1 -1
- package/dist/router/create-pages-utils/inferred-path-types.d.ts +37 -2
- package/dist/router/create-pages-utils/inferred-path-types.js.map +1 -1
- package/dist/router/create-pages.d.ts +10 -1
- package/dist/router/create-pages.js +34 -7
- package/dist/router/create-pages.js.map +1 -1
- package/dist/router/define-router.d.ts +1 -0
- package/dist/router/define-router.js +34 -2
- package/dist/router/define-router.js.map +1 -1
- package/dist/router/fs-router.d.ts +1 -0
- package/dist/router/fs-router.js.map +1 -1
- package/package.json +5 -5
- package/dist/lib/utils/rsc-stream.d.ts +0 -1
- package/dist/lib/utils/rsc-stream.js +0 -84
- package/dist/lib/utils/rsc-stream.js.map +0 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ 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.
|
|
32
|
+
**Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.15.0`
|
|
33
33
|
|
|
34
34
|
## Rendering
|
|
35
35
|
|
|
@@ -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
|
}
|
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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>;
|
package/dist/lib/utils/stream.js
CHANGED
|
@@ -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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"}
|
|
@@ -192,7 +192,7 @@ export async function generateFsRouterTypes(pagesDir) {
|
|
|
192
192
|
'// deno-fmt-ignore-file',
|
|
193
193
|
'// biome-ignore format: generated types do not need formatting',
|
|
194
194
|
'// prettier-ignore',
|
|
195
|
-
hasAnyGetConfig ? "import type { PathsForPages, GetConfigResponse } from 'waku/router';" : "import type { PathsForPages } from 'waku/router';"
|
|
195
|
+
hasAnyGetConfig ? "import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';" : "import type { PathsForPages } from 'waku/router';"
|
|
196
196
|
];
|
|
197
197
|
if (hasAnyGetConfig) {
|
|
198
198
|
lines.push('');
|
|
@@ -214,7 +214,9 @@ export async function generateFsRouterTypes(pagesDir) {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
lines[lines.length - 1] += ';';
|
|
217
|
-
lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ' }',
|
|
217
|
+
lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ' }', ...hasAnyGetConfig ? [
|
|
218
|
+
' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}'
|
|
219
|
+
] : [], '}', '');
|
|
218
220
|
const result = lines.join('\n');
|
|
219
221
|
return result;
|
|
220
222
|
};
|
|
@@ -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 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, 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 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 ...(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","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","result"],"mappings":"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"}
|
package/dist/lib/vite-rsc/ssr.js
CHANGED
|
@@ -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
|
|
11
|
+
import { batchReadableStream } from '../utils/stream.js';
|
|
13
12
|
function createFromReadableStream(stream) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/vite-rsc/ssr.tsx"],"sourcesContent":["import { type ReactNode, captureOwnerStack, use } from 'react';\nimport { createFromReadableStream as createFromReadableStreamBase } from '@vitejs/plugin-rsc/ssr';\nimport type { ReactFormState } from 'react-dom/client';\nimport { renderToReadableStream } from 'react-dom/server.edge';\nimport { injectRSCPayload } from 'rsc-html-stream/server';\nimport htmlShell from 'virtual:vite-rsc-waku/html-shell';\nimport { INTERNAL_ServerRoot } from '../../minimal/client.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/vite-rsc/ssr.tsx"],"sourcesContent":["import { type ReactNode, captureOwnerStack, use } from 'react';\nimport { createFromReadableStream as createFromReadableStreamBase } from '@vitejs/plugin-rsc/ssr';\nimport type { ReactFormState } from 'react-dom/client';\nimport { renderToReadableStream } from 'react-dom/server.edge';\nimport { injectRSCPayload } from 'rsc-html-stream/server';\nimport htmlShell from 'virtual:vite-rsc-waku/html-shell';\nimport { INTERNAL_ServerRoot } from '../../minimal/client.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { getBootstrapPreamble } from '../utils/ssr.js';\nimport { batchReadableStream } from '../utils/stream.js';\n\nfunction createFromReadableStream<T>(\n stream: ReadableStream<Uint8Array>,\n): Promise<T> {\n // DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154\n return createFromReadableStreamBase<T>(\n import.meta.env.DEV\n ? stream.pipeThrough(\n new TransformStream({\n flush: () => new Promise((r) => setTimeout(r, 5000)),\n }),\n )\n : stream,\n );\n}\n\ntype RenderHtmlStream = (\n rscStream: ReadableStream<Uint8Array>,\n rscHtmlStream: ReadableStream<Uint8Array>,\n options: {\n rscPath: string | undefined;\n formState: ReactFormState | undefined;\n nonce: string | undefined;\n extraScriptContent: string | undefined;\n debugId: string | undefined;\n },\n) => Promise<{ stream: ReadableStream; status: number | undefined }>;\n\ntype RscElementsPayload = Record<string, unknown>;\ntype RscHtmlPayload = ReactNode;\n\n// This code runs on `ssr` environment,\n// i.e. it runs on server but without `react-server` condition.\n// These utilities are used by `rsc` environment through\n// `import.meta.viteRsc.loadModule` API.\n\nexport const renderHtmlStream: RenderHtmlStream = async (\n rscStream,\n rscHtmlStream,\n options,\n) => {\n const [stream1, stream2] = rscStream.tee();\n\n let elementsPromise: Promise<RscElementsPayload>;\n let htmlPromise: Promise<RscHtmlPayload>;\n\n // deserialize RSC stream back to React VDOM\n function SsrRoot() {\n // RSC stream needs to be deserialized inside SSR component.\n // This is for ReactDomServer preinit/preload (e.g. client reference modulepreload, css)\n // https://github.com/facebook/react/pull/31799#discussion_r1886166075\n elementsPromise ??= createFromReadableStream<RscElementsPayload>(stream1);\n htmlPromise ??= createFromReadableStream<RscHtmlPayload>(rscHtmlStream);\n return (\n <INTERNAL_ServerRoot elementsPromise={elementsPromise}>\n {use(htmlPromise)}\n </INTERNAL_ServerRoot>\n );\n }\n\n // render html\n const bootstrapScriptContent = await loadBootstrapScriptContent();\n let htmlStream: Awaited<ReturnType<typeof renderToReadableStream>>;\n let status: number | undefined;\n try {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent:\n getBootstrapPreamble({\n hydrate: true,\n debugId: options.debugId,\n }) +\n bootstrapScriptContent +\n (options.extraScriptContent || ''),\n onError: (e: unknown) => {\n if (\n e &&\n typeof e === 'object' &&\n 'digest' in e &&\n typeof e.digest === 'string'\n ) {\n return e.digest;\n }\n console.error(\n '[SSR Error]',\n sanitizeLog(captureOwnerStack?.() || ''),\n '\\n',\n sanitizeLog(e),\n );\n },\n ...(options.nonce ? { nonce: options.nonce } : {}),\n ...(options.formState ? { formState: options.formState } : {}),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n if (info?.location) {\n // keep unstable_redirect error as http redirection\n throw e;\n }\n status = info?.status || 500;\n // SSR empty html and go full CSR on browser, which can revive RSC errors.\n const ssrErrorRoot = (\n <html>\n <body></body>\n </html>\n );\n htmlStream = await renderToReadableStream(ssrErrorRoot, {\n bootstrapScriptContent:\n getBootstrapPreamble({\n hydrate: false,\n }) +\n bootstrapScriptContent +\n (options.extraScriptContent || ''),\n ...(options.nonce ? { nonce: options.nonce } : {}),\n });\n }\n const responseStream: ReadableStream<Uint8Array> = htmlStream.pipeThrough(\n injectRSCPayload(\n batchReadableStream(stream2),\n options.nonce ? { nonce: options.nonce } : {},\n ),\n );\n\n return { stream: responseStream, status };\n};\n\nexport async function renderHtmlFallback() {\n const bootstrapScriptContent = await loadBootstrapScriptContent();\n const html = htmlShell.replace(\n '</body>',\n () => `<script>${bootstrapScriptContent}</script></body>`,\n );\n return html;\n}\n\nfunction loadBootstrapScriptContent(): Promise<string> {\n return import.meta.viteRsc.loadBootstrapScriptContent('index');\n}\n"],"names":["captureOwnerStack","use","createFromReadableStream","createFromReadableStreamBase","renderToReadableStream","injectRSCPayload","htmlShell","INTERNAL_ServerRoot","getErrorInfo","sanitizeLog","getBootstrapPreamble","batchReadableStream","stream","env","DEV","pipeThrough","TransformStream","flush","Promise","r","setTimeout","renderHtmlStream","rscStream","rscHtmlStream","options","stream1","stream2","tee","elementsPromise","htmlPromise","SsrRoot","bootstrapScriptContent","loadBootstrapScriptContent","htmlStream","status","hydrate","debugId","extraScriptContent","onError","e","digest","console","error","nonce","formState","info","location","ssrErrorRoot","html","body","responseStream","renderHtmlFallback","replace","viteRsc"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const ENTRY = "e";
|
|
2
|
+
export declare const SET_ELEMENTS = "s";
|
|
3
|
+
export declare const FETCH_ENHANCERS = "f";
|
|
4
|
+
export declare const FETCH_RSC_INPUT_TRANSFORMERS = "t";
|
|
5
|
+
export declare const CALL_SERVER_ELEMENTS_LISTENERS = "l";
|
|
6
|
+
export type SetElements = (updater: (prev: Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>) => void;
|
|
7
|
+
export type FetchEnhancer = (fetchFn: typeof fetch) => typeof fetch;
|
|
8
|
+
type FetchEnhancers = Set<FetchEnhancer>;
|
|
9
|
+
export type FetchRscInputTransformer = (rscPath: string, rscParams: unknown, prefetchOnly: boolean) => readonly [rscPath: string, rscParams: unknown, prefetchOnly: boolean];
|
|
10
|
+
type FetchRscInputTransformers = Set<FetchRscInputTransformer>;
|
|
11
|
+
type CallServerElementsListeners = Set<(elements: Record<string, unknown>) => void>;
|
|
12
|
+
export type FetchRscStore = {
|
|
13
|
+
[ENTRY]?: [
|
|
14
|
+
rscPath: string,
|
|
15
|
+
rscParams: unknown,
|
|
16
|
+
elementsPromise: Promise<Record<string, unknown>>
|
|
17
|
+
];
|
|
18
|
+
[SET_ELEMENTS]?: SetElements;
|
|
19
|
+
[FETCH_ENHANCERS]?: FetchEnhancers;
|
|
20
|
+
[FETCH_RSC_INPUT_TRANSFORMERS]?: FetchRscInputTransformers;
|
|
21
|
+
[CALL_SERVER_ELEMENTS_LISTENERS]?: CallServerElementsListeners;
|
|
22
|
+
};
|
|
23
|
+
export declare const fetchRscStore: FetchRscStore;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const ENTRY = 'e';
|
|
2
|
+
export const SET_ELEMENTS = 's';
|
|
3
|
+
export const FETCH_ENHANCERS = 'f';
|
|
4
|
+
export const FETCH_RSC_INPUT_TRANSFORMERS = 't';
|
|
5
|
+
export const CALL_SERVER_ELEMENTS_LISTENERS = 'l';
|
|
6
|
+
// Internal module-level RSC store. This module is intentionally absent from the
|
|
7
|
+
// package "exports" map, so it stays private to consumers while tests can still
|
|
8
|
+
// import it to reset state.
|
|
9
|
+
export const fetchRscStore = {};
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=fetch-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/minimal/client-utils/fetch-store.ts"],"sourcesContent":["export const ENTRY = 'e';\nexport const SET_ELEMENTS = 's';\nexport const FETCH_ENHANCERS = 'f';\nexport const FETCH_RSC_INPUT_TRANSFORMERS = 't';\nexport const CALL_SERVER_ELEMENTS_LISTENERS = 'l';\n\nexport type SetElements = (\n updater: (\n prev: Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n) => void;\n\nexport type FetchEnhancer = (fetchFn: typeof fetch) => typeof fetch;\ntype FetchEnhancers = Set<FetchEnhancer>;\n\nexport type FetchRscInputTransformer = (\n rscPath: string,\n rscParams: unknown,\n prefetchOnly: boolean,\n) => readonly [rscPath: string, rscParams: unknown, prefetchOnly: boolean];\ntype FetchRscInputTransformers = Set<FetchRscInputTransformer>;\n\ntype CallServerElementsListeners = Set<\n (elements: Record<string, unknown>) => void\n>;\n\nexport type FetchRscStore = {\n [ENTRY]?: [\n rscPath: string,\n rscParams: unknown,\n elementsPromise: Promise<Record<string, unknown>>,\n ];\n [SET_ELEMENTS]?: SetElements;\n [FETCH_ENHANCERS]?: FetchEnhancers;\n [FETCH_RSC_INPUT_TRANSFORMERS]?: FetchRscInputTransformers;\n [CALL_SERVER_ELEMENTS_LISTENERS]?: CallServerElementsListeners;\n};\n\n// Internal module-level RSC store. This module is intentionally absent from the\n// package \"exports\" map, so it stays private to consumers while tests can still\n// import it to reset state.\nexport const fetchRscStore: FetchRscStore = {};\n"],"names":["ENTRY","SET_ELEMENTS","FETCH_ENHANCERS","FETCH_RSC_INPUT_TRANSFORMERS","CALL_SERVER_ELEMENTS_LISTENERS","fetchRscStore"],"mappings":"AAAA,OAAO,MAAMA,QAAQ,IAAI;AACzB,OAAO,MAAMC,eAAe,IAAI;AAChC,OAAO,MAAMC,kBAAkB,IAAI;AACnC,OAAO,MAAMC,+BAA+B,IAAI;AAChD,OAAO,MAAMC,iCAAiC,IAAI;AAkClD,gFAAgF;AAChF,gFAAgF;AAChF,4BAA4B;AAC5B,OAAO,MAAMC,gBAA+B,CAAC,EAAE"}
|
package/dist/minimal/client.d.ts
CHANGED
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FetchEnhancer, FetchRscInputTransformer } from './client-utils/fetch-store.js';
|
|
2
3
|
type Elements = Record<string, unknown>;
|
|
3
|
-
declare const ENTRY = "e";
|
|
4
|
-
declare const SET_ELEMENTS = "s";
|
|
5
|
-
declare const FETCH_ENHANCERS = "f";
|
|
6
|
-
declare const FETCH_RSC_INPUT_TRANSFORMERS = "t";
|
|
7
|
-
declare const CALL_SERVER_ELEMENTS_LISTENERS = "l";
|
|
8
|
-
type SetElements = (updater: (prev: Promise<Elements>) => Promise<Elements>) => void;
|
|
9
|
-
type FetchEnhancer = (fetchFn: typeof fetch) => typeof fetch;
|
|
10
|
-
type FetchEnhancers = Set<FetchEnhancer>;
|
|
11
|
-
type FetchRscInputTransformer = (rscPath: string, rscParams: unknown, prefetchOnly: boolean) => readonly [rscPath: string, rscParams: unknown, prefetchOnly: boolean];
|
|
12
|
-
type FetchRscInputTransformers = Set<FetchRscInputTransformer>;
|
|
13
|
-
type CallServerElementsListeners = Set<(elements: Elements) => void>;
|
|
14
|
-
type FetchRscStore = {
|
|
15
|
-
[ENTRY]?: [
|
|
16
|
-
rscPath: string,
|
|
17
|
-
rscParams: unknown,
|
|
18
|
-
elementsPromise: Promise<Elements>
|
|
19
|
-
];
|
|
20
|
-
[SET_ELEMENTS]?: SetElements;
|
|
21
|
-
[FETCH_ENHANCERS]?: FetchEnhancers;
|
|
22
|
-
[FETCH_RSC_INPUT_TRANSFORMERS]?: FetchRscInputTransformers;
|
|
23
|
-
[CALL_SERVER_ELEMENTS_LISTENERS]?: CallServerElementsListeners;
|
|
24
|
-
};
|
|
25
4
|
type FetchRscOptions = {
|
|
26
5
|
signal?: AbortSignal;
|
|
27
6
|
onBuildIdMismatch?: () => void;
|
|
@@ -49,12 +28,7 @@ export declare const unstable_registerFetchEnhancer: (enhance: FetchEnhancer) =>
|
|
|
49
28
|
* (`rscPath`, `rscParams`, `prefetchOnly`) before each request. Returns a
|
|
50
29
|
* function that unregisters the transformer.
|
|
51
30
|
*/
|
|
52
|
-
export declare
|
|
53
|
-
/**
|
|
54
|
-
* @deprecated Pass only the transformer. The store argument is ignored and this
|
|
55
|
-
* overload will be removed in a future release.
|
|
56
|
-
*/
|
|
57
|
-
export declare function unstable_registerFetchRscInputTransformer(store: FetchRscStore, transformFetchRscInput: FetchRscInputTransformer): Unregister;
|
|
31
|
+
export declare const unstable_registerFetchRscInputTransformer: (transformFetchRscInput: FetchRscInputTransformer) => Unregister;
|
|
58
32
|
/** Fetch elements for an RSC path, reusing a cached or prefetched result. */
|
|
59
33
|
export declare const unstable_fetchRsc: (rscPath: string, rscParams?: unknown, options?: FetchRscOptions) => Promise<Elements>;
|
|
60
34
|
/** Eagerly fetch and cache elements so a later fetch reuses them. */
|
|
@@ -71,12 +45,6 @@ export declare const Root: ({ initialRscPath, initialRscParams, children, }: {
|
|
|
71
45
|
export declare const useRefetch: () => Refetch;
|
|
72
46
|
export declare const Children: () => ReactNode;
|
|
73
47
|
export declare const useElementsPromise_UNSTABLE: () => Promise<Elements>;
|
|
74
|
-
/**
|
|
75
|
-
* @deprecated The store is an internal singleton; you no longer need to pass it
|
|
76
|
-
* to `unstable_registerFetchRscInputTransformer`. This will be removed in a
|
|
77
|
-
* future release.
|
|
78
|
-
*/
|
|
79
|
-
export declare const useFetchRscStore_UNSTABLE: () => FetchRscStore;
|
|
80
48
|
/**
|
|
81
49
|
* Slot component
|
|
82
50
|
* This is used under the Root component.
|
package/dist/minimal/client.js
CHANGED
|
@@ -7,7 +7,7 @@ import { consumeInitialRscEntry } from '../lib/utils/initial-rsc.js';
|
|
|
7
7
|
import { addPrefetchEntry, consumePrefetchEntry, hasPrefetchEntry } from '../lib/utils/prefetch-cache.js';
|
|
8
8
|
import { setupDebugChannel } from '../lib/utils/react-debug-channel.js';
|
|
9
9
|
import { encodeFuncId, encodeRscPath } from '../lib/utils/rsc-path.js';
|
|
10
|
-
import {
|
|
10
|
+
import { CALL_SERVER_ELEMENTS_LISTENERS, ENTRY, FETCH_ENHANCERS, FETCH_RSC_INPUT_TRANSFORMERS, SET_ELEMENTS, fetchRscStore } from './client-utils/fetch-store.js';
|
|
11
11
|
const { createFromFetch, encodeReply, createTemporaryReferenceSet } = RSDWClient;
|
|
12
12
|
const DEFAULT_HTML_HEAD = [
|
|
13
13
|
/*#__PURE__*/ _jsx("meta", {
|
|
@@ -55,12 +55,6 @@ const mergeElementsPromise = (a, b)=>{
|
|
|
55
55
|
const cache2 = getCached(()=>new WeakMap(), cache1, a);
|
|
56
56
|
return getCached(getResult, cache2, b);
|
|
57
57
|
};
|
|
58
|
-
const ENTRY = 'e';
|
|
59
|
-
const SET_ELEMENTS = 's';
|
|
60
|
-
const FETCH_ENHANCERS = 'f';
|
|
61
|
-
const FETCH_RSC_INPUT_TRANSFORMERS = 't';
|
|
62
|
-
const CALL_SERVER_ELEMENTS_LISTENERS = 'l';
|
|
63
|
-
const fetchRscStore = {};
|
|
64
58
|
const getFetchFn = ()=>{
|
|
65
59
|
let fetchFn = fetch;
|
|
66
60
|
const enhancers = fetchRscStore[FETCH_ENHANCERS];
|
|
@@ -132,10 +126,6 @@ const fetchRscElements = (rscPath, rscParams, options)=>{
|
|
|
132
126
|
const responsePromise = initial ? initial.response : requestRsc(fetchFn, rscPath, rscParams, temporaryReferences, options?.signal);
|
|
133
127
|
const elements = decodeRsc(responsePromise, temporaryReferences, debug?.debugChannel);
|
|
134
128
|
reloadOnBuildIdMismatch(elements, options?.onBuildIdMismatch);
|
|
135
|
-
if (initial) {
|
|
136
|
-
const { close } = initial;
|
|
137
|
-
waitForRootPrerequisites(elements).then(close, close);
|
|
138
|
-
}
|
|
139
129
|
return elements;
|
|
140
130
|
};
|
|
141
131
|
const applyInputTransformers = (rscPath, rscParams, prefetchOnly)=>{
|
|
@@ -201,24 +191,17 @@ const fetchRscInternal = (rscPath, rscParams, prefetchOnly, options)=>{
|
|
|
201
191
|
fetchEnhancers.delete(enhance);
|
|
202
192
|
};
|
|
203
193
|
};
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} else {
|
|
210
|
-
transformFetchRscInput = maybeTransform;
|
|
211
|
-
if (!registerTransformerStoreArgWarned) {
|
|
212
|
-
registerTransformerStoreArgWarned = true;
|
|
213
|
-
console.warn('[waku] Passing a store to `unstable_registerFetchRscInputTransformer` is deprecated. Pass only the transformer.');
|
|
214
|
-
}
|
|
215
|
-
}
|
|
194
|
+
/**
|
|
195
|
+
* Register a transformer that rewrites the RSC fetch input
|
|
196
|
+
* (`rscPath`, `rscParams`, `prefetchOnly`) before each request. Returns a
|
|
197
|
+
* function that unregisters the transformer.
|
|
198
|
+
*/ export const unstable_registerFetchRscInputTransformer = (transformFetchRscInput)=>{
|
|
216
199
|
const fetchRscInputTransformers = fetchRscStore[FETCH_RSC_INPUT_TRANSFORMERS] ||= new Set();
|
|
217
200
|
fetchRscInputTransformers.add(transformFetchRscInput);
|
|
218
201
|
return ()=>{
|
|
219
202
|
fetchRscInputTransformers.delete(transformFetchRscInput);
|
|
220
203
|
};
|
|
221
|
-
}
|
|
204
|
+
};
|
|
222
205
|
const registerHmrRefetch = (refetch)=>{
|
|
223
206
|
globalThis.__WAKU_RSC_RELOAD_LISTENERS__ ||= [];
|
|
224
207
|
const index = globalThis.__WAKU_RSC_RELOAD_LISTENERS__.indexOf(globalThis.__WAKU_REFETCH_RSC__);
|
|
@@ -293,18 +276,6 @@ export const useElementsPromise_UNSTABLE = ()=>{
|
|
|
293
276
|
}
|
|
294
277
|
return elementsPromise;
|
|
295
278
|
};
|
|
296
|
-
let useFetchRscStoreDeprecationWarned = false;
|
|
297
|
-
/**
|
|
298
|
-
* @deprecated The store is an internal singleton; you no longer need to pass it
|
|
299
|
-
* to `unstable_registerFetchRscInputTransformer`. This will be removed in a
|
|
300
|
-
* future release.
|
|
301
|
-
*/ export const useFetchRscStore_UNSTABLE = ()=>{
|
|
302
|
-
if (!useFetchRscStoreDeprecationWarned) {
|
|
303
|
-
useFetchRscStoreDeprecationWarned = true;
|
|
304
|
-
console.warn('[waku] `useFetchRscStore_UNSTABLE` is deprecated and will be removed.');
|
|
305
|
-
}
|
|
306
|
-
return fetchRscStore;
|
|
307
|
-
};
|
|
308
279
|
/**
|
|
309
280
|
* Slot component
|
|
310
281
|
* This is used under the Root component.
|