dinou 6.1.0 → 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 +99 -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 +17 -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 -107
  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,158 +1,203 @@
1
1
  import path from "node:path";
2
+ import stripNamespace from "../helpers-esbuild/strip-namespace.mjs";
3
+
4
+ const normKey = (p) => {
5
+ if (!p) return "";
6
+ let s = path.resolve(p).replace(/\\/g, "/");
7
+ if (process.platform === "win32") {
8
+ s = s.replace(/^([a-zA-Z]):/, (_, d) => d.toLowerCase() + ":");
9
+ }
10
+ return s;
11
+ };
12
+
13
+ export default function stableChunkNamesAndMapsPlugin({ dev = true, changedIds } = {}) {
14
+ let isInitial = true;
2
15
 
3
- export default function stableChunkNamesAndMapsPlugin({ dev = true } = {}) {
4
16
  return {
5
17
  name: "stable-chunk-names",
6
18
  setup(build) {
7
19
  build.onEnd(async (result) => {
8
- if (!result.metafile || !result.outputFiles?.length) return;
9
- const outdir = build.initialOptions.outdir;
10
- if (!outdir) return;
11
-
12
- const renames = new Map(); // oldFilename → newFilename (e.g., 'chunk-ABC.js' → 'chunk-stable.js')
13
- const normalizeRel = (p) => p.replace(/\\/g, "/");
14
- // First, rename chunks
15
- for (const [oldRelPath, info] of Object.entries(
16
- result.metafile.outputs
17
- )) {
18
- if (info.entryPoint || !oldRelPath.endsWith(".js")) continue;
19
- const inputs = Object.keys(info.inputs);
20
- const sourceFile = inputs.find(
21
- (f) => f.startsWith("src/") && /\.(js|jsx|ts|tsx)$/.test(f)
22
- );
23
- if (!sourceFile) continue;
24
- // Stable name based on the source file (always the same)
25
- const rel = path.relative("src", sourceFile);
26
- const normalizedRel = rel.replace(/\\/g, "/");
27
- const dir = path.dirname(normalizedRel);
28
- const base = path.basename(
29
- normalizedRel,
30
- path.extname(normalizedRel)
31
- );
32
- const stableName =
33
- dir === "." ? base : `${dir.replace(/\//g, "-")}-${base}`;
34
- let finalName;
35
- if (dev) {
36
- finalName = `${stableName}.js`; // 100% stable in dev
37
- } else {
20
+ const tStable0 = Date.now();
21
+ try {
22
+ if (!result.metafile || !result.outputFiles?.length) return;
23
+ const outdir = build.initialOptions.outdir;
24
+ if (!outdir) return;
25
+
26
+ const normalizeRel = (p) => p.replace(/\\/g, "/");
27
+ const isIncremental = !isInitial && changedIds && changedIds.size > 0;
28
+ const renames = new Map(); // oldFilename → newFilename (e.g., 'chunk-ABC.js' → 'chunk-stable.js')
29
+
30
+ // First, rename chunks
31
+ for (const [oldRelPath, info] of Object.entries(
32
+ result.metafile.outputs
33
+ )) {
34
+ if (info.entryPoint || !oldRelPath.endsWith(".js")) continue;
35
+ const inputs = Object.keys(info.inputs || {});
36
+ const sourceFile = inputs.find(
37
+ (f) =>
38
+ (f.startsWith("src/") ||
39
+ f.includes(".dinou/swc/src/") ||
40
+ f.includes(".dinou\\swc\\src\\")) &&
41
+ /\.(js|jsx|ts|tsx)$/.test(f)
42
+ );
43
+ if (!sourceFile) continue;
44
+ // Clean source file if it was loaded from .dinou/swc/
45
+ const cleanSourceFile = sourceFile.replace(/^.*\.dinou[/\\]swc[/\\]/, "");
46
+ // Stable name based on the source file (always the same)
47
+ const rel = path.relative("src", cleanSourceFile);
48
+ const normalizedRel = rel.replace(/\\/g, "/");
49
+ const dir = path.dirname(normalizedRel);
50
+ const base = path.basename(
51
+ normalizedRel,
52
+ path.extname(normalizedRel)
53
+ );
54
+ const stableName =
55
+ dir === "." ? base : `${dir.replace(/\//g, "-")}-${base}`;
38
56
  const hash = oldRelPath.match(/-([A-Z0-9]+)\./)?.[1] || "";
39
- finalName = `${stableName}-${hash}.js`;
57
+ const finalName = dev ? `${stableName}.js` : (hash ? `${stableName}-${hash}.js` : `${stableName}.js`);
58
+ const finalRelPath = `chunk-${finalName}`;
59
+ const oldLocal = path.basename(oldRelPath);
60
+ if (Array.from(renames.values()).includes(finalRelPath)) {
61
+ renames.set(oldLocal, oldLocal);
62
+ } else {
63
+ renames.set(oldLocal, finalRelPath);
64
+ }
40
65
  }
41
- const finalRelPath = `chunk-${finalName}`;
42
- const oldLocal = path.basename(oldRelPath);
43
- renames.set(oldLocal, finalRelPath);
44
- }
45
- // Second, rename maps corresponding to the chunks
46
- for (const [oldRelPath, info] of Object.entries(
47
- result.metafile.outputs
48
- )) {
49
- if (!oldRelPath.endsWith(".js.map")) continue;
50
- const jsRelPath = oldRelPath.replace(".map", "");
51
- const jsLocal = path.basename(jsRelPath);
52
- if (renames.has(jsLocal)) {
53
- const newJsLocal = renames.get(jsLocal);
54
- const newMapLocal = newJsLocal.replace(/\.js$/, ".js.map");
55
- const oldMapLocal = path.basename(oldRelPath);
56
- renames.set(oldMapLocal, newMapLocal);
66
+ // Second, rename maps corresponding to the chunks
67
+ for (const [oldRelPath, info] of Object.entries(
68
+ result.metafile.outputs
69
+ )) {
70
+ if (!oldRelPath.endsWith(".js.map")) continue;
71
+ const jsRelPath = oldRelPath.replace(".map", "");
72
+ const jsLocal = path.basename(jsRelPath);
73
+ if (renames.has(jsLocal)) {
74
+ const newJsLocal = renames.get(jsLocal);
75
+ const newMapLocal = newJsLocal.replace(/\.js$/, ".js.map");
76
+ const oldMapLocal = path.basename(oldRelPath);
77
+ renames.set(oldMapLocal, newMapLocal);
78
+ }
57
79
  }
58
- }
59
- // Step 3: Update references in importers (imports in .js)
60
- const outputs = result.metafile.outputs;
61
- const escapeRegExp = (string) =>
62
- string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
63
- for (const relPath in outputs) {
64
- const output = outputs[relPath];
65
- if (!output.imports || !relPath.endsWith(".js")) continue;
66
- const importerFile = result.outputFiles.find(
67
- (f) =>
68
- normalizeRel(path.relative(process.cwd(), f.path)) === relPath
69
- );
70
- if (!importerFile) continue;
71
- let content = new TextDecoder().decode(importerFile.contents);
72
- for (const imp of output.imports) {
73
- const importedRelPath = imp.path;
74
- const oldImportedLocal = path.basename(importedRelPath);
75
- const newImportedLocal = renames.get(oldImportedLocal);
76
- if (!newImportedLocal) continue;
77
- // Replace for double quotes with ./
78
- const oldDouble = `"./${escapeRegExp(oldImportedLocal)}"`;
79
- const newDouble = `"./${newImportedLocal}"`;
80
- content = content.replace(new RegExp(oldDouble, "g"), newDouble);
81
- // Replace for double quotes without ./
82
- const oldDoubleNoDot = `"${escapeRegExp(oldImportedLocal)}"`;
83
- const newDoubleNoDot = `"${newImportedLocal}"`;
80
+
81
+ if (renames.size === 0) return;
82
+ if (typeof globalThis !== "undefined") {
83
+ globalThis.__DINOU_CHUNK_RENAMES__ = renames;
84
+ }
85
+
86
+ // Step 3: Update references in importers (imports in .js)
87
+ const outputs = result.metafile.outputs;
88
+ const escapeRegExp = (string) =>
89
+ string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
90
+
91
+ // Pre-index outputFiles for O(1) lookup
92
+ const outputFilesMap = new Map();
93
+ for (const f of result.outputFiles) {
94
+ outputFilesMap.set(normalizeRel(path.relative(process.cwd(), f.path)), f);
95
+ }
96
+
97
+ for (const relPath in outputs) {
98
+ const output = outputs[relPath];
99
+ if (!output.imports || !relPath.endsWith(".js")) continue;
100
+
101
+ const importerFile = outputFilesMap.get(relPath);
102
+ if (!importerFile) continue;
103
+
104
+ // Only decode and regex-replace if this output imports any renamed chunk
105
+ const hasRenamedChunk = output.imports.some((imp) => renames.has(path.basename(imp.path)));
106
+ if (!hasRenamedChunk) continue;
107
+
108
+ let content = new TextDecoder().decode(importerFile.contents);
109
+ for (const imp of output.imports) {
110
+ const importedRelPath = imp.path;
111
+ const oldImportedLocal = path.basename(importedRelPath);
112
+ const newImportedLocal = renames.get(oldImportedLocal);
113
+ if (!newImportedLocal) continue;
114
+ // Replace for double quotes with ./
115
+ const oldDouble = `"./${escapeRegExp(oldImportedLocal)}"`;
116
+ const newDouble = `"./${newImportedLocal}"`;
117
+ content = content.replace(new RegExp(oldDouble, "g"), newDouble);
118
+ // Replace for double quotes without ./
119
+ const oldDoubleNoDot = `"${escapeRegExp(oldImportedLocal)}"`;
120
+ const newDoubleNoDot = `"${newImportedLocal}"`;
121
+ content = content.replace(
122
+ new RegExp(oldDoubleNoDot, "g"),
123
+ newDoubleNoDot
124
+ );
125
+ // Replace for single quotes with ./
126
+ const oldSingle = `'./${escapeRegExp(oldImportedLocal)}'`;
127
+ const newSingle = `'./${newImportedLocal}'`;
128
+ content = content.replace(new RegExp(oldSingle, "g"), newSingle);
129
+ // Replace for single quotes without ./
130
+ const oldSingleNoDot = `'${escapeRegExp(oldImportedLocal)}'`;
131
+ const newSingleNoDot = `'${newImportedLocal}'`;
132
+ content = content.replace(
133
+ new RegExp(oldSingleNoDot, "g"),
134
+ newSingleNoDot
135
+ );
136
+ }
137
+ importerFile.contents = new TextEncoder().encode(content);
138
+ }
139
+
140
+ // Step 4: Update sourceMappingURL in the .js files being renamed
141
+ for (const file of result.outputFiles) {
142
+ if (!file.path.endsWith(".js")) continue;
143
+ const oldRelPath = normalizeRel(
144
+ path.relative(process.cwd(), file.path)
145
+ );
146
+ const oldLocal = path.basename(oldRelPath);
147
+ if (!renames.has(oldLocal)) continue;
148
+
149
+ const newLocal = renames.get(oldLocal);
150
+ const oldMapLocal = oldLocal.replace(/\.js$/, ".js.map");
151
+ const newMapLocal = newLocal.replace(/\.js$/, ".js.map");
152
+ let content = new TextDecoder().decode(file.contents);
153
+ const escOld = escapeRegExp(oldMapLocal);
154
+ const escNew = newMapLocal;
84
155
  content = content.replace(
85
- new RegExp(oldDoubleNoDot, "g"),
86
- newDoubleNoDot
156
+ new RegExp(`sourceMappingURL=${escOld}`, "g"),
157
+ `sourceMappingURL=${escNew}`
87
158
  );
88
- // Replace for single quotes with ./
89
- const oldSingle = `'./${escapeRegExp(oldImportedLocal)}'`;
90
- const newSingle = `'./${newImportedLocal}'`;
91
- content = content.replace(new RegExp(oldSingle, "g"), newSingle);
92
- // Replace for single quotes without ./
93
- const oldSingleNoDot = `'${escapeRegExp(oldImportedLocal)}'`;
94
- const newSingleNoDot = `'${newImportedLocal}'`;
95
159
  content = content.replace(
96
- new RegExp(oldSingleNoDot, "g"),
97
- newSingleNoDot
160
+ new RegExp(`sourceMappingURL=\\./${escOld}`, "g"),
161
+ `sourceMappingURL=./${escNew}`
98
162
  );
163
+ file.contents = new TextEncoder().encode(content);
99
164
  }
100
- importerFile.contents = new TextEncoder().encode(content);
101
- }
102
- // New: Update sourceMappingURL in the .js files being renamed
103
- for (const file of result.outputFiles) {
104
- if (!file.path.endsWith(".js")) continue;
105
- const oldRelPath = normalizeRel(
106
- path.relative(process.cwd(), file.path)
107
- );
108
- const oldLocal = path.basename(oldRelPath);
109
- if (!renames.has(oldLocal)) continue;
110
- const newLocal = renames.get(oldLocal);
111
- const oldMapLocal = oldLocal.replace(/\.js$/, ".js.map");
112
- const newMapLocal = newLocal.replace(/\.js$/, ".js.map");
113
- let content = new TextDecoder().decode(file.contents);
114
- const escOld = escapeRegExp(oldMapLocal);
115
- const escNew = newMapLocal;
116
- content = content.replace(
117
- new RegExp(`sourceMappingURL=${escOld}`, "g"),
118
- `sourceMappingURL=${escNew}`
119
- );
120
- content = content.replace(
121
- new RegExp(`sourceMappingURL=\\./${escOld}`, "g"),
122
- `sourceMappingURL=./${escNew}`
123
- );
124
- file.contents = new TextEncoder().encode(content);
125
- }
126
- // Update paths in outputFiles for chunks and maps
127
- for (const file of result.outputFiles) {
128
- const relPath = normalizeRel(path.relative(process.cwd(), file.path));
129
- const oldLocal = path.basename(relPath);
130
- const newLocal = renames.get(oldLocal);
131
- if (newLocal) {
132
- file.path = path.join(path.dirname(file.path), newLocal);
165
+
166
+ // Step 5: Update paths in outputFiles for chunks and maps
167
+ for (const file of result.outputFiles) {
168
+ const relPath = normalizeRel(path.relative(process.cwd(), file.path));
169
+ const oldLocal = path.basename(relPath);
170
+ const newLocal = renames.get(oldLocal);
171
+ if (newLocal) {
172
+ file.path = path.join(path.dirname(file.path), newLocal);
173
+ }
133
174
  }
134
- }
135
- // Update metafile for consistency
136
- const newOutputs = {};
137
- for (const oldRelPath in outputs) {
138
- const oldLocal = path.basename(oldRelPath);
139
- const newLocal = renames.get(oldLocal);
140
- const newRelPath = newLocal
141
- ? normalizeRel(path.join(path.dirname(oldRelPath), newLocal))
142
- : oldRelPath;
143
- newOutputs[newRelPath] = outputs[oldRelPath];
144
- if (newOutputs[newRelPath].imports) {
145
- for (let i = 0; i < newOutputs[newRelPath].imports.length; i++) {
146
- const imp = newOutputs[newRelPath].imports[i];
147
- const oldImpLocal = path.basename(imp.path);
148
- const newImpLocal = renames.get(oldImpLocal) || oldImpLocal;
149
- imp.path = normalizeRel(
150
- path.join(path.dirname(imp.path), newImpLocal)
151
- );
175
+
176
+ // Step 6: Update metafile for consistency
177
+ const newOutputs = {};
178
+ for (const oldRelPath in outputs) {
179
+ const oldLocal = path.basename(oldRelPath);
180
+ const newLocal = renames.get(oldLocal);
181
+ const newRelPath = newLocal
182
+ ? normalizeRel(path.join(path.dirname(oldRelPath), newLocal))
183
+ : oldRelPath;
184
+ newOutputs[newRelPath] = outputs[oldRelPath];
185
+ if (newOutputs[newRelPath].imports) {
186
+ for (let i = 0; i < newOutputs[newRelPath].imports.length; i++) {
187
+ const imp = newOutputs[newRelPath].imports[i];
188
+ const oldImpLocal = path.basename(imp.path);
189
+ const newImpLocal = renames.get(oldImpLocal) || oldImpLocal;
190
+ imp.path = normalizeRel(
191
+ path.join(path.dirname(imp.path), newImpLocal)
192
+ );
193
+ }
152
194
  }
153
195
  }
196
+ result.metafile.outputs = newOutputs;
197
+ } finally {
198
+ isInitial = false;
199
+ globalThis.__DINOU_STABLE_TIME__ = Date.now() - tStable0;
154
200
  }
155
- result.metafile.outputs = newOutputs;
156
201
  });
157
202
  },
158
203
  };
@@ -1,11 +1,14 @@
1
- // // postcss-extract-plugin.js
1
+ // postcss-extract-plugin.js
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
 
5
+ let cachedExtractedCSS = "";
6
+ let cachedCSSBuffer = null;
7
+
5
8
  const createPostCSSExtractPlugin = (options = {}) => {
6
9
  const { outputFile = "styles.css", shouldExtract = () => true } = options;
7
10
 
8
- let extractedCSS = "";
11
+ let currentExtractedCSS = "";
9
12
 
10
13
  // Define the PostCSS plugin to intercept and extract CSS rules
11
14
  const postcssPlugin = {
@@ -15,8 +18,8 @@ const createPostCSSExtractPlugin = (options = {}) => {
15
18
  const filePath = result.opts.from;
16
19
 
17
20
  if (shouldExtract(filePath, root)) {
18
- extractedCSS += root.toString();
19
- extractedCSS += "\n";
21
+ currentExtractedCSS += root.toString();
22
+ currentExtractedCSS += "\n";
20
23
 
21
24
  // Remove all CSS rules from the original file to prevent duplicate injection
22
25
  root.removeAll();
@@ -24,23 +27,47 @@ const createPostCSSExtractPlugin = (options = {}) => {
24
27
  },
25
28
  };
26
29
 
27
- // Write the collected CSS contents to the final output file on disk
30
+ // Write the collected CSS contents to the final output file on disk or memory
28
31
  const finalize = () => {
29
- if (!extractedCSS) return;
30
- const outputDir = path.dirname(outputFile);
32
+ if (currentExtractedCSS) {
33
+ cachedExtractedCSS = currentExtractedCSS;
34
+ cachedCSSBuffer = Buffer.from(cachedExtractedCSS);
35
+ currentExtractedCSS = "";
36
+ }
31
37
 
32
- if (!fs.existsSync(outputDir)) {
33
- fs.mkdirSync(outputDir, { recursive: true });
38
+ if (!cachedCSSBuffer) return;
39
+
40
+ // 1. Ensure it's ALWAYS stored in globalThis.__DINOU_MEM_FILES__
41
+ if (typeof globalThis !== "undefined") {
42
+ if (!globalThis.__DINOU_MEM_FILES__) {
43
+ globalThis.__DINOU_MEM_FILES__ = new Map();
44
+ }
45
+ globalThis.__DINOU_MEM_FILES__.set("styles.css", cachedCSSBuffer);
46
+ globalThis.__DINOU_MEM_FILES__.set("/styles.css", cachedCSSBuffer);
34
47
  }
35
48
 
36
- fs.writeFileSync(outputFile, extractedCSS);
49
+ // 2. Write to disk if in production OR if DINOU_WRITE_TO_DISK is requested in dev
50
+ const isProduction = process.env.NODE_ENV === "production";
51
+ const shouldWriteToDisk = process.env.DINOU_WRITE_TO_DISK === "true" || isProduction;
52
+ if (shouldWriteToDisk && cachedExtractedCSS) {
53
+ const outputDir = path.dirname(outputFile);
54
+ if (!fs.existsSync(outputDir)) {
55
+ fs.mkdirSync(outputDir, { recursive: true });
56
+ }
57
+ fs.writeFileSync(outputFile, cachedExtractedCSS);
58
+ }
59
+ };
37
60
 
38
- extractedCSS = "";
61
+ const addExtractedCss = (cssText) => {
62
+ if (cssText) {
63
+ currentExtractedCSS += cssText + "\n";
64
+ }
39
65
  };
40
66
 
41
67
  return {
42
68
  plugin: postcssPlugin,
43
69
  finalize,
70
+ addExtractedCss,
44
71
  };
45
72
  };
46
73
 
@@ -95,18 +95,21 @@ class HotModuleState {
95
95
  }
96
96
 
97
97
  export function createHotContext(id) {
98
- const existing = REGISTERED_MODULES[id];
98
+ const normId = id.startsWith("/") ? id : "/" + id;
99
+ const existing = REGISTERED_MODULES[normId] || REGISTERED_MODULES[id];
99
100
  if (existing) {
100
- existing.lock();
101
+ existing.isLocked = false;
101
102
  return existing;
102
103
  }
103
- const state = new HotModuleState(id);
104
+ const state = new HotModuleState(normId);
105
+ REGISTERED_MODULES[normId] = state;
104
106
  REGISTERED_MODULES[id] = state;
105
107
  return state;
106
108
  }
107
109
 
108
110
  async function applyUpdate(id) {
109
- const state = REGISTERED_MODULES[id];
111
+ const normId = id.startsWith("/") ? id : "/" + id;
112
+ const state = REGISTERED_MODULES[normId] || REGISTERED_MODULES[id];
110
113
  if (!state || state.isDeclined) {
111
114
  return false;
112
115
  }
@@ -120,10 +123,12 @@ async function applyUpdate(id) {
120
123
 
121
124
  const updateID = Date.now();
122
125
  for (const { deps, callback: acceptCallback } of acceptCallbacks) {
123
- const url = `/${id.replace(/^\/+/, "")}?mtime=${updateID}`;
124
126
  const [module, ...depModules] = await Promise.all([
125
- import(url),
126
- ...deps.map((d) => import(`/${d.replace(/^\/+/, "")}?mtime=${updateID}`)),
127
+ import(normId + `?mtime=${updateID}`),
128
+ ...deps.map((d) => {
129
+ const depId = d.startsWith("/") ? d : "/" + d;
130
+ return import(depId + `?mtime=${updateID}`);
131
+ }),
127
132
  ]);
128
133
  acceptCallback({ module, deps: depModules });
129
134
  }
@@ -140,18 +145,60 @@ socket.addEventListener("message", ({ data: _data }) => {
140
145
  return;
141
146
  }
142
147
 
148
+ if (data.type === "rsc-update") {
149
+ if (typeof window !== "undefined" && typeof window.__DINOU_ROUTER_REFRESH__ === "function") {
150
+ window.__DINOU_ROUTER_REFRESH__();
151
+ return;
152
+ }
153
+ reload();
154
+ return;
155
+ }
156
+
157
+ if (
158
+ data.type === "style-update" ||
159
+ (data.type === "update" && data.url && (data.url.endsWith(".css") || data.url.includes("styles.css")))
160
+ ) {
161
+ const targetUrl = data.url ? data.url.split("?")[0] : "";
162
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
163
+ for (const link of links) {
164
+ const rawHref = link.getAttribute("href");
165
+ if (!rawHref) continue;
166
+ const cleanHref = rawHref.split("?")[0];
167
+ if (!targetUrl || cleanHref.endsWith(targetUrl) || targetUrl.endsWith(cleanHref) || cleanHref.includes("styles.css")) {
168
+ const nextUrl = new URL(link.href, window.location.origin);
169
+ nextUrl.searchParams.set("t", String(Date.now()));
170
+ link.href = nextUrl.href;
171
+ }
172
+ }
173
+ return;
174
+ }
175
+
143
176
  if (data.type !== "update") {
144
177
  return;
145
178
  }
146
179
 
180
+ const tRecv = Date.now();
181
+ const isDebug = typeof window !== "undefined" && Boolean(window.__DINOU_DEBUG__);
182
+
183
+ if (isDebug) {
184
+ console.log(`⏱️ [BROWSER HMR] Received update for ${data.url} at ${new Date().toTimeString().slice(0, 8)}.${String(Date.now() % 1000).padStart(3, '0')}`);
185
+ }
186
+
147
187
  applyUpdate(data.url)
148
188
  .then((ok) => {
149
189
  if (!ok) {
190
+ if (isDebug) console.warn(`⏱️ [BROWSER HMR] applyUpdate returned false, falling back to full reload!`);
150
191
  reload();
192
+ } else {
193
+ if (isDebug) {
194
+ console.log(`⏱️ [BROWSER HMR] Fast Refresh finished applying in ${Date.now() - tRecv}ms!`);
195
+ } else {
196
+ console.log(`[ESM-HMR] hot updated: ${data.url}`);
197
+ }
151
198
  }
152
199
  })
153
200
  .catch((err) => {
154
- console.error(err);
201
+ console.error(`[ESM-HMR] applyUpdate error:`, err);
155
202
  reload();
156
203
  });
157
204
  });
@@ -16,7 +16,7 @@ class EsmHmrEngine {
16
16
  this.dependencyTree = map;
17
17
  const wss = options.server
18
18
  ? new WebSocket.Server({ noServer: true })
19
- : new WebSocket.Server({ port: 3001 });
19
+ : new WebSocket.Server({ port: options.port || Number(process.env.HMR_PORT) || 3001 });
20
20
 
21
21
  if (options.server) {
22
22
  options.server.on("upgrade", (req, socket, head) => {