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,2589 @@
|
|
|
1
|
+
// dinou/node/dev.mjs
|
|
2
|
+
// Incremental Dual-Bundle Development Server for Dinou.
|
|
3
|
+
// Eliminates child_process.fork() by compiling Pass A (RSC) and Pass B (SSR)
|
|
4
|
+
// using esbuild.context() in memory, running a single unified streaming process.
|
|
5
|
+
|
|
6
|
+
process.env.DINOU_DEV = "true";
|
|
7
|
+
if (!process.env.NODE_ENV) {
|
|
8
|
+
process.env.NODE_ENV = "development";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import http from "node:http";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import crypto from "node:crypto";
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
+
import { Readable } from "node:stream";
|
|
18
|
+
import {
|
|
19
|
+
startSpinner,
|
|
20
|
+
updateSpinner,
|
|
21
|
+
stopSpinner,
|
|
22
|
+
logSuccess,
|
|
23
|
+
logInfo,
|
|
24
|
+
showIdleStatus,
|
|
25
|
+
printReadyBanner,
|
|
26
|
+
} from "./terminal-status.mjs";
|
|
27
|
+
import { resolveDevPorts } from "./port-selector.mjs";
|
|
28
|
+
|
|
29
|
+
// Resolve dev HTTP and HMR ports before initiating background compilation
|
|
30
|
+
const { port: PORT, hmrPort: HMR_PORT } = await resolveDevPorts();
|
|
31
|
+
|
|
32
|
+
const devStartTime = Date.now();
|
|
33
|
+
const devTimings = {};
|
|
34
|
+
startSpinner("Initializing Incremental Dual-Bundle Engine (No fork)...");
|
|
35
|
+
|
|
36
|
+
const projectRoot = process.cwd();
|
|
37
|
+
const require = createRequire(path.resolve(projectRoot, "package.json"));
|
|
38
|
+
const esbuild = require("esbuild");
|
|
39
|
+
const chokidar = require("chokidar");
|
|
40
|
+
|
|
41
|
+
// Protect file descriptor operations against transient EMFILE spikes on Windows
|
|
42
|
+
try {
|
|
43
|
+
const gracefulFs = require("graceful-fs");
|
|
44
|
+
gracefulFs.gracefulify(fs);
|
|
45
|
+
} catch (e) {}
|
|
46
|
+
|
|
47
|
+
async function dynamicImportWithRetry(fileUrl, maxRetries = 6, delayMs = 60) {
|
|
48
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
49
|
+
try {
|
|
50
|
+
return await import(fileUrl);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if ((err.code === "EMFILE" || err.code === "EBUSY" || err.code === "EPERM") && i < maxRetries - 1) {
|
|
53
|
+
await new Promise((r) => setTimeout(r, delayMs * (i + 1)));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
process.env.NODE_ENV = "development";
|
|
62
|
+
process.env.DINOU_DEV = "true";
|
|
63
|
+
process.env.DINOU_RUNTIME = "node-bundle";
|
|
64
|
+
if (typeof globalThis !== "undefined") {
|
|
65
|
+
globalThis.__DINOU_DEV__ = true;
|
|
66
|
+
globalThis.__DINOU_RUNTIME__ = "node-bundle";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isWebpackBuild = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
70
|
+
const devDir = path.resolve(projectRoot, ".dinou/node-dev");
|
|
71
|
+
fs.mkdirSync(devDir, { recursive: true });
|
|
72
|
+
|
|
73
|
+
// Locate Dinou core directory
|
|
74
|
+
const dinouDir = fs.existsSync(path.resolve(projectRoot, "dinou"))
|
|
75
|
+
? path.resolve(projectRoot, "dinou")
|
|
76
|
+
: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
77
|
+
const dinouDirSlash = dinouDir.replace(/\\/g, "/");
|
|
78
|
+
|
|
79
|
+
function getFallbackChunkId(absPath) {
|
|
80
|
+
const norm = absPath.replace(/\\/g, "/");
|
|
81
|
+
const name = path.basename(absPath, path.extname(absPath));
|
|
82
|
+
if (isWebpackBuild) {
|
|
83
|
+
return `/${name}.js`;
|
|
84
|
+
}
|
|
85
|
+
const buildTool = (process.env.DINOU_BUILD_TOOL || "esbuild").toLowerCase();
|
|
86
|
+
if (buildTool === "rollup") {
|
|
87
|
+
return `/${name}.js`;
|
|
88
|
+
}
|
|
89
|
+
const hash = crypto.createHash("sha1").update(norm).digest("hex").slice(0, 8);
|
|
90
|
+
return `/${name}-${hash}.js`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { generateRouteModulesCode, generateRouteTypes } = require(path.join(dinouDir, "core/route-generator.js"));
|
|
94
|
+
try {
|
|
95
|
+
generateRouteTypes(projectRoot);
|
|
96
|
+
} catch (e) {}
|
|
97
|
+
const parseExports = require(path.join(dinouDir, "core/parse-exports.js"));
|
|
98
|
+
const { useClientRegex, useServerRegex } = require(path.join(dinouDir, "constants.js"));
|
|
99
|
+
const { nodeToWebRequest, sendWebResponseToNode } = require(path.join(dinouDir, "core/http-adapter.js"));
|
|
100
|
+
const { setStorageAdapter, FileSystemStorage, MemoryStorage } = require(path.join(dinouDir, "core/storage-adapter.js"));
|
|
101
|
+
const createScopedName = require(path.join(dinouDir, "core/createScopedName.js"));
|
|
102
|
+
const { regex: assetRegex } = require(path.join(dinouDir, "core/asset-extensions.js"));
|
|
103
|
+
const {
|
|
104
|
+
isSupportedClientModule,
|
|
105
|
+
scanProjectDependenciesForClientComponents,
|
|
106
|
+
scanProjectDependenciesForClientComponentsAsync,
|
|
107
|
+
} = require(path.join(dinouDir, "core/scan-dependency-components.js"));
|
|
108
|
+
|
|
109
|
+
const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
110
|
+
|
|
111
|
+
// Initialize Dinou Storage
|
|
112
|
+
try {
|
|
113
|
+
const dist2Dir = path.resolve(projectRoot, ".dinou/dist2");
|
|
114
|
+
setStorageAdapter(new FileSystemStorage(dist2Dir));
|
|
115
|
+
} catch (e) {
|
|
116
|
+
setStorageAdapter(new MemoryStorage());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Candidate Link and Redirect paths
|
|
120
|
+
const candidateDinouRoots = [
|
|
121
|
+
dinouDir,
|
|
122
|
+
path.resolve(projectRoot, "dinou"),
|
|
123
|
+
path.resolve(projectRoot, "node_modules/dinou/dinou"),
|
|
124
|
+
path.resolve(projectRoot, "node_modules/dinou"),
|
|
125
|
+
];
|
|
126
|
+
const candidateLinkPaths = new Set();
|
|
127
|
+
const candidateRedirectPaths = new Set();
|
|
128
|
+
const candidateSlotPaths = new Set();
|
|
129
|
+
for (const r of candidateDinouRoots) {
|
|
130
|
+
candidateLinkPaths.add(path.resolve(r, "core/link.jsx"));
|
|
131
|
+
candidateRedirectPaths.add(path.resolve(r, "core/client-redirect.jsx"));
|
|
132
|
+
candidateSlotPaths.add(path.resolve(r, "core/slot.js"));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function generateAllUrlVariants(absPath) {
|
|
136
|
+
const norm = absPath.replace(/\\/g, "/");
|
|
137
|
+
const fileUrl = pathToFileURL(absPath).href;
|
|
138
|
+
const urls = new Set([fileUrl]);
|
|
139
|
+
urls.add(fileUrl.replace(/file:\/\/\/([a-zA-Z]):/, (m, d) => 'file:///' + d.toLowerCase() + ':'));
|
|
140
|
+
urls.add(fileUrl.replace(/file:\/\/\/([a-zA-Z]):/, (m, d) => 'file:///' + d.toUpperCase() + ':'));
|
|
141
|
+
urls.add("file:///" + norm);
|
|
142
|
+
urls.add("file://" + norm);
|
|
143
|
+
return Array.from(urls);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const srcDir = path.resolve(projectRoot, "src");
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async function findClientComponents(parsedClientManifest = {}) {
|
|
150
|
+
const clientFiles = new Set();
|
|
151
|
+
async function walk(dir) {
|
|
152
|
+
if (!fs.existsSync(dir)) return;
|
|
153
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const full = path.join(dir, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
await walk(full);
|
|
158
|
+
} else if (/\.[jt]sx?$/.test(entry.name)) {
|
|
159
|
+
try {
|
|
160
|
+
const content = fs.readFileSync(full, "utf8");
|
|
161
|
+
if (useClientRegex.test(content.trim()) && isSupportedClientModule(full, content)) {
|
|
162
|
+
clientFiles.add(path.resolve(full));
|
|
163
|
+
}
|
|
164
|
+
} catch (e) {}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await yieldToEventLoop();
|
|
168
|
+
}
|
|
169
|
+
await walk(srcDir);
|
|
170
|
+
|
|
171
|
+
for (const f of [...candidateLinkPaths, ...candidateRedirectPaths, ...candidateSlotPaths]) {
|
|
172
|
+
if (fs.existsSync(f)) clientFiles.add(path.resolve(f));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof scanProjectDependenciesForClientComponentsAsync === "function") {
|
|
176
|
+
await scanProjectDependenciesForClientComponentsAsync(projectRoot, clientFiles, useClientRegex);
|
|
177
|
+
} else {
|
|
178
|
+
scanProjectDependenciesForClientComponents(projectRoot, clientFiles, useClientRegex);
|
|
179
|
+
}
|
|
180
|
+
await yieldToEventLoop();
|
|
181
|
+
|
|
182
|
+
for (const k of Object.keys(parsedClientManifest)) {
|
|
183
|
+
const fileUrl = k.split("#")[0];
|
|
184
|
+
if (fileUrl.startsWith("file:///")) {
|
|
185
|
+
try {
|
|
186
|
+
const filePath = fileURLToPath(fileUrl);
|
|
187
|
+
const norm = filePath.replace(/\\/g, "/");
|
|
188
|
+
if (norm.includes("/out/") || norm.includes("/dist/") || norm.includes("/.dinou/")) continue;
|
|
189
|
+
const base = path.basename(filePath);
|
|
190
|
+
if (
|
|
191
|
+
base === "client.jsx" ||
|
|
192
|
+
base === "client-webpack.jsx" ||
|
|
193
|
+
base.startsWith("react-refresh") ||
|
|
194
|
+
base === "runtime.js" ||
|
|
195
|
+
filePath.includes("react-refresh")
|
|
196
|
+
) continue;
|
|
197
|
+
if (fs.existsSync(filePath) && isSupportedClientModule(filePath)) {
|
|
198
|
+
clientFiles.add(path.resolve(filePath));
|
|
199
|
+
}
|
|
200
|
+
} catch (e) {}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return Array.from(clientFiles);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Manifest paths and helpers
|
|
208
|
+
function findManifest(filename, fallbackFolder) {
|
|
209
|
+
const candidates = isWebpackBuild
|
|
210
|
+
? [
|
|
211
|
+
path.resolve(projectRoot, ".dinou/public", filename),
|
|
212
|
+
path.resolve(projectRoot, ".dinou", fallbackFolder, filename),
|
|
213
|
+
]
|
|
214
|
+
: [
|
|
215
|
+
path.resolve(projectRoot, ".dinou", fallbackFolder, filename),
|
|
216
|
+
path.resolve(projectRoot, ".dinou/public", filename),
|
|
217
|
+
];
|
|
218
|
+
for (const c of candidates) {
|
|
219
|
+
if (fs.existsSync(c)) return c;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function readJsonSafe(filePath) {
|
|
225
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
226
|
+
try {
|
|
227
|
+
const text = fs.readFileSync(filePath, "utf8").trim();
|
|
228
|
+
if (text.length > 2) return JSON.parse(text);
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let parsedClientManifest = {};
|
|
234
|
+
let parsedServerFunctionsManifest = {};
|
|
235
|
+
let parsedAssetManifest = {};
|
|
236
|
+
let clientComponents = [];
|
|
237
|
+
let linkChunkId = null;
|
|
238
|
+
let redirectChunkId = null;
|
|
239
|
+
let slotChunkId = null;
|
|
240
|
+
const knownClientFiles = new Set();
|
|
241
|
+
const knownServerFiles = new Set();
|
|
242
|
+
const compExportsCache = new Map();
|
|
243
|
+
|
|
244
|
+
function getCachedFileExports(filePath) {
|
|
245
|
+
try {
|
|
246
|
+
const stat = fs.statSync(filePath);
|
|
247
|
+
const cached = compExportsCache.get(filePath);
|
|
248
|
+
if (cached && cached.mtime === stat.mtimeMs) {
|
|
249
|
+
return cached.exports;
|
|
250
|
+
}
|
|
251
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
252
|
+
const exports = parseExports(content);
|
|
253
|
+
if (!exports.includes("default")) {
|
|
254
|
+
exports.push("default");
|
|
255
|
+
}
|
|
256
|
+
const prevExports = cached ? cached.exports : null;
|
|
257
|
+
const exportsChanged = prevExports
|
|
258
|
+
? (prevExports.length !== exports.length || prevExports.some((e, i) => e !== exports[i]))
|
|
259
|
+
: false;
|
|
260
|
+
compExportsCache.set(filePath, { mtime: stat.mtimeMs, exports, exportsChanged });
|
|
261
|
+
return exports;
|
|
262
|
+
} catch (e) {
|
|
263
|
+
return ["default"];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function updateManifestsState(options = {}) {
|
|
268
|
+
const forceRescan = options.forceRescan || false;
|
|
269
|
+
const cPath = findManifest("react-client-manifest.json", "react_client_manifest");
|
|
270
|
+
const sfPath = findManifest("server-functions-manifest.json", "server_functions_manifest");
|
|
271
|
+
const aPath = findManifest("manifest.json", "public");
|
|
272
|
+
|
|
273
|
+
const rawClient = globalThis.__DINOU_RAW_CLIENT_MANIFEST__ || readJsonSafe(cPath);
|
|
274
|
+
if (rawClient) {
|
|
275
|
+
const cleanClient = {};
|
|
276
|
+
for (const [k, v] of Object.entries(rawClient)) {
|
|
277
|
+
const normK = k.replace(/\\/g, "/");
|
|
278
|
+
const normId = (v?.id || "").replace(/\\/g, "/");
|
|
279
|
+
if (
|
|
280
|
+
normK.includes("/out/") ||
|
|
281
|
+
normK.includes("/dist/") ||
|
|
282
|
+
normK.includes("/.dinou/") ||
|
|
283
|
+
normId.includes("/out/") ||
|
|
284
|
+
normId.includes("/dist/") ||
|
|
285
|
+
normId.includes("/.dinou/")
|
|
286
|
+
) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
cleanClient[k] = v;
|
|
290
|
+
}
|
|
291
|
+
parsedClientManifest = cleanClient;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const rawSf = globalThis.__DINOU_RAW_SERVER_FUNCTIONS_MANIFEST__ || readJsonSafe(sfPath);
|
|
295
|
+
if (rawSf) {
|
|
296
|
+
parsedServerFunctionsManifest = rawSf;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const rawAsset = globalThis.__DINOU_RAW_ASSET_MANIFEST__ || readJsonSafe(aPath);
|
|
300
|
+
if (rawAsset) {
|
|
301
|
+
parsedAssetManifest = rawAsset;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Normalize client manifest
|
|
305
|
+
const normalized = { ...parsedClientManifest };
|
|
306
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
307
|
+
if (k.startsWith("file:///c:/")) {
|
|
308
|
+
normalized["file:///C:/" + k.slice(11)] = v;
|
|
309
|
+
} else if (k.startsWith("file:///C:/")) {
|
|
310
|
+
normalized["file:///c:/" + k.slice(11)] = v;
|
|
311
|
+
}
|
|
312
|
+
const hashIdx = k.indexOf("#");
|
|
313
|
+
if (hashIdx === -1) {
|
|
314
|
+
const defKey = k + "#default";
|
|
315
|
+
const defEntry = { ...v, name: "default" };
|
|
316
|
+
normalized[defKey] = defEntry;
|
|
317
|
+
if (k.startsWith("file:///c:/")) {
|
|
318
|
+
normalized["file:///C:/" + k.slice(11) + "#default"] = defEntry;
|
|
319
|
+
} else if (k.startsWith("file:///C:/")) {
|
|
320
|
+
normalized["file:///c:/" + k.slice(11) + "#default"] = defEntry;
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
const expName = k.slice(hashIdx + 1);
|
|
324
|
+
const expEntry = { ...v, name: expName };
|
|
325
|
+
normalized[k] = expEntry;
|
|
326
|
+
const baseKey = k.slice(0, hashIdx);
|
|
327
|
+
if (!normalized[baseKey]) normalized[baseKey] = v;
|
|
328
|
+
if (baseKey.startsWith("file:///c:/")) {
|
|
329
|
+
normalized["file:///C:/" + baseKey.slice(11)] = v;
|
|
330
|
+
normalized["file:///C:/" + baseKey.slice(11) + "#" + expName] = expEntry;
|
|
331
|
+
} else if (baseKey.startsWith("file:///C:/")) {
|
|
332
|
+
normalized["file:///c:/" + baseKey.slice(11)] = v;
|
|
333
|
+
normalized["file:///c:/" + baseKey.slice(11) + "#" + expName] = expEntry;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let resolvedLinkChunkId = null;
|
|
339
|
+
let resolvedRedirectChunkId = null;
|
|
340
|
+
let linkEntry = null;
|
|
341
|
+
let redirectEntry = null;
|
|
342
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
343
|
+
const norm = k.replace(/\\/g, "/");
|
|
344
|
+
if (norm.includes("/out/") || norm.includes("/dist/") || norm.includes("/.dinou/")) continue;
|
|
345
|
+
if (k.includes("/core/link.jsx") || k.includes("dinouLink")) {
|
|
346
|
+
if (v?.id) { resolvedLinkChunkId = v.id; linkEntry = v; }
|
|
347
|
+
}
|
|
348
|
+
if (k.includes("/core/client-redirect.jsx") || k.includes("dinouClientRedirect")) {
|
|
349
|
+
if (v?.id) { resolvedRedirectChunkId = v.id; redirectEntry = v; }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
linkChunkId = resolvedLinkChunkId;
|
|
353
|
+
redirectChunkId = resolvedRedirectChunkId;
|
|
354
|
+
|
|
355
|
+
if (!isWebpackBuild) {
|
|
356
|
+
if (!redirectChunkId || redirectChunkId.includes("node_modules")) {
|
|
357
|
+
redirectChunkId = "/dinouClientRedirect.js";
|
|
358
|
+
}
|
|
359
|
+
if (!linkChunkId || linkChunkId.includes("node_modules")) {
|
|
360
|
+
linkChunkId = "/dinouLink.js";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (linkChunkId) {
|
|
365
|
+
const linkChunks = isWebpackBuild ? (linkEntry?.chunks || [linkChunkId]) : "Link";
|
|
366
|
+
const linkDefaultChunks = isWebpackBuild ? (linkEntry?.chunks || [linkChunkId]) : "default";
|
|
367
|
+
for (const lp of candidateLinkPaths) {
|
|
368
|
+
for (const url of generateAllUrlVariants(lp)) {
|
|
369
|
+
normalized[`${url}#Link`] = { id: linkChunkId, chunks: linkChunks, name: "Link" };
|
|
370
|
+
normalized[`${url}#default`] = { id: linkChunkId, chunks: linkDefaultChunks, name: "default" };
|
|
371
|
+
normalized[url] = { id: linkChunkId, chunks: linkDefaultChunks, name: "default" };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (redirectChunkId) {
|
|
377
|
+
const redirectChunks = isWebpackBuild ? (redirectEntry?.chunks || [redirectChunkId]) : "ClientRedirect";
|
|
378
|
+
const redirectDefaultChunks = isWebpackBuild ? (redirectEntry?.chunks || [redirectChunkId]) : "default";
|
|
379
|
+
for (const rp of candidateRedirectPaths) {
|
|
380
|
+
for (const url of generateAllUrlVariants(rp)) {
|
|
381
|
+
normalized[`${url}#ClientRedirect`] = { id: redirectChunkId, chunks: redirectChunks, name: "ClientRedirect" };
|
|
382
|
+
normalized[`${url}#default`] = { id: redirectChunkId, chunks: redirectDefaultChunks, name: "default" };
|
|
383
|
+
normalized[url] = { id: redirectChunkId, chunks: redirectDefaultChunks, name: "default" };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
slotChunkId = null;
|
|
389
|
+
let slotEntry = null;
|
|
390
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
391
|
+
if (k.includes("/core/slot.") || k.includes("dinouSlot")) {
|
|
392
|
+
if (v && v.id) {
|
|
393
|
+
slotChunkId = v.id;
|
|
394
|
+
slotEntry = v;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!isWebpackBuild && (!slotChunkId || slotChunkId.includes("node_modules"))) {
|
|
399
|
+
slotChunkId = "/dinouSlot.js";
|
|
400
|
+
}
|
|
401
|
+
if (slotChunkId) {
|
|
402
|
+
const slotChunks = isWebpackBuild ? (slotEntry?.chunks || [slotChunkId]) : "DinouPageSlot";
|
|
403
|
+
const slotBoundaryChunks = isWebpackBuild ? (slotEntry?.chunks || [slotChunkId]) : "DinouCacheSlotBoundary";
|
|
404
|
+
const slotDefaultChunks = isWebpackBuild ? (slotEntry?.chunks || [slotChunkId]) : "default";
|
|
405
|
+
for (const sp of candidateSlotPaths) {
|
|
406
|
+
for (const url of generateAllUrlVariants(sp)) {
|
|
407
|
+
normalized[`${url}#DinouPageSlot`] = { id: slotChunkId, chunks: slotChunks, name: "DinouPageSlot" };
|
|
408
|
+
normalized[`${url}#DinouCacheSlotBoundary`] = { id: slotChunkId, chunks: slotBoundaryChunks, name: "DinouCacheSlotBoundary" };
|
|
409
|
+
normalized[`${url}#default`] = { id: slotChunkId, chunks: slotDefaultChunks, name: "default" };
|
|
410
|
+
normalized[url] = { id: slotChunkId, chunks: slotDefaultChunks, name: "default" };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Detect if parsedClientManifest introduces any brand new client components not in knownClientFiles
|
|
416
|
+
let hasNewClientFiles = false;
|
|
417
|
+
if (!forceRescan && clientComponents.length > 0) {
|
|
418
|
+
for (const k of Object.keys(parsedClientManifest)) {
|
|
419
|
+
const fileUrl = k.split("#")[0];
|
|
420
|
+
if (fileUrl.startsWith("file:///")) {
|
|
421
|
+
try {
|
|
422
|
+
const fp = fileURLToPath(fileUrl);
|
|
423
|
+
if (!knownClientFiles.has(path.resolve(fp)) && fs.existsSync(fp)) {
|
|
424
|
+
hasNewClientFiles = true;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
} catch (e) {}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let componentsChanged = false;
|
|
433
|
+
if (forceRescan || clientComponents.length === 0 || hasNewClientFiles) {
|
|
434
|
+
const prevSet = new Set(clientComponents.map((c) => path.resolve(c)));
|
|
435
|
+
clientComponents = await findClientComponents(parsedClientManifest);
|
|
436
|
+
knownClientFiles.clear();
|
|
437
|
+
let compScanIdx = 0;
|
|
438
|
+
for (const comp of clientComponents) {
|
|
439
|
+
const resolved = path.resolve(comp);
|
|
440
|
+
knownClientFiles.add(resolved);
|
|
441
|
+
if (!prevSet.has(resolved)) {
|
|
442
|
+
componentsChanged = true;
|
|
443
|
+
}
|
|
444
|
+
const urlVariants = generateAllUrlVariants(comp);
|
|
445
|
+
const fileExports = getCachedFileExports(comp);
|
|
446
|
+
|
|
447
|
+
if (++compScanIdx % 5 === 0) {
|
|
448
|
+
await yieldToEventLoop();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const isValidEntryId = (entry) => Boolean(entry?.id && (isWebpackBuild || entry.id.endsWith(".js")));
|
|
452
|
+
|
|
453
|
+
// Check if parsedClientManifest already has an entry for this component
|
|
454
|
+
let matchedEntry = null;
|
|
455
|
+
for (const u of urlVariants) {
|
|
456
|
+
if (isValidEntryId(parsedClientManifest[u])) { matchedEntry = parsedClientManifest[u]; break; }
|
|
457
|
+
if (isValidEntryId(parsedClientManifest[`${u}#default`])) { matchedEntry = parsedClientManifest[`${u}#default`]; break; }
|
|
458
|
+
for (const exp of fileExports) {
|
|
459
|
+
const expEntry = parsedClientManifest[`${u}#${exp}`];
|
|
460
|
+
if (isValidEntryId(expEntry)) {
|
|
461
|
+
matchedEntry = expEntry;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (matchedEntry) break;
|
|
466
|
+
}
|
|
467
|
+
if (!matchedEntry) {
|
|
468
|
+
for (const u of urlVariants) {
|
|
469
|
+
const uLower = u.toLowerCase();
|
|
470
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
471
|
+
const kLower = k.toLowerCase();
|
|
472
|
+
if ((kLower === uLower || kLower.startsWith(uLower + "#")) && isValidEntryId(v)) {
|
|
473
|
+
matchedEntry = v;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (matchedEntry) break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const compId = isValidEntryId(matchedEntry)
|
|
482
|
+
? matchedEntry.id
|
|
483
|
+
: getFallbackChunkId(comp);
|
|
484
|
+
const compChunks = isWebpackBuild ? (matchedEntry?.chunks || []) : "default";
|
|
485
|
+
|
|
486
|
+
for (const u of urlVariants) {
|
|
487
|
+
if (!normalized[u] || !normalized[u].id || !isValidEntryId(normalized[u])) {
|
|
488
|
+
normalized[u] = { id: compId, chunks: compChunks, name: "*" };
|
|
489
|
+
} else if (isValidEntryId(matchedEntry)) {
|
|
490
|
+
normalized[u].id = compId;
|
|
491
|
+
normalized[u].chunks = compChunks;
|
|
492
|
+
}
|
|
493
|
+
for (const exp of fileExports) {
|
|
494
|
+
const hashKey = `${u}#${exp}`;
|
|
495
|
+
const expEntry = parsedClientManifest[hashKey];
|
|
496
|
+
const expId = isValidEntryId(expEntry)
|
|
497
|
+
? expEntry.id
|
|
498
|
+
: compId;
|
|
499
|
+
const expChunks = isWebpackBuild ? (expEntry?.chunks || compChunks) : exp;
|
|
500
|
+
|
|
501
|
+
normalized[hashKey] = { id: expId, chunks: expChunks, name: exp };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (prevSet.size !== clientComponents.length) {
|
|
506
|
+
componentsChanged = true;
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
// Fast path: update normalized mapping for known client components without re-reading files or parsing AST
|
|
510
|
+
const isValidEntryId = (entry) => Boolean(entry?.id && (isWebpackBuild || entry.id.endsWith(".js")));
|
|
511
|
+
for (const comp of clientComponents) {
|
|
512
|
+
const urlVariants = generateAllUrlVariants(comp);
|
|
513
|
+
const fileExports = getCachedFileExports(comp);
|
|
514
|
+
const cachedExportInfo = compExportsCache.get(comp);
|
|
515
|
+
if (cachedExportInfo?.exportsChanged) {
|
|
516
|
+
componentsChanged = true;
|
|
517
|
+
}
|
|
518
|
+
let matchedEntry = null;
|
|
519
|
+
for (const u of urlVariants) {
|
|
520
|
+
if (isValidEntryId(parsedClientManifest[u])) { matchedEntry = parsedClientManifest[u]; break; }
|
|
521
|
+
if (isValidEntryId(parsedClientManifest[`${u}#default`])) { matchedEntry = parsedClientManifest[`${u}#default`]; break; }
|
|
522
|
+
for (const exp of fileExports) {
|
|
523
|
+
const expEntry = parsedClientManifest[`${u}#${exp}`];
|
|
524
|
+
if (isValidEntryId(expEntry)) {
|
|
525
|
+
matchedEntry = expEntry;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (matchedEntry) break;
|
|
530
|
+
}
|
|
531
|
+
if (!matchedEntry) {
|
|
532
|
+
for (const u of urlVariants) {
|
|
533
|
+
const uLower = u.toLowerCase();
|
|
534
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
535
|
+
const kLower = k.toLowerCase();
|
|
536
|
+
if ((kLower === uLower || kLower.startsWith(uLower + "#")) && isValidEntryId(v)) {
|
|
537
|
+
matchedEntry = v;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (matchedEntry) break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const compId = isValidEntryId(matchedEntry)
|
|
546
|
+
? matchedEntry.id
|
|
547
|
+
: getFallbackChunkId(comp);
|
|
548
|
+
const compChunks = isWebpackBuild ? (matchedEntry?.chunks || []) : "default";
|
|
549
|
+
|
|
550
|
+
for (const u of urlVariants) {
|
|
551
|
+
if (!normalized[u] || !normalized[u].id || !isValidEntryId(normalized[u])) {
|
|
552
|
+
normalized[u] = { id: compId, chunks: compChunks, name: "*" };
|
|
553
|
+
} else if (isValidEntryId(matchedEntry)) {
|
|
554
|
+
normalized[u].id = compId;
|
|
555
|
+
normalized[u].chunks = compChunks;
|
|
556
|
+
}
|
|
557
|
+
for (const exp of fileExports) {
|
|
558
|
+
const hashKey = `${u}#${exp}`;
|
|
559
|
+
const expEntry = parsedClientManifest[hashKey];
|
|
560
|
+
const expId = isValidEntryId(expEntry)
|
|
561
|
+
? expEntry.id
|
|
562
|
+
: compId;
|
|
563
|
+
const expChunks = isWebpackBuild ? (expEntry?.chunks || compChunks) : exp;
|
|
564
|
+
|
|
565
|
+
normalized[hashKey] = { id: expId, chunks: expChunks, name: exp };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
knownServerFiles.clear();
|
|
572
|
+
for (const f of Object.keys(parsedServerFunctionsManifest)) {
|
|
573
|
+
knownServerFiles.add(path.resolve(projectRoot, f));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (Object.keys(normalized).length > 0) {
|
|
577
|
+
globalThis.__DINOU_CLIENT_MANIFEST__ = normalized;
|
|
578
|
+
}
|
|
579
|
+
globalThis.__DINOU_SERVER_FUNCTIONS_MANIFEST__ = parsedServerFunctionsManifest;
|
|
580
|
+
globalThis.__DINOU_ASSET_MANIFEST__ = parsedAssetManifest;
|
|
581
|
+
|
|
582
|
+
// Import map generation for non-Webpack (esbuild / rollup)
|
|
583
|
+
let importMapHtml = "";
|
|
584
|
+
if (!isWebpackBuild && Object.keys(parsedClientManifest).length > 0) {
|
|
585
|
+
const imports = {};
|
|
586
|
+
const moduleBasePath = pathToFileURL(projectRoot).href + "/";
|
|
587
|
+
for (const [key, val] of Object.entries(parsedClientManifest)) {
|
|
588
|
+
let specifier = key;
|
|
589
|
+
if (specifier.startsWith(moduleBasePath)) {
|
|
590
|
+
specifier = specifier.slice(moduleBasePath.length);
|
|
591
|
+
}
|
|
592
|
+
const hashIdx = specifier.indexOf("#");
|
|
593
|
+
if (hashIdx !== -1) specifier = specifier.slice(0, hashIdx);
|
|
594
|
+
imports[specifier] = val.id;
|
|
595
|
+
|
|
596
|
+
const srcIdx = key.indexOf("/src/");
|
|
597
|
+
if (srcIdx !== -1) {
|
|
598
|
+
const srcRel = "src/" + key.slice(srcIdx + 5).split("#")[0];
|
|
599
|
+
imports[srcRel] = val.id;
|
|
600
|
+
imports["/" + srcRel] = val.id;
|
|
601
|
+
imports["./" + srcRel] = val.id;
|
|
602
|
+
}
|
|
603
|
+
const dinouIdx = key.lastIndexOf("/dinou/");
|
|
604
|
+
if (dinouIdx !== -1) {
|
|
605
|
+
const dinouRel = "dinou/" + key.slice(dinouIdx + 7).split("#")[0];
|
|
606
|
+
imports[dinouRel] = val.id;
|
|
607
|
+
imports["/" + dinouRel] = val.id;
|
|
608
|
+
imports["./" + dinouRel] = val.id;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (linkChunkId) {
|
|
612
|
+
imports["dinou/core/link.jsx"] = linkChunkId;
|
|
613
|
+
imports["dinou/core/link"] = linkChunkId;
|
|
614
|
+
imports["/dinou/core/link.jsx"] = linkChunkId;
|
|
615
|
+
imports["./dinou/core/link.jsx"] = linkChunkId;
|
|
616
|
+
for (const lp of candidateLinkPaths) {
|
|
617
|
+
for (const u of generateAllUrlVariants(lp)) imports[u] = linkChunkId;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (redirectChunkId) {
|
|
621
|
+
imports["dinou/core/client-redirect.jsx"] = redirectChunkId;
|
|
622
|
+
imports["dinou/core/client-redirect"] = redirectChunkId;
|
|
623
|
+
imports["/dinou/core/client-redirect.jsx"] = redirectChunkId;
|
|
624
|
+
imports["./dinou/core/client-redirect.jsx"] = redirectChunkId;
|
|
625
|
+
for (const rp of candidateRedirectPaths) {
|
|
626
|
+
for (const u of generateAllUrlVariants(rp)) imports[u] = redirectChunkId;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (slotChunkId) {
|
|
630
|
+
imports["dinou/core/slot.js"] = slotChunkId;
|
|
631
|
+
imports["dinou/core/slot"] = slotChunkId;
|
|
632
|
+
imports["/dinou/core/slot.js"] = slotChunkId;
|
|
633
|
+
imports["./dinou/core/slot.js"] = slotChunkId;
|
|
634
|
+
for (const sp of candidateSlotPaths) {
|
|
635
|
+
for (const u of generateAllUrlVariants(sp)) imports[u] = slotChunkId;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
importMapHtml = `<script type="importmap">{"imports":${JSON.stringify(imports)}}</script>`;
|
|
639
|
+
}
|
|
640
|
+
globalThis.__DINOU_IMPORT_MAP_HTML__ = importMapHtml;
|
|
641
|
+
|
|
642
|
+
// Dynamic SSR module map and chunk reverse mapping for 0-rebuild dev SSR
|
|
643
|
+
const dynamicSsrModuleMap = {};
|
|
644
|
+
const chunkToFile = {};
|
|
645
|
+
|
|
646
|
+
const registerModuleMapEntry = (chunkId, fileUrl, expName) => {
|
|
647
|
+
if (!chunkId || !fileUrl) return;
|
|
648
|
+
const ids = [chunkId];
|
|
649
|
+
if (chunkId.startsWith("/")) ids.push(chunkId.slice(1));
|
|
650
|
+
else ids.push("/" + chunkId);
|
|
651
|
+
|
|
652
|
+
// Canonicalize fileUrl to 3 slashes (standard file URL)
|
|
653
|
+
let canonicalUrl = fileUrl;
|
|
654
|
+
if (canonicalUrl.startsWith("file://") && !canonicalUrl.startsWith("file:///")) {
|
|
655
|
+
canonicalUrl = "file:///" + canonicalUrl.slice(7);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const urls = [canonicalUrl];
|
|
659
|
+
if (canonicalUrl.startsWith("file:///c:/")) urls.push("file:///C:/" + canonicalUrl.slice(11));
|
|
660
|
+
else if (canonicalUrl.startsWith("file:///C:/")) urls.push("file:///c:/" + canonicalUrl.slice(11));
|
|
661
|
+
|
|
662
|
+
// Also include 2-slash variants
|
|
663
|
+
const twoSlashUrls = urls.map((u) => "file://" + u.slice(8));
|
|
664
|
+
const allUrls = [...urls, ...twoSlashUrls];
|
|
665
|
+
|
|
666
|
+
for (const id of ids) {
|
|
667
|
+
if (!dynamicSsrModuleMap[id]) {
|
|
668
|
+
dynamicSsrModuleMap[id] = { "*": { id: canonicalUrl, chunks: [], name: "*" } };
|
|
669
|
+
}
|
|
670
|
+
if (expName) {
|
|
671
|
+
dynamicSsrModuleMap[id][expName] = { id: canonicalUrl, chunks: [], name: expName };
|
|
672
|
+
}
|
|
673
|
+
chunkToFile[id] = canonicalUrl;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
for (const u of allUrls) {
|
|
677
|
+
if (!dynamicSsrModuleMap[u]) {
|
|
678
|
+
dynamicSsrModuleMap[u] = { "*": { id: canonicalUrl, chunks: [], name: "*" } };
|
|
679
|
+
}
|
|
680
|
+
if (expName) {
|
|
681
|
+
dynamicSsrModuleMap[u][expName] = { id: canonicalUrl, chunks: [], name: expName };
|
|
682
|
+
}
|
|
683
|
+
chunkToFile[u] = canonicalUrl;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
688
|
+
const fileUrl = k.split("#")[0];
|
|
689
|
+
const expName = k.includes("#") ? k.split("#")[1] : null;
|
|
690
|
+
if (v?.id) {
|
|
691
|
+
registerModuleMapEntry(v.id, fileUrl, expName);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
for (const [k, v] of Object.entries(normalized)) {
|
|
696
|
+
const fileUrl = k.split("#")[0];
|
|
697
|
+
const expName = k.includes("#") ? k.split("#")[1] : null;
|
|
698
|
+
if (v?.id && (isWebpackBuild || v.id.endsWith(".js"))) {
|
|
699
|
+
registerModuleMapEntry(v.id, fileUrl, expName);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (linkChunkId) {
|
|
704
|
+
const linkCompIndex = clientComponents.findIndex((c) => candidateLinkPaths.has(path.resolve(c)));
|
|
705
|
+
if (linkCompIndex !== -1) {
|
|
706
|
+
const fileUrl = pathToFileURL(clientComponents[linkCompIndex]).href;
|
|
707
|
+
registerModuleMapEntry(linkChunkId, fileUrl, "Link");
|
|
708
|
+
registerModuleMapEntry(linkChunkId, fileUrl, "default");
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (redirectChunkId) {
|
|
713
|
+
const redirectCompIndex = clientComponents.findIndex((c) => candidateRedirectPaths.has(path.resolve(c)));
|
|
714
|
+
if (redirectCompIndex !== -1) {
|
|
715
|
+
const fileUrl = pathToFileURL(clientComponents[redirectCompIndex]).href;
|
|
716
|
+
registerModuleMapEntry(redirectChunkId, fileUrl, "ClientRedirect");
|
|
717
|
+
registerModuleMapEntry(redirectChunkId, fileUrl, "default");
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (slotChunkId) {
|
|
722
|
+
const slotCompIndex = clientComponents.findIndex((c) => candidateSlotPaths.has(path.resolve(c)));
|
|
723
|
+
if (slotCompIndex !== -1) {
|
|
724
|
+
const fileUrl = pathToFileURL(clientComponents[slotCompIndex]).href;
|
|
725
|
+
registerModuleMapEntry(slotChunkId, fileUrl, "DinouPageSlot");
|
|
726
|
+
registerModuleMapEntry(slotChunkId, fileUrl, "DinouCacheSlotBoundary");
|
|
727
|
+
registerModuleMapEntry(slotChunkId, fileUrl, "default");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
globalThis.__DINOU_DYNAMIC_SSR_MODULE_MAP__ = dynamicSsrModuleMap;
|
|
732
|
+
globalThis.__DINOU_SSR_CHUNK_TO_FILE__ = chunkToFile;
|
|
733
|
+
|
|
734
|
+
return { linkChunkId, redirectChunkId, slotChunkId, componentsChanged };
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Initial manifest scan
|
|
738
|
+
await updateManifestsState({ forceRescan: true });
|
|
739
|
+
|
|
740
|
+
// Generate route modules and entry files
|
|
741
|
+
function generateEntryFiles() {
|
|
742
|
+
const routeModulesCode = generateRouteModulesCode(projectRoot, "../..");
|
|
743
|
+
fs.writeFileSync(path.join(devDir, "route-modules.mjs"), routeModulesCode, "utf8");
|
|
744
|
+
|
|
745
|
+
const discoveredLayouts = [];
|
|
746
|
+
function findLayoutRoutes(dir, rel = "") {
|
|
747
|
+
if (!fs.existsSync(dir)) return;
|
|
748
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
749
|
+
for (const entry of entries) {
|
|
750
|
+
if (entry.isDirectory()) {
|
|
751
|
+
findLayoutRoutes(path.join(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
752
|
+
} else if (/^layout\.[jt]sx?$/.test(entry.name)) {
|
|
753
|
+
discoveredLayouts.push(rel);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
findLayoutRoutes(srcDir);
|
|
758
|
+
globalThis.__DINOU_LAYOUTS__ = discoveredLayouts;
|
|
759
|
+
|
|
760
|
+
const envSetupContent = `// Auto-generated Dev Environment Setup
|
|
761
|
+
if (typeof globalThis.__webpack_require__ === 'undefined') {
|
|
762
|
+
globalThis.__webpack_require__ = function(id) {
|
|
763
|
+
if (globalThis.__webpack_modules__ && globalThis.__webpack_modules__[id]) {
|
|
764
|
+
return globalThis.__webpack_modules__[id];
|
|
765
|
+
}
|
|
766
|
+
return {};
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
if (typeof globalThis.__webpack_require__.u === 'undefined') {
|
|
770
|
+
globalThis.__webpack_require__.u = function(chunkId) { return '' + chunkId + '.js'; };
|
|
771
|
+
}
|
|
772
|
+
if (typeof globalThis.__webpack_chunk_load__ === 'undefined') {
|
|
773
|
+
globalThis.__webpack_chunk_load__ = () => Promise.resolve();
|
|
774
|
+
}
|
|
775
|
+
globalThis.__DINOU_LAYOUTS__ = ${JSON.stringify(discoveredLayouts)};
|
|
776
|
+
`;
|
|
777
|
+
fs.writeFileSync(path.join(devDir, "env-setup.mjs"), envSetupContent, "utf8");
|
|
778
|
+
|
|
779
|
+
// SSR Client Manifest
|
|
780
|
+
let ssrManifestCode = `// Auto-generated client modules registry for Dev SSR\n`;
|
|
781
|
+
clientComponents.forEach((compPath, index) => {
|
|
782
|
+
const normPath = compPath.replace(/\\/g, "/");
|
|
783
|
+
ssrManifestCode += `import * as mod_${index} from ${JSON.stringify(normPath)};\n`;
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const serverFunctionFiles = Object.keys(parsedServerFunctionsManifest);
|
|
787
|
+
serverFunctionFiles.forEach((relPath, index) => {
|
|
788
|
+
const absPath = path.resolve(projectRoot, relPath).replace(/\\/g, "/");
|
|
789
|
+
ssrManifestCode += `import * as sf_${index} from ${JSON.stringify(absPath)};\n`;
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
ssrManifestCode += `\nexport const clientModules = {\n`;
|
|
793
|
+
const emittedClientModules = new Set();
|
|
794
|
+
function addClientModule(key, valueExpr) {
|
|
795
|
+
if (!emittedClientModules.has(key)) {
|
|
796
|
+
emittedClientModules.add(key);
|
|
797
|
+
ssrManifestCode += ` ${JSON.stringify(key)}: ${valueExpr},\n`;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
clientComponents.forEach((compPath, index) => {
|
|
802
|
+
for (const u of generateAllUrlVariants(compPath)) {
|
|
803
|
+
addClientModule(u, `mod_${index}`);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
808
|
+
const fileUrl = k.split("#")[0];
|
|
809
|
+
const compIndex = clientComponents.findIndex(
|
|
810
|
+
(c) => pathToFileURL(c).href === fileUrl || pathToFileURL(c).href.toLowerCase() === fileUrl.toLowerCase()
|
|
811
|
+
);
|
|
812
|
+
if (compIndex !== -1) {
|
|
813
|
+
if (v?.id) {
|
|
814
|
+
addClientModule(v.id, `mod_${compIndex}`);
|
|
815
|
+
if (v.id.startsWith("/")) addClientModule(v.id.slice(1), `mod_${compIndex}`);
|
|
816
|
+
else addClientModule("/" + v.id, `mod_${compIndex}`);
|
|
817
|
+
}
|
|
818
|
+
addClientModule(fileUrl, `mod_${compIndex}`);
|
|
819
|
+
if (fileUrl.startsWith("file://") && !fileUrl.startsWith("file:///")) {
|
|
820
|
+
addClientModule("file:///" + fileUrl.slice(7), `mod_${compIndex}`);
|
|
821
|
+
} else if (fileUrl.startsWith("file:///")) {
|
|
822
|
+
addClientModule("file://" + fileUrl.slice(8), `mod_${compIndex}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (linkChunkId) {
|
|
828
|
+
const linkCompIndex = clientComponents.findIndex((c) => candidateLinkPaths.has(path.resolve(c)));
|
|
829
|
+
if (linkCompIndex !== -1) {
|
|
830
|
+
addClientModule(linkChunkId, `mod_${linkCompIndex}`);
|
|
831
|
+
for (const lp of candidateLinkPaths) {
|
|
832
|
+
for (const u of generateAllUrlVariants(lp)) addClientModule(u, `mod_${linkCompIndex}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (redirectChunkId) {
|
|
838
|
+
const redirectCompIndex = clientComponents.findIndex((c) => candidateRedirectPaths.has(path.resolve(c)));
|
|
839
|
+
if (redirectCompIndex !== -1) {
|
|
840
|
+
addClientModule(redirectChunkId, `mod_${redirectCompIndex}`);
|
|
841
|
+
for (const rp of candidateRedirectPaths) {
|
|
842
|
+
for (const u of generateAllUrlVariants(rp)) addClientModule(u, `mod_${redirectCompIndex}`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (slotChunkId) {
|
|
848
|
+
const slotCompIndex = clientComponents.findIndex((c) => candidateSlotPaths.has(path.resolve(c)));
|
|
849
|
+
if (slotCompIndex !== -1) {
|
|
850
|
+
addClientModule(slotChunkId, `mod_${slotCompIndex}`);
|
|
851
|
+
for (const sp of candidateSlotPaths) {
|
|
852
|
+
for (const u of generateAllUrlVariants(sp)) addClientModule(u, `mod_${slotCompIndex}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
serverFunctionFiles.forEach((relPath, index) => {
|
|
858
|
+
const normRel = relPath.replace(/\\/g, "/");
|
|
859
|
+
const relFileUrl = "file:///" + normRel;
|
|
860
|
+
const absPath = path.resolve(projectRoot, relPath);
|
|
861
|
+
const fullFileUrl = pathToFileURL(absPath).href;
|
|
862
|
+
const altFullFileUrl = fullFileUrl.replace(/file:\/\/\/([a-zA-Z]):/, (m, d) => 'file:///' + (d === d.toLowerCase() ? d.toUpperCase() : d.toLowerCase()) + ':');
|
|
863
|
+
addClientModule(relFileUrl, `sf_${index}`);
|
|
864
|
+
addClientModule(fullFileUrl, `sf_${index}`);
|
|
865
|
+
if (altFullFileUrl !== fullFileUrl) addClientModule(altFullFileUrl, `sf_${index}`);
|
|
866
|
+
});
|
|
867
|
+
ssrManifestCode += `};\n\n`;
|
|
868
|
+
|
|
869
|
+
ssrManifestCode += `export const ssrConsumerManifest = {\n moduleMap: {\n`;
|
|
870
|
+
const emittedModuleMap = new Set();
|
|
871
|
+
function addModuleMap(key, valueObjStr) {
|
|
872
|
+
if (!emittedModuleMap.has(key)) {
|
|
873
|
+
emittedModuleMap.add(key);
|
|
874
|
+
ssrManifestCode += ` ${JSON.stringify(key)}: ${valueObjStr},\n`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
clientComponents.forEach((compPath) => {
|
|
879
|
+
const fileUrl = pathToFileURL(compPath).href;
|
|
880
|
+
for (const u of generateAllUrlVariants(compPath)) {
|
|
881
|
+
addModuleMap(u, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
for (const [k, v] of Object.entries(parsedClientManifest)) {
|
|
886
|
+
const fileUrl = k.split("#")[0];
|
|
887
|
+
if (v?.id) {
|
|
888
|
+
addModuleMap(v.id, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
889
|
+
if (v.id.startsWith("/")) {
|
|
890
|
+
addModuleMap(v.id.slice(1), `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
891
|
+
} else {
|
|
892
|
+
addModuleMap("/" + v.id, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (linkChunkId) {
|
|
898
|
+
const linkCompIndex = clientComponents.findIndex((c) => candidateLinkPaths.has(path.resolve(c)));
|
|
899
|
+
if (linkCompIndex !== -1) {
|
|
900
|
+
const fileUrl = pathToFileURL(clientComponents[linkCompIndex]).href;
|
|
901
|
+
addModuleMap(linkChunkId, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
902
|
+
for (const lp of candidateLinkPaths) {
|
|
903
|
+
for (const u of generateAllUrlVariants(lp)) addModuleMap(u, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (redirectChunkId) {
|
|
909
|
+
const redirectCompIndex = clientComponents.findIndex((c) => candidateRedirectPaths.has(path.resolve(c)));
|
|
910
|
+
if (redirectCompIndex !== -1) {
|
|
911
|
+
const fileUrl = pathToFileURL(clientComponents[redirectCompIndex]).href;
|
|
912
|
+
addModuleMap(redirectChunkId, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
913
|
+
for (const rp of candidateRedirectPaths) {
|
|
914
|
+
for (const u of generateAllUrlVariants(rp)) addModuleMap(u, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (slotChunkId) {
|
|
920
|
+
const slotCompIndex = clientComponents.findIndex((c) => candidateSlotPaths.has(path.resolve(c)));
|
|
921
|
+
if (slotCompIndex !== -1) {
|
|
922
|
+
const fileUrl = pathToFileURL(clientComponents[slotCompIndex]).href;
|
|
923
|
+
addModuleMap(slotChunkId, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
924
|
+
for (const sp of candidateSlotPaths) {
|
|
925
|
+
for (const u of generateAllUrlVariants(sp)) addModuleMap(u, `{ "*": { id: ${JSON.stringify(fileUrl)}, chunks: [], name: "*" } }`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
ssrManifestCode += ` },\n serverModuleMap: {\n`;
|
|
930
|
+
|
|
931
|
+
const emittedServerModuleMap = new Set();
|
|
932
|
+
function addServerModuleMap(key, valueObjStr) {
|
|
933
|
+
if (!emittedServerModuleMap.has(key)) {
|
|
934
|
+
emittedServerModuleMap.add(key);
|
|
935
|
+
ssrManifestCode += ` ${JSON.stringify(key)}: ${valueObjStr},\n`;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
serverFunctionFiles.forEach((relPath) => {
|
|
940
|
+
const normRel = relPath.replace(/\\/g, "/");
|
|
941
|
+
const relFileUrl = "file:///" + normRel;
|
|
942
|
+
const absPath = path.resolve(projectRoot, relPath);
|
|
943
|
+
const fullFileUrl = pathToFileURL(absPath).href;
|
|
944
|
+
const altFullFileUrl = fullFileUrl.replace(/file:\/\/\/([a-zA-Z]):/, (m, d) => 'file:///' + (d === d.toLowerCase() ? d.toUpperCase() : d.toLowerCase()) + ':');
|
|
945
|
+
addServerModuleMap(relFileUrl, `{ id: ${JSON.stringify(relFileUrl)}, chunks: [], name: "*" }`);
|
|
946
|
+
addServerModuleMap(fullFileUrl, `{ id: ${JSON.stringify(relFileUrl)}, chunks: [], name: "*" }`);
|
|
947
|
+
if (altFullFileUrl !== fullFileUrl) addServerModuleMap(altFullFileUrl, `{ id: ${JSON.stringify(relFileUrl)}, chunks: [], name: "*" }`);
|
|
948
|
+
|
|
949
|
+
const fns = parsedServerFunctionsManifest[relPath] || [];
|
|
950
|
+
for (const fn of fns) {
|
|
951
|
+
addServerModuleMap(relFileUrl + "#" + fn, `{ id: ${JSON.stringify(relFileUrl)}, chunks: [], name: ${JSON.stringify(fn)} }`);
|
|
952
|
+
addServerModuleMap(fullFileUrl + "#" + fn, `{ id: ${JSON.stringify(relFileUrl)}, chunks: [], name: ${JSON.stringify(fn)} }`);
|
|
953
|
+
if (altFullFileUrl !== fullFileUrl) addServerModuleMap(altFullFileUrl + "#" + fn, `{ id: ${JSON.stringify(relFileUrl)}, chunks: [], name: ${JSON.stringify(fn)} }`);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
ssrManifestCode += ` },\n moduleLoading: null,\n};\n`;
|
|
957
|
+
fs.writeFileSync(path.join(devDir, "ssr-client-manifest.mjs"), ssrManifestCode, "utf8");
|
|
958
|
+
|
|
959
|
+
// RSC Engine Entry
|
|
960
|
+
const rscEntryContent = `// Auto-generated RSC Engine entry for Dev (Dual-Bundle)
|
|
961
|
+
import "./env-setup.mjs";
|
|
962
|
+
import "./route-modules.mjs";
|
|
963
|
+
export { handleRequest } from "${dinouDirSlash}/core/handler.js";
|
|
964
|
+
`;
|
|
965
|
+
fs.writeFileSync(path.join(devDir, "rsc-entry.mjs"), rscEntryContent, "utf8");
|
|
966
|
+
|
|
967
|
+
// SSR Engine Entry
|
|
968
|
+
const ssrEntryContent = `// Auto-generated SSR Engine entry for Dev (Dual-Bundle)
|
|
969
|
+
import { renderRscStreamToHtmlStream } from "${dinouDirSlash}/core/edge-ssr.js";
|
|
970
|
+
import { clientModules, ssrConsumerManifest } from "./ssr-client-manifest.mjs";
|
|
971
|
+
|
|
972
|
+
const wrapModule = (mod) => {
|
|
973
|
+
if (!mod || typeof mod !== "object") return mod;
|
|
974
|
+
if (mod.__esModule) return mod;
|
|
975
|
+
return new Proxy(mod, {
|
|
976
|
+
get(target, prop, receiver) {
|
|
977
|
+
if (prop === "__esModule") return true;
|
|
978
|
+
return Reflect.get(target, prop, receiver);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
const lowerMap = new Map();
|
|
984
|
+
globalThis.__webpack_require__ = (id) => {
|
|
985
|
+
let mod = clientModules[id];
|
|
986
|
+
if (!mod && typeof id === "string") {
|
|
987
|
+
if (lowerMap.size === 0) {
|
|
988
|
+
for (const k of Object.keys(clientModules)) {
|
|
989
|
+
lowerMap.set(k.toLowerCase(), clientModules[k]);
|
|
990
|
+
if (k.startsWith("file:///")) {
|
|
991
|
+
lowerMap.set(("file://" + k.slice(8)).toLowerCase(), clientModules[k]);
|
|
992
|
+
} else if (k.startsWith("file://")) {
|
|
993
|
+
lowerMap.set(("file:///" + k.slice(7)).toLowerCase(), clientModules[k]);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
mod = lowerMap.get(id.toLowerCase());
|
|
998
|
+
if (!mod) {
|
|
999
|
+
if (id.startsWith("file://") && !id.startsWith("file:///")) {
|
|
1000
|
+
const threeSlash = "file:///" + id.slice(7);
|
|
1001
|
+
mod = clientModules[threeSlash] || lowerMap.get(threeSlash.toLowerCase());
|
|
1002
|
+
} else if (id.startsWith("file:///")) {
|
|
1003
|
+
const twoSlash = "file://" + id.slice(8);
|
|
1004
|
+
mod = clientModules[twoSlash] || lowerMap.get(twoSlash.toLowerCase());
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (!mod && typeof id === "string") {
|
|
1009
|
+
const chunkMap = globalThis.__DINOU_SSR_CHUNK_TO_FILE__;
|
|
1010
|
+
let fileUrl = chunkMap?.[id] || chunkMap?.[id.startsWith("/") ? id.slice(1) : "/" + id];
|
|
1011
|
+
if (!fileUrl && id.startsWith("file://")) {
|
|
1012
|
+
const altId = id.startsWith("file:///") ? "file://" + id.slice(8) : "file:///" + id.slice(7);
|
|
1013
|
+
fileUrl = chunkMap?.[altId];
|
|
1014
|
+
}
|
|
1015
|
+
if (!fileUrl) {
|
|
1016
|
+
const clientManifest = globalThis.__DINOU_CLIENT_MANIFEST__ || globalThis.__DINOU_RAW_CLIENT_MANIFEST__;
|
|
1017
|
+
if (clientManifest) {
|
|
1018
|
+
const cleanId = id.startsWith("/") ? id.slice(1) : id;
|
|
1019
|
+
for (const [k, v] of Object.entries(clientManifest)) {
|
|
1020
|
+
if (!v?.id) continue;
|
|
1021
|
+
const cleanVId = v.id.startsWith("/") ? v.id.slice(1) : v.id;
|
|
1022
|
+
if (v.id === id || cleanVId === cleanId || ("/" + cleanVId) === id) {
|
|
1023
|
+
fileUrl = k.split("#")[0];
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (fileUrl) {
|
|
1030
|
+
mod = clientModules[fileUrl] || lowerMap.get(fileUrl.toLowerCase());
|
|
1031
|
+
if (!mod) {
|
|
1032
|
+
if (fileUrl.startsWith("file://") && !fileUrl.startsWith("file:///")) {
|
|
1033
|
+
const threeSlash = "file:///" + fileUrl.slice(7);
|
|
1034
|
+
mod = clientModules[threeSlash] || lowerMap.get(threeSlash.toLowerCase());
|
|
1035
|
+
} else if (fileUrl.startsWith("file:///")) {
|
|
1036
|
+
const twoSlash = "file://" + fileUrl.slice(8);
|
|
1037
|
+
mod = clientModules[twoSlash] || lowerMap.get(twoSlash.toLowerCase());
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (mod) return wrapModule(mod);
|
|
1043
|
+
console.error("[SSR Engine Dev] Module not found in __webpack_require__:", id);
|
|
1044
|
+
return {};
|
|
1045
|
+
};
|
|
1046
|
+
globalThis.__webpack_require__.u = (chunkId) => "" + chunkId + ".js";
|
|
1047
|
+
globalThis.__webpack_chunk_load__ = () => Promise.resolve();
|
|
1048
|
+
|
|
1049
|
+
const dynamicModuleMap = new Proxy(ssrConsumerManifest.moduleMap || {}, {
|
|
1050
|
+
get(target, prop) {
|
|
1051
|
+
if (typeof prop !== "string") return target[prop];
|
|
1052
|
+
if (prop === "__isDinouProxy") return true;
|
|
1053
|
+
if (prop in target) return target[prop];
|
|
1054
|
+
|
|
1055
|
+
const dynamicMap = globalThis.__DINOU_DYNAMIC_SSR_MODULE_MAP__;
|
|
1056
|
+
if (dynamicMap) {
|
|
1057
|
+
if (prop in dynamicMap) {
|
|
1058
|
+
target[prop] = dynamicMap[prop];
|
|
1059
|
+
return dynamicMap[prop];
|
|
1060
|
+
}
|
|
1061
|
+
const altProp = prop.startsWith("/") ? prop.slice(1) : "/" + prop;
|
|
1062
|
+
if (altProp in dynamicMap) {
|
|
1063
|
+
target[prop] = dynamicMap[altProp];
|
|
1064
|
+
return dynamicMap[altProp];
|
|
1065
|
+
}
|
|
1066
|
+
if (prop.startsWith("file://")) {
|
|
1067
|
+
const slashAlt = prop.startsWith("file:///") ? "file://" + prop.slice(8) : "file:///" + prop.slice(7);
|
|
1068
|
+
if (slashAlt in dynamicMap) {
|
|
1069
|
+
target[prop] = dynamicMap[slashAlt];
|
|
1070
|
+
return dynamicMap[slashAlt];
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const clientManifest = globalThis.__DINOU_CLIENT_MANIFEST__ || globalThis.__DINOU_RAW_CLIENT_MANIFEST__;
|
|
1076
|
+
if (clientManifest) {
|
|
1077
|
+
const cleanProp = prop.startsWith("/") ? prop.slice(1) : prop;
|
|
1078
|
+
for (const [k, v] of Object.entries(clientManifest)) {
|
|
1079
|
+
if (!v?.id) continue;
|
|
1080
|
+
const cleanVId = v.id.startsWith("/") ? v.id.slice(1) : v.id;
|
|
1081
|
+
if (v.id === prop || cleanVId === cleanProp || ("/" + cleanVId) === prop) {
|
|
1082
|
+
const fileUrl = k.split("#")[0];
|
|
1083
|
+
const canonicalUrl = fileUrl.startsWith("file://") && !fileUrl.startsWith("file:///") ? "file:///" + fileUrl.slice(7) : fileUrl;
|
|
1084
|
+
const entry = { "*": { id: canonicalUrl, chunks: [], name: "*" } };
|
|
1085
|
+
target[prop] = entry;
|
|
1086
|
+
return entry;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (prop.startsWith("file://")) {
|
|
1092
|
+
const canonicalProp = prop.startsWith("file:///") ? prop : "file:///" + prop.slice(7);
|
|
1093
|
+
const entry = { "*": { id: canonicalProp, chunks: [], name: "*" } };
|
|
1094
|
+
target[prop] = entry;
|
|
1095
|
+
return entry;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return target[prop];
|
|
1099
|
+
},
|
|
1100
|
+
has(target, prop) {
|
|
1101
|
+
if (prop === "__isDinouProxy") return true;
|
|
1102
|
+
if (prop in target) return true;
|
|
1103
|
+
const dynamicMap = globalThis.__DINOU_DYNAMIC_SSR_MODULE_MAP__;
|
|
1104
|
+
if (dynamicMap && (prop in dynamicMap || (prop.startsWith("/") ? prop.slice(1) : "/" + prop) in dynamicMap)) return true;
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
export async function renderHtml(rscStream, options = {}) {
|
|
1110
|
+
const manifest = {
|
|
1111
|
+
...ssrConsumerManifest,
|
|
1112
|
+
moduleMap: dynamicModuleMap,
|
|
1113
|
+
};
|
|
1114
|
+
return renderRscStreamToHtmlStream(rscStream, manifest, options);
|
|
1115
|
+
}
|
|
1116
|
+
`;
|
|
1117
|
+
fs.writeFileSync(path.join(devDir, "ssr-entry.mjs"), ssrEntryContent, "utf8");
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
generateEntryFiles();
|
|
1121
|
+
await yieldToEventLoop();
|
|
1122
|
+
|
|
1123
|
+
// Exports Cache for fast parsing
|
|
1124
|
+
const exportsCache = new Map();
|
|
1125
|
+
function getCachedExports(filePath, code) {
|
|
1126
|
+
let entry = exportsCache.get(filePath);
|
|
1127
|
+
if (!entry || entry.code !== code) {
|
|
1128
|
+
entry = { code, exports: parseExports(code) };
|
|
1129
|
+
exportsCache.set(filePath, entry);
|
|
1130
|
+
}
|
|
1131
|
+
return entry.exports;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Transform & File Content Cache for Server Plugins (mtimeMs based)
|
|
1135
|
+
const fileTransformCache = new Map();
|
|
1136
|
+
|
|
1137
|
+
function getCachedFileTransform(filePath, type, transformFn) {
|
|
1138
|
+
try {
|
|
1139
|
+
const stats = fs.statSync(filePath);
|
|
1140
|
+
const key = `${filePath}::${type}`;
|
|
1141
|
+
const cached = fileTransformCache.get(key);
|
|
1142
|
+
if (cached && cached.mtime === stats.mtimeMs) {
|
|
1143
|
+
return cached.result;
|
|
1144
|
+
}
|
|
1145
|
+
const result = transformFn();
|
|
1146
|
+
fileTransformCache.set(key, { mtime: stats.mtimeMs, result });
|
|
1147
|
+
return result;
|
|
1148
|
+
} catch (e) {
|
|
1149
|
+
return transformFn();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Plugins
|
|
1154
|
+
const clientReferencesPlugin = {
|
|
1155
|
+
name: "dinou-client-references",
|
|
1156
|
+
setup(build) {
|
|
1157
|
+
build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
|
|
1158
|
+
if (args.path.includes("node_modules")) {
|
|
1159
|
+
if (
|
|
1160
|
+
args.path.includes("react-server-dom") ||
|
|
1161
|
+
args.path.includes("@roggc/react-server-dom-esm") ||
|
|
1162
|
+
args.path.includes("node_modules/react/") ||
|
|
1163
|
+
args.path.includes("node_modules\\react\\") ||
|
|
1164
|
+
args.path.includes("node_modules/react-dom/") ||
|
|
1165
|
+
args.path.includes("node_modules\\react-dom\\")
|
|
1166
|
+
) {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const normalizedPath = args.path.replace(/\\/g, "/");
|
|
1171
|
+
if (normalizedPath.includes("dinou/core/navigation")) return null;
|
|
1172
|
+
|
|
1173
|
+
return getCachedFileTransform(args.path, "client-proxy", () => {
|
|
1174
|
+
let code;
|
|
1175
|
+
try { code = fs.readFileSync(args.path, "utf8"); } catch (e) { return null; }
|
|
1176
|
+
if (!isSupportedClientModule(args.path, code)) return null;
|
|
1177
|
+
if (!useClientRegex.test(code.trim())) return null;
|
|
1178
|
+
|
|
1179
|
+
const exports = getCachedExports(args.path, code);
|
|
1180
|
+
const absPath = path.resolve(args.path);
|
|
1181
|
+
const fileUrl = pathToFileURL(absPath).href;
|
|
1182
|
+
|
|
1183
|
+
let proxyCode = `import { createClientModuleProxy } from "react-server-dom-webpack/server.edge";\n`;
|
|
1184
|
+
proxyCode += `const proxy = createClientModuleProxy(${JSON.stringify(fileUrl)});\n`;
|
|
1185
|
+
for (const name of exports) {
|
|
1186
|
+
if (name === "default") {
|
|
1187
|
+
proxyCode += `export default proxy.default;\n`;
|
|
1188
|
+
} else {
|
|
1189
|
+
proxyCode += `export const ${name} = proxy[${JSON.stringify(name)}];\n`;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (!exports.includes("default")) {
|
|
1193
|
+
proxyCode += `export default proxy.default;\n`;
|
|
1194
|
+
}
|
|
1195
|
+
return { contents: proxyCode, loader: "js" };
|
|
1196
|
+
});
|
|
1197
|
+
});
|
|
1198
|
+
},
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
const serverReferencesPlugin = {
|
|
1202
|
+
name: "dinou-server-references",
|
|
1203
|
+
setup(build) {
|
|
1204
|
+
build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
|
|
1205
|
+
if (args.path.includes("node_modules")) return null;
|
|
1206
|
+
|
|
1207
|
+
return getCachedFileTransform(args.path, "server-ref-rsc", () => {
|
|
1208
|
+
let code;
|
|
1209
|
+
try { code = fs.readFileSync(args.path, "utf8"); } catch (e) { return null; }
|
|
1210
|
+
if (!useServerRegex.test(code.trim())) return null;
|
|
1211
|
+
|
|
1212
|
+
const exports = getCachedExports(args.path, code);
|
|
1213
|
+
const absPath = path.resolve(args.path);
|
|
1214
|
+
const relPath = path.relative(projectRoot, absPath).replace(/\\/g, "/");
|
|
1215
|
+
const relativeFileUrl = "file:///" + relPath;
|
|
1216
|
+
|
|
1217
|
+
let transformed = code + "\n\n";
|
|
1218
|
+
transformed += `import { registerServerReference } from "react-server-dom-webpack/server.edge";\n`;
|
|
1219
|
+
for (const name of exports) {
|
|
1220
|
+
if (name !== "default") {
|
|
1221
|
+
transformed += `registerServerReference(${name}, ${JSON.stringify(relativeFileUrl)}, ${JSON.stringify(name)});\n`;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const ext = path.extname(args.path);
|
|
1225
|
+
return { contents: transformed, loader: ext === ".ts" ? "ts" : ext === ".tsx" ? "tsx" : ext === ".jsx" ? "jsx" : "js" };
|
|
1226
|
+
});
|
|
1227
|
+
});
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
const serverReferencesPluginSsr = {
|
|
1232
|
+
name: "dinou-server-references-ssr",
|
|
1233
|
+
setup(build) {
|
|
1234
|
+
build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
|
|
1235
|
+
if (args.path.includes("node_modules")) return null;
|
|
1236
|
+
|
|
1237
|
+
return getCachedFileTransform(args.path, "server-ref-ssr", () => {
|
|
1238
|
+
let code;
|
|
1239
|
+
try { code = fs.readFileSync(args.path, "utf8"); } catch (e) { return null; }
|
|
1240
|
+
if (!useServerRegex.test(code.trim())) return null;
|
|
1241
|
+
|
|
1242
|
+
const exports = getCachedExports(args.path, code);
|
|
1243
|
+
const absPath = path.resolve(args.path);
|
|
1244
|
+
const relPath = path.relative(projectRoot, absPath).replace(/\\/g, "/");
|
|
1245
|
+
const relativeFileUrl = "file:///" + relPath;
|
|
1246
|
+
|
|
1247
|
+
let transformed = code + "\n\n";
|
|
1248
|
+
transformed += `import { registerServerReference } from "react-server-dom-webpack/client.edge";\n`;
|
|
1249
|
+
for (const name of exports) {
|
|
1250
|
+
if (name !== "default") {
|
|
1251
|
+
transformed += `registerServerReference(${name}, ${JSON.stringify(relativeFileUrl + "#" + name)});\n`;
|
|
1252
|
+
transformed += `registerServerReference(${name}, ${JSON.stringify(pathToFileURL(absPath).href + "#" + name)});\n`;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const ext = path.extname(args.path);
|
|
1256
|
+
return { contents: transformed, loader: ext === ".ts" ? "ts" : ext === ".tsx" ? "tsx" : ext === ".jsx" ? "jsx" : "js" };
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
},
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
const nodeBuiltins = [
|
|
1263
|
+
"assert", "async_hooks", "buffer", "child_process", "cluster", "console",
|
|
1264
|
+
"constants", "crypto", "dgram", "diagnostics_channel", "dns", "domain",
|
|
1265
|
+
"events", "fs", "fs/promises", "http", "http2", "https", "inspector",
|
|
1266
|
+
"module", "net", "os", "path", "path/posix", "path/win32", "perf_hooks",
|
|
1267
|
+
"process", "punycode", "querystring", "readline", "repl", "stream",
|
|
1268
|
+
"stream/consumers", "stream/promises", "stream/web", "string_decoder",
|
|
1269
|
+
"timers", "timers/promises", "tls", "trace_events", "tty", "url",
|
|
1270
|
+
"util", "util/types", "v8", "vm", "wasi", "worker_threads", "zlib"
|
|
1271
|
+
];
|
|
1272
|
+
const externalList = [...nodeBuiltins, ...nodeBuiltins.map((b) => "node:" + b)];
|
|
1273
|
+
|
|
1274
|
+
const commonAlias = {
|
|
1275
|
+
"@": path.resolve(projectRoot, "src"),
|
|
1276
|
+
"dinou/config": path.resolve(dinouDir, "core/config.js"),
|
|
1277
|
+
dinou: dinouDir,
|
|
1278
|
+
};
|
|
1279
|
+
const commonLoader = {
|
|
1280
|
+
".js": "jsx", ".jsx": "jsx", ".ts": "ts", ".tsx": "tsx",
|
|
1281
|
+
".json": "json", ".css": "empty",
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
const serverAssetPlugin = {
|
|
1285
|
+
name: "dinou-server-asset-plugin",
|
|
1286
|
+
setup(build) {
|
|
1287
|
+
build.onResolve({ filter: assetRegex }, (args) => {
|
|
1288
|
+
let resolvedPath;
|
|
1289
|
+
if (args.path.startsWith("@/")) {
|
|
1290
|
+
resolvedPath = path.resolve(projectRoot, "src", args.path.slice(2));
|
|
1291
|
+
} else if (path.isAbsolute(args.path)) {
|
|
1292
|
+
resolvedPath = args.path;
|
|
1293
|
+
} else {
|
|
1294
|
+
resolvedPath = path.resolve(args.resolveDir, args.path);
|
|
1295
|
+
}
|
|
1296
|
+
return { path: resolvedPath, namespace: "dinou-server-asset" };
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
build.onLoad({ filter: /.*/, namespace: "dinou-server-asset" }, (args) => {
|
|
1300
|
+
const ext = path.extname(args.path);
|
|
1301
|
+
const base = path.basename(args.path, ext);
|
|
1302
|
+
const scoped = createScopedName(base, args.path);
|
|
1303
|
+
const assetUrl = `/assets/${scoped}${ext}`;
|
|
1304
|
+
const assetKey = `assets/${scoped}${ext}`;
|
|
1305
|
+
|
|
1306
|
+
try {
|
|
1307
|
+
const fileBuf = fs.readFileSync(args.path);
|
|
1308
|
+
if (typeof globalThis !== "undefined") {
|
|
1309
|
+
if (!globalThis.__DINOU_MEM_FILES__) {
|
|
1310
|
+
globalThis.__DINOU_MEM_FILES__ = new Map();
|
|
1311
|
+
}
|
|
1312
|
+
globalThis.__DINOU_MEM_FILES__.set(assetKey, fileBuf);
|
|
1313
|
+
globalThis.__DINOU_MEM_FILES__.set("/" + assetKey, fileBuf);
|
|
1314
|
+
globalThis.__DINOU_MEM_FILES__.set(`${scoped}${ext}`, fileBuf);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (process.env.DINOU_WRITE_TO_DISK === "true") {
|
|
1318
|
+
const outAssetDir = path.resolve(projectRoot, ".dinou/public/assets");
|
|
1319
|
+
const outAssetPath = path.join(outAssetDir, `${scoped}${ext}`);
|
|
1320
|
+
if (!fs.existsSync(outAssetPath)) {
|
|
1321
|
+
fs.mkdirSync(outAssetDir, { recursive: true });
|
|
1322
|
+
fs.writeFileSync(outAssetPath, fileBuf);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
} catch (e) {}
|
|
1326
|
+
|
|
1327
|
+
return {
|
|
1328
|
+
contents: `export default ${JSON.stringify(assetUrl)};`,
|
|
1329
|
+
loader: "js",
|
|
1330
|
+
};
|
|
1331
|
+
});
|
|
1332
|
+
},
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
const resolvePkgCache = new Map();
|
|
1336
|
+
|
|
1337
|
+
function createDevExternalPackagesPlugin(isRsc = true) {
|
|
1338
|
+
return {
|
|
1339
|
+
name: `dinou-dev-external-packages-${isRsc ? "rsc" : "ssr"}`,
|
|
1340
|
+
setup(build) {
|
|
1341
|
+
build.onResolve({ filter: /^[^.\/]|^\.[^.\/]/ }, async (args) => {
|
|
1342
|
+
// 1. Windows or Unix absolute paths
|
|
1343
|
+
if (path.isAbsolute(args.path) || /^[a-zA-Z]:[\\\/]/.test(args.path)) {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
// 2. Relative paths or leading slashes
|
|
1347
|
+
if (args.path.startsWith(".") || args.path.startsWith("/") || args.path.startsWith("\\")) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
// 3. Virtual modules
|
|
1351
|
+
if (args.path.startsWith("\0") || args.path.startsWith("virtual:")) {
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
// 4. Aliases
|
|
1355
|
+
if (args.path.startsWith("@/") || args.path === "@") {
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
if (args.path === "dinou" || args.path.startsWith("dinou/")) {
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
// 5. In RSC (ctxA), React packages must remain bundled to preserve react-server condition.
|
|
1362
|
+
// In SSR (ctxB), React packages MUST be external so client components and the renderer share the same React singleton!
|
|
1363
|
+
if (isRsc) {
|
|
1364
|
+
if (
|
|
1365
|
+
args.path === "react" ||
|
|
1366
|
+
args.path.startsWith("react/") ||
|
|
1367
|
+
args.path === "react-dom" ||
|
|
1368
|
+
args.path.startsWith("react-dom/") ||
|
|
1369
|
+
args.path === "react-server-dom-webpack" ||
|
|
1370
|
+
args.path.startsWith("react-server-dom-webpack/") ||
|
|
1371
|
+
args.path === "@roggc/react-server-dom-esm" ||
|
|
1372
|
+
args.path.startsWith("@roggc/react-server-dom-esm/")
|
|
1373
|
+
) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
// 6. CSS / Stylesheets (handled by esbuild empty loader)
|
|
1378
|
+
if (/\.(css|scss|sass|less)$/i.test(args.path)) {
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
// 7. Client components from node_modules:
|
|
1382
|
+
// In RSC (ctxA), client components need to be intercepted by clientReferencesPlugin to create the proxy.
|
|
1383
|
+
if (isRsc) {
|
|
1384
|
+
try {
|
|
1385
|
+
const cacheKey = `${args.path}::${args.resolveDir || projectRoot}`;
|
|
1386
|
+
let resolved = resolvePkgCache.get(cacheKey);
|
|
1387
|
+
if (resolved === undefined) {
|
|
1388
|
+
try {
|
|
1389
|
+
resolved = require.resolve(args.path, { paths: [args.resolveDir || projectRoot] });
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
resolved = null;
|
|
1392
|
+
}
|
|
1393
|
+
resolvePkgCache.set(cacheKey, resolved);
|
|
1394
|
+
}
|
|
1395
|
+
if (resolved && knownClientFiles && knownClientFiles.has(path.resolve(resolved))) {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
} catch (e) {}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
return { path: args.path, external: true };
|
|
1402
|
+
});
|
|
1403
|
+
},
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const banner = {
|
|
1408
|
+
js: `import { createRequire as ___createRequire } from 'node:module';
|
|
1409
|
+
import { AsyncLocalStorage as ___AsyncLocalStorage } from 'node:async_hooks';
|
|
1410
|
+
const require = ___createRequire(import.meta.url || 'file:///server.js');
|
|
1411
|
+
const __dirname = '';
|
|
1412
|
+
const __filename = '';
|
|
1413
|
+
globalThis.__dinou_require__ = require;
|
|
1414
|
+
globalThis.__DINOU_DEV__ = true;
|
|
1415
|
+
if (typeof globalThis.AsyncLocalStorage === 'undefined' && typeof ___AsyncLocalStorage !== 'undefined') {
|
|
1416
|
+
globalThis.AsyncLocalStorage = ___AsyncLocalStorage;
|
|
1417
|
+
}
|
|
1418
|
+
if (typeof globalThis.__webpack_require__ === 'undefined') {
|
|
1419
|
+
globalThis.__webpack_require__ = function(id) {
|
|
1420
|
+
if (globalThis.__webpack_modules__ && globalThis.__webpack_modules__[id]) {
|
|
1421
|
+
return globalThis.__webpack_modules__[id];
|
|
1422
|
+
}
|
|
1423
|
+
return {};
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
if (typeof globalThis.__webpack_require__.u === 'undefined') {
|
|
1427
|
+
globalThis.__webpack_require__.u = function(chunkId) { return '' + chunkId + '.js'; };
|
|
1428
|
+
}
|
|
1429
|
+
if (typeof globalThis.__webpack_chunk_load__ === 'undefined') {
|
|
1430
|
+
globalThis.__webpack_chunk_load__ = () => Promise.resolve();
|
|
1431
|
+
}
|
|
1432
|
+
var __webpack_require__ = function(id) {
|
|
1433
|
+
return globalThis.__webpack_require__ ? globalThis.__webpack_require__(id) : {};
|
|
1434
|
+
};
|
|
1435
|
+
__webpack_require__.u = function(chunkId) {
|
|
1436
|
+
return (globalThis.__webpack_require__ && globalThis.__webpack_require__.u)
|
|
1437
|
+
? globalThis.__webpack_require__.u(chunkId)
|
|
1438
|
+
: '' + chunkId + '.js';
|
|
1439
|
+
};
|
|
1440
|
+
var __webpack_chunk_load__ = function(chunkId) {
|
|
1441
|
+
return globalThis.__webpack_chunk_load__ ? globalThis.__webpack_chunk_load__(chunkId) : Promise.resolve();
|
|
1442
|
+
};
|
|
1443
|
+
`,
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
// Create esbuild contexts
|
|
1447
|
+
devTimings.discovery = Date.now() - devStartTime;
|
|
1448
|
+
updateSpinner("Initializing Incremental Dual-Bundle Engine (No fork)...");
|
|
1449
|
+
const rscOutfile = path.join(devDir, "rsc-engine.mjs");
|
|
1450
|
+
const ssrOutfile = path.join(devDir, "ssr-engine.mjs");
|
|
1451
|
+
|
|
1452
|
+
const ctxA = await esbuild.context({
|
|
1453
|
+
entryPoints: { "rsc-engine": path.join(devDir, "rsc-entry.mjs") },
|
|
1454
|
+
outdir: devDir,
|
|
1455
|
+
splitting: true,
|
|
1456
|
+
outExtension: { ".js": ".mjs" },
|
|
1457
|
+
chunkNames: "chunks/rsc/[name]-[hash]",
|
|
1458
|
+
bundle: true,
|
|
1459
|
+
format: "esm",
|
|
1460
|
+
target: "node20",
|
|
1461
|
+
platform: "node",
|
|
1462
|
+
mainFields: ["module", "main"],
|
|
1463
|
+
conditions: ["node", "worker", "react-server"],
|
|
1464
|
+
external: externalList,
|
|
1465
|
+
banner,
|
|
1466
|
+
plugins: [clientReferencesPlugin, serverReferencesPlugin, serverAssetPlugin, createDevExternalPackagesPlugin(true)],
|
|
1467
|
+
alias: commonAlias,
|
|
1468
|
+
loader: commonLoader,
|
|
1469
|
+
jsx: "automatic",
|
|
1470
|
+
define: {
|
|
1471
|
+
"process.env.NODE_ENV": '"development"',
|
|
1472
|
+
"process.env.DINOU_DEV": '"true"',
|
|
1473
|
+
"process.env.DINOU_RUNTIME": '"node-bundle"',
|
|
1474
|
+
"process.env.DINOU_BUILD_TOOL": JSON.stringify(isWebpackBuild ? "webpack" : (process.env.DINOU_BUILD_TOOL || "esbuild")),
|
|
1475
|
+
},
|
|
1476
|
+
logLevel: "warning",
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
const ctxB = await esbuild.context({
|
|
1480
|
+
entryPoints: { "ssr-engine": path.join(devDir, "ssr-entry.mjs") },
|
|
1481
|
+
outdir: devDir,
|
|
1482
|
+
splitting: true,
|
|
1483
|
+
outExtension: { ".js": ".mjs" },
|
|
1484
|
+
chunkNames: "chunks/ssr/[name]-[hash]",
|
|
1485
|
+
bundle: true,
|
|
1486
|
+
format: "esm",
|
|
1487
|
+
target: "node20",
|
|
1488
|
+
platform: "node",
|
|
1489
|
+
mainFields: ["module", "main"],
|
|
1490
|
+
conditions: ["node", "worker", "browser"],
|
|
1491
|
+
external: externalList,
|
|
1492
|
+
banner,
|
|
1493
|
+
plugins: [serverReferencesPluginSsr, serverAssetPlugin, createDevExternalPackagesPlugin(false)],
|
|
1494
|
+
alias: commonAlias,
|
|
1495
|
+
loader: commonLoader,
|
|
1496
|
+
jsx: "automatic",
|
|
1497
|
+
define: {
|
|
1498
|
+
"process.env.NODE_ENV": '"development"',
|
|
1499
|
+
"process.env.DINOU_DEV": '"true"',
|
|
1500
|
+
"process.env.DINOU_RUNTIME": '"node-bundle"',
|
|
1501
|
+
"process.env.DINOU_BUILD_TOOL": JSON.stringify(isWebpackBuild ? "webpack" : (process.env.DINOU_BUILD_TOOL || "esbuild")),
|
|
1502
|
+
},
|
|
1503
|
+
logLevel: "warning",
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
let rscModule = null;
|
|
1507
|
+
let ssrModule = null;
|
|
1508
|
+
let ssrModulePromise = null;
|
|
1509
|
+
let engineVersion = 1;
|
|
1510
|
+
let activeRebuildPromise = null;
|
|
1511
|
+
let activeSsrSyncPromise = null;
|
|
1512
|
+
let initialEngineBuildPromise = null;
|
|
1513
|
+
let ssrSyncTimeout = null;
|
|
1514
|
+
|
|
1515
|
+
function syncSsrEngine() {
|
|
1516
|
+
if (ssrSyncTimeout) {
|
|
1517
|
+
clearTimeout(ssrSyncTimeout);
|
|
1518
|
+
ssrSyncTimeout = null;
|
|
1519
|
+
}
|
|
1520
|
+
const v = "?v=" + Date.now();
|
|
1521
|
+
const promise = (async () => {
|
|
1522
|
+
try {
|
|
1523
|
+
const tB = Date.now();
|
|
1524
|
+
await ctxB.rebuild();
|
|
1525
|
+
logTimeline(`ctxB (SSR Engine) rebuilt in ${Date.now() - tB}ms`);
|
|
1526
|
+
const mod = await dynamicImportWithRetry(pathToFileURL(ssrOutfile).href + v);
|
|
1527
|
+
ssrModule = mod;
|
|
1528
|
+
logTimeline(`SSR module imported into V8 runtime`);
|
|
1529
|
+
return mod;
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
console.error("❌ [SSR Engine Rebuild Error]:", err);
|
|
1532
|
+
throw err;
|
|
1533
|
+
} finally {
|
|
1534
|
+
if (activeSsrSyncPromise === promise) activeSsrSyncPromise = null;
|
|
1535
|
+
if (ssrModulePromise === promise) ssrModulePromise = null;
|
|
1536
|
+
}
|
|
1537
|
+
})();
|
|
1538
|
+
activeSsrSyncPromise = promise;
|
|
1539
|
+
ssrModulePromise = promise;
|
|
1540
|
+
return promise;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
function scheduleSsrSync(delay = 50) {
|
|
1544
|
+
if (ssrSyncTimeout) clearTimeout(ssrSyncTimeout);
|
|
1545
|
+
ssrSyncTimeout = setTimeout(() => {
|
|
1546
|
+
ssrSyncTimeout = null;
|
|
1547
|
+
syncSsrEngine().catch(() => {});
|
|
1548
|
+
}, delay);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
async function getSsrModule() {
|
|
1552
|
+
if (ssrSyncTimeout) {
|
|
1553
|
+
clearTimeout(ssrSyncTimeout);
|
|
1554
|
+
ssrSyncTimeout = null;
|
|
1555
|
+
return await syncSsrEngine();
|
|
1556
|
+
}
|
|
1557
|
+
if (activeSsrSyncPromise) {
|
|
1558
|
+
return await activeSsrSyncPromise;
|
|
1559
|
+
}
|
|
1560
|
+
if (ssrModulePromise) {
|
|
1561
|
+
return await ssrModulePromise;
|
|
1562
|
+
}
|
|
1563
|
+
if (ssrModule) return ssrModule;
|
|
1564
|
+
const v = "?v=" + engineVersion;
|
|
1565
|
+
ssrModulePromise = dynamicImportWithRetry(pathToFileURL(ssrOutfile).href + v).then((mod) => {
|
|
1566
|
+
ssrModule = mod;
|
|
1567
|
+
ssrModulePromise = null;
|
|
1568
|
+
return mod;
|
|
1569
|
+
});
|
|
1570
|
+
return await ssrModulePromise;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
async function doInitialBuild() {
|
|
1574
|
+
const tBuild0 = Date.now();
|
|
1575
|
+
await Promise.all([ctxA.rebuild(), ctxB.rebuild()]);
|
|
1576
|
+
devTimings.engineBuild = Date.now() - tBuild0;
|
|
1577
|
+
await yieldToEventLoop();
|
|
1578
|
+
|
|
1579
|
+
const tImport0 = Date.now();
|
|
1580
|
+
const v = "?v=" + engineVersion;
|
|
1581
|
+
rscModule = await dynamicImportWithRetry(pathToFileURL(rscOutfile).href + v);
|
|
1582
|
+
await yieldToEventLoop();
|
|
1583
|
+
devTimings.engineImport = Date.now() - tImport0;
|
|
1584
|
+
|
|
1585
|
+
if (process.env.DINOU_STANDALONE_SERVER === "true") {
|
|
1586
|
+
ssrModule = await dynamicImportWithRetry(pathToFileURL(ssrOutfile).href + v);
|
|
1587
|
+
ssrModulePromise = Promise.resolve(ssrModule);
|
|
1588
|
+
updateSpinner("Dual-Bundle engine compiled. Starting server...");
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
if (process.env.DINOU_STANDALONE_SERVER === "true") {
|
|
1593
|
+
await doInitialBuild();
|
|
1594
|
+
} else {
|
|
1595
|
+
// Launch Dual-Bundle engine compilation and import concurrently in the background while client bundler runs
|
|
1596
|
+
initialEngineBuildPromise = doInitialBuild().catch((err) => {
|
|
1597
|
+
console.error("❌ [Dinou Dev] Initial Dual-Bundle engine build error:", err);
|
|
1598
|
+
});
|
|
1599
|
+
updateSpinner("Starting in-process client bundler...");
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
const isDebug =
|
|
1603
|
+
process.env.DINOU_DEBUG === "true" ||
|
|
1604
|
+
process.env.DINOU_DEBUG === "1" ||
|
|
1605
|
+
process.env.DEBUG === "true" ||
|
|
1606
|
+
process.env.DEBUG === "1";
|
|
1607
|
+
|
|
1608
|
+
globalThis.__TIMELINE_T0__ = 0;
|
|
1609
|
+
const timelineTime = () => new Date().toTimeString().slice(0, 8) + "." + String(Date.now() % 1000).padStart(3, "0");
|
|
1610
|
+
const timelineRel = () => globalThis.__TIMELINE_T0__ ? `[+${Date.now() - globalThis.__TIMELINE_T0__}ms]` : `[+0ms]`;
|
|
1611
|
+
const logTimeline = (msg) => {
|
|
1612
|
+
if (isDebug) {
|
|
1613
|
+
console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} ${msg}`);
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// Client bundler handle & broadcast helper
|
|
1618
|
+
let clientBundlerHandle = null;
|
|
1619
|
+
let clientBundlerPromise = null;
|
|
1620
|
+
|
|
1621
|
+
let activeClientBuildPromise = null;
|
|
1622
|
+
let activeClientBuildResolve = null;
|
|
1623
|
+
let clientBuildStartTime = 0;
|
|
1624
|
+
|
|
1625
|
+
function notifyClientBuildStart() {
|
|
1626
|
+
clientBuildStartTime = Date.now();
|
|
1627
|
+
logTimeline(`Client Bundler build started...`);
|
|
1628
|
+
if (!activeClientBuildPromise) {
|
|
1629
|
+
activeClientBuildPromise = new Promise((resolve) => {
|
|
1630
|
+
activeClientBuildResolve = resolve;
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
let lastClientBuildDetails = "";
|
|
1636
|
+
|
|
1637
|
+
function notifyClientBuildEnd() {
|
|
1638
|
+
const elapsed = Date.now() - clientBuildStartTime;
|
|
1639
|
+
const tool = (isWebpackBuild ? "webpack" : (process.env.DINOU_BUILD_TOOL || "esbuild")).toLowerCase();
|
|
1640
|
+
|
|
1641
|
+
if (tool === "esbuild") {
|
|
1642
|
+
const core = globalThis.__ESBUILD_CORE_TIME__ || 0;
|
|
1643
|
+
const swc = globalThis.__DINOU_SWC_TIME__ || 0;
|
|
1644
|
+
const swcCount = globalThis.__DINOU_SWC_COUNT__ || 0;
|
|
1645
|
+
const assetsTime = globalThis.__DINOU_ASSETS_TIME__ || 0;
|
|
1646
|
+
const stable = globalThis.__DINOU_STABLE_TIME__ || 0;
|
|
1647
|
+
const wrap = globalThis.__DINOU_WRAP_TIME__ || 0;
|
|
1648
|
+
const writeTotal = globalThis.__DINOU_WRITE_TOTAL__ || 0;
|
|
1649
|
+
const broadcast = globalThis.__DINOU_BROADCAST_TIME__ || 0;
|
|
1650
|
+
const rcm = globalThis.__DINOU_RCM_TIME__ || 0;
|
|
1651
|
+
const details = [];
|
|
1652
|
+
if (core > 0) details.push(`core: ${core}ms`);
|
|
1653
|
+
if (swcCount > 0) details.push(`SWC: ${swc}ms (${swcCount} files)`);
|
|
1654
|
+
if (assetsTime > 0) details.push(`Assets: ${assetsTime}ms`);
|
|
1655
|
+
if (stable > 0) details.push(`Stable: ${stable}ms`);
|
|
1656
|
+
if (wrap > 0) details.push(`Wrap: ${wrap}ms`);
|
|
1657
|
+
if (writeTotal > 0) details.push(`Write: ${writeTotal}ms`);
|
|
1658
|
+
if (broadcast > 0) details.push(`Bcast: ${broadcast}ms`);
|
|
1659
|
+
if (rcm > 0) details.push(`RCM: ${rcm}ms`);
|
|
1660
|
+
const detailsStr = details.length > 0 ? ` [${details.join(" | ")}]` : "";
|
|
1661
|
+
lastClientBuildDetails = isDebug ? detailsStr : "";
|
|
1662
|
+
delete globalThis.__DINOU_SWC_TIME__;
|
|
1663
|
+
delete globalThis.__DINOU_SWC_COUNT__;
|
|
1664
|
+
logTimeline(`Client Bundler (esbuild) build finished in ${elapsed}ms${detailsStr}`);
|
|
1665
|
+
} else if (tool === "rollup") {
|
|
1666
|
+
const manifestTime = globalThis.__DINOU_ROLLUP_MANIFEST_TIME__ || 0;
|
|
1667
|
+
const details = [];
|
|
1668
|
+
if (manifestTime > 0) details.push(`Manifest AST: ${manifestTime}ms`);
|
|
1669
|
+
if (globalThis.__DINOU_ROLLUP_TIMINGS__) {
|
|
1670
|
+
for (const t of globalThis.__DINOU_ROLLUP_TIMINGS__.slice(0, 5)) {
|
|
1671
|
+
const cleanName = t.name
|
|
1672
|
+
.replace(/^[#-]+\s*/, "")
|
|
1673
|
+
.replace(/plugin\s+\d+\s*\((.+?)\)/, "$1");
|
|
1674
|
+
details.push(`${cleanName}: ${t.ms}ms`);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
const detailsStr = details.length > 0 ? ` [${details.join(" | ")}]` : "";
|
|
1678
|
+
logTimeline(`Client Bundler (Rollup) build finished in ${elapsed}ms${detailsStr}`);
|
|
1679
|
+
} else {
|
|
1680
|
+
logTimeline(`Client Bundler (${tool}) build finished in ${elapsed}ms`);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if (activeClientBuildResolve) {
|
|
1684
|
+
activeClientBuildResolve();
|
|
1685
|
+
activeClientBuildResolve = null;
|
|
1686
|
+
}
|
|
1687
|
+
activeClientBuildPromise = null;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
async function broadcastToClients(msg) {
|
|
1691
|
+
try {
|
|
1692
|
+
const handle = clientBundlerHandle || (await clientBundlerPromise);
|
|
1693
|
+
handle?.broadcast?.(msg);
|
|
1694
|
+
} catch (e) {}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// Trigger an incremental rebuild with batching and queueing
|
|
1698
|
+
let isRebuilding = false;
|
|
1699
|
+
const queuedChanges = new Map();
|
|
1700
|
+
|
|
1701
|
+
async function doRebuildBatch(changesMap) {
|
|
1702
|
+
const t0 = Date.now();
|
|
1703
|
+
try {
|
|
1704
|
+
const fileCount = changesMap.size;
|
|
1705
|
+
const firstPath = changesMap.keys().next().value || "";
|
|
1706
|
+
const baseName = fileCount === 1 ? path.basename(firstPath) : `${fileCount} files`;
|
|
1707
|
+
logTimeline(`doRebuildBatch executing for ${baseName}`);
|
|
1708
|
+
startSpinner(`Recompiling changes in ${baseName}...`);
|
|
1709
|
+
|
|
1710
|
+
let needsStructureRebuild = false;
|
|
1711
|
+
let needsClientBundlerRestart = false;
|
|
1712
|
+
let hasClientChanges = false;
|
|
1713
|
+
let hasServerChanges = false;
|
|
1714
|
+
let hasCssChanges = false;
|
|
1715
|
+
const clientRebuildPromises = [];
|
|
1716
|
+
|
|
1717
|
+
for (const [absFilePath, eventType] of changesMap) {
|
|
1718
|
+
const isStructureChange = eventType === "add" || eventType === "unlink";
|
|
1719
|
+
if (isStructureChange) {
|
|
1720
|
+
needsStructureRebuild = true;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
const normLower = absFilePath.toLowerCase();
|
|
1724
|
+
const isCssFile = normLower.endsWith(".css") || normLower.endsWith(".scss") || normLower.endsWith(".less");
|
|
1725
|
+
if (isCssFile) {
|
|
1726
|
+
hasCssChanges = true;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
let isClientFile = false;
|
|
1730
|
+
let isServerFile = false;
|
|
1731
|
+
|
|
1732
|
+
if (fs.existsSync(absFilePath)) {
|
|
1733
|
+
try {
|
|
1734
|
+
const content = fs.readFileSync(absFilePath, "utf8");
|
|
1735
|
+
isClientFile = useClientRegex.test(content.trim());
|
|
1736
|
+
isServerFile = useServerRegex.test(content.trim());
|
|
1737
|
+
} catch (e) {}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const wasClientFile = knownClientFiles.has(absFilePath);
|
|
1741
|
+
const clientDirectiveChanged = isClientFile !== wasClientFile;
|
|
1742
|
+
|
|
1743
|
+
const wasServerFile = knownServerFiles.has(absFilePath);
|
|
1744
|
+
const serverDirectiveChanged = isServerFile !== wasServerFile;
|
|
1745
|
+
|
|
1746
|
+
const isJsTsSource =
|
|
1747
|
+
normLower.endsWith(".tsx") ||
|
|
1748
|
+
normLower.endsWith(".jsx") ||
|
|
1749
|
+
normLower.endsWith(".ts") ||
|
|
1750
|
+
normLower.endsWith(".js");
|
|
1751
|
+
|
|
1752
|
+
const isClientRelevant =
|
|
1753
|
+
isClientFile ||
|
|
1754
|
+
wasClientFile ||
|
|
1755
|
+
clientDirectiveChanged ||
|
|
1756
|
+
isCssFile ||
|
|
1757
|
+
isJsTsSource;
|
|
1758
|
+
|
|
1759
|
+
if (isClientFile) {
|
|
1760
|
+
getCachedFileExports(absFilePath);
|
|
1761
|
+
const cached = compExportsCache.get(absFilePath);
|
|
1762
|
+
if (cached?.exportsChanged) {
|
|
1763
|
+
needsStructureRebuild = true;
|
|
1764
|
+
needsClientBundlerRestart = true;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
if (clientDirectiveChanged || serverDirectiveChanged) {
|
|
1769
|
+
needsStructureRebuild = true;
|
|
1770
|
+
}
|
|
1771
|
+
if (clientDirectiveChanged || (isStructureChange && (isClientRelevant || isCssFile))) {
|
|
1772
|
+
needsClientBundlerRestart = true;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
if (clientBundlerHandle?.notifyFileChanged && isClientRelevant) {
|
|
1776
|
+
clientRebuildPromises.push(clientBundlerHandle.notifyFileChanged(absFilePath));
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
if (isClientFile) {
|
|
1780
|
+
hasClientChanges = true;
|
|
1781
|
+
}
|
|
1782
|
+
if (isServerFile || (!isClientFile && !isCssFile)) {
|
|
1783
|
+
hasServerChanges = true;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
if (needsClientBundlerRestart && clientBundlerHandle?.restart) {
|
|
1788
|
+
updateSpinner(`Directive or structure change detected. Recreating client bundle...`);
|
|
1789
|
+
await clientBundlerHandle.restart();
|
|
1790
|
+
if (!needsStructureRebuild) {
|
|
1791
|
+
await broadcastToClients({ type: "reload" });
|
|
1792
|
+
logSuccess(`Recreated bundle for ${baseName} in ${Date.now() - t0}ms`);
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
if (needsStructureRebuild) {
|
|
1798
|
+
await updateManifestsState({ forceRescan: true });
|
|
1799
|
+
generateEntryFiles();
|
|
1800
|
+
await Promise.all([ctxA.rebuild(), ctxB.rebuild()]);
|
|
1801
|
+
engineVersion = Date.now();
|
|
1802
|
+
const v = "?v=" + engineVersion;
|
|
1803
|
+
rscModule = await dynamicImportWithRetry(pathToFileURL(rscOutfile).href + v);
|
|
1804
|
+
ssrModule = await dynamicImportWithRetry(pathToFileURL(ssrOutfile).href + v);
|
|
1805
|
+
logTimeline(`SSR module imported into V8 runtime`);
|
|
1806
|
+
logSuccess(`Rebuilt in ${Date.now() - t0}ms (${baseName})`);
|
|
1807
|
+
if (clientRebuildPromises.length > 0) {
|
|
1808
|
+
await Promise.all(clientRebuildPromises);
|
|
1809
|
+
}
|
|
1810
|
+
if (activeClientBuildPromise) {
|
|
1811
|
+
await activeClientBuildPromise;
|
|
1812
|
+
}
|
|
1813
|
+
if (!hasCssChanges) {
|
|
1814
|
+
await broadcastToClients({ type: "reload" });
|
|
1815
|
+
}
|
|
1816
|
+
} else {
|
|
1817
|
+
if (clientRebuildPromises.length > 0) {
|
|
1818
|
+
await Promise.all(clientRebuildPromises);
|
|
1819
|
+
}
|
|
1820
|
+
if (activeClientBuildPromise) {
|
|
1821
|
+
await activeClientBuildPromise;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
const rebuildTasks = [];
|
|
1825
|
+
if (hasServerChanges) {
|
|
1826
|
+
rebuildTasks.push((async () => {
|
|
1827
|
+
await ctxA.rebuild();
|
|
1828
|
+
engineVersion = Date.now();
|
|
1829
|
+
const v = "?v=" + engineVersion;
|
|
1830
|
+
rscModule = await dynamicImportWithRetry(pathToFileURL(rscOutfile).href + v);
|
|
1831
|
+
logTimeline(`RSC engine rebuilt`);
|
|
1832
|
+
})());
|
|
1833
|
+
}
|
|
1834
|
+
if (hasClientChanges) {
|
|
1835
|
+
rebuildTasks.push(syncSsrEngine());
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
await Promise.all(rebuildTasks);
|
|
1839
|
+
|
|
1840
|
+
const buildDetails = isDebug && lastClientBuildDetails ? lastClientBuildDetails : "";
|
|
1841
|
+
lastClientBuildDetails = "";
|
|
1842
|
+
logSuccess(`Rebuilt in ${Date.now() - t0}ms (${baseName})${buildDetails}`);
|
|
1843
|
+
|
|
1844
|
+
if (hasServerChanges && !hasCssChanges) {
|
|
1845
|
+
for (const [absFilePath] of changesMap) {
|
|
1846
|
+
broadcastToClients({ type: "rsc-update", path: absFilePath });
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
} catch (err) {
|
|
1851
|
+
stopSpinner();
|
|
1852
|
+
console.error("❌ [Dinou Dev] Rebuild error:", err);
|
|
1853
|
+
showIdleStatus();
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
async function triggerRebuildBatch(changesMap) {
|
|
1858
|
+
for (const [p, evt] of changesMap) {
|
|
1859
|
+
queuedChanges.set(p, evt);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
if (isRebuilding) {
|
|
1863
|
+
return activeRebuildPromise;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
isRebuilding = true;
|
|
1867
|
+
activeRebuildPromise = (async () => {
|
|
1868
|
+
try {
|
|
1869
|
+
while (queuedChanges.size > 0) {
|
|
1870
|
+
const currentBatch = new Map(queuedChanges);
|
|
1871
|
+
queuedChanges.clear();
|
|
1872
|
+
await doRebuildBatch(currentBatch);
|
|
1873
|
+
}
|
|
1874
|
+
} finally {
|
|
1875
|
+
isRebuilding = false;
|
|
1876
|
+
activeRebuildPromise = null;
|
|
1877
|
+
}
|
|
1878
|
+
})();
|
|
1879
|
+
|
|
1880
|
+
return activeRebuildPromise;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
async function triggerRebuild(filePath = "", eventType = "change") {
|
|
1884
|
+
const map = new Map();
|
|
1885
|
+
if (filePath) map.set(path.resolve(filePath), eventType);
|
|
1886
|
+
return triggerRebuildBatch(map);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// Watch src/ with chokidar
|
|
1890
|
+
let srcDebounce = null;
|
|
1891
|
+
const pendingSrcChanges = new Map();
|
|
1892
|
+
|
|
1893
|
+
const srcWatcher = chokidar.watch(srcDir, {
|
|
1894
|
+
ignoreInitial: true,
|
|
1895
|
+
ignored: [/node_modules/, /\.git/],
|
|
1896
|
+
awaitWriteFinish: {
|
|
1897
|
+
stabilityThreshold: 20,
|
|
1898
|
+
pollInterval: 10,
|
|
1899
|
+
},
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
srcWatcher.on("all", (event, fullPath) => {
|
|
1903
|
+
globalThis.__TIMELINE_T0__ = Date.now();
|
|
1904
|
+
logTimeline(`File change detected: ${event} ${path.basename(fullPath)}`);
|
|
1905
|
+
pendingSrcChanges.set(path.resolve(fullPath), event);
|
|
1906
|
+
if ((event === "add" || event === "unlink") && /(?:^|[\\/])page\.[jt]sx?$/i.test(fullPath)) {
|
|
1907
|
+
try {
|
|
1908
|
+
generateRouteTypes(projectRoot);
|
|
1909
|
+
} catch (e) {}
|
|
1910
|
+
}
|
|
1911
|
+
if (srcDebounce) clearTimeout(srcDebounce);
|
|
1912
|
+
srcDebounce = setTimeout(() => {
|
|
1913
|
+
srcDebounce = null;
|
|
1914
|
+
logTimeline(`Debounce timer fired, triggering rebuild...`);
|
|
1915
|
+
const batch = new Map(pendingSrcChanges);
|
|
1916
|
+
pendingSrcChanges.clear();
|
|
1917
|
+
triggerRebuildBatch(batch);
|
|
1918
|
+
}, 20);
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
// Watch manifest folder for client bundler output
|
|
1922
|
+
const manifestFolder = isWebpackBuild
|
|
1923
|
+
? path.resolve(projectRoot, ".dinou/public")
|
|
1924
|
+
: path.resolve(projectRoot, ".dinou/react_client_manifest");
|
|
1925
|
+
|
|
1926
|
+
let clientManifestReady = false;
|
|
1927
|
+
|
|
1928
|
+
function checkClientFilesPresent() {
|
|
1929
|
+
if (globalThis.__DINOU_MEM_FILES__ && globalThis.__DINOU_MEM_FILES__.has("main.js")) {
|
|
1930
|
+
return true;
|
|
1931
|
+
}
|
|
1932
|
+
const cPath = findManifest("react-client-manifest.json", "react_client_manifest");
|
|
1933
|
+
if (!globalThis.__DINOU_RAW_CLIENT_MANIFEST__ && !readJsonSafe(cPath)) return false;
|
|
1934
|
+
if (isWebpackBuild) {
|
|
1935
|
+
if (globalThis.__DINOU_RAW_ASSET_MANIFEST__) return true;
|
|
1936
|
+
const mPath = path.resolve(projectRoot, ".dinou/public/manifest.json");
|
|
1937
|
+
return fs.existsSync(mPath);
|
|
1938
|
+
}
|
|
1939
|
+
const mainPath = path.resolve(projectRoot, ".dinou/public/main.js");
|
|
1940
|
+
return fs.existsSync(mainPath);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// Initial readiness check
|
|
1944
|
+
if (checkClientFilesPresent()) {
|
|
1945
|
+
clientManifestReady = true;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
let manifestSyncPromise = null;
|
|
1949
|
+
let pendingManifestSync = false;
|
|
1950
|
+
|
|
1951
|
+
async function onManifestUpdated() {
|
|
1952
|
+
if (manifestSyncPromise) {
|
|
1953
|
+
pendingManifestSync = true;
|
|
1954
|
+
return manifestSyncPromise;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
manifestSyncPromise = (async () => {
|
|
1958
|
+
try {
|
|
1959
|
+
do {
|
|
1960
|
+
pendingManifestSync = false;
|
|
1961
|
+
|
|
1962
|
+
// If initial background build of Dual-Bundle engine is still in flight, wait for it
|
|
1963
|
+
if (initialEngineBuildPromise) {
|
|
1964
|
+
updateSpinner("Finalizing Dual-Bundle engine...");
|
|
1965
|
+
await initialEngineBuildPromise;
|
|
1966
|
+
initialEngineBuildPromise = null;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
const { componentsChanged } = await updateManifestsState({ forceRescan: false });
|
|
1970
|
+
|
|
1971
|
+
// If engines were already compiled and client component list hasn't changed,
|
|
1972
|
+
// we take the instant in-memory fast path (<1ms) with zero rebuild or V8 re-eval!
|
|
1973
|
+
if (rscModule && !componentsChanged) {
|
|
1974
|
+
clientManifestReady = true;
|
|
1975
|
+
break;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
updateSpinner("Synchronizing Dual-Bundle engine with client build...");
|
|
1979
|
+
generateEntryFiles();
|
|
1980
|
+
await yieldToEventLoop();
|
|
1981
|
+
|
|
1982
|
+
const tEngine0 = Date.now();
|
|
1983
|
+
await Promise.all([ctxA.rebuild(), ctxB.rebuild()]);
|
|
1984
|
+
if (devTimings.engineBuild == null) {
|
|
1985
|
+
devTimings.engineBuild = Date.now() - tEngine0;
|
|
1986
|
+
}
|
|
1987
|
+
engineVersion = Date.now();
|
|
1988
|
+
const v = "?v=" + engineVersion;
|
|
1989
|
+
await yieldToEventLoop();
|
|
1990
|
+
|
|
1991
|
+
const tImport0 = Date.now();
|
|
1992
|
+
rscModule = await dynamicImportWithRetry(pathToFileURL(rscOutfile).href + v);
|
|
1993
|
+
await yieldToEventLoop();
|
|
1994
|
+
ssrModule = null;
|
|
1995
|
+
ssrModulePromise = null;
|
|
1996
|
+
getSsrModule().catch(() => {});
|
|
1997
|
+
|
|
1998
|
+
if (devTimings.engineImport == null) {
|
|
1999
|
+
devTimings.engineImport = Date.now() - tImport0;
|
|
2000
|
+
}
|
|
2001
|
+
clientManifestReady = true;
|
|
2002
|
+
} while (pendingManifestSync);
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
console.error("❌ [Dinou Dev] Error synchronizing with client manifest:", err);
|
|
2005
|
+
} finally {
|
|
2006
|
+
manifestSyncPromise = null;
|
|
2007
|
+
showIdleStatus();
|
|
2008
|
+
}
|
|
2009
|
+
})();
|
|
2010
|
+
|
|
2011
|
+
return manifestSyncPromise;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
const dotDinouDir = path.resolve(projectRoot, ".dinou");
|
|
2015
|
+
if (!fs.existsSync(dotDinouDir)) {
|
|
2016
|
+
fs.mkdirSync(dotDinouDir, { recursive: true });
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
const MIME_TYPES = {
|
|
2020
|
+
".js": "application/javascript; charset=utf-8",
|
|
2021
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
2022
|
+
".css": "text/css; charset=utf-8",
|
|
2023
|
+
".json": "application/json",
|
|
2024
|
+
".svg": "image/svg+xml",
|
|
2025
|
+
".png": "image/png",
|
|
2026
|
+
".jpg": "image/jpeg",
|
|
2027
|
+
".jpeg": "image/jpeg",
|
|
2028
|
+
".webp": "image/webp",
|
|
2029
|
+
".ico": "image/x-icon",
|
|
2030
|
+
".txt": "text/plain; charset=utf-8",
|
|
2031
|
+
".woff": "font/woff",
|
|
2032
|
+
".woff2": "font/woff2",
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
const candidateStaticDirs = [
|
|
2036
|
+
path.resolve(projectRoot, ".dinou/public"),
|
|
2037
|
+
path.resolve(projectRoot, "public"),
|
|
2038
|
+
];
|
|
2039
|
+
|
|
2040
|
+
function isManifestReady() {
|
|
2041
|
+
return (
|
|
2042
|
+
clientManifestReady &&
|
|
2043
|
+
!manifestSyncPromise &&
|
|
2044
|
+
!initialEngineBuildPromise &&
|
|
2045
|
+
!!rscModule &&
|
|
2046
|
+
checkClientFilesPresent()
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
async function startClientBundler(tool) {
|
|
2051
|
+
const normTool = (tool || "esbuild").toLowerCase();
|
|
2052
|
+
updateSpinner(`Starting in-process client bundler (${normTool})...`);
|
|
2053
|
+
|
|
2054
|
+
if (normTool === "esbuild") {
|
|
2055
|
+
const { startEsbuildDev } = await import(
|
|
2056
|
+
pathToFileURL(path.resolve(dinouDir, "esbuild/dev.mjs")).href
|
|
2057
|
+
);
|
|
2058
|
+
return await startEsbuildDev({
|
|
2059
|
+
onRebuilt: () => onManifestUpdated(),
|
|
2060
|
+
onBuildStart: notifyClientBuildStart,
|
|
2061
|
+
onBuildEnd: notifyClientBuildEnd,
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
if (normTool === "rollup") {
|
|
2066
|
+
const { watch } = require("rollup");
|
|
2067
|
+
const getRollupConfig = require(path.resolve(dinouDir, "rollup/rollup.config.js"));
|
|
2068
|
+
const { getHmrEngine, closeHmrServer, notifyFileChanged } = require(path.resolve(dinouDir, "rollup/react-refresh/rollup-plugin-esm-hmr.js"));
|
|
2069
|
+
const reactClientManifestPlugin = require(path.resolve(dinouDir, "rollup/rollup-plugins/rollup-plugin-react-client-manifest.js"));
|
|
2070
|
+
reactClientManifestPlugin.setOnManifestUpdated?.(() => onManifestUpdated());
|
|
2071
|
+
|
|
2072
|
+
let currentWatcher = null;
|
|
2073
|
+
let nextRollupBuildPromise = null;
|
|
2074
|
+
let nextRollupBuildResolve = null;
|
|
2075
|
+
let rollupBuildSafetyTimeout = null;
|
|
2076
|
+
|
|
2077
|
+
function getOrCreateNextRollupBuildPromise() {
|
|
2078
|
+
if (!nextRollupBuildPromise) {
|
|
2079
|
+
nextRollupBuildPromise = new Promise((resolve) => {
|
|
2080
|
+
nextRollupBuildResolve = resolve;
|
|
2081
|
+
});
|
|
2082
|
+
// Safety timeout: if Rollup does not trigger or finish within 6000ms (e.g. unchanged or ignored file), resolve
|
|
2083
|
+
if (!activeClientBuildPromise) {
|
|
2084
|
+
rollupBuildSafetyTimeout = setTimeout(() => {
|
|
2085
|
+
if (!activeClientBuildPromise && nextRollupBuildResolve) {
|
|
2086
|
+
const r = nextRollupBuildResolve;
|
|
2087
|
+
nextRollupBuildResolve = null;
|
|
2088
|
+
nextRollupBuildPromise = null;
|
|
2089
|
+
r();
|
|
2090
|
+
}
|
|
2091
|
+
}, 6000);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return nextRollupBuildPromise;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
async function startRollupWatcher() {
|
|
2098
|
+
if (currentWatcher) {
|
|
2099
|
+
try { await currentWatcher.close(); } catch (e) {}
|
|
2100
|
+
currentWatcher = null;
|
|
2101
|
+
}
|
|
2102
|
+
if (typeof globalThis !== "undefined") {
|
|
2103
|
+
if (globalThis.__DINOU_MEM_FILES__) {
|
|
2104
|
+
globalThis.__DINOU_MEM_FILES__.clear();
|
|
2105
|
+
}
|
|
2106
|
+
delete globalThis.__DINOU_RAW_CLIENT_MANIFEST__;
|
|
2107
|
+
delete globalThis.__DINOU_RAW_SERVER_FUNCTIONS_MANIFEST__;
|
|
2108
|
+
delete globalThis.__DINOU_RAW_ASSET_MANIFEST__;
|
|
2109
|
+
}
|
|
2110
|
+
const rollupConfig = await getRollupConfig();
|
|
2111
|
+
currentWatcher = watch(rollupConfig);
|
|
2112
|
+
|
|
2113
|
+
currentWatcher.on("change", (id, change) => {
|
|
2114
|
+
if (process.env.DINOU_DEBUG) {
|
|
2115
|
+
logTimeline(`[Rollup Watcher] File changed: ${id} (${change?.event || "change"})`);
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
return new Promise((resolve) => {
|
|
2120
|
+
let initialResolved = false;
|
|
2121
|
+
currentWatcher.on("event", async (event) => {
|
|
2122
|
+
if (event.code === "BUNDLE_START") {
|
|
2123
|
+
if (rollupBuildSafetyTimeout) {
|
|
2124
|
+
clearTimeout(rollupBuildSafetyTimeout);
|
|
2125
|
+
rollupBuildSafetyTimeout = null;
|
|
2126
|
+
}
|
|
2127
|
+
updateSpinner("Bundling client with Rollup...");
|
|
2128
|
+
notifyClientBuildStart();
|
|
2129
|
+
getOrCreateNextRollupBuildPromise();
|
|
2130
|
+
} else if (event.code === "BUNDLE_END") {
|
|
2131
|
+
if (rollupBuildSafetyTimeout) {
|
|
2132
|
+
clearTimeout(rollupBuildSafetyTimeout);
|
|
2133
|
+
rollupBuildSafetyTimeout = null;
|
|
2134
|
+
}
|
|
2135
|
+
logSuccess(`Client bundle completed in ${event.duration}ms`);
|
|
2136
|
+
if (event.result?.getTimings) {
|
|
2137
|
+
const rawTimings = event.result.getTimings();
|
|
2138
|
+
const pluginTimes = [];
|
|
2139
|
+
for (const [key, val] of Object.entries(rawTimings)) {
|
|
2140
|
+
if (Array.isArray(val) && val[0] > 100) {
|
|
2141
|
+
pluginTimes.push({ name: key, ms: Math.round(val[0]) });
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
pluginTimes.sort((a, b) => b.ms - a.ms);
|
|
2145
|
+
globalThis.__DINOU_ROLLUP_TIMINGS__ = pluginTimes;
|
|
2146
|
+
}
|
|
2147
|
+
await onManifestUpdated();
|
|
2148
|
+
notifyClientBuildEnd();
|
|
2149
|
+
if (nextRollupBuildResolve) {
|
|
2150
|
+
const r = nextRollupBuildResolve;
|
|
2151
|
+
nextRollupBuildResolve = null;
|
|
2152
|
+
nextRollupBuildPromise = null;
|
|
2153
|
+
r();
|
|
2154
|
+
}
|
|
2155
|
+
if (!initialResolved) {
|
|
2156
|
+
initialResolved = true;
|
|
2157
|
+
resolve();
|
|
2158
|
+
}
|
|
2159
|
+
} else if (event.code === "ERROR") {
|
|
2160
|
+
if (rollupBuildSafetyTimeout) {
|
|
2161
|
+
clearTimeout(rollupBuildSafetyTimeout);
|
|
2162
|
+
rollupBuildSafetyTimeout = null;
|
|
2163
|
+
}
|
|
2164
|
+
console.error("❌ [Rollup Dev Error]:", event.error);
|
|
2165
|
+
notifyClientBuildEnd();
|
|
2166
|
+
if (nextRollupBuildResolve) {
|
|
2167
|
+
const r = nextRollupBuildResolve;
|
|
2168
|
+
nextRollupBuildResolve = null;
|
|
2169
|
+
nextRollupBuildPromise = null;
|
|
2170
|
+
r();
|
|
2171
|
+
}
|
|
2172
|
+
if (!initialResolved) {
|
|
2173
|
+
initialResolved = true;
|
|
2174
|
+
resolve();
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
await startRollupWatcher();
|
|
2182
|
+
|
|
2183
|
+
return {
|
|
2184
|
+
notifyFileChanged: (filePath) => {
|
|
2185
|
+
notifyFileChanged?.(filePath);
|
|
2186
|
+
return getOrCreateNextRollupBuildPromise();
|
|
2187
|
+
},
|
|
2188
|
+
broadcast: (msg) => {
|
|
2189
|
+
getHmrEngine()?.broadcastMessage?.(msg);
|
|
2190
|
+
},
|
|
2191
|
+
restart: async () => {
|
|
2192
|
+
console.log("⚡ [Rollup Dev] Recreating client bundle due to directive change...");
|
|
2193
|
+
await startRollupWatcher();
|
|
2194
|
+
await onManifestUpdated();
|
|
2195
|
+
},
|
|
2196
|
+
close: async () => {
|
|
2197
|
+
try {
|
|
2198
|
+
if (currentWatcher) await currentWatcher.close();
|
|
2199
|
+
closeHmrServer?.();
|
|
2200
|
+
} catch (e) {}
|
|
2201
|
+
},
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
if (normTool === "webpack") {
|
|
2206
|
+
if (typeof globalThis !== "undefined") {
|
|
2207
|
+
if (globalThis.__DINOU_MEM_FILES__) {
|
|
2208
|
+
globalThis.__DINOU_MEM_FILES__.clear();
|
|
2209
|
+
}
|
|
2210
|
+
delete globalThis.__DINOU_RAW_CLIENT_MANIFEST__;
|
|
2211
|
+
delete globalThis.__DINOU_RAW_SERVER_FUNCTIONS_MANIFEST__;
|
|
2212
|
+
delete globalThis.__DINOU_RAW_ASSET_MANIFEST__;
|
|
2213
|
+
}
|
|
2214
|
+
const webpack = require("webpack");
|
|
2215
|
+
const WebpackDevServer = require("webpack-dev-server");
|
|
2216
|
+
const getWebpackConfig = require(path.resolve(dinouDir, "webpack/webpack.config.js"));
|
|
2217
|
+
const webpackConfig = await getWebpackConfig();
|
|
2218
|
+
const compiler = webpack(webpackConfig);
|
|
2219
|
+
|
|
2220
|
+
let devServer = null;
|
|
2221
|
+
let buildWaitPromise = null;
|
|
2222
|
+
let buildWaitResolve = null;
|
|
2223
|
+
|
|
2224
|
+
function startBuildWait() {
|
|
2225
|
+
notifyClientBuildStart();
|
|
2226
|
+
if (!buildWaitPromise) {
|
|
2227
|
+
buildWaitPromise = new Promise((resolve) => {
|
|
2228
|
+
buildWaitResolve = resolve;
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
function endBuildWait() {
|
|
2234
|
+
notifyClientBuildEnd();
|
|
2235
|
+
if (buildWaitResolve) {
|
|
2236
|
+
const resolve = buildWaitResolve;
|
|
2237
|
+
buildWaitResolve = null;
|
|
2238
|
+
buildWaitPromise = null;
|
|
2239
|
+
resolve();
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
compiler.hooks.invalid.tap("DinouBuildStart", () => {
|
|
2244
|
+
startBuildWait();
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
compiler.hooks.watchRun.tap("DinouBuildStart", () => {
|
|
2248
|
+
startBuildWait();
|
|
2249
|
+
});
|
|
2250
|
+
|
|
2251
|
+
return new Promise((resolve, reject) => {
|
|
2252
|
+
let initialResolved = false;
|
|
2253
|
+
|
|
2254
|
+
compiler.hooks.done.tapPromise("DinouClientSync", async (stats) => {
|
|
2255
|
+
try {
|
|
2256
|
+
await onManifestUpdated();
|
|
2257
|
+
} finally {
|
|
2258
|
+
endBuildWait();
|
|
2259
|
+
}
|
|
2260
|
+
if (!initialResolved) {
|
|
2261
|
+
initialResolved = true;
|
|
2262
|
+
resolve({
|
|
2263
|
+
broadcast: (msg) => {
|
|
2264
|
+
if (devServer?.sendMessage && devServer?.sockets) {
|
|
2265
|
+
devServer.sendMessage(devServer.sockets, "ok");
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
restart: async () => {
|
|
2269
|
+
console.log("⚡ [Webpack Dev] Waiting for client bundle compilation due to directive change...");
|
|
2270
|
+
for (let i = 0; i < 10 && !buildWaitPromise && !activeClientBuildPromise; i++) {
|
|
2271
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
2272
|
+
}
|
|
2273
|
+
if (buildWaitPromise) {
|
|
2274
|
+
await buildWaitPromise;
|
|
2275
|
+
} else if (activeClientBuildPromise) {
|
|
2276
|
+
await activeClientBuildPromise;
|
|
2277
|
+
}
|
|
2278
|
+
await onManifestUpdated();
|
|
2279
|
+
},
|
|
2280
|
+
close: async () => {
|
|
2281
|
+
if (devServer) {
|
|
2282
|
+
try {
|
|
2283
|
+
await devServer.stop();
|
|
2284
|
+
} catch (e) {}
|
|
2285
|
+
}
|
|
2286
|
+
},
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
devServer = new WebpackDevServer(webpackConfig.devServer, compiler);
|
|
2292
|
+
devServer.start().catch((err) => {
|
|
2293
|
+
console.error("❌ [Webpack Dev Server Error]:", err);
|
|
2294
|
+
if (!initialResolved) {
|
|
2295
|
+
initialResolved = true;
|
|
2296
|
+
reject(err);
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
throw new Error(`Unsupported DINOU_BUILD_TOOL: ${tool}`);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// HTTP Server
|
|
2306
|
+
const server = http.createServer(async (req, res) => {
|
|
2307
|
+
try {
|
|
2308
|
+
if (clientBundlerPromise) {
|
|
2309
|
+
await clientBundlerPromise;
|
|
2310
|
+
}
|
|
2311
|
+
if (initialEngineBuildPromise) {
|
|
2312
|
+
await initialEngineBuildPromise;
|
|
2313
|
+
}
|
|
2314
|
+
if (activeClientBuildPromise) {
|
|
2315
|
+
await activeClientBuildPromise;
|
|
2316
|
+
}
|
|
2317
|
+
// If source changes are pending debounce, process them immediately
|
|
2318
|
+
if (srcDebounce) {
|
|
2319
|
+
clearTimeout(srcDebounce);
|
|
2320
|
+
srcDebounce = null;
|
|
2321
|
+
const batch = new Map(pendingSrcChanges);
|
|
2322
|
+
pendingSrcChanges.clear();
|
|
2323
|
+
await triggerRebuildBatch(batch);
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
const host = req.headers["x-forwarded-host"] || req.headers.host || "localhost";
|
|
2327
|
+
const protocol = req.headers["x-forwarded-proto"] || "http";
|
|
2328
|
+
const fullUrl = new URL(req.url, `${protocol}://${host}`);
|
|
2329
|
+
const pathname = fullUrl.pathname;
|
|
2330
|
+
|
|
2331
|
+
// 1. Playwright readiness check
|
|
2332
|
+
if (pathname === "/__DINOU_STATUS_PLAYWRIGHT__") {
|
|
2333
|
+
if (!clientManifestReady && checkClientFilesPresent()) {
|
|
2334
|
+
await onManifestUpdated();
|
|
2335
|
+
}
|
|
2336
|
+
const ready = isManifestReady();
|
|
2337
|
+
res.statusCode = 200;
|
|
2338
|
+
res.setHeader("content-type", "application/json");
|
|
2339
|
+
res.end(JSON.stringify({
|
|
2340
|
+
status: "ok",
|
|
2341
|
+
isReady: ready,
|
|
2342
|
+
mode: "development",
|
|
2343
|
+
}));
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// 2. Static files delivery from memory, .dinou/public, or public/
|
|
2348
|
+
if (pathname !== "/") {
|
|
2349
|
+
const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
2350
|
+
const mappedPath = (parsedAssetManifest && parsedAssetManifest[cleanPath]) || cleanPath;
|
|
2351
|
+
const ext = path.extname(cleanPath).toLowerCase();
|
|
2352
|
+
|
|
2353
|
+
// 2.a In-memory fast path for client bundles and assets generated in dev
|
|
2354
|
+
if (globalThis.__DINOU_MEM_FILES__) {
|
|
2355
|
+
let memBuf =
|
|
2356
|
+
globalThis.__DINOU_MEM_FILES__.get(cleanPath) ||
|
|
2357
|
+
globalThis.__DINOU_MEM_FILES__.get(mappedPath) ||
|
|
2358
|
+
globalThis.__DINOU_MEM_FILES__.get("/" + cleanPath);
|
|
2359
|
+
|
|
2360
|
+
// Active Route Bundling: On-demand compilation of requested client bundle
|
|
2361
|
+
if (!memBuf && cleanPath.endsWith(".js") && clientBundlerHandle?.ensureActiveRoute) {
|
|
2362
|
+
const base = path.basename(cleanPath, ".js");
|
|
2363
|
+
const activated = await clientBundlerHandle.ensureActiveRoute(base);
|
|
2364
|
+
if (activated) {
|
|
2365
|
+
memBuf =
|
|
2366
|
+
globalThis.__DINOU_MEM_FILES__.get(cleanPath) ||
|
|
2367
|
+
globalThis.__DINOU_MEM_FILES__.get(mappedPath) ||
|
|
2368
|
+
globalThis.__DINOU_MEM_FILES__.get("/" + cleanPath);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (memBuf) {
|
|
2373
|
+
const fileExt = path.extname(cleanPath).toLowerCase();
|
|
2374
|
+
const contentType = MIME_TYPES[fileExt] || "application/octet-stream";
|
|
2375
|
+
res.statusCode = 200;
|
|
2376
|
+
res.setHeader("content-type", contentType);
|
|
2377
|
+
res.setHeader("content-length", String(memBuf.length));
|
|
2378
|
+
res.setHeader("cache-control", "no-store, no-cache, must-revalidate");
|
|
2379
|
+
res.end(memBuf);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
let foundFilePath = null;
|
|
2385
|
+
for (const baseDir of candidateStaticDirs) {
|
|
2386
|
+
for (const targetName of [mappedPath, cleanPath]) {
|
|
2387
|
+
const filePath = path.join(baseDir, targetName);
|
|
2388
|
+
if (fs.existsSync(filePath)) {
|
|
2389
|
+
try {
|
|
2390
|
+
if (fs.statSync(filePath).isFile()) {
|
|
2391
|
+
foundFilePath = filePath;
|
|
2392
|
+
break;
|
|
2393
|
+
}
|
|
2394
|
+
} catch (e) {}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (foundFilePath) break;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// If it's a client bundle file that is currently being written or created by the bundler
|
|
2401
|
+
const isClientBundleFile =
|
|
2402
|
+
cleanPath.endsWith(".js") ||
|
|
2403
|
+
cleanPath.endsWith(".mjs") ||
|
|
2404
|
+
cleanPath.endsWith(".css") ||
|
|
2405
|
+
cleanPath.endsWith(".map") ||
|
|
2406
|
+
cleanPath.startsWith("assets/");
|
|
2407
|
+
if (!foundFilePath && isClientBundleFile) {
|
|
2408
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
2409
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
2410
|
+
for (const baseDir of candidateStaticDirs) {
|
|
2411
|
+
for (const targetName of [mappedPath, cleanPath]) {
|
|
2412
|
+
const filePath = path.join(baseDir, targetName);
|
|
2413
|
+
if (fs.existsSync(filePath)) {
|
|
2414
|
+
try {
|
|
2415
|
+
if (fs.statSync(filePath).isFile()) {
|
|
2416
|
+
foundFilePath = filePath;
|
|
2417
|
+
break;
|
|
2418
|
+
}
|
|
2419
|
+
} catch (e) {}
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
if (foundFilePath) break;
|
|
2423
|
+
}
|
|
2424
|
+
if (foundFilePath) break;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
if (!foundFilePath) {
|
|
2428
|
+
res.statusCode = 404;
|
|
2429
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
2430
|
+
res.end("Not Found");
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
if (foundFilePath) {
|
|
2436
|
+
try {
|
|
2437
|
+
const fileExt = path.extname(foundFilePath).toLowerCase();
|
|
2438
|
+
const contentType = MIME_TYPES[fileExt] || "application/octet-stream";
|
|
2439
|
+
|
|
2440
|
+
// Safely read buffer into memory, retrying briefly if the bundler currently has it truncated or locked
|
|
2441
|
+
let buf = null;
|
|
2442
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
2443
|
+
try {
|
|
2444
|
+
buf = fs.readFileSync(foundFilePath);
|
|
2445
|
+
if (buf.length > 0 || (!fileExt.endsWith(".js") && !fileExt.endsWith(".css"))) {
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
} catch (readErr) {
|
|
2449
|
+
// File might be momentarily locked on Windows during write
|
|
2450
|
+
}
|
|
2451
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
if (buf !== null) {
|
|
2455
|
+
res.statusCode = 200;
|
|
2456
|
+
res.setHeader("content-type", contentType);
|
|
2457
|
+
res.setHeader("content-length", String(buf.length));
|
|
2458
|
+
res.setHeader("cache-control", "no-store, no-cache, must-revalidate");
|
|
2459
|
+
res.end(buf);
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
} catch (e) {}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// 3. Dynamic RSC + Native SSR Streaming
|
|
2467
|
+
if (activeRebuildPromise) {
|
|
2468
|
+
await activeRebuildPromise;
|
|
2469
|
+
}
|
|
2470
|
+
if (manifestSyncPromise) {
|
|
2471
|
+
await manifestSyncPromise;
|
|
2472
|
+
}
|
|
2473
|
+
if (activeSsrSyncPromise) {
|
|
2474
|
+
await activeSsrSyncPromise;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// Active Route Bundling: Proactively activate requested route in dev
|
|
2478
|
+
const routePath = pathname.replace(/^\/____rsc_(?:payload|page|layout|payload_static|payload_old|page_static|layout_static)____/, "");
|
|
2479
|
+
if (clientBundlerHandle?.ensureActiveRoute && routePath && routePath !== "/") {
|
|
2480
|
+
try {
|
|
2481
|
+
await clientBundlerHandle.ensureActiveRoute(routePath);
|
|
2482
|
+
} catch (e) {}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
const webReq = nodeToWebRequest(req);
|
|
2486
|
+
const ssr = await getSsrModule();
|
|
2487
|
+
const webRes = await rscModule.handleRequest(webReq, {
|
|
2488
|
+
runtime: "node-bundle",
|
|
2489
|
+
renderHtmlStream: ssr.renderHtml,
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
if (webRes.status === 404) {
|
|
2493
|
+
console.log(`[DEV 404] ${req.method} ${pathname}`);
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
await sendWebResponseToNode(webRes, res);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
console.error("[Dinou Dev Server] Request error:", err);
|
|
2499
|
+
if (!res.headersSent) {
|
|
2500
|
+
res.statusCode = 500;
|
|
2501
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2502
|
+
const errMsg = (err?.message || String(err)).replace(/</g, "<").replace(/>/g, ">");
|
|
2503
|
+
const errStack = (err?.stack || "").replace(/</g, "<").replace(/>/g, ">");
|
|
2504
|
+
res.end(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Dinou Dev Error</title></head><body style="margin:0;background:#fff1f2;padding:20px;"><div style="font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:32px;max-width:960px;margin:40px auto;background-color:#fff1f2;border:1px solid #fecdd3;border-radius:12px;color:#9f1239;box-shadow:0 10px 25px -5px rgba(0,0,0,0.1);"><div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;"><span style="background:#e11d48;color:white;padding:2px 8px;border-radius:9999px;font-size:12px;font-weight:bold;">Dinou Dev Error</span><h2 style="margin:0;font-size:1.25rem;font-weight:700;color:#881337;">${errMsg}</h2></div><p style="margin:0 0 16px 0;font-size:0.875rem;color:#9f1239;line-height:1.5;">An unhandled error occurred in the dev server request handler.</p>${errStack ? `<pre style="background:#0f172a;color:#f8fafc;padding:16px;border-radius:8px;overflow-x:auto;font-size:0.8125rem;line-height:1.6;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;">${errStack}</pre>` : ""}</div></body></html>`);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
|
|
2509
|
+
server.on("error", (err) => {
|
|
2510
|
+
if (err.code === "EADDRINUSE") {
|
|
2511
|
+
console.error(`\n❌ FATAL ERROR: Port ${PORT} is already in use!`);
|
|
2512
|
+
} else {
|
|
2513
|
+
console.error("❌ [Dinou Dev Server Error]:", err);
|
|
2514
|
+
}
|
|
2515
|
+
process.exit(1);
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2518
|
+
server.listen(PORT, async () => {
|
|
2519
|
+
const buildTool = isWebpackBuild ? "webpack" : (process.env.DINOU_BUILD_TOOL || "esbuild");
|
|
2520
|
+
|
|
2521
|
+
if (process.env.DINOU_STANDALONE_SERVER !== "true") {
|
|
2522
|
+
updateSpinner(`Bundling client with ${buildTool}...`);
|
|
2523
|
+
try {
|
|
2524
|
+
const tClient0 = Date.now();
|
|
2525
|
+
clientBundlerPromise = startClientBundler(buildTool);
|
|
2526
|
+
clientBundlerHandle = await clientBundlerPromise;
|
|
2527
|
+
devTimings.clientBundlerTotal = Date.now() - tClient0;
|
|
2528
|
+
if (clientBundlerHandle?.timings) {
|
|
2529
|
+
devTimings.clientEntriesBabel = clientBundlerHandle.timings.clientEntriesBabel;
|
|
2530
|
+
devTimings.clientBuild = clientBundlerHandle.timings.clientBuild;
|
|
2531
|
+
devTimings.postCss = clientBundlerHandle.timings.postCss;
|
|
2532
|
+
devTimings.postCssCount = clientBundlerHandle.timings.postCssCount;
|
|
2533
|
+
devTimings.swc = clientBundlerHandle.timings.swc;
|
|
2534
|
+
devTimings.swcCount = clientBundlerHandle.timings.swcCount;
|
|
2535
|
+
devTimings.sf = clientBundlerHandle.timings.sf;
|
|
2536
|
+
devTimings.sfCount = clientBundlerHandle.timings.sfCount;
|
|
2537
|
+
devTimings.rcm = clientBundlerHandle.timings.rcm;
|
|
2538
|
+
devTimings.stable = clientBundlerHandle.timings.stable;
|
|
2539
|
+
devTimings.writeDisk = clientBundlerHandle.timings.writeDisk;
|
|
2540
|
+
}
|
|
2541
|
+
if (manifestSyncPromise) {
|
|
2542
|
+
await manifestSyncPromise;
|
|
2543
|
+
}
|
|
2544
|
+
if (initialEngineBuildPromise) {
|
|
2545
|
+
await initialEngineBuildPromise;
|
|
2546
|
+
}
|
|
2547
|
+
printReadyBanner({
|
|
2548
|
+
port: PORT,
|
|
2549
|
+
tool: isWebpackBuild ? "Webpack" : buildTool,
|
|
2550
|
+
durationMs: Date.now() - devStartTime,
|
|
2551
|
+
timings: devTimings,
|
|
2552
|
+
});
|
|
2553
|
+
getSsrModule().catch(() => {});
|
|
2554
|
+
} catch (err) {
|
|
2555
|
+
stopSpinner();
|
|
2556
|
+
console.error("❌ [Dinou Dev] Failed to start client bundler:", err);
|
|
2557
|
+
} finally {
|
|
2558
|
+
clientBundlerPromise = null;
|
|
2559
|
+
}
|
|
2560
|
+
} else {
|
|
2561
|
+
printReadyBanner({
|
|
2562
|
+
port: PORT,
|
|
2563
|
+
tool: isWebpackBuild ? "Webpack" : (process.env.DINOU_BUILD_TOOL || "esbuild"),
|
|
2564
|
+
durationMs: Date.now() - devStartTime,
|
|
2565
|
+
timings: devTimings,
|
|
2566
|
+
});
|
|
2567
|
+
getSsrModule().catch(() => {});
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2571
|
+
// Clean exit on termination
|
|
2572
|
+
let isCleaningUp = false;
|
|
2573
|
+
async function cleanup() {
|
|
2574
|
+
if (isCleaningUp) return;
|
|
2575
|
+
isCleaningUp = true;
|
|
2576
|
+
stopSpinner();
|
|
2577
|
+
try {
|
|
2578
|
+
srcWatcher.close();
|
|
2579
|
+
if (clientBundlerHandle?.close) {
|
|
2580
|
+
await clientBundlerHandle.close();
|
|
2581
|
+
}
|
|
2582
|
+
server.close();
|
|
2583
|
+
await ctxA.dispose();
|
|
2584
|
+
await ctxB.dispose();
|
|
2585
|
+
} catch (e) {}
|
|
2586
|
+
process.exit(0);
|
|
2587
|
+
}
|
|
2588
|
+
process.on("SIGINT", cleanup);
|
|
2589
|
+
process.on("SIGTERM", cleanup);
|