dinou 6.1.1 → 7.1.0
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/CHANGELOG.md +106 -0
- package/README.md +9352 -1081
- package/cli.js +314 -91
- package/dinou/adapters/bun.js +62 -0
- package/dinou/adapters/cloudflare.js +65 -0
- package/dinou/adapters/deno.js +48 -0
- package/dinou/adapters/netlify.js +85 -0
- package/dinou/adapters/node.mjs +31 -0
- package/dinou/bun/build.mjs +1077 -0
- package/dinou/cloudflare/build.mjs +1121 -0
- package/dinou/cloudflare/shims/child_process.js +14 -0
- package/dinou/cloudflare/shims/empty.js +2 -0
- package/dinou/cloudflare/shims/fs.js +99 -0
- package/dinou/cloudflare/shims/os.js +29 -0
- package/dinou/config.js +1 -0
- package/dinou/config.mjs +2 -0
- package/dinou/constants.js +29 -4
- package/dinou/core/babel-esm-loader.js +66 -37
- package/dinou/core/bailout-proxy.js +61 -0
- package/dinou/core/build-static-pages.js +322 -165
- package/dinou/core/cache-revalidate.js +471 -144
- package/dinou/core/cache-slot.js +387 -0
- package/dinou/core/client-redirect.jsx +12 -7
- package/dinou/core/client-webpack.jsx +457 -82
- package/dinou/core/client.jsx +452 -82
- package/dinou/core/config.js +11 -0
- package/dinou/core/dinou-paths.js +52 -0
- package/dinou/core/edge-ssr.js +122 -0
- package/dinou/core/export-static.mjs +64 -0
- package/dinou/core/generate-route-types.js +252 -0
- package/dinou/core/generate-static-page.js +55 -94
- package/dinou/core/generate-static-rsc.js +47 -15
- package/dinou/core/generate-static.js +212 -10
- package/dinou/core/generating-isg.js +5 -0
- package/dinou/core/get-asset-from-manifest.js +9 -3
- package/dinou/core/get-error-jsx.js +221 -41
- package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
- package/dinou/core/get-jsx.js +175 -96
- package/dinou/core/handler.js +2303 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +183 -0
- package/dinou/core/link.jsx +18 -6
- package/dinou/core/load-dinou-config.js +60 -0
- package/dinou/core/manifest-provider.js +261 -0
- package/dinou/core/navigation-utils.js +4 -0
- package/dinou/core/navigation.js +50 -39
- package/dinou/core/parse-exports.js +252 -2
- package/dinou/core/ppr-context.js +105 -0
- package/dinou/core/ppr-runtime.js +263 -0
- package/dinou/core/redirect.jsx +20 -19
- package/dinou/core/render-jsx-to-client-jsx.js +91 -87
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +34 -0
- package/dinou/core/revalidating.js +28 -9
- package/dinou/core/route-generator.js +107 -0
- package/dinou/core/rsc-renderer.js +90 -0
- package/dinou/core/run-ssg.js +19 -0
- package/dinou/core/scan-dependency-components.js +218 -0
- package/dinou/core/server-function-proxy-webpack.js +3 -0
- package/dinou/core/server-function-proxy.js +3 -0
- package/dinou/core/slot.js +302 -0
- package/dinou/core/status-manifest.js +41 -1
- package/dinou/core/storage-adapter.js +522 -0
- package/dinou/core/url-resolver.js +16 -16
- package/dinou/core/vfs.js +139 -68
- package/dinou/deno/build.mjs +1112 -0
- package/dinou/deno/seed-kv.js +115 -0
- package/dinou/esbuild/build.mjs +51 -1
- package/dinou/esbuild/dev.mjs +290 -224
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
- package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
- package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
- package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
- package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
- package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
- package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
- package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
- package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
- package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
- package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
- package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
- package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
- package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
- package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
- package/dinou/index.d.ts +315 -11
- package/dinou/index.js +12 -6
- package/dinou/index.mjs +9 -0
- package/dinou/node/build.mjs +195 -0
- package/dinou/node/bundle-dual-engine.mjs +1012 -0
- package/dinou/node/dev.mjs +2589 -0
- package/dinou/node/port-selector.mjs +164 -0
- package/dinou/node/terminal-status.mjs +345 -0
- package/dinou/package.json +16 -14
- package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
- package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
- package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
- package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
- package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
- package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
- package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
- package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
- package/dinou/rollup/rollup.config.js +155 -43
- package/dinou/server.d.ts +114 -12
- package/dinou/server.js +35 -10
- package/dinou/server.mjs +30 -9
- package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
- package/dinou/webpack/loaders/server-functions-loader.js +22 -1
- package/dinou/webpack/loaders/swc-loader.js +76 -0
- package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
- package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
- package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
- package/dinou/webpack/webpack.config.js +227 -29
- package/eject.js +113 -34
- package/package.json +18 -16
- package/dinou/core/asset-require-hook.js +0 -58
- package/dinou/core/client-error-webpack.jsx +0 -432
- package/dinou/core/client-error.jsx +0 -432
- package/dinou/core/concurrency-manager.js +0 -48
- package/dinou/core/context-proxy.js +0 -68
- package/dinou/core/generate-static-pages.js +0 -130
- package/dinou/core/generate-static-rscs.js +0 -151
- package/dinou/core/get-ssg-metadata.js +0 -31
- package/dinou/core/register-loader.mjs +0 -11
- package/dinou/core/register-paths.js +0 -27
- package/dinou/core/render-app-to-html.js +0 -430
- package/dinou/core/render-html.js +0 -407
- package/dinou/core/server.js +0 -1542
- package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// dinou/core/ppr-context.js
|
|
2
|
+
// PPR Pre-rendering Context and Suspense Hole Tracker for Dinou v7.2.
|
|
3
|
+
|
|
4
|
+
const { AsyncLocalStorage } = require("async_hooks");
|
|
5
|
+
|
|
6
|
+
const pprStorage = new AsyncLocalStorage();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Executes a function within a PPR pre-render context.
|
|
10
|
+
* @param {object} options
|
|
11
|
+
* @param {Function} callback
|
|
12
|
+
* @returns {Promise<any>}
|
|
13
|
+
*/
|
|
14
|
+
function runWithPprContext(options, callback) {
|
|
15
|
+
const store = {
|
|
16
|
+
isPpr: Boolean(options?.isPpr),
|
|
17
|
+
suspenseDepth: 0,
|
|
18
|
+
currentHoleId: null,
|
|
19
|
+
holes: new Map(),
|
|
20
|
+
nextHoleIndex: 0,
|
|
21
|
+
...options,
|
|
22
|
+
};
|
|
23
|
+
return pprStorage.run(store, callback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getPprStore() {
|
|
27
|
+
return pprStorage.getStore() || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isPprBuildActive() {
|
|
31
|
+
const store = getPprStore();
|
|
32
|
+
return Boolean(store && store.isPpr);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isInsideSuspense() {
|
|
36
|
+
const store = getPprStore();
|
|
37
|
+
return Boolean(store && store.suspenseDepth > 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function enterSuspense(holeId = null) {
|
|
41
|
+
const store = getPprStore();
|
|
42
|
+
if (store) {
|
|
43
|
+
store.suspenseDepth++;
|
|
44
|
+
if (!holeId) {
|
|
45
|
+
store.nextHoleIndex++;
|
|
46
|
+
holeId = `ppr-hole-${store.nextHoleIndex}`;
|
|
47
|
+
}
|
|
48
|
+
store.currentHoleId = holeId;
|
|
49
|
+
return holeId;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function exitSuspense() {
|
|
55
|
+
const store = getPprStore();
|
|
56
|
+
if (store) {
|
|
57
|
+
store.suspenseDepth = Math.max(0, store.suspenseDepth - 1);
|
|
58
|
+
if (store.suspenseDepth === 0) {
|
|
59
|
+
store.currentHoleId = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function markDynamicAccess(type = "dynamic") {
|
|
65
|
+
const store = getPprStore();
|
|
66
|
+
if (store && store.isPpr && store.suspenseDepth > 0) {
|
|
67
|
+
const holeId = store.currentHoleId || `ppr-hole-${store.nextHoleIndex}`;
|
|
68
|
+
const error = new Error(`[Dinou PPR] Dynamic access to '${type}' postponed inside Suspense`);
|
|
69
|
+
error.$$typeof = Symbol.for("dinou.ppr.postpone");
|
|
70
|
+
error.pprType = type;
|
|
71
|
+
error.holeId = holeId;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function registerHole(holeId, meta = {}) {
|
|
77
|
+
const store = getPprStore();
|
|
78
|
+
if (store) {
|
|
79
|
+
store.holes.set(holeId, {
|
|
80
|
+
id: holeId,
|
|
81
|
+
registeredAt: Date.now(),
|
|
82
|
+
...meta,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getRegisteredHoles() {
|
|
88
|
+
const store = getPprStore();
|
|
89
|
+
if (store) {
|
|
90
|
+
return Array.from(store.holes.keys());
|
|
91
|
+
}
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
runWithPprContext,
|
|
97
|
+
getPprStore,
|
|
98
|
+
isPprBuildActive,
|
|
99
|
+
isInsideSuspense,
|
|
100
|
+
enterSuspense,
|
|
101
|
+
exitSuspense,
|
|
102
|
+
markDynamicAccess,
|
|
103
|
+
registerHole,
|
|
104
|
+
getRegisteredHoles,
|
|
105
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// dinou/core/ppr-runtime.js
|
|
2
|
+
// Dinou Partial Prerendering (PPR) Runtime Streaming Resume Engine.
|
|
3
|
+
// Sends the pre-rendered shell immediately (0ms TTFB) and progressively
|
|
4
|
+
// streams dynamic Suspense holes as they resolve over the same HTTP connection.
|
|
5
|
+
// Designed for the Dual-Bundle architecture (Pass A: RSC, Pass B: SSR).
|
|
6
|
+
|
|
7
|
+
const React = require("react");
|
|
8
|
+
const { renderRSCStream } = require("./rsc-renderer.js");
|
|
9
|
+
const { getClientManifest } = require("./manifest-provider.js");
|
|
10
|
+
const { requestStorage, setCurrentContext } = require("./request-context.js");
|
|
11
|
+
const getJSX = require("./get-jsx.js");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Traverses a React JSX element tree to find Suspense boundaries,
|
|
15
|
+
* evaluates their dynamic children, and streams replacement chunks.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} node - Root JSX node (layout + page)
|
|
18
|
+
* @param {Array<string|object>} expectedHoles - Holes registered at build time
|
|
19
|
+
* @param {WritableStreamDefaultWriter} writer - Response stream writer
|
|
20
|
+
* @param {TextEncoder} encoder - Text encoder
|
|
21
|
+
* @param {object} platformContext - Dinou platform context (containing renderHtmlStream)
|
|
22
|
+
* @param {boolean} isDevelopment - Dev mode flag
|
|
23
|
+
*/
|
|
24
|
+
async function streamDynamicHoles(
|
|
25
|
+
node,
|
|
26
|
+
expectedHoles,
|
|
27
|
+
writer,
|
|
28
|
+
encoder,
|
|
29
|
+
platformContext,
|
|
30
|
+
isDevelopment = false
|
|
31
|
+
) {
|
|
32
|
+
let holeIndex = 0;
|
|
33
|
+
const holePromises = [];
|
|
34
|
+
|
|
35
|
+
const knownHoleIds = new Set(
|
|
36
|
+
(expectedHoles || []).map((h) => (typeof h === "object" ? h.id : h))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const clientManifest = getClientManifest();
|
|
40
|
+
|
|
41
|
+
function isClientComponent(type) {
|
|
42
|
+
if (!type) return false;
|
|
43
|
+
return (
|
|
44
|
+
type.$$typeof === Symbol.for("react.client.reference") ||
|
|
45
|
+
Boolean(type.$$id) ||
|
|
46
|
+
Boolean(type.filepath)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function traverse(current) {
|
|
51
|
+
if (!current) return;
|
|
52
|
+
|
|
53
|
+
// Handle Client Component: do NOT execute, only traverse children
|
|
54
|
+
if (isClientComponent(current?.type) || current?.$$typeof === Symbol.for("react.client.reference")) {
|
|
55
|
+
if (current?.props?.children) {
|
|
56
|
+
await traverse(current.props.children);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle React Server Component function
|
|
62
|
+
if (typeof current?.type === "function") {
|
|
63
|
+
try {
|
|
64
|
+
const evaluated = await current.type(current.props || {});
|
|
65
|
+
await traverse(evaluated);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error("[Dinou PPR] Error evaluating component in hole traversal:", err);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle React.forward_ref
|
|
73
|
+
if (
|
|
74
|
+
typeof current?.type === "object" &&
|
|
75
|
+
current.type !== null &&
|
|
76
|
+
current.type.$$typeof === Symbol.for("react.forward_ref")
|
|
77
|
+
) {
|
|
78
|
+
try {
|
|
79
|
+
const rendered = current.type.render(current.props || {}, current.ref);
|
|
80
|
+
await traverse(rendered);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error("[Dinou PPR] Error evaluating forward_ref in hole traversal:", err);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle React.Suspense
|
|
88
|
+
if (
|
|
89
|
+
current?.type === Symbol.for("react.suspense") ||
|
|
90
|
+
current?.type === React.Suspense
|
|
91
|
+
) {
|
|
92
|
+
holeIndex++;
|
|
93
|
+
const currentHoleId = `ppr-hole-${holeIndex}`;
|
|
94
|
+
|
|
95
|
+
// Check if this boundary was registered as a dynamic hole
|
|
96
|
+
const shouldStream =
|
|
97
|
+
knownHoleIds.size === 0 || knownHoleIds.has(currentHoleId);
|
|
98
|
+
|
|
99
|
+
if (shouldStream) {
|
|
100
|
+
const children = current.props?.children;
|
|
101
|
+
const promise = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
let html = "";
|
|
104
|
+
if (platformContext && platformContext.renderHtmlStream) {
|
|
105
|
+
const rscStream = renderRSCStream(children, clientManifest, { runtime: "edge" });
|
|
106
|
+
const htmlStream = await platformContext.renderHtmlStream(rscStream, {
|
|
107
|
+
bootstrapModules: [],
|
|
108
|
+
});
|
|
109
|
+
html = await new Response(htmlStream).text();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const chunk = `\n<template id="ppr-content-${currentHoleId}">${html}</template>\n<script>(function(){var c=document.getElementById("ppr-content-${currentHoleId}");if(!c)return;var b=document.getElementById("B:${holeIndex - 1}");var t=document.querySelector('[data-ppr-hole="${currentHoleId}"]')||(b?b.nextElementSibling:null);try{if(t){t.replaceWith(c.content?c.content:...c.childNodes);}}finally{c.remove();}})();</script>\n`;
|
|
113
|
+
await writer.write(encoder.encode(chunk));
|
|
114
|
+
} catch (holeErr) {
|
|
115
|
+
console.error(`[Dinou PPR] Error resolving dynamic hole ${currentHoleId}:`, holeErr);
|
|
116
|
+
const errMsg = isDevelopment
|
|
117
|
+
? (holeErr?.message || "Unknown dynamic error")
|
|
118
|
+
: "Error loading dynamic content";
|
|
119
|
+
const errChunk = `\n<template id="ppr-content-${currentHoleId}"><div style="color:#e11d48;padding:8px 12px;background:#fff1f2;border:1px solid #fecdd3;border-radius:6px;font-size:0.875rem;font-family:system-ui,sans-serif;">[PPR Error: ${errMsg}]</div></template>\n<script>(function(){var c=document.getElementById("ppr-content-${currentHoleId}");if(!c)return;var b=document.getElementById("B:${holeIndex - 1}");var t=document.querySelector('[data-ppr-hole="${currentHoleId}"]')||(b?b.nextElementSibling:null);try{if(t){t.replaceWith(c.content?c.content:...c.childNodes);}}finally{c.remove();}})();</script>\n`;
|
|
120
|
+
await writer.write(encoder.encode(errChunk));
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
holePromises.push(promise);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Handle Arrays (children)
|
|
129
|
+
if (Array.isArray(current)) {
|
|
130
|
+
for (const item of current) {
|
|
131
|
+
await traverse(item);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Traverse children props
|
|
137
|
+
if (current?.props?.children) {
|
|
138
|
+
await traverse(current.props.children);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await traverse(node);
|
|
143
|
+
await Promise.all(holePromises);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handles PPR Runtime Streaming Resume.
|
|
148
|
+
* Immediately flushes shellPrelude to the client, then resolves dynamic holes in parallel.
|
|
149
|
+
*/
|
|
150
|
+
async function handlePprResume({
|
|
151
|
+
reqPath,
|
|
152
|
+
queryObj,
|
|
153
|
+
simReq,
|
|
154
|
+
bridge,
|
|
155
|
+
platformContext,
|
|
156
|
+
rootContext,
|
|
157
|
+
cachedItem,
|
|
158
|
+
metadata,
|
|
159
|
+
isDevelopment,
|
|
160
|
+
getDiscoveredLayouts,
|
|
161
|
+
createRequestContext,
|
|
162
|
+
copyCustomContextProperties,
|
|
163
|
+
}) {
|
|
164
|
+
let shellHtml = cachedItem.content;
|
|
165
|
+
if (typeof shellHtml === "string") {
|
|
166
|
+
if (!shellHtml.includes("window.__DINOU_LAYOUTS__")) {
|
|
167
|
+
const knownLayouts = typeof getDiscoveredLayouts === "function" ? getDiscoveredLayouts() : {};
|
|
168
|
+
shellHtml = shellHtml.replace(
|
|
169
|
+
"</head>",
|
|
170
|
+
`<script>window.__DINOU_LAYOUTS__=${JSON.stringify(knownLayouts)};</script></head>`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
shellHtml = shellHtml.replace(
|
|
174
|
+
/window\.__DINOU_USE_STATIC__\s*=\s*true;?/g,
|
|
175
|
+
"window.__DINOU_USE_STATIC__ = false; window.__DINOU_PPR__ = true;"
|
|
176
|
+
);
|
|
177
|
+
if (!shellHtml.includes("window.__DINOU_PPR__")) {
|
|
178
|
+
shellHtml = shellHtml.replace(
|
|
179
|
+
"</head>",
|
|
180
|
+
`<script>window.__DINOU_USE_STATIC__ = false; window.__DINOU_PPR__ = true;</script></head>`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Separate shell into prelude (up to </body>) and postlude
|
|
186
|
+
const lowerHtml = shellHtml.toLowerCase();
|
|
187
|
+
const bodyCloseIndex = lowerHtml.lastIndexOf("</body>");
|
|
188
|
+
let shellPrelude =
|
|
189
|
+
bodyCloseIndex !== -1 ? shellHtml.slice(0, bodyCloseIndex) : shellHtml;
|
|
190
|
+
let shellPostlude =
|
|
191
|
+
bodyCloseIndex !== -1 ? shellHtml.slice(bodyCloseIndex) : "</body></html>";
|
|
192
|
+
|
|
193
|
+
const { readable, writable } = new TransformStream();
|
|
194
|
+
const writer = writable.getWriter();
|
|
195
|
+
const encoder = new TextEncoder();
|
|
196
|
+
|
|
197
|
+
// Stream dynamic holes in parallel with the prelude
|
|
198
|
+
const resumePromise = (async () => {
|
|
199
|
+
try {
|
|
200
|
+
// 1. Immediately flush static shell prelude (0ms TTFB)
|
|
201
|
+
await writer.write(encoder.encode(shellPrelude));
|
|
202
|
+
|
|
203
|
+
const dynamicState = { value: true };
|
|
204
|
+
const context = createRequestContext(simReq, bridge, platformContext, dynamicState);
|
|
205
|
+
if (typeof copyCustomContextProperties === "function") {
|
|
206
|
+
copyCustomContextProperties(rootContext, context);
|
|
207
|
+
}
|
|
208
|
+
if (typeof setCurrentContext === "function") {
|
|
209
|
+
setCurrentContext(context);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await requestStorage.run(context, async () => {
|
|
213
|
+
const isNotFound = { value: false };
|
|
214
|
+
const jsx = await getJSX(
|
|
215
|
+
reqPath,
|
|
216
|
+
queryObj,
|
|
217
|
+
isNotFound,
|
|
218
|
+
isDevelopment,
|
|
219
|
+
false
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const holes = metadata?.holes || [];
|
|
223
|
+
await streamDynamicHoles(
|
|
224
|
+
jsx,
|
|
225
|
+
holes,
|
|
226
|
+
writer,
|
|
227
|
+
encoder,
|
|
228
|
+
platformContext,
|
|
229
|
+
isDevelopment
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
} catch (streamErr) {
|
|
233
|
+
console.error("[Dinou PPR] Runtime streaming resume error:", streamErr);
|
|
234
|
+
} finally {
|
|
235
|
+
try {
|
|
236
|
+
await writer.write(encoder.encode(shellPostlude));
|
|
237
|
+
await writer.close();
|
|
238
|
+
} catch (_) {}
|
|
239
|
+
}
|
|
240
|
+
})();
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
platformContext &&
|
|
244
|
+
platformContext.ctx &&
|
|
245
|
+
typeof platformContext.ctx.waitUntil === "function"
|
|
246
|
+
) {
|
|
247
|
+
platformContext.ctx.waitUntil(resumePromise);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const headers = new Headers(bridge.headers);
|
|
251
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
252
|
+
headers.set("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
253
|
+
|
|
254
|
+
return new Response(readable, {
|
|
255
|
+
status: metadata?.status || 200,
|
|
256
|
+
headers,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
streamDynamicHoles,
|
|
262
|
+
handlePprResume,
|
|
263
|
+
};
|
package/dinou/core/redirect.jsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ClientRedirect } from "./client-redirect.jsx";
|
|
2
2
|
|
|
3
|
+
const DINOU_CONTEXT_KEY = Symbol.for("dinou.request.context.storage");
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Universal redirection function.
|
|
5
7
|
* Use it with 'return': return redirect('/login');
|
|
@@ -7,26 +9,23 @@ import { ClientRedirect } from "./client-redirect.jsx";
|
|
|
7
9
|
export function redirect(destination) {
|
|
8
10
|
// 1. We try to get the server context
|
|
9
11
|
if (typeof window === "undefined") {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const { getContext } = dynamicRequire(
|
|
19
|
-
/* webpackIgnore: true */ "./request-context.js"
|
|
20
|
-
);
|
|
21
|
-
const ctx = getContext();
|
|
22
|
-
|
|
23
|
-
// 2. If we are on the server and headers have NOT been sent yet...
|
|
24
|
-
// We can do a real HTTP redirect (Status 307).
|
|
25
|
-
// This is better for SEO and speed in Hard Navigation.
|
|
26
|
-
if (ctx && ctx.res) {
|
|
27
|
-
ctx.res.redirect(destination);
|
|
28
|
-
return <ClientRedirect to={destination} />;
|
|
12
|
+
let ctx;
|
|
13
|
+
try {
|
|
14
|
+
const storage = globalThis[DINOU_CONTEXT_KEY];
|
|
15
|
+
if (storage && typeof storage.getStore === "function") {
|
|
16
|
+
ctx = storage.getStore();
|
|
17
|
+
}
|
|
18
|
+
if (!ctx && typeof globalThis !== "undefined") {
|
|
19
|
+
ctx = globalThis[Symbol.for("dinou.request.context.current")];
|
|
29
20
|
}
|
|
21
|
+
} catch (e) { }
|
|
22
|
+
|
|
23
|
+
// 2. If we are on the server and headers have NOT been sent yet...
|
|
24
|
+
// We can do a real HTTP redirect (Status 307 or x-rsc-redirect).
|
|
25
|
+
// This is better for SEO and speed in Hard Navigation.
|
|
26
|
+
if (ctx && ctx.res && typeof ctx.res.redirect === "function") {
|
|
27
|
+
ctx.res.redirect(destination);
|
|
28
|
+
return <ClientRedirect to={destination} />;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -34,3 +33,5 @@ export function redirect(destination) {
|
|
|
34
33
|
// We return the component that will force the redirection in the browser.
|
|
35
34
|
return <ClientRedirect to={destination} />;
|
|
36
35
|
}
|
|
36
|
+
|
|
37
|
+
export default redirect;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isPprBuildActive,
|
|
3
|
+
enterSuspense,
|
|
4
|
+
exitSuspense,
|
|
5
|
+
registerHole,
|
|
6
|
+
} = require("./ppr-context.js");
|
|
7
|
+
|
|
1
8
|
// Function to check if a component is a client component
|
|
2
9
|
function isClientComponent(type) {
|
|
3
10
|
if (!type) {
|
|
@@ -10,21 +17,23 @@ function isClientComponent(type) {
|
|
|
10
17
|
);
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
function
|
|
20
|
+
async function asyncRenderJSXToClientJSX(jsx, key = null) {
|
|
14
21
|
if (
|
|
15
22
|
typeof jsx === "string" ||
|
|
16
23
|
typeof jsx === "number" ||
|
|
17
24
|
typeof jsx === "boolean" ||
|
|
18
25
|
typeof jsx === "function" ||
|
|
19
26
|
typeof jsx === "undefined" ||
|
|
20
|
-
jsx
|
|
27
|
+
jsx === null
|
|
21
28
|
) {
|
|
22
29
|
return jsx;
|
|
23
30
|
} else if (Array.isArray(jsx)) {
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
return await Promise.all(
|
|
32
|
+
jsx.map((child) =>
|
|
33
|
+
asyncRenderJSXToClientJSX(
|
|
34
|
+
child,
|
|
35
|
+
child?.key ?? null
|
|
36
|
+
)
|
|
28
37
|
)
|
|
29
38
|
);
|
|
30
39
|
} else if (typeof jsx === "symbol") {
|
|
@@ -34,21 +43,64 @@ function renderJSXToClientJSX(jsx, key = null) {
|
|
|
34
43
|
$$typeof: Symbol.for("react.transitional.element"),
|
|
35
44
|
type: Symbol.for("react.fragment"),
|
|
36
45
|
props: {},
|
|
37
|
-
key
|
|
46
|
+
key,
|
|
38
47
|
};
|
|
39
48
|
}
|
|
40
49
|
console.error("Unsupported symbol:", String(jsx));
|
|
41
50
|
throw new Error(`Unsupported symbol: ${String(jsx)}`);
|
|
42
51
|
} else if (typeof jsx === "object") {
|
|
43
52
|
if (jsx.$$typeof === Symbol.for("react.transitional.element")) {
|
|
44
|
-
if (
|
|
53
|
+
if (jsx.type === Symbol.for("react.suspense")) {
|
|
54
|
+
if (isPprBuildActive()) {
|
|
55
|
+
const holeId = enterSuspense();
|
|
56
|
+
try {
|
|
57
|
+
const renderedChildren = await asyncRenderJSXToClientJSX(jsx.props?.children);
|
|
58
|
+
exitSuspense();
|
|
59
|
+
return {
|
|
60
|
+
...jsx,
|
|
61
|
+
props: {
|
|
62
|
+
...jsx.props,
|
|
63
|
+
children: renderedChildren,
|
|
64
|
+
fallback: await asyncRenderJSXToClientJSX(jsx.props?.fallback),
|
|
65
|
+
},
|
|
66
|
+
key: key ?? jsx.key,
|
|
67
|
+
};
|
|
68
|
+
} catch (err) {
|
|
69
|
+
exitSuspense();
|
|
70
|
+
if (
|
|
71
|
+
(err && err.$$typeof === Symbol.for("dinou.ppr.postpone")) ||
|
|
72
|
+
(err && typeof err?.then === "function") ||
|
|
73
|
+
(err && err?.$$typeof === Symbol.for("react.postpone"))
|
|
74
|
+
) {
|
|
75
|
+
registerHole(holeId, { type: err.pprType || "suspense" });
|
|
76
|
+
const renderedFallback = await asyncRenderJSXToClientJSX(jsx.props?.fallback);
|
|
77
|
+
return {
|
|
78
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
79
|
+
type: "div",
|
|
80
|
+
props: {
|
|
81
|
+
"data-ppr-hole": holeId,
|
|
82
|
+
style: { display: "contents" },
|
|
83
|
+
children: renderedFallback,
|
|
84
|
+
},
|
|
85
|
+
key: key ?? jsx.key,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...jsx,
|
|
93
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
94
|
+
key: key ?? jsx.key,
|
|
95
|
+
};
|
|
96
|
+
} else if (
|
|
45
97
|
jsx.type === Symbol.for("react.fragment") ||
|
|
46
|
-
jsx.type === Symbol.for("react.
|
|
98
|
+
jsx.type === Symbol.for("react.view_transition") ||
|
|
47
99
|
typeof jsx.type === "string"
|
|
48
100
|
) {
|
|
49
101
|
return {
|
|
50
102
|
...jsx,
|
|
51
|
-
props:
|
|
103
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
52
104
|
key: key ?? jsx.key,
|
|
53
105
|
};
|
|
54
106
|
} else if (typeof jsx.type === "function") {
|
|
@@ -59,94 +111,47 @@ function renderJSXToClientJSX(jsx, key = null) {
|
|
|
59
111
|
...jsx,
|
|
60
112
|
$$typeof: Symbol.for("react.transitional.element"),
|
|
61
113
|
type: Component,
|
|
62
|
-
props:
|
|
114
|
+
props: await asyncRenderJSXToClientJSX(props),
|
|
63
115
|
key: key ?? jsx.key,
|
|
64
116
|
};
|
|
65
117
|
} else {
|
|
66
118
|
// Server component: execute and process
|
|
67
|
-
const returnedJsx = Component(props);
|
|
68
|
-
return
|
|
119
|
+
const returnedJsx = await Component(props);
|
|
120
|
+
return await asyncRenderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
69
121
|
}
|
|
70
|
-
} else
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} else if (jsx instanceof Promise) {
|
|
75
|
-
return jsx;
|
|
76
|
-
} else {
|
|
77
|
-
// Process object props (e.g., { className: "foo" })
|
|
78
|
-
return Object.fromEntries(
|
|
79
|
-
Object.entries(jsx).map(([propName, value]) => [
|
|
80
|
-
propName,
|
|
81
|
-
renderJSXToClientJSX(value),
|
|
82
|
-
])
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
throw new Error("Not implemented");
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function asyncRenderJSXToClientJSX(jsx, key = null) {
|
|
91
|
-
if (
|
|
92
|
-
typeof jsx === "string" ||
|
|
93
|
-
typeof jsx === "number" ||
|
|
94
|
-
typeof jsx === "boolean" ||
|
|
95
|
-
typeof jsx === "function" ||
|
|
96
|
-
typeof jsx === "undefined" ||
|
|
97
|
-
jsx === null
|
|
98
|
-
) {
|
|
99
|
-
return jsx;
|
|
100
|
-
} else if (Array.isArray(jsx)) {
|
|
101
|
-
return await Promise.all(
|
|
102
|
-
jsx.map((child, i) =>
|
|
103
|
-
asyncRenderJSXToClientJSX(
|
|
104
|
-
child,
|
|
105
|
-
i + (typeof child?.type === "string" ? "_" + child?.type : "")
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
);
|
|
109
|
-
} else if (typeof jsx === "symbol") {
|
|
110
|
-
if (jsx === Symbol.for("react.fragment")) {
|
|
111
|
-
// Handle Fragment as an empty props object
|
|
112
|
-
return {
|
|
113
|
-
$$typeof: Symbol.for("react.transitional.element"),
|
|
114
|
-
type: Symbol.for("react.fragment"),
|
|
115
|
-
props: { key },
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
console.error("Unsupported symbol:", String(jsx));
|
|
119
|
-
throw new Error(`Unsupported symbol: ${String(jsx)}`);
|
|
120
|
-
} else if (typeof jsx === "object") {
|
|
121
|
-
if (jsx.$$typeof === Symbol.for("react.transitional.element")) {
|
|
122
|
-
if (
|
|
123
|
-
jsx.type === Symbol.for("react.fragment") ||
|
|
124
|
-
jsx.type === Symbol.for("react.suspense") ||
|
|
125
|
-
typeof jsx.type === "string"
|
|
122
|
+
} else if (
|
|
123
|
+
typeof jsx.type === "object" &&
|
|
124
|
+
jsx.type !== null &&
|
|
125
|
+
jsx.type.$$typeof === Symbol.for("react.forward_ref")
|
|
126
126
|
) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
if (isClientComponent(jsx.type)) {
|
|
128
|
+
return {
|
|
129
|
+
...jsx,
|
|
130
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
131
|
+
type: jsx.type,
|
|
132
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
131
133
|
key: key ?? jsx.key,
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
};
|
|
135
|
+
} else {
|
|
136
|
+
const returnedJsx = await jsx.type.render(jsx.props, jsx.ref);
|
|
137
|
+
return await asyncRenderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
138
|
+
}
|
|
139
|
+
} else if (
|
|
140
|
+
typeof jsx.type === "object" &&
|
|
141
|
+
jsx.type !== null &&
|
|
142
|
+
jsx.type.$$typeof === Symbol.for("react.memo")
|
|
143
|
+
) {
|
|
144
|
+
if (isClientComponent(jsx.type)) {
|
|
138
145
|
return {
|
|
139
146
|
...jsx,
|
|
140
147
|
$$typeof: Symbol.for("react.transitional.element"),
|
|
141
|
-
type:
|
|
142
|
-
props:
|
|
143
|
-
|
|
144
|
-
key: key ?? jsx.key,
|
|
145
|
-
},
|
|
148
|
+
type: jsx.type,
|
|
149
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
150
|
+
key: key ?? jsx.key,
|
|
146
151
|
};
|
|
147
152
|
} else {
|
|
148
|
-
|
|
149
|
-
const returnedJsx = await
|
|
153
|
+
const innerComp = jsx.type.type;
|
|
154
|
+
const returnedJsx = typeof innerComp === "function" ? await innerComp(jsx.props) : null;
|
|
150
155
|
return await asyncRenderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
151
156
|
}
|
|
152
157
|
} else {
|
|
@@ -172,6 +177,5 @@ async function asyncRenderJSXToClientJSX(jsx, key = null) {
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
module.exports = {
|
|
175
|
-
renderJSXToClientJSX,
|
|
176
180
|
asyncRenderJSXToClientJSX,
|
|
177
181
|
};
|