dinou 6.1.1 → 7.0.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 +94 -0
- package/README.md +9461 -1080
- package/cli.js +392 -169
- 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 +309 -157
- 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 +172 -95
- package/dinou/core/handler.js +2296 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +181 -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 +207 -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 +133 -15
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +32 -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 +173 -94
- package/package.json +110 -108
- 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) {
|
|
@@ -21,10 +28,10 @@ function renderJSXToClientJSX(jsx, key = null) {
|
|
|
21
28
|
) {
|
|
22
29
|
return jsx;
|
|
23
30
|
} else if (Array.isArray(jsx)) {
|
|
24
|
-
return jsx.map((child
|
|
31
|
+
return jsx.map((child) =>
|
|
25
32
|
renderJSXToClientJSX(
|
|
26
33
|
child,
|
|
27
|
-
|
|
34
|
+
child?.key ?? null
|
|
28
35
|
)
|
|
29
36
|
);
|
|
30
37
|
} else if (typeof jsx === "symbol") {
|
|
@@ -44,6 +51,7 @@ function renderJSXToClientJSX(jsx, key = null) {
|
|
|
44
51
|
if (
|
|
45
52
|
jsx.type === Symbol.for("react.fragment") ||
|
|
46
53
|
jsx.type === Symbol.for("react.suspense") ||
|
|
54
|
+
jsx.type === Symbol.for("react.view_transition") ||
|
|
47
55
|
typeof jsx.type === "string"
|
|
48
56
|
) {
|
|
49
57
|
return {
|
|
@@ -67,6 +75,41 @@ function renderJSXToClientJSX(jsx, key = null) {
|
|
|
67
75
|
const returnedJsx = Component(props);
|
|
68
76
|
return renderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
69
77
|
}
|
|
78
|
+
} else if (
|
|
79
|
+
typeof jsx.type === "object" &&
|
|
80
|
+
jsx.type !== null &&
|
|
81
|
+
jsx.type.$$typeof === Symbol.for("react.forward_ref")
|
|
82
|
+
) {
|
|
83
|
+
if (isClientComponent(jsx.type)) {
|
|
84
|
+
return {
|
|
85
|
+
...jsx,
|
|
86
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
87
|
+
type: jsx.type,
|
|
88
|
+
props: renderJSXToClientJSX(jsx.props),
|
|
89
|
+
key: key ?? jsx.key,
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
const returnedJsx = jsx.type.render(jsx.props, jsx.ref);
|
|
93
|
+
return renderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
94
|
+
}
|
|
95
|
+
} else if (
|
|
96
|
+
typeof jsx.type === "object" &&
|
|
97
|
+
jsx.type !== null &&
|
|
98
|
+
jsx.type.$$typeof === Symbol.for("react.memo")
|
|
99
|
+
) {
|
|
100
|
+
if (isClientComponent(jsx.type)) {
|
|
101
|
+
return {
|
|
102
|
+
...jsx,
|
|
103
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
104
|
+
type: jsx.type,
|
|
105
|
+
props: renderJSXToClientJSX(jsx.props),
|
|
106
|
+
key: key ?? jsx.key,
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
const innerComp = jsx.type.type;
|
|
110
|
+
const returnedJsx = typeof innerComp === "function" ? innerComp(jsx.props) : null;
|
|
111
|
+
return renderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
112
|
+
}
|
|
70
113
|
} else {
|
|
71
114
|
console.error("Unsupported JSX type:", jsx.type);
|
|
72
115
|
throw new Error("Unsupported JSX type");
|
|
@@ -99,10 +142,10 @@ async function asyncRenderJSXToClientJSX(jsx, key = null) {
|
|
|
99
142
|
return jsx;
|
|
100
143
|
} else if (Array.isArray(jsx)) {
|
|
101
144
|
return await Promise.all(
|
|
102
|
-
jsx.map((child
|
|
145
|
+
jsx.map((child) =>
|
|
103
146
|
asyncRenderJSXToClientJSX(
|
|
104
147
|
child,
|
|
105
|
-
|
|
148
|
+
child?.key ?? null
|
|
106
149
|
)
|
|
107
150
|
)
|
|
108
151
|
);
|
|
@@ -112,24 +155,66 @@ async function asyncRenderJSXToClientJSX(jsx, key = null) {
|
|
|
112
155
|
return {
|
|
113
156
|
$$typeof: Symbol.for("react.transitional.element"),
|
|
114
157
|
type: Symbol.for("react.fragment"),
|
|
115
|
-
props: {
|
|
158
|
+
props: {},
|
|
159
|
+
key,
|
|
116
160
|
};
|
|
117
161
|
}
|
|
118
162
|
console.error("Unsupported symbol:", String(jsx));
|
|
119
163
|
throw new Error(`Unsupported symbol: ${String(jsx)}`);
|
|
120
164
|
} else if (typeof jsx === "object") {
|
|
121
165
|
if (jsx.$$typeof === Symbol.for("react.transitional.element")) {
|
|
122
|
-
if (
|
|
166
|
+
if (jsx.type === Symbol.for("react.suspense")) {
|
|
167
|
+
if (isPprBuildActive()) {
|
|
168
|
+
const holeId = enterSuspense();
|
|
169
|
+
try {
|
|
170
|
+
const renderedChildren = await asyncRenderJSXToClientJSX(jsx.props?.children);
|
|
171
|
+
exitSuspense();
|
|
172
|
+
return {
|
|
173
|
+
...jsx,
|
|
174
|
+
props: {
|
|
175
|
+
...jsx.props,
|
|
176
|
+
children: renderedChildren,
|
|
177
|
+
fallback: await asyncRenderJSXToClientJSX(jsx.props?.fallback),
|
|
178
|
+
},
|
|
179
|
+
key: key ?? jsx.key,
|
|
180
|
+
};
|
|
181
|
+
} catch (err) {
|
|
182
|
+
exitSuspense();
|
|
183
|
+
if (
|
|
184
|
+
(err && err.$$typeof === Symbol.for("dinou.ppr.postpone")) ||
|
|
185
|
+
(err && typeof err?.then === "function") ||
|
|
186
|
+
(err && err?.$$typeof === Symbol.for("react.postpone"))
|
|
187
|
+
) {
|
|
188
|
+
registerHole(holeId, { type: err.pprType || "suspense" });
|
|
189
|
+
const renderedFallback = await asyncRenderJSXToClientJSX(jsx.props?.fallback);
|
|
190
|
+
return {
|
|
191
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
192
|
+
type: "div",
|
|
193
|
+
props: {
|
|
194
|
+
"data-ppr-hole": holeId,
|
|
195
|
+
style: { display: "contents" },
|
|
196
|
+
children: renderedFallback,
|
|
197
|
+
},
|
|
198
|
+
key: key ?? jsx.key,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
...jsx,
|
|
206
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
207
|
+
key: key ?? jsx.key,
|
|
208
|
+
};
|
|
209
|
+
} else if (
|
|
123
210
|
jsx.type === Symbol.for("react.fragment") ||
|
|
124
|
-
jsx.type === Symbol.for("react.
|
|
211
|
+
jsx.type === Symbol.for("react.view_transition") ||
|
|
125
212
|
typeof jsx.type === "string"
|
|
126
213
|
) {
|
|
127
214
|
return {
|
|
128
215
|
...jsx,
|
|
129
|
-
props:
|
|
130
|
-
|
|
131
|
-
key: key ?? jsx.key,
|
|
132
|
-
},
|
|
216
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
217
|
+
key: key ?? jsx.key,
|
|
133
218
|
};
|
|
134
219
|
} else if (typeof jsx.type === "function") {
|
|
135
220
|
const Component = jsx.type;
|
|
@@ -139,16 +224,49 @@ async function asyncRenderJSXToClientJSX(jsx, key = null) {
|
|
|
139
224
|
...jsx,
|
|
140
225
|
$$typeof: Symbol.for("react.transitional.element"),
|
|
141
226
|
type: Component,
|
|
142
|
-
props:
|
|
143
|
-
|
|
144
|
-
key: key ?? jsx.key,
|
|
145
|
-
},
|
|
227
|
+
props: await asyncRenderJSXToClientJSX(props),
|
|
228
|
+
key: key ?? jsx.key,
|
|
146
229
|
};
|
|
147
230
|
} else {
|
|
148
231
|
// Server component: execute and process
|
|
149
232
|
const returnedJsx = await Component(props);
|
|
150
233
|
return await asyncRenderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
151
234
|
}
|
|
235
|
+
} else if (
|
|
236
|
+
typeof jsx.type === "object" &&
|
|
237
|
+
jsx.type !== null &&
|
|
238
|
+
jsx.type.$$typeof === Symbol.for("react.forward_ref")
|
|
239
|
+
) {
|
|
240
|
+
if (isClientComponent(jsx.type)) {
|
|
241
|
+
return {
|
|
242
|
+
...jsx,
|
|
243
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
244
|
+
type: jsx.type,
|
|
245
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
246
|
+
key: key ?? jsx.key,
|
|
247
|
+
};
|
|
248
|
+
} else {
|
|
249
|
+
const returnedJsx = await jsx.type.render(jsx.props, jsx.ref);
|
|
250
|
+
return await asyncRenderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
251
|
+
}
|
|
252
|
+
} else if (
|
|
253
|
+
typeof jsx.type === "object" &&
|
|
254
|
+
jsx.type !== null &&
|
|
255
|
+
jsx.type.$$typeof === Symbol.for("react.memo")
|
|
256
|
+
) {
|
|
257
|
+
if (isClientComponent(jsx.type)) {
|
|
258
|
+
return {
|
|
259
|
+
...jsx,
|
|
260
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
261
|
+
type: jsx.type,
|
|
262
|
+
props: await asyncRenderJSXToClientJSX(jsx.props),
|
|
263
|
+
key: key ?? jsx.key,
|
|
264
|
+
};
|
|
265
|
+
} else {
|
|
266
|
+
const innerComp = jsx.type.type;
|
|
267
|
+
const returnedJsx = typeof innerComp === "function" ? await innerComp(jsx.props) : null;
|
|
268
|
+
return await asyncRenderJSXToClientJSX(returnedJsx, key ?? jsx.key);
|
|
269
|
+
}
|
|
152
270
|
} else {
|
|
153
271
|
console.error("Unsupported JSX type:", jsx.type);
|
|
154
272
|
throw new Error("Unsupported JSX type");
|