waku 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/lib/builder.js +1 -1
- package/dist/cjs/lib/config.js +13 -8
- package/dist/cjs/lib/middleware/ssr/utils.js +23 -11
- package/dist/cjs/lib/vite-plugin/rsc-index-plugin.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/lib/builder.js +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +13 -8
- package/dist/lib/middleware/ssr/utils.d.ts +1 -1
- package/dist/lib/middleware/ssr/utils.js +24 -12
- package/dist/lib/vite-plugin/rsc-index-plugin.js +1 -1
- package/package.json +1 -1
- package/src/config.ts +1 -1
- package/src/lib/builder.ts +1 -1
- package/src/lib/config.ts +15 -6
- package/src/lib/middleware/ssr/utils.ts +30 -13
- package/src/lib/middleware/ssr.ts +1 -1
- package/src/lib/vite-plugin/rsc-index-plugin.ts +1 -1
package/README.md
CHANGED
|
@@ -29,9 +29,8 @@ pnpm create waku # pnpm not working for now
|
|
|
29
29
|
- [x] `waku start` (production server)
|
|
30
30
|
- [x] Exportable build
|
|
31
31
|
- [x] Static site generation
|
|
32
|
-
- [ ] Client rendering fallback
|
|
33
32
|
- [x] Opt-in router (reference implementation)
|
|
34
|
-
- [
|
|
33
|
+
- [x] Opt-in SSR (HTML generation only)
|
|
35
34
|
|
|
36
35
|
## Tweets
|
|
37
36
|
|
|
@@ -72,6 +71,7 @@ pnpm create waku # pnpm not working for now
|
|
|
72
71
|
- https://twitter.com/dai_shi/status/1664286329763684353
|
|
73
72
|
- https://twitter.com/dai_shi/status/1664989534889861123
|
|
74
73
|
- https://twitter.com/dai_shi/status/1667545252654366721
|
|
74
|
+
- https://twitter.com/dai_shi/status/1670650381762961408
|
|
75
75
|
|
|
76
76
|
</details>
|
|
77
77
|
|
package/dist/cjs/lib/builder.js
CHANGED
|
@@ -272,7 +272,7 @@ const emitHtmlFiles = async (config, buildConfig, getClientModules)=>{
|
|
|
272
272
|
const code = (0, _utils.generatePrefetchCode)(basePrefix, elementsForPrefetch, moduleIdsForPrefetch) + (customCode || "");
|
|
273
273
|
if (code) {
|
|
274
274
|
// HACK is this too naive to inject script code?
|
|
275
|
-
data = data.replace(/<\/
|
|
275
|
+
data = data.replace(/<\/head>/, `<script>${code}</script></head>`);
|
|
276
276
|
}
|
|
277
277
|
const htmlReadable = !skipSsr && await renderHtml(config, pathStr, data);
|
|
278
278
|
if (htmlReadable) {
|
package/dist/cjs/lib/config.js
CHANGED
|
@@ -18,16 +18,21 @@ _export(exports, {
|
|
|
18
18
|
});
|
|
19
19
|
const _vite = require("vite");
|
|
20
20
|
const splitHTML = (htmlStr)=>{
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const P1 = [
|
|
22
|
+
"<!--placeholder1-->\\s*<div[^>]*>",
|
|
23
|
+
"</div>\\s*<!--/placeholder1-->"
|
|
24
|
+
];
|
|
25
|
+
const P2 = [
|
|
26
|
+
"<!--placeholder2-->",
|
|
27
|
+
"<!--/placeholder2-->"
|
|
28
|
+
];
|
|
29
|
+
const anyRE = "[\\s\\S]*";
|
|
30
|
+
const match = htmlStr.match(new RegExp(// prettier-ignore
|
|
31
|
+
"^(" + anyRE + P1[0] + ")" + anyRE + "(" + P1[1] + anyRE + P2[0] + ")" + anyRE + "(" + P2[1] + anyRE + ")$"));
|
|
32
|
+
if (match?.length !== 1 + 3) {
|
|
25
33
|
throw new Error("Failed to split HTML");
|
|
26
34
|
}
|
|
27
|
-
return
|
|
28
|
-
splitted[0] + startStr,
|
|
29
|
-
endStr + splitted[1]
|
|
30
|
-
];
|
|
35
|
+
return match.slice(1);
|
|
31
36
|
};
|
|
32
37
|
const getFallback = (id)=>{
|
|
33
38
|
if (id.endsWith("#Waku_SSR_Capable_Link")) {
|
|
@@ -19,6 +19,27 @@ function _interop_require_default(obj) {
|
|
|
19
19
|
// eslint-disable-next-line import/no-named-as-default-member
|
|
20
20
|
const { renderToPipeableStream } = _server.default;
|
|
21
21
|
const { createFromNodeStream } = _clientnodeunbundled.default;
|
|
22
|
+
const interleaveHtmlSnippets = (preamble, intermediate, postamble)=>{
|
|
23
|
+
let initialized = false;
|
|
24
|
+
return new _nodestream.Transform({
|
|
25
|
+
transform (chunk, encoding, callback) {
|
|
26
|
+
if (encoding !== "buffer") {
|
|
27
|
+
throw new Error("Unknown encoding");
|
|
28
|
+
}
|
|
29
|
+
if (!initialized) {
|
|
30
|
+
initialized = true;
|
|
31
|
+
const data = chunk.toString();
|
|
32
|
+
callback(null, preamble + data + intermediate);
|
|
33
|
+
} else {
|
|
34
|
+
callback(null, chunk);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
final (callback) {
|
|
38
|
+
this.push(postamble);
|
|
39
|
+
callback();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
22
43
|
const renderHtmlToReadable = (htmlStr, rscStream, splitHTML, getFallback)=>{
|
|
23
44
|
const bundlerConfig = new Proxy({}, {
|
|
24
45
|
get (_target, filePath) {
|
|
@@ -34,16 +55,8 @@ const renderHtmlToReadable = (htmlStr, rscStream, splitHTML, getFallback)=>{
|
|
|
34
55
|
});
|
|
35
56
|
}
|
|
36
57
|
});
|
|
37
|
-
const passthrough = new _nodestream.PassThrough();
|
|
38
|
-
const [preamble, postamble] = splitHTML(htmlStr);
|
|
39
|
-
passthrough.write(preamble, "utf8");
|
|
40
58
|
const data = createFromNodeStream(rscStream, bundlerConfig);
|
|
41
|
-
|
|
42
|
-
passthrough.end = (...args)=>{
|
|
43
|
-
passthrough.write(postamble, "utf8");
|
|
44
|
-
return origEnd.apply(passthrough, args); // FIXME how to avoid any?
|
|
45
|
-
};
|
|
46
|
-
renderToPipeableStream(data, {
|
|
59
|
+
return renderToPipeableStream(data, {
|
|
47
60
|
onError (err) {
|
|
48
61
|
if (err instanceof Error && err.message.startsWith("Client-only component")) {
|
|
49
62
|
// ignore
|
|
@@ -51,6 +64,5 @@ const renderHtmlToReadable = (htmlStr, rscStream, splitHTML, getFallback)=>{
|
|
|
51
64
|
}
|
|
52
65
|
console.error(err);
|
|
53
66
|
}
|
|
54
|
-
}).pipe(
|
|
55
|
-
return passthrough;
|
|
67
|
+
}).pipe(interleaveHtmlSnippets(...splitHTML(htmlStr)));
|
|
56
68
|
};
|
package/dist/config.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface FrameworkConfig {
|
|
|
6
6
|
rscPrefix?: string;
|
|
7
7
|
ssr?: {
|
|
8
8
|
rscServer?: string;
|
|
9
|
-
splitHTML?: (htmlStr: string) => readonly [string, string];
|
|
9
|
+
splitHTML?: (htmlStr: string) => readonly [string, string, string];
|
|
10
10
|
getFallback?: (id: string) => string;
|
|
11
11
|
};
|
|
12
12
|
}
|
package/dist/lib/builder.js
CHANGED
|
@@ -257,7 +257,7 @@ const emitHtmlFiles = async (config, buildConfig, getClientModules)=>{
|
|
|
257
257
|
const code = generatePrefetchCode(basePrefix, elementsForPrefetch, moduleIdsForPrefetch) + (customCode || "");
|
|
258
258
|
if (code) {
|
|
259
259
|
// HACK is this too naive to inject script code?
|
|
260
|
-
data = data.replace(/<\/
|
|
260
|
+
data = data.replace(/<\/head>/, `<script>${code}</script></head>`);
|
|
261
261
|
}
|
|
262
262
|
const htmlReadable = !skipSsr && await renderHtml(config, pathStr, data);
|
|
263
263
|
if (htmlReadable) {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare function resolveConfig(command: "build" | "serve"): Promise<{
|
|
|
11
11
|
rscPrefix: string;
|
|
12
12
|
ssr: {
|
|
13
13
|
rscServer: string;
|
|
14
|
-
splitHTML: (htmlStr: string) => readonly [string, string];
|
|
14
|
+
splitHTML: (htmlStr: string) => readonly [string, string, string];
|
|
15
15
|
getFallback: (id: string) => string;
|
|
16
16
|
};
|
|
17
17
|
};
|
package/dist/lib/config.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { resolveConfig as viteResolveConfig } from "vite";
|
|
2
2
|
const splitHTML = (htmlStr)=>{
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const P1 = [
|
|
4
|
+
"<!--placeholder1-->\\s*<div[^>]*>",
|
|
5
|
+
"</div>\\s*<!--/placeholder1-->"
|
|
6
|
+
];
|
|
7
|
+
const P2 = [
|
|
8
|
+
"<!--placeholder2-->",
|
|
9
|
+
"<!--/placeholder2-->"
|
|
10
|
+
];
|
|
11
|
+
const anyRE = "[\\s\\S]*";
|
|
12
|
+
const match = htmlStr.match(new RegExp(// prettier-ignore
|
|
13
|
+
"^(" + anyRE + P1[0] + ")" + anyRE + "(" + P1[1] + anyRE + P2[0] + ")" + anyRE + "(" + P2[1] + anyRE + ")$"));
|
|
14
|
+
if (match?.length !== 1 + 3) {
|
|
7
15
|
throw new Error("Failed to split HTML");
|
|
8
16
|
}
|
|
9
|
-
return
|
|
10
|
-
splitted[0] + startStr,
|
|
11
|
-
endStr + splitted[1]
|
|
12
|
-
];
|
|
17
|
+
return match.slice(1);
|
|
13
18
|
};
|
|
14
19
|
const getFallback = (id)=>{
|
|
15
20
|
if (id.endsWith("#Waku_SSR_Capable_Link")) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { Readable } from "node:stream";
|
|
2
|
-
export declare const renderHtmlToReadable: (htmlStr: string, rscStream: Readable, splitHTML: (htmlStr: string) => readonly [string, string], getFallback: (id: string) => string) => Readable;
|
|
2
|
+
export declare const renderHtmlToReadable: (htmlStr: string, rscStream: Readable, splitHTML: (htmlStr: string) => readonly [string, string, string], getFallback: (id: string) => string) => Readable;
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
2
|
import RDServer from "react-dom/server";
|
|
3
3
|
import RSDWClient from "react-server-dom-webpack/client.node.unbundled";
|
|
4
4
|
// eslint-disable-next-line import/no-named-as-default-member
|
|
5
5
|
const { renderToPipeableStream } = RDServer;
|
|
6
6
|
const { createFromNodeStream } = RSDWClient;
|
|
7
|
+
const interleaveHtmlSnippets = (preamble, intermediate, postamble)=>{
|
|
8
|
+
let initialized = false;
|
|
9
|
+
return new Transform({
|
|
10
|
+
transform (chunk, encoding, callback) {
|
|
11
|
+
if (encoding !== "buffer") {
|
|
12
|
+
throw new Error("Unknown encoding");
|
|
13
|
+
}
|
|
14
|
+
if (!initialized) {
|
|
15
|
+
initialized = true;
|
|
16
|
+
const data = chunk.toString();
|
|
17
|
+
callback(null, preamble + data + intermediate);
|
|
18
|
+
} else {
|
|
19
|
+
callback(null, chunk);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
final (callback) {
|
|
23
|
+
this.push(postamble);
|
|
24
|
+
callback();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
7
28
|
export const renderHtmlToReadable = (htmlStr, rscStream, splitHTML, getFallback)=>{
|
|
8
29
|
const bundlerConfig = new Proxy({}, {
|
|
9
30
|
get (_target, filePath) {
|
|
@@ -19,16 +40,8 @@ export const renderHtmlToReadable = (htmlStr, rscStream, splitHTML, getFallback)
|
|
|
19
40
|
});
|
|
20
41
|
}
|
|
21
42
|
});
|
|
22
|
-
const passthrough = new PassThrough();
|
|
23
|
-
const [preamble, postamble] = splitHTML(htmlStr);
|
|
24
|
-
passthrough.write(preamble, "utf8");
|
|
25
43
|
const data = createFromNodeStream(rscStream, bundlerConfig);
|
|
26
|
-
|
|
27
|
-
passthrough.end = (...args)=>{
|
|
28
|
-
passthrough.write(postamble, "utf8");
|
|
29
|
-
return origEnd.apply(passthrough, args); // FIXME how to avoid any?
|
|
30
|
-
};
|
|
31
|
-
renderToPipeableStream(data, {
|
|
44
|
+
return renderToPipeableStream(data, {
|
|
32
45
|
onError (err) {
|
|
33
46
|
if (err instanceof Error && err.message.startsWith("Client-only component")) {
|
|
34
47
|
// ignore
|
|
@@ -36,6 +49,5 @@ export const renderHtmlToReadable = (htmlStr, rscStream, splitHTML, getFallback)
|
|
|
36
49
|
}
|
|
37
50
|
console.error(err);
|
|
38
51
|
}
|
|
39
|
-
}).pipe(
|
|
40
|
-
return passthrough;
|
|
52
|
+
}).pipe(interleaveHtmlSnippets(...splitHTML(htmlStr)));
|
|
41
53
|
};
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface FrameworkConfig {
|
|
|
7
7
|
rscPrefix?: string; // defaults to "RSC/"
|
|
8
8
|
ssr?: {
|
|
9
9
|
rscServer?: string;
|
|
10
|
-
splitHTML?: (htmlStr: string) => readonly [string, string];
|
|
10
|
+
splitHTML?: (htmlStr: string) => readonly [string, string, string];
|
|
11
11
|
getFallback?: (id: string) => string;
|
|
12
12
|
};
|
|
13
13
|
}
|
package/src/lib/builder.ts
CHANGED
|
@@ -332,7 +332,7 @@ const emitHtmlFiles = async (
|
|
|
332
332
|
) + (customCode || "");
|
|
333
333
|
if (code) {
|
|
334
334
|
// HACK is this too naive to inject script code?
|
|
335
|
-
data = data.replace(/<\/
|
|
335
|
+
data = data.replace(/<\/head>/, `<script>${code}</script></head>`);
|
|
336
336
|
}
|
|
337
337
|
const htmlReadable =
|
|
338
338
|
!skipSsr && (await renderHtml(config, pathStr, data));
|
package/src/lib/config.ts
CHANGED
|
@@ -8,14 +8,23 @@ type DeepRequired<T> = T extends (...args: any[]) => any
|
|
|
8
8
|
? { [P in keyof T]-?: DeepRequired<T[P]> }
|
|
9
9
|
: T;
|
|
10
10
|
|
|
11
|
-
const splitHTML = (htmlStr: string): readonly [string, string] => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const splitHTML = (htmlStr: string): readonly [string, string, string] => {
|
|
12
|
+
const P1 = [
|
|
13
|
+
"<!--placeholder1-->\\s*<div[^>]*>",
|
|
14
|
+
"</div>\\s*<!--/placeholder1-->",
|
|
15
|
+
] as const;
|
|
16
|
+
const P2 = ["<!--placeholder2-->", "<!--/placeholder2-->"] as const;
|
|
17
|
+
const anyRE = "[\\s\\S]*";
|
|
18
|
+
const match = htmlStr.match(
|
|
19
|
+
new RegExp(
|
|
20
|
+
// prettier-ignore
|
|
21
|
+
"^(" + anyRE + P1[0] + ")" + anyRE + "(" + P1[1] + anyRE + P2[0] + ")" + anyRE + "(" + P2[1] + anyRE + ")$"
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
if (match?.length !== 1 + 3) {
|
|
16
25
|
throw new Error("Failed to split HTML");
|
|
17
26
|
}
|
|
18
|
-
return
|
|
27
|
+
return match.slice(1) as [string, string, string];
|
|
19
28
|
};
|
|
20
29
|
|
|
21
30
|
const getFallback = (id: string) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
2
|
import type { Readable } from "node:stream";
|
|
3
3
|
|
|
4
4
|
import RDServer from "react-dom/server";
|
|
@@ -8,10 +8,36 @@ import RSDWClient from "react-server-dom-webpack/client.node.unbundled";
|
|
|
8
8
|
const { renderToPipeableStream } = RDServer;
|
|
9
9
|
const { createFromNodeStream } = RSDWClient;
|
|
10
10
|
|
|
11
|
+
const interleaveHtmlSnippets = (
|
|
12
|
+
preamble: string,
|
|
13
|
+
intermediate: string,
|
|
14
|
+
postamble: string
|
|
15
|
+
) => {
|
|
16
|
+
let initialized = false;
|
|
17
|
+
return new Transform({
|
|
18
|
+
transform(chunk, encoding, callback) {
|
|
19
|
+
if (encoding !== ("buffer" as any)) {
|
|
20
|
+
throw new Error("Unknown encoding");
|
|
21
|
+
}
|
|
22
|
+
if (!initialized) {
|
|
23
|
+
initialized = true;
|
|
24
|
+
const data = chunk.toString();
|
|
25
|
+
callback(null, preamble + data + intermediate);
|
|
26
|
+
} else {
|
|
27
|
+
callback(null, chunk);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
final(callback) {
|
|
31
|
+
this.push(postamble);
|
|
32
|
+
callback();
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
11
37
|
export const renderHtmlToReadable = (
|
|
12
38
|
htmlStr: string, // Hope stream works, but it'd be too tricky
|
|
13
39
|
rscStream: Readable,
|
|
14
|
-
splitHTML: (htmlStr: string) => readonly [string, string],
|
|
40
|
+
splitHTML: (htmlStr: string) => readonly [string, string, string],
|
|
15
41
|
getFallback: (id: string) => string
|
|
16
42
|
): Readable => {
|
|
17
43
|
const bundlerConfig = new Proxy(
|
|
@@ -31,16 +57,8 @@ export const renderHtmlToReadable = (
|
|
|
31
57
|
},
|
|
32
58
|
}
|
|
33
59
|
);
|
|
34
|
-
const passthrough = new PassThrough();
|
|
35
|
-
const [preamble, postamble] = splitHTML(htmlStr);
|
|
36
|
-
passthrough.write(preamble, "utf8");
|
|
37
60
|
const data = createFromNodeStream(rscStream, bundlerConfig);
|
|
38
|
-
|
|
39
|
-
passthrough.end = (...args) => {
|
|
40
|
-
passthrough.write(postamble, "utf8");
|
|
41
|
-
return origEnd.apply(passthrough, args as any); // FIXME how to avoid any?
|
|
42
|
-
};
|
|
43
|
-
renderToPipeableStream(data, {
|
|
61
|
+
return renderToPipeableStream(data, {
|
|
44
62
|
onError(err) {
|
|
45
63
|
if (
|
|
46
64
|
err instanceof Error &&
|
|
@@ -51,6 +69,5 @@ export const renderHtmlToReadable = (
|
|
|
51
69
|
}
|
|
52
70
|
console.error(err);
|
|
53
71
|
},
|
|
54
|
-
}).pipe(
|
|
55
|
-
return passthrough;
|
|
72
|
+
}).pipe(interleaveHtmlSnippets(...splitHTML(htmlStr)));
|
|
56
73
|
};
|
|
@@ -26,7 +26,7 @@ const renderHTML = async (
|
|
|
26
26
|
rscServer: URL,
|
|
27
27
|
config: Awaited<ReturnType<typeof resolveConfig>>,
|
|
28
28
|
ssrConfig: NonNullable<Awaited<ReturnType<GetSsrConfig>>>
|
|
29
|
-
)
|
|
29
|
+
) => {
|
|
30
30
|
const rscPrefix = config.framework.rscPrefix;
|
|
31
31
|
const { splitHTML, getFallback } = config.framework.ssr;
|
|
32
32
|
const htmlResPromise = fetch(rscServer + pathStr.slice(1), {
|