dinou 6.1.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +9461 -1080
  3. package/cli.js +392 -169
  4. package/dinou/adapters/bun.js +62 -0
  5. package/dinou/adapters/cloudflare.js +65 -0
  6. package/dinou/adapters/deno.js +48 -0
  7. package/dinou/adapters/netlify.js +85 -0
  8. package/dinou/adapters/node.mjs +31 -0
  9. package/dinou/bun/build.mjs +1077 -0
  10. package/dinou/cloudflare/build.mjs +1121 -0
  11. package/dinou/cloudflare/shims/child_process.js +14 -0
  12. package/dinou/cloudflare/shims/empty.js +2 -0
  13. package/dinou/cloudflare/shims/fs.js +99 -0
  14. package/dinou/cloudflare/shims/os.js +29 -0
  15. package/dinou/config.js +1 -0
  16. package/dinou/config.mjs +2 -0
  17. package/dinou/constants.js +29 -4
  18. package/dinou/core/babel-esm-loader.js +66 -37
  19. package/dinou/core/bailout-proxy.js +61 -0
  20. package/dinou/core/build-static-pages.js +309 -157
  21. package/dinou/core/cache-revalidate.js +471 -144
  22. package/dinou/core/cache-slot.js +387 -0
  23. package/dinou/core/client-redirect.jsx +12 -7
  24. package/dinou/core/client-webpack.jsx +457 -82
  25. package/dinou/core/client.jsx +452 -82
  26. package/dinou/core/config.js +11 -0
  27. package/dinou/core/dinou-paths.js +52 -0
  28. package/dinou/core/edge-ssr.js +122 -0
  29. package/dinou/core/export-static.mjs +64 -0
  30. package/dinou/core/generate-route-types.js +252 -0
  31. package/dinou/core/generate-static-page.js +55 -94
  32. package/dinou/core/generate-static-rsc.js +47 -15
  33. package/dinou/core/generate-static.js +212 -10
  34. package/dinou/core/generating-isg.js +5 -0
  35. package/dinou/core/get-asset-from-manifest.js +9 -3
  36. package/dinou/core/get-error-jsx.js +221 -41
  37. package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
  38. package/dinou/core/get-jsx.js +172 -95
  39. package/dinou/core/handler.js +2296 -0
  40. package/dinou/core/http-adapter.js +115 -0
  41. package/dinou/core/import-module.js +83 -53
  42. package/dinou/core/layout-functions.js +181 -0
  43. package/dinou/core/link.jsx +18 -6
  44. package/dinou/core/load-dinou-config.js +60 -0
  45. package/dinou/core/manifest-provider.js +261 -0
  46. package/dinou/core/navigation-utils.js +4 -0
  47. package/dinou/core/navigation.js +50 -39
  48. package/dinou/core/parse-exports.js +207 -2
  49. package/dinou/core/ppr-context.js +105 -0
  50. package/dinou/core/ppr-runtime.js +263 -0
  51. package/dinou/core/redirect.jsx +20 -19
  52. package/dinou/core/render-jsx-to-client-jsx.js +133 -15
  53. package/dinou/core/request-context.js +47 -13
  54. package/dinou/core/resolve-module-ppr.js +32 -0
  55. package/dinou/core/revalidating.js +28 -9
  56. package/dinou/core/route-generator.js +107 -0
  57. package/dinou/core/rsc-renderer.js +90 -0
  58. package/dinou/core/run-ssg.js +19 -0
  59. package/dinou/core/scan-dependency-components.js +218 -0
  60. package/dinou/core/server-function-proxy-webpack.js +3 -0
  61. package/dinou/core/server-function-proxy.js +3 -0
  62. package/dinou/core/slot.js +302 -0
  63. package/dinou/core/status-manifest.js +41 -1
  64. package/dinou/core/storage-adapter.js +522 -0
  65. package/dinou/core/url-resolver.js +16 -16
  66. package/dinou/core/vfs.js +139 -68
  67. package/dinou/deno/build.mjs +1112 -0
  68. package/dinou/deno/seed-kv.js +115 -0
  69. package/dinou/esbuild/build.mjs +51 -1
  70. package/dinou/esbuild/dev.mjs +290 -224
  71. package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
  72. package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
  73. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
  74. package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
  75. package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
  76. package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
  77. package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
  78. package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
  79. package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
  80. package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
  81. package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
  82. package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
  83. package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
  84. package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
  85. package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
  86. package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
  87. package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
  88. package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
  89. package/dinou/index.d.ts +315 -11
  90. package/dinou/index.js +12 -6
  91. package/dinou/index.mjs +9 -0
  92. package/dinou/node/build.mjs +195 -0
  93. package/dinou/node/bundle-dual-engine.mjs +1012 -0
  94. package/dinou/node/dev.mjs +2589 -0
  95. package/dinou/node/port-selector.mjs +164 -0
  96. package/dinou/node/terminal-status.mjs +345 -0
  97. package/dinou/package.json +16 -14
  98. package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
  99. package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
  100. package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
  101. package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
  102. package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
  103. package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
  104. package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
  105. package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
  106. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
  107. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
  108. package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
  109. package/dinou/rollup/rollup.config.js +155 -43
  110. package/dinou/server.d.ts +114 -12
  111. package/dinou/server.js +35 -10
  112. package/dinou/server.mjs +30 -9
  113. package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
  114. package/dinou/webpack/loaders/server-functions-loader.js +22 -1
  115. package/dinou/webpack/loaders/swc-loader.js +76 -0
  116. package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
  117. package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
  118. package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
  119. package/dinou/webpack/webpack.config.js +227 -29
  120. package/eject.js +173 -94
  121. package/package.json +110 -108
  122. package/dinou/core/asset-require-hook.js +0 -58
  123. package/dinou/core/client-error-webpack.jsx +0 -432
  124. package/dinou/core/client-error.jsx +0 -432
  125. package/dinou/core/concurrency-manager.js +0 -48
  126. package/dinou/core/context-proxy.js +0 -68
  127. package/dinou/core/generate-static-pages.js +0 -130
  128. package/dinou/core/generate-static-rscs.js +0 -151
  129. package/dinou/core/get-ssg-metadata.js +0 -31
  130. package/dinou/core/register-loader.mjs +0 -11
  131. package/dinou/core/register-paths.js +0 -27
  132. package/dinou/core/render-app-to-html.js +0 -430
  133. package/dinou/core/render-html.js +0 -407
  134. package/dinou/core/server.js +0 -1542
  135. package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
@@ -3,16 +3,49 @@ const path = require("path");
3
3
  const { dirname } = require("path");
4
4
  const glob = require("fast-glob");
5
5
  const { pathToFileURL } = require("url");
6
+ let swc = null;
7
+ try {
8
+ swc = require("@swc/core");
9
+ } catch (e) {}
6
10
  const parser = require("@babel/parser");
7
- const traverse = require("@babel/traverse").default;
11
+ const _traverseRaw = require("@babel/traverse");
12
+ const traverse = typeof _traverseRaw === "function" ? _traverseRaw : (_traverseRaw.default || _traverseRaw);
8
13
  const { regex } = require("../../core/asset-extensions.js");
9
14
  const createScopedName = require("../../core/createScopedName.js");
10
15
  const { getAbsPathWithExt } = require("../../core/get-abs-path-with-ext.js");
11
- const { useClientRegex } = require("../../constants.js");
16
+ const { useClientRegex, useServerRegex } = require("../../constants.js");
12
17
  const parseExports = require("../../core/parse-exports.js");
13
18
 
14
19
  function getDefaultExportName(code) {
15
20
  let name = null;
21
+ if (swc && typeof swc.parseSync === "function") {
22
+ try {
23
+ const ast = swc.parseSync(code, { syntax: "typescript", tsx: true });
24
+ for (const item of ast.body) {
25
+ if (item.type === "ExportDefaultDeclaration") {
26
+ const d = item.decl;
27
+ if (d) {
28
+ if (d.type === "Identifier") {
29
+ name = d.value;
30
+ } else if (
31
+ (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") &&
32
+ d.identifier?.value
33
+ ) {
34
+ name = d.identifier.value;
35
+ }
36
+ }
37
+ break;
38
+ } else if (item.type === "ExportDefaultExpression") {
39
+ if (item.expression?.type === "Identifier") {
40
+ name = item.expression.value;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ if (name) return name;
46
+ } catch (e) {}
47
+ }
48
+
16
49
  try {
17
50
  const ast = parser.parse(code, {
18
51
  sourceType: "module",
@@ -41,32 +74,121 @@ function reactClientManifestPlugin({
41
74
  srcDir = path.resolve("src"),
42
75
  manifestPath = ".dinou/react_client_manifest/react-client-manifest.json",
43
76
  assetInclude = regex,
77
+ serverFiles = new Set(),
44
78
  } = {}) {
45
79
  const manifest = {};
46
80
  const clientModules = new Set();
47
81
  const serverModules = new Set();
82
+ let lastManifest = null;
83
+
84
+ const urlToManifestKeys = new Map();
85
+ const defaultExportCache = new Map();
86
+
87
+ function normalizeFsPath(p) {
88
+ return path.resolve(p).replace(/\\/g, "/").toLowerCase();
89
+ }
90
+
91
+ function alignDrive(p) {
92
+ const resolved = path.resolve(p);
93
+ const cwd = process.cwd();
94
+ if (process.platform === "win32" && resolved.length > 2 && resolved[1] === ":" && cwd.length > 2 && cwd[1] === ":") {
95
+ return cwd[0] + resolved.slice(1);
96
+ }
97
+ return resolved;
98
+ }
99
+
100
+ function getStableChunkName(absPath) {
101
+ const norm = absPath.replace(/\\/g, "/");
102
+ if (norm.endsWith("/core/client-redirect.jsx") || norm.endsWith("/core/client-redirect.js")) {
103
+ return "dinouClientRedirect";
104
+ }
105
+ if (norm.endsWith("/core/link.jsx") || norm.endsWith("/core/link.js")) {
106
+ return "dinouLink";
107
+ }
108
+ if (norm.endsWith("/core/slot.js")) {
109
+ return "dinouSlot";
110
+ }
111
+ const rel = path.relative(process.cwd(), absPath).replace(/\\/g, "/");
112
+ const clean = rel
113
+ .replace(/\.[jt]sx?$/, "")
114
+ .replace(/^src\//, "")
115
+ .replace(/[^a-zA-Z0-9_-]/g, "_");
116
+ return clean || path.basename(absPath, path.extname(absPath));
117
+ }
118
+
119
+ function normalizeFileUrl(p) {
120
+ const abs = path.resolve(p);
121
+ const norm =
122
+ abs.length > 1 && abs[1] === ":"
123
+ ? abs[0].toUpperCase() + abs.slice(1)
124
+ : abs;
125
+ return pathToFileURL(norm).href;
126
+ }
127
+
128
+ function setManifestEntry(fileUrl, expName, entry) {
129
+ const keys = [expName === "default" ? fileUrl : `${fileUrl}#${expName}`];
130
+ if (process.platform === "win32") {
131
+ const winMatch = fileUrl.match(/^file:\/\/\/([a-zA-Z]):(\/.*)$/);
132
+ if (winMatch) {
133
+ const drive = winMatch[1];
134
+ const altDrive = drive === drive.toLowerCase() ? drive.toUpperCase() : drive.toLowerCase();
135
+ const altUrl = `file:///${altDrive}:${winMatch[2]}`;
136
+ keys.push(expName === "default" ? altUrl : `${altUrl}#${expName}`);
137
+ }
138
+ }
139
+ const fileUrlLower = fileUrl.toLowerCase();
140
+ let keySet = urlToManifestKeys.get(fileUrlLower);
141
+ if (!keySet) {
142
+ keySet = new Set();
143
+ urlToManifestKeys.set(fileUrlLower, keySet);
144
+ }
145
+ for (const k of keys) {
146
+ manifest[k] = { ...entry };
147
+ keySet.add(k);
148
+ }
149
+ }
48
150
 
49
151
  function updateManifestForModule(absPath, code, isClientModule) {
50
- const fileUrl = pathToFileURL(absPath).href;
51
- const relPath =
52
- "./" + path.relative(process.cwd(), absPath).replace(/\\/g, "/");
152
+ const fileUrl = normalizeFileUrl(absPath);
153
+ const fileUrlLower = fileUrl.toLowerCase();
154
+ const stableChunkName = getStableChunkName(absPath);
155
+ const stableChunkUrl = "/" + stableChunkName + ".js";
53
156
 
54
- for (const key in manifest) {
55
- if (key.startsWith(fileUrl)) {
56
- delete manifest[key];
157
+ // Delete previous keys from manifest and determine if an existing chunk ID should be kept
158
+ const oldKeys = urlToManifestKeys.get(fileUrlLower);
159
+ let previousChunkId = null;
160
+ if (oldKeys) {
161
+ for (const k of oldKeys) {
162
+ if (!previousChunkId && manifest[k]?.id && manifest[k].id.startsWith("/")) {
163
+ previousChunkId = manifest[k].id;
164
+ }
165
+ delete manifest[k];
166
+ }
167
+ oldKeys.clear();
168
+ } else {
169
+ for (const key in manifest) {
170
+ if (key.toLowerCase().startsWith(fileUrlLower)) {
171
+ if (!previousChunkId && manifest[key]?.id && manifest[key].id.startsWith("/")) {
172
+ previousChunkId = manifest[key].id;
173
+ }
174
+ delete manifest[key];
175
+ }
57
176
  }
58
177
  }
59
178
 
60
179
  if (isClientModule) {
61
180
  const exports = parseExports(code);
181
+ const defaultName = getDefaultExportName(code);
182
+ if (defaultName) {
183
+ defaultExportCache.set(path.resolve(absPath), defaultName);
184
+ }
185
+ const chunkIdToUse = previousChunkId || stableChunkUrl;
62
186
  for (const expName of exports) {
63
- const manifestKey =
64
- expName === "default" ? fileUrl : `${fileUrl}#${expName}`;
65
- manifest[manifestKey] = {
66
- id: relPath,
187
+ setManifestEntry(fileUrl, expName, {
188
+ id: chunkIdToUse,
67
189
  chunks: expName,
68
190
  name: expName,
69
- };
191
+ });
70
192
  }
71
193
  }
72
194
  }
@@ -76,31 +198,51 @@ function reactClientManifestPlugin({
76
198
  code,
77
199
  baseFilePath,
78
200
  visited = new Set(),
79
- pluginContext
201
+ pluginContext,
202
+ sharedCache = null
80
203
  ) {
81
204
  if (visited.has(baseFilePath)) {
82
205
  return { imports: [], assets: [], csss: [] };
83
206
  }
84
207
  visited.add(baseFilePath);
85
208
 
86
- const ast = parser.parse(code, {
87
- sourceType: "module",
88
- plugins: ["jsx", "typescript"],
89
- });
209
+ if (sharedCache && sharedCache.has(baseFilePath)) {
210
+ return sharedCache.get(baseFilePath);
211
+ }
212
+
213
+ const importSources = [];
214
+ if (swc && typeof swc.parseSync === "function") {
215
+ try {
216
+ const ast = swc.parseSync(code, { syntax: "typescript", tsx: true });
217
+ for (const item of ast.body) {
218
+ if (item.type === "ImportDeclaration" && item.source?.value) {
219
+ importSources.push(item.source.value);
220
+ }
221
+ }
222
+ } catch (e) {}
223
+ }
224
+
225
+ if (importSources.length === 0 && (!swc || typeof swc.parseSync !== "function")) {
226
+ try {
227
+ const ast = parser.parse(code, {
228
+ sourceType: "module",
229
+ plugins: ["jsx", "typescript"],
230
+ });
231
+ traverse(ast, {
232
+ ImportDeclaration(nodePath) {
233
+ if (nodePath.node?.source?.value) {
234
+ importSources.push(nodePath.node.source.value);
235
+ }
236
+ },
237
+ });
238
+ } catch (e) {}
239
+ }
240
+
90
241
  const imports = new Set();
91
242
  const assets = new Set();
92
243
  const csss = new Set();
93
244
 
94
- // Collect all ImportDeclarations first (to await resolves in batch if needed, but sequential is fine)
95
- const importNodes = [];
96
- traverse(ast, {
97
- ImportDeclaration(nodePath) {
98
- importNodes.push(nodePath);
99
- },
100
- });
101
-
102
- for (const nodePath of importNodes) {
103
- const source = nodePath.node.source.value;
245
+ for (const source of importSources) {
104
246
  // console.log("source", source);
105
247
 
106
248
  // Resolve the import
@@ -128,16 +270,29 @@ function reactClientManifestPlugin({
128
270
  continue; // Don't recurse for assets
129
271
  }
130
272
 
273
+ // Do not recurse into third-party libraries in node_modules for server asset discovery
274
+ if (absImportPathWithExt.includes("node_modules")) {
275
+ continue;
276
+ }
277
+
131
278
  // Otherwise, it's a code import
132
279
  imports.add(absImportPathWithExt);
133
280
 
134
281
  try {
135
282
  const importCode = readFileSync(absImportPathWithExt, "utf8");
283
+ // Optimization: stop recursing at Client Component boundaries.
284
+ // Everything inside a "use client" module is bundled by Rollup's client pipeline,
285
+ // and dinou-asset-plugin already emits all assets referenced by client modules.
286
+ if (useClientRegex.test(importCode.trim())) {
287
+ continue;
288
+ }
289
+
136
290
  const nested = await getImportsAndAssetsAndCsss(
137
291
  importCode,
138
292
  absImportPathWithExt,
139
293
  visited,
140
- pluginContext
294
+ pluginContext,
295
+ sharedCache
141
296
  );
142
297
  nested.imports.forEach((nestedPath) => imports.add(nestedPath));
143
298
  nested.assets.forEach((nestedPath) => assets.add(nestedPath));
@@ -150,20 +305,27 @@ function reactClientManifestPlugin({
150
305
  }
151
306
  }
152
307
 
153
- return {
308
+ const result = {
154
309
  imports: Array.from(imports),
155
310
  assets: Array.from(assets),
156
311
  csss: Array.from(csss),
157
312
  };
313
+ if (sharedCache) {
314
+ sharedCache.set(baseFilePath, result);
315
+ }
316
+ return result;
158
317
  }
159
318
 
160
319
  // New helper to emit a single asset (used in buildStart and watchChange)
161
320
  function emitAsset(absAssetPath, pluginContext) {
162
- const source = readFileSync(absAssetPath);
163
321
  const base = path.basename(absAssetPath, path.extname(absAssetPath));
164
322
  const scoped = createScopedName(base, absAssetPath);
165
323
  const ext = path.extname(absAssetPath);
166
324
  const fileName = `assets/${scoped}${ext}`;
325
+ const emitted = globalThis.__DINOU_EMITTED_ASSETS__ || (globalThis.__DINOU_EMITTED_ASSETS__ = new Set());
326
+ if (emitted.has(fileName)) return;
327
+ emitted.add(fileName);
328
+ const source = readFileSync(absAssetPath);
167
329
  pluginContext.emitFile({
168
330
  type: "asset",
169
331
  fileName,
@@ -176,10 +338,68 @@ function reactClientManifestPlugin({
176
338
  return fileName.startsWith("page.") || fileName.startsWith("layout.");
177
339
  }
178
340
 
341
+ let isInitial = true;
342
+ const knownClientChunks = new Map();
343
+ const knownCssChunks = new Map();
344
+ let hasPendingCssChange = false;
179
345
 
180
346
  return {
181
347
  name: "react-client-manifest",
182
348
  async buildStart(options) {
349
+ if (globalThis.__DINOU_EMITTED_ASSETS__) {
350
+ globalThis.__DINOU_EMITTED_ASSETS__.clear();
351
+ }
352
+
353
+ if (!isInitial) {
354
+ for (const entry of knownClientChunks.values()) {
355
+ this.emitFile({
356
+ type: "chunk",
357
+ id: entry.id,
358
+ name: entry.name,
359
+ });
360
+ }
361
+ if (hasPendingCssChange) {
362
+ for (const entry of knownCssChunks.values()) {
363
+ this.emitFile({
364
+ type: "chunk",
365
+ id: entry.id,
366
+ name: entry.name,
367
+ });
368
+ }
369
+ hasPendingCssChange = false;
370
+ }
371
+
372
+ // Quick scan for newly added client files in incremental builds
373
+ try {
374
+ const freshSrcFiles = await glob(["**/*.{js,jsx,ts,tsx}"], {
375
+ cwd: srcDir,
376
+ absolute: true,
377
+ });
378
+ for (const absPath of freshSrcFiles) {
379
+ const normAbs = alignDrive(absPath);
380
+ const normKey = normalizeFsPath(normAbs);
381
+ if (knownClientChunks.has(normKey)) continue;
382
+ let code = "";
383
+ try { code = readFileSync(normAbs, "utf8"); } catch (e) { continue; }
384
+ if (useClientRegex.test(code.trim())) {
385
+ clientModules.add(normKey);
386
+ updateManifestForModule(normAbs, code, true);
387
+ this.addWatchFile(normAbs);
388
+ const chunkName = getStableChunkName(normAbs);
389
+ knownClientChunks.set(normKey, { id: normAbs, name: chunkName });
390
+ this.emitFile({
391
+ type: "chunk",
392
+ id: normAbs,
393
+ name: chunkName,
394
+ });
395
+ }
396
+ }
397
+ } catch (e) {}
398
+ return;
399
+ }
400
+ isInitial = false;
401
+ const tManifest0 = Date.now();
402
+
183
403
  const srcFiles = await glob(["**/*.{js,jsx,ts,tsx}"], {
184
404
  cwd: srcDir,
185
405
  absolute: true,
@@ -200,155 +420,224 @@ function reactClientManifestPlugin({
200
420
  entryPoints = Object.values(inputOption);
201
421
  }
202
422
  const uniqueFiles = new Set([...srcFiles, ...entryPoints]);
423
+ const emittedChunks = new Set();
424
+ const emittedAssets = new Set();
425
+ const sharedAstCache = new Map();
203
426
 
427
+ let fileScanIdx = 0;
204
428
  for (const absPath of uniqueFiles) {
205
- const code = readFileSync(absPath, "utf8");
206
- const normalizedPath = absPath.split(path.sep).join(path.posix.sep);
429
+ if (++fileScanIdx % 50 === 0) {
430
+ await new Promise((resolve) => setImmediate(resolve));
431
+ }
432
+ const normAbs = alignDrive(absPath);
433
+ const normKey = normalizeFsPath(normAbs);
434
+ const code = readFileSync(normAbs, "utf8");
207
435
  const isClientModule = useClientRegex.test(code.trim());
436
+ const isServerModule = useServerRegex.test(code.trim());
437
+ if (isServerModule) {
438
+ serverFiles.add(normKey);
439
+ }
208
440
 
209
441
  if (isClientModule) {
210
- clientModules.add(normalizedPath);
211
- updateManifestForModule(absPath, code, true);
212
- this.emitFile({
213
- type: "chunk",
214
- id: absPath,
215
- name: path.basename(absPath, path.extname(absPath)),
216
- });
217
- } else if (isPageOrLayout(absPath)) {
218
- serverModules.add(normalizedPath);
219
- this.addWatchFile(absPath);
442
+ clientModules.add(normKey);
443
+ updateManifestForModule(normAbs, code, true);
444
+ this.addWatchFile(normAbs);
445
+ const chunkName = getStableChunkName(normAbs);
446
+ knownClientChunks.set(normKey, { id: normAbs, name: chunkName });
447
+ if (!emittedChunks.has(normKey)) {
448
+ emittedChunks.add(normKey);
449
+ this.emitFile({
450
+ type: "chunk",
451
+ id: normAbs,
452
+ name: chunkName,
453
+ });
454
+ }
455
+ } else if (isPageOrLayout(normAbs)) {
456
+ serverModules.add(normKey);
457
+ this.addWatchFile(normAbs);
220
458
  const { imports, assets, csss } = await getImportsAndAssetsAndCsss(
221
459
  code,
222
- absPath,
460
+ normAbs,
223
461
  new Set(),
224
- this
462
+ this,
463
+ sharedAstCache
225
464
  );
226
- // console.log("assets", assets);
227
465
  for (const importPath of imports) {
228
466
  this.addWatchFile(importPath);
229
467
  }
230
468
  // Emit assets for server components (replicate dinouAssetPlugin logic)
231
469
  for (const assetPath of assets) {
232
470
  this.addWatchFile(assetPath);
233
- emitAsset(assetPath, this); // Emit assets
471
+ if (!emittedAssets.has(assetPath)) {
472
+ emittedAssets.add(assetPath);
473
+ emitAsset(assetPath, this);
474
+ }
234
475
  }
235
476
  for (const cssPath of csss) {
236
477
  this.addWatchFile(cssPath);
237
- // Emit CSS as a Rollup asset so postcss() processes it
238
- this.emitFile({
239
- type: "chunk",
240
- id: cssPath,
241
- name: path.basename(cssPath, path.extname(cssPath)),
242
- });
478
+ const normCssAbs = alignDrive(cssPath);
479
+ const normCssKey = normalizeFsPath(normCssAbs);
480
+ const chunkName = path.basename(normCssAbs, path.extname(normCssAbs));
481
+ knownCssChunks.set(normCssKey, { id: normCssAbs, name: chunkName });
482
+ if (!emittedChunks.has(normCssKey)) {
483
+ emittedChunks.add(normCssKey);
484
+ // Emit CSS as a Rollup asset so postcss() processes it
485
+ this.emitFile({
486
+ type: "chunk",
487
+ id: normCssAbs,
488
+ name: chunkName,
489
+ });
490
+ }
243
491
  }
244
492
  }
245
493
  }
494
+ globalThis.__DINOU_ROLLUP_MANIFEST_TIME__ = Date.now() - tManifest0;
246
495
  },
247
496
  async transform(code, id) {
248
- if (id.includes("\0") || id.startsWith("commonjsHelpers") || id.includes("node_modules/rollup") || id.includes("react-refresh")) return;
249
- const normalizedId = id.split(path.sep).join(path.posix.sep);
250
- const isClientModule = useClientRegex.test(code.trim());
497
+ if (
498
+ id.includes("\0") ||
499
+ (id.includes("node_modules") && !id.includes("dinou")) ||
500
+ id.startsWith("commonjsHelpers") ||
501
+ id.includes("react-refresh")
502
+ )
503
+ return;
504
+ if (!useClientRegex.test(code)) return;
505
+ const normAbs = alignDrive(id);
506
+ const normId = normalizeFsPath(normAbs);
251
507
 
252
- if (isClientModule) {
253
- // console.log("👉 [react-client-manifest] Found client module in transform:", normalizedId);
254
- if (!clientModules.has(normalizedId)) {
255
- clientModules.add(normalizedId);
256
- updateManifestForModule(id, code, true);
257
- this.emitFile({
258
- type: "chunk",
259
- id: id,
260
- name: path.basename(id, path.extname(id)),
261
- });
262
- }
508
+ if (!clientModules.has(normId)) {
509
+ clientModules.add(normId);
510
+ updateManifestForModule(normAbs, code, true);
511
+ const chunkName = getStableChunkName(normAbs);
512
+ knownClientChunks.set(normId, { id: normAbs, name: chunkName });
513
+ this.emitFile({
514
+ type: "chunk",
515
+ id: normAbs,
516
+ name: chunkName,
517
+ });
263
518
  }
264
519
  },
265
520
  async watchChange(id) {
521
+ const lower = id.toLowerCase();
522
+ if (lower.endsWith(".css") || lower.endsWith(".scss") || lower.endsWith(".less")) {
523
+ hasPendingCssChange = true;
524
+ return;
525
+ }
266
526
  if (
267
- !id.endsWith(".tsx") &&
268
- !id.endsWith(".jsx") &&
269
- !id.endsWith(".js") &&
270
- !id.endsWith(".ts")
527
+ !lower.endsWith(".tsx") &&
528
+ !lower.endsWith(".jsx") &&
529
+ !lower.endsWith(".js") &&
530
+ !lower.endsWith(".ts")
271
531
  )
272
532
  return;
273
- const normalizedId = id.split(path.sep).join(path.posix.sep);
274
- if (!existsSync(id)) {
275
- const fileUrl = pathToFileURL(id).href;
533
+ const normAbs = alignDrive(id);
534
+ const normId = normalizeFsPath(normAbs);
535
+ if (!existsSync(normAbs)) {
536
+ const fileUrl = normalizeFileUrl(normAbs);
537
+ const fileUrlLower = fileUrl.toLowerCase();
276
538
  for (const key in manifest) {
277
- if (key.startsWith(fileUrl)) {
539
+ if (key.toLowerCase().startsWith(fileUrlLower)) {
278
540
  delete manifest[key];
279
541
  }
280
542
  }
281
- clientModules.delete(normalizedId);
282
- serverModules.delete(normalizedId);
543
+ clientModules.delete(normId);
544
+ serverModules.delete(normId);
545
+ serverFiles.delete(normId);
546
+ knownClientChunks.delete(normId);
283
547
  return;
284
548
  }
285
- const code = readFileSync(id, "utf8");
549
+ const code = readFileSync(normAbs, "utf8");
286
550
  const isClientModule = useClientRegex.test(code.trim());
551
+ const isServerModule = useServerRegex.test(code.trim());
552
+ if (isServerModule) {
553
+ serverFiles.add(normId);
554
+ } else {
555
+ serverFiles.delete(normId);
556
+ }
287
557
 
288
- updateManifestForModule(id, code, isClientModule);
558
+ updateManifestForModule(normAbs, code, isClientModule);
289
559
 
290
560
  if (isClientModule) {
291
- clientModules.add(normalizedId);
292
- serverModules.delete(normalizedId);
293
- this.addWatchFile(id);
561
+ clientModules.add(normId);
562
+ const chunkName = getStableChunkName(normAbs);
563
+ knownClientChunks.set(normId, { id: normAbs, name: chunkName });
564
+ serverModules.delete(normId);
565
+ this.addWatchFile(normAbs);
294
566
  } else {
295
- clientModules.delete(normalizedId);
296
- if (isPageOrLayout(id)) {
297
- serverModules.add(normalizedId);
298
- this.addWatchFile(id);
567
+ clientModules.delete(normId);
568
+ knownClientChunks.delete(normId);
569
+ if (isPageOrLayout(normAbs)) {
570
+ serverModules.add(normId);
571
+ this.addWatchFile(normAbs);
299
572
  const { imports, assets, csss } = await getImportsAndAssetsAndCsss(
300
573
  code,
301
- id,
574
+ normAbs,
302
575
  new Set(),
303
576
  this
304
577
  );
305
578
  for (const importPath of imports) {
306
579
  this.addWatchFile(importPath);
307
580
  }
308
- // console.log("assets", assets);
309
581
  for (const assetPath of assets) {
310
582
  this.addWatchFile(assetPath);
311
- // emitAsset(assetPath, this); // Re-emit assets on server file change
312
583
  }
313
584
  for (const cssPath of csss) {
314
585
  this.addWatchFile(cssPath);
586
+ const normCssAbs = alignDrive(cssPath);
587
+ const normCssKey = normalizeFsPath(normCssAbs);
588
+ const chunkName = path.basename(normCssAbs, path.extname(normCssAbs));
589
+ knownCssChunks.set(normCssKey, { id: normCssAbs, name: chunkName });
315
590
  }
316
591
  } else {
317
- serverModules.delete(normalizedId);
592
+ serverModules.delete(normId);
318
593
  }
319
594
  }
320
595
  },
321
596
  generateBundle(outputOptions, bundle) {
597
+ const normFsPathCache = new Map();
598
+ function getNormFsPath(p) {
599
+ let n = normFsPathCache.get(p);
600
+ if (!n) {
601
+ n = normalizeFsPath(p);
602
+ normFsPathCache.set(p, n);
603
+ }
604
+ return n;
605
+ }
606
+
322
607
  for (const [fileName, chunk] of Object.entries(bundle)) {
323
608
  if (chunk.type !== "chunk") continue;
609
+ const chunkUrl = "/" + fileName;
324
610
 
325
611
  // Process entry point facadeModuleId for default export preservation
326
612
  if (chunk.facadeModuleId) {
327
613
  const absModulePath = path.resolve(chunk.facadeModuleId);
328
- if (!absModulePath.includes("\0") && !absModulePath.startsWith("commonjsHelpers")) {
329
- const fileUrl = pathToFileURL(absModulePath).href;
330
- manifest[fileUrl] = {
331
- id: "/" + fileName,
332
- chunks: "default",
333
- name: "default",
334
- };
335
- manifest[fileUrl + "#default"] = {
336
- id: "/" + fileName,
337
- chunks: "default",
338
- name: "default",
339
- };
340
-
341
- if (!chunk.exports.includes("default")) {
342
- try {
343
- const originalCode = readFileSync(absModulePath, "utf8");
344
- const defaultName = getDefaultExportName(originalCode);
614
+ if (
615
+ !absModulePath.includes("\0") &&
616
+ !absModulePath.startsWith("commonjsHelpers") &&
617
+ (!absModulePath.includes("node_modules") || absModulePath.includes("dinou"))
618
+ ) {
619
+ const normPath = getNormFsPath(absModulePath);
620
+ if (clientModules.has(normPath)) {
621
+ const fileUrl = normalizeFileUrl(absModulePath);
622
+ const fileUrlLower = fileUrl.toLowerCase();
623
+ const keys = urlToManifestKeys.get(fileUrlLower);
624
+ if (keys) {
625
+ for (const k of keys) {
626
+ if (manifest[k]) manifest[k].id = chunkUrl;
627
+ }
628
+ }
629
+ setManifestEntry(fileUrl, "default", {
630
+ id: chunkUrl,
631
+ chunks: "default",
632
+ name: "default",
633
+ });
634
+
635
+ if (!chunk.exports.includes("default")) {
636
+ const defaultName = defaultExportCache.get(absModulePath);
345
637
  if (defaultName && chunk.exports.includes(defaultName)) {
346
638
  chunk.code += `\nexport { ${defaultName} as default };\n`;
347
639
  chunk.exports.push("default");
348
- // console.log(`👉 [react-client-manifest] Appended default export alias to chunk ${fileName}: export { ${defaultName} as default };`);
349
640
  }
350
- } catch (err) {
351
- // Ignore errors
352
641
  }
353
642
  }
354
643
  }
@@ -356,26 +645,82 @@ function reactClientManifestPlugin({
356
645
 
357
646
  // Map all modules in the chunk to this chunk in the manifest (only if they are client modules)
358
647
  for (const modulePath of Object.keys(chunk.modules)) {
359
- if (modulePath.includes("\0") || modulePath.startsWith("commonjsHelpers")) continue;
360
- const absModulePath = path.resolve(modulePath);
361
- const normalizedPath = absModulePath.split(path.sep).join(path.posix.sep);
362
-
363
- if (clientModules.has(normalizedPath)) {
364
- const fileUrl = pathToFileURL(absModulePath).href;
365
- // Update the chunk id for all exports of this module
366
- for (const key of Object.keys(manifest)) {
367
- if (key === fileUrl || key.startsWith(fileUrl + "#")) {
368
- manifest[key].id = "/" + fileName;
648
+ if (
649
+ modulePath.includes("\0") ||
650
+ modulePath.startsWith("commonjsHelpers") ||
651
+ (modulePath.includes("node_modules") && !modulePath.includes("dinou"))
652
+ ) {
653
+ continue;
654
+ }
655
+ const normPath = getNormFsPath(modulePath);
656
+
657
+ if (clientModules.has(normPath)) {
658
+ const fileUrl = normalizeFileUrl(modulePath);
659
+ const fileUrlLower = fileUrl.toLowerCase();
660
+ const keys = urlToManifestKeys.get(fileUrlLower);
661
+ if (keys) {
662
+ for (const k of keys) {
663
+ if (manifest[k]) manifest[k].id = chunkUrl;
369
664
  }
370
665
  }
371
666
  }
372
667
  }
373
668
  }
374
- const serialized = JSON.stringify(manifest, null, 2);
375
- mkdirSync(dirname(manifestPath), { recursive: true });
376
- writeFileSync(manifestPath, serialized);
669
+
670
+ function areManifestsSemanticallyEqual(m1, m2) {
671
+ if (!m1 || !m2) return false;
672
+ const k1 = Object.keys(m1);
673
+ const k2 = Object.keys(m2);
674
+ if (k1.length !== k2.length) return false;
675
+ for (const k of k1) {
676
+ const v1 = m1[k];
677
+ const v2 = m2[k];
678
+ if (!v2) return false;
679
+ if (
680
+ v1.id !== v2.id ||
681
+ v1.name !== v2.name ||
682
+ v1.chunks !== v2.chunks
683
+ ) {
684
+ return false;
685
+ }
686
+ }
687
+ return true;
688
+ }
689
+
690
+ if (typeof globalThis !== "undefined") {
691
+ globalThis.__DINOU_RAW_CLIENT_MANIFEST__ = { ...manifest };
692
+ }
693
+
694
+ const isDev = process.env.NODE_ENV !== "production" || process.env.DINOU_DEV === "true";
695
+ const shouldWriteToDisk = process.env.DINOU_WRITE_TO_DISK === "true" || !isDev;
696
+
697
+ let existingManifest = null;
698
+ if (shouldWriteToDisk && existsSync(manifestPath)) {
699
+ try {
700
+ existingManifest = JSON.parse(readFileSync(manifestPath, "utf8"));
701
+ } catch (e) {}
702
+ }
703
+
704
+ if (!lastManifest || !areManifestsSemanticallyEqual(lastManifest, manifest)) {
705
+ lastManifest = JSON.parse(JSON.stringify(manifest));
706
+ if (shouldWriteToDisk) {
707
+ const sortedManifest = {};
708
+ for (const k of Object.keys(manifest).sort()) {
709
+ sortedManifest[k] = manifest[k];
710
+ }
711
+ mkdirSync(dirname(manifestPath), { recursive: true });
712
+ writeFileSync(manifestPath, JSON.stringify(sortedManifest, null, 2));
713
+ }
714
+ manifestUpdatedCallback?.();
715
+ }
377
716
  },
378
717
  };
379
718
  }
380
719
 
720
+ let manifestUpdatedCallback = null;
721
+ function setOnManifestUpdated(cb) {
722
+ manifestUpdatedCallback = cb;
723
+ }
724
+ reactClientManifestPlugin.setOnManifestUpdated = setOnManifestUpdated;
725
+
381
726
  module.exports = reactClientManifestPlugin;