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.
Files changed (135) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +9352 -1081
  3. package/cli.js +314 -91
  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 +322 -165
  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 +175 -96
  39. package/dinou/core/handler.js +2303 -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 +183 -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 +252 -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 +91 -87
  53. package/dinou/core/request-context.js +47 -13
  54. package/dinou/core/resolve-module-ppr.js +34 -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 +113 -34
  121. package/package.json +18 -16
  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
@@ -1,25 +1,81 @@
1
1
  // plugins-esbuild/esm-hmr-plugin.mjs
2
2
  import fs from "node:fs/promises";
3
+ import fsSync from "node:fs";
3
4
  import path from "node:path";
4
- import { transformSync } from "@swc/core";
5
+ import { transform } from "@swc/core";
5
6
  import { createServer } from "node:http";
6
7
  import { EsmHmrEngine } from "./esm-hmr/server.js";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import write from "../helpers-esbuild/write.mjs";
9
10
  import normalizePath from "../helpers-esbuild/normalize-path.mjs";
11
+ import stripNamespace from "../helpers-esbuild/strip-namespace.mjs";
10
12
 
11
13
  const norm = (p) => path.resolve(p).replace(/\\/g, "/");
14
+ const normKey = (p) => {
15
+ if (!p) return "";
16
+ let s = path.resolve(p).replace(/\\/g, "/");
17
+ if (process.platform === "win32") {
18
+ s = s.replace(/^([a-zA-Z]):/, (_, d) => d.toLowerCase() + ":");
19
+ }
20
+ return s;
21
+ };
22
+
12
23
  let serverStarted = false;
13
24
 
25
+ const swcGlobalCache = new Map();
26
+ let swcCacheLoaded = false;
27
+ let swcCacheDirty = false;
28
+
29
+ function getSwcCachePath() {
30
+ const dir = path.resolve(process.cwd(), ".dinou/cache");
31
+ return { dir, file: path.join(dir, "swc-dev-cache.json") };
32
+ }
33
+
34
+ function loadSwcCache() {
35
+ if (swcCacheLoaded) return;
36
+ swcCacheLoaded = true;
37
+ try {
38
+ const { file } = getSwcCachePath();
39
+ if (fsSync.existsSync(file)) {
40
+ const parsed = JSON.parse(fsSync.readFileSync(file, "utf8"));
41
+ for (const [k, v] of Object.entries(parsed)) {
42
+ swcGlobalCache.set(k, v);
43
+ }
44
+ }
45
+ } catch (e) {}
46
+ }
47
+
48
+ async function saveSwcCache() {
49
+ if (!swcCacheDirty) return;
50
+ swcCacheDirty = false;
51
+ try {
52
+ const { dir, file } = getSwcCachePath();
53
+ if (!fsSync.existsSync(dir)) {
54
+ fsSync.mkdirSync(dir, { recursive: true });
55
+ }
56
+ const data = {};
57
+ for (const [k, v] of swcGlobalCache.entries()) {
58
+ data[k] = v;
59
+ }
60
+ await fs.writeFile(file, JSON.stringify(data), "utf8");
61
+ } catch (e) {}
62
+ }
63
+
14
64
  export default function esmHmrPlugin({
15
65
  entryNames = ["main", "error"],
16
66
  changedIds,
17
67
  hmrEngine,
68
+ hmrPort,
18
69
  } = {}) {
70
+ const port = hmrPort || Number(process.env.HMR_PORT || (Number(process.env.PORT || 3000) + 1));
71
+
19
72
  return {
20
73
  name: "esm-hmr",
21
74
 
22
75
  setup(build) {
76
+ let isInitialBuild = true;
77
+ let swcTotalTime = 0;
78
+ let swcCount = 0;
23
79
  const outdir = build.initialOptions.outdir || ".dinou/public";
24
80
  const entryPoints = build.initialOptions.entryPoints;
25
81
 
@@ -29,94 +85,69 @@ export default function esmHmrPlugin({
29
85
 
30
86
  if (!serverStarted) {
31
87
  const server = createServer();
88
+ server.on("error", (err) => {
89
+ if (err.code === "EADDRINUSE") {
90
+ console.warn(`⚠️ [esm-hmr] Port ${port} already in use. HMR may be affected if another server is running.`);
91
+ } else {
92
+ console.error("❌ [esm-hmr] Server error:", err);
93
+ }
94
+ });
32
95
  hmrEngine.value = new EsmHmrEngine({ server });
33
- server.listen(3001, () => {
34
- // console.log("[esm-hmr] WebSocket server listening on port 3001");
96
+ hmrEngine.value.server = server;
97
+ server.listen(port, () => {
98
+ // console.log(`[esm-hmr] WebSocket server listening on port ${port}`);
35
99
  });
36
100
  serverStarted = true;
37
101
  }
38
102
 
39
- build.onStart(async () => {
40
- for (const entryName of entryNames) {
41
- const entryPath = entryPoints?.[entryName];
42
- if (!entryPath) return;
43
-
44
- const absPath = path.resolve(entryPath);
45
- entryAbsPaths.push(absPath);
46
- entrySources.push(await fs.readFile(absPath, "utf8"));
47
- entryOutputNames.push(entryName + ".js");
48
- }
49
- });
50
-
51
- build.onLoad({ filter: /.*/ }, async (args) => {
52
- const abs = path.resolve(args.path);
103
+ const rootEntryMap = new Map();
104
+ let entryPointsSet = null;
53
105
 
54
- const absNorm = norm(abs);
106
+ let onLoadCount = 0;
107
+ let onLoadTime = 0;
55
108
 
56
- // 1. Check if it is a ROOT Entry (client.jsx or error.tsx)
57
- // These are the ones saved in 'entryAbsPaths'
58
- const rootIndex = entryAbsPaths.findIndex(
59
- (entryPath) => norm(entryPath) === absNorm,
60
- );
109
+ build.onStart(async () => {
110
+ swcTotalTime = 0;
111
+ swcCount = 0;
112
+ onLoadCount = 0;
113
+ onLoadTime = 0;
114
+ if (!entryPointsSet) {
115
+ entryPointsSet = new Set(
116
+ Object.values(entryPoints || {}).map((val) => normKey(path.resolve(val)))
117
+ );
118
+ }
119
+ if (rootEntryMap.size === 0) {
120
+ for (const entryName of entryNames) {
121
+ const entryPath = entryPoints?.[entryName];
122
+ if (!entryPath) continue;
61
123
 
62
- // CASE A: It is Main or Error (Roots)
63
- if (rootIndex !== -1) {
64
- const source = entrySources[rootIndex];
65
- if (source) {
66
- let injectCode = `import { createHotContext } from "/__hmr_client__.js";\n`;
67
- injectCode += `window.__hotContext = createHotContext;\n`;
68
-
69
- // IMPORTANT: We return RAW 'source' + injection.
70
- // Without passing through Babel/SWC to prevent $RefreshSig$ from breaking initialization.
71
- return {
72
- contents: injectCode + source,
73
- loader: "jsx",
74
- };
124
+ const absPath = path.resolve(entryPath);
125
+ const source = await fs.readFile(absPath, "utf8");
126
+ entryAbsPaths.push(absPath);
127
+ entrySources.push(source);
128
+ entryOutputNames.push(entryName + ".js");
129
+ rootEntryMap.set(normKey(absPath), source);
75
130
  }
76
- return null;
77
131
  }
132
+ });
78
133
 
79
- // 2. Check if it is any OTHER Entry Point from the esbuild configuration
80
- // (Here are your pages, layouts, components...)
81
- const isAnEntryPoint = Object.values(entryPoints).some(
82
- (val) => norm(path.resolve(val)) === absNorm,
83
- );
134
+ build.onLoad({ filter: /[/\\]client\.[jt]sx?$/i }, async (args) => {
135
+ const abs = path.resolve(args.path);
136
+ const absNorm = normKey(abs);
84
137
 
85
- // CASE B: It is a user page or component
86
- if (isAnEntryPoint) {
87
- // HERE we DO apply SWC transformation to enable React Fast Refresh
88
- const source = await fs.readFile(args.path, "utf8");
89
- try {
90
- const { code } = transformSync(source, {
91
- filename: abs,
92
- jsc: {
93
- parser: {
94
- syntax: "typescript",
95
- tsx: true,
96
- dynamicImport: true,
97
- },
98
- target: "es2022",
99
- transform: {
100
- react: {
101
- refresh: true,
102
- development: true,
103
- runtime: "automatic",
104
- },
105
- },
106
- },
107
- });
138
+ // Check if it is a ROOT Entry (client.jsx)
139
+ const rootSource = rootEntryMap.get(absNorm);
140
+ if (rootSource) {
141
+ let injectCode = `import { createHotContext } from "/__hmr_client__.js";\n`;
142
+ injectCode += `window.__hotContext = createHotContext;\n`;
108
143
 
109
- return {
110
- contents: code,
111
- loader: "js",
112
- };
113
- } catch (e) {
114
- console.error("SWC Error:", e);
115
- return null;
116
- }
144
+ return {
145
+ contents: injectCode + rootSource,
146
+ loader: "jsx",
147
+ watchFiles: [abs],
148
+ };
117
149
  }
118
150
 
119
- // CASE C: It is not an entry point (libraries, internal helpers, node_modules...)
120
151
  return null;
121
152
  });
122
153
 
@@ -126,7 +157,15 @@ export default function esmHmrPlugin({
126
157
  path.dirname(fileURLToPath(import.meta.url)),
127
158
  "./esm-hmr/client.mjs",
128
159
  );
129
- const clientCode = await fs.readFile(clientPath, "utf8");
160
+ let clientCode = await fs.readFile(clientPath, "utf8");
161
+ const isDebug =
162
+ process.env.DINOU_DEBUG === "true" ||
163
+ process.env.DINOU_DEBUG === "1" ||
164
+ process.env.DEBUG === "true" ||
165
+ process.env.DEBUG === "1";
166
+ if (isDebug) {
167
+ clientCode = `window.__DINOU_DEBUG__ = true;\n` + clientCode;
168
+ }
130
169
  const assetPath = path.join(outdir, "__hmr_client__.js");
131
170
  result.outputFiles.push({
132
171
  path: assetPath,
@@ -135,6 +174,7 @@ export default function esmHmrPlugin({
135
174
  });
136
175
 
137
176
  build.onEnd(async (result) => {
177
+ const tWrap0 = Date.now();
138
178
  if (!result.metafile) {
139
179
  // console.warn(
140
180
  // "[hmr-plugin] Metafile is missing. Enable 'metafile: true'"
@@ -144,15 +184,17 @@ export default function esmHmrPlugin({
144
184
  const bundleFiles = Object.keys(result.metafile.outputs);
145
185
  const normalizeRel = (p) => p.replace(/\\/g, "/");
146
186
 
187
+ const outputFilesByRelPath = new Map();
188
+ for (const f of result.outputFiles) {
189
+ outputFilesByRelPath.set(normalizeRel(path.relative(process.cwd(), f.path)), f);
190
+ }
191
+
147
192
  for (const bF of bundleFiles) {
148
193
  if (!bF.endsWith(".js")) {
149
194
  continue;
150
195
  }
151
196
  const relPath = normalizeRel(bF);
152
- const outputFile = result.outputFiles.find(
153
- (f) =>
154
- normalizeRel(path.relative(process.cwd(), f.path)) === relPath,
155
- );
197
+ const outputFile = outputFilesByRelPath.get(relPath);
156
198
  if (!outputFile) continue;
157
199
  const baseName = path.basename(bF, ".js");
158
200
 
@@ -160,27 +202,47 @@ export default function esmHmrPlugin({
160
202
  const outfile_basename = path.basename(outfile);
161
203
  const urlId = "/" + outfile_basename;
162
204
  const safeId = JSON.stringify(urlId);
163
- const frameworkEntries = [
205
+ const frameworkEntries = new Set([
164
206
  "main.js",
165
- "error.js",
166
207
  "serverFunctionProxy.js",
167
208
  "runtime.js",
168
209
  "react-refresh-entry.js",
169
- ];
170
- if (frameworkEntries.some((e) => e === outfile_basename)) continue;
210
+ ]);
211
+ if (frameworkEntries.has(outfile_basename)) continue;
212
+
213
+ // Only wrap user component chunks that contain actual app code (not third-party libraries/vendor)
214
+ const outputInfo = result.metafile.outputs[bF];
215
+ const inputFiles = Object.keys(outputInfo?.inputs || {});
216
+ const hasUserCode = inputFiles.some(
217
+ (f) =>
218
+ !f.includes("node_modules") &&
219
+ !f.includes("/dinou/core/") &&
220
+ !f.includes("\\dinou\\core\\") &&
221
+ /\.(jsx?|tsx?)$/.test(f)
222
+ );
223
+ if (!hasUserCode) continue;
224
+
171
225
  const source = new TextDecoder().decode(outputFile.contents);
226
+ if (source.includes("__reactRefreshRuntime")) {
227
+ continue;
228
+ }
172
229
 
173
230
  const imports = Array.from(
174
231
  source.matchAll(/import\s+["'](.+?)["']/g),
175
232
  ).map((m) => m[1]);
176
233
 
177
- hmrEngine.value.setEntry(urlId, imports, true);
234
+ if (hmrEngine.value) {
235
+ hmrEngine.value.setEntry(urlId, imports, true);
236
+ const acceptedEntry = hmrEngine.value.getEntry(urlId, true);
237
+ if (acceptedEntry) acceptedEntry.isHmrAccepted = true;
238
+ }
178
239
  const wrappedCode = `
179
240
  const RefreshRuntime = window.__reactRefreshRuntime;
180
241
  let prevRefreshReg = window.$RefreshReg$;
181
242
  let prevRefreshSig = window.$RefreshSig$;
182
243
  window.$RefreshReg$ = (type, id) => {
183
- RefreshRuntime.register(type, ${safeId} + '#' + id);
244
+ const fullId = (id && id.includes('#')) ? id : (${safeId} + '#' + id);
245
+ RefreshRuntime.register(type, fullId);
184
246
  };
185
247
  window.$RefreshSig$ = RefreshRuntime?.createSignatureFunctionForTransform;
186
248
  if (!import.meta.hot) import.meta.hot = window.__hotContext?.(${safeId});
@@ -200,49 +262,124 @@ export default function esmHmrPlugin({
200
262
  window.$RefreshReg$ = prevRefreshReg;
201
263
  window.$RefreshSig$ = prevRefreshSig;
202
264
  `;
203
- outputFile.contents = new TextEncoder().encode(wrappedCode);
265
+ const encodedWrapped = new TextEncoder().encode(wrappedCode);
266
+ outputFile.contents = encodedWrapped;
204
267
  }
268
+ globalThis.__DINOU_WRAP_TIME__ = Date.now() - tWrap0;
205
269
  });
206
270
 
207
271
  build.onEnd(write);
208
272
 
209
273
  build.onEnd(async (result) => {
274
+ const tBroadcast0 = Date.now();
275
+ if (!globalThis.__DINOU_SWC_TIME__) {
276
+ globalThis.__DINOU_SWC_TIME__ = swcTotalTime;
277
+ globalThis.__DINOU_SWC_COUNT__ = swcCount;
278
+ }
279
+ if (isInitialBuild) {
280
+ isInitialBuild = false;
281
+ changedIds?.clear();
282
+ return;
283
+ }
284
+
210
285
  if (!result.metafile) {
211
- // console.warn(
212
- // "[hmr-plugin] Metafile is missing. Enable 'metafile: true'"
213
- // );
214
286
  return;
215
287
  }
216
288
 
217
289
  if (changedIds.size === 0) return;
290
+
291
+ // If only CSS files were modified, styles are updated via style-update in cssProcessorPlugin.
292
+ // Do NOT trigger full reload or JS component updates.
293
+ const isCssOnly = Array.from(changedIds).every((id) => {
294
+ const lower = id.toLowerCase();
295
+ return (
296
+ (lower.endsWith(".css") || lower.endsWith(".scss") || lower.endsWith(".less")) &&
297
+ !lower.endsWith(".module.css")
298
+ );
299
+ });
300
+ if (isCssOnly) {
301
+ changedIds.clear();
302
+ return;
303
+ }
304
+
218
305
  const bundleFiles = Object.keys(result.metafile.outputs);
219
306
  const pendingUpdateUrls = new Set();
220
307
  let needsFullReload = false;
308
+
309
+ const frameworkEntries = new Set([
310
+ "main.js",
311
+ "serverFunctionProxy.js",
312
+ "runtime.js",
313
+ "react-refresh-entry.js",
314
+ ]);
315
+
221
316
  for (const fileName of bundleFiles) {
317
+ // Only inspect actual JavaScript output chunks (skip sourcemaps, css, images, etc.)
318
+ if (!fileName.endsWith(".js") || fileName.endsWith(".js.map")) {
319
+ continue;
320
+ }
321
+ const baseName = path.basename(fileName);
322
+ if (frameworkEntries.has(baseName)) {
323
+ continue;
324
+ }
325
+
222
326
  const chunk = result.metafile.outputs[fileName];
223
327
  const modules = Object.keys(chunk?.inputs ?? {});
224
328
 
225
- for (const modulePath of modules) {
226
- if (changedIds.has(normalizePath(path.resolve(modulePath)))) {
227
- const url = "/" + path.relative(outdir, fileName);
228
- const entry = hmrEngine.value.getEntry(url);
229
- if (entry?.isHmrAccepted) {
230
- pendingUpdateUrls.add(url);
231
- } else {
232
- needsFullReload = true;
233
- }
329
+ const isChangedByModule = modules.some((modulePath) => {
330
+ const cleanPath = stripNamespace(modulePath);
331
+ const norm = normKey(cleanPath);
332
+ const lower = norm.toLowerCase();
333
+ if (
334
+ (lower.endsWith(".css") || lower.endsWith(".scss") || lower.endsWith(".less")) &&
335
+ !lower.endsWith(".module.css")
336
+ ) {
337
+ return false;
338
+ }
339
+ return changedIds.has(norm);
340
+ });
341
+
342
+ if (isChangedByModule) {
343
+ const url = "/" + path.relative(outdir, fileName).replace(/\\/g, "/");
344
+ const baseNameUrl = "/" + baseName;
345
+ const entry = hmrEngine.value.getEntry(url) || hmrEngine.value.getEntry(baseNameUrl);
346
+ if (entry?.isHmrAccepted) {
347
+ pendingUpdateUrls.add(baseNameUrl);
348
+ } else {
349
+ needsFullReload = true;
234
350
  }
235
351
  }
236
352
  }
237
353
 
238
- if (needsFullReload || pendingUpdateUrls.size === 0) {
239
- hmrEngine.value.broadcastMessage({ type: "reload" });
240
- } else {
354
+ const timelineTime = () => new Date().toTimeString().slice(0, 8) + "." + String(Date.now() % 1000).padStart(3, "0");
355
+ const timelineRel = () => globalThis.__TIMELINE_T0__ ? `[+${Date.now() - globalThis.__TIMELINE_T0__}ms]` : ``;
356
+
357
+ const isDebug =
358
+ process.env.DINOU_DEBUG === "true" ||
359
+ process.env.DINOU_DEBUG === "1" ||
360
+ process.env.DEBUG === "true" ||
361
+ process.env.DEBUG === "1";
362
+
363
+ if (pendingUpdateUrls.size > 0 && !needsFullReload) {
241
364
  for (const url of pendingUpdateUrls) {
365
+ if (isDebug) {
366
+ console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} ⚡ [HMR Broadcast] Sending update to browser: ${url}`);
367
+ } else {
368
+ console.log(`⚡ [HMR] Updating client component: ${url}`);
369
+ }
242
370
  hmrEngine.value.broadcastMessage({ type: "update", url });
243
371
  }
372
+ } else if (needsFullReload) {
373
+ if (isDebug) {
374
+ console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} ⚡ [HMR Broadcast] Full reload triggered (needsFullReload: ${needsFullReload})`);
375
+ } else {
376
+ console.log(`⚡ [HMR] Full reload triggered`);
377
+ }
378
+ hmrEngine.value.broadcastMessage({ type: "reload" });
244
379
  }
380
+ saveSwcCache().catch(() => {});
245
381
  changedIds.clear();
382
+ globalThis.__DINOU_BROADCAST_TIME__ = Date.now() - tBroadcast0;
246
383
  });
247
384
  },
248
385
  };
@@ -9,12 +9,26 @@ export function isReactRefreshBoundary(RefreshRuntime, moduleExports) {
9
9
  let hasExports = false;
10
10
  let areAllExportsComponents = true;
11
11
  for (const key in moduleExports) {
12
- if (key === "__esModule") continue;
12
+ if (
13
+ key === "__esModule" ||
14
+ key === "metadata" ||
15
+ key === "revalidate" ||
16
+ key === "dynamic" ||
17
+ key === "dynamicParams" ||
18
+ key === "fetchCache" ||
19
+ key === "preferredRegion" ||
20
+ key === "runtime" ||
21
+ key === "maxDuration" ||
22
+ key === "generateStaticParams" ||
23
+ key === "generateMetadata" ||
24
+ key === "frontmatter" ||
25
+ key === "toc" ||
26
+ key === "headings"
27
+ ) {
28
+ continue;
29
+ }
13
30
 
14
31
  hasExports = true;
15
- const desc = Object.getOwnPropertyDescriptor(moduleExports, key);
16
- if (desc && desc.get) return false;
17
-
18
32
  const exportValue = moduleExports[key];
19
33
  if (!RefreshRuntime.isLikelyComponentType(exportValue)) {
20
34
  areAllExportsComponents = false;