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,4 +1,3 @@
1
- import { TsconfigPathsPlugin } from "@esbuild-plugins/tsconfig-paths";
2
1
  import reactClientManifestPlugin from "../plugins-esbuild/react-client-manifest-plugin.mjs";
3
2
  import serverFunctionsPlugin from "../plugins-esbuild/server-functions-plugin.mjs";
4
3
  import cssProcessorPlugin from "../plugins-esbuild/css-processor-plugin.mjs";
@@ -6,8 +5,9 @@ import esmHmrPlugin from "../react-refresh/esm-hmr-plugin.mjs";
6
5
  import stableChunkNamesAndMapsPlugin from "../plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs";
7
6
  import assetsPlugin from "../plugins-esbuild/assets-plugin.mjs";
8
7
  import skipMissingEntryPointsPlugin from "../plugins-esbuild/skip-missing-entry-points-plugin.mjs";
9
- import copyStaticFiles from "esbuild-copy-static-files";
10
- import { existsSync } from "node:fs";
8
+ import { swcRedirectPlugin } from "./swc-disk-cache.mjs";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
11
 
12
12
  export default function getConfigEsbuild({
13
13
  entryPoints,
@@ -15,35 +15,123 @@ export default function getConfigEsbuild({
15
15
  manifest = {},
16
16
  changedIds,
17
17
  hmrEngine,
18
+ serverFiles,
19
+ onManifestUpdated,
20
+ onBuildStart,
21
+ onBuildEnd,
22
+ hmrPort,
18
23
  }) {
24
+ let esbuildRebuildStart = 0;
19
25
  let plugins = [
26
+ swcRedirectPlugin({ projectRoot: process.cwd() }),
27
+ ...(onBuildStart
28
+ ? [
29
+ {
30
+ name: "build-start-notifier",
31
+ setup(build) {
32
+ build.onStart(() => {
33
+ esbuildRebuildStart = Date.now();
34
+ onBuildStart();
35
+ });
36
+ build.onEnd(() => {
37
+ globalThis.__ESBUILD_PURE_TIME__ = Date.now() - esbuildRebuildStart;
38
+ });
39
+ },
40
+ },
41
+ ]
42
+ : []),
20
43
  skipMissingEntryPointsPlugin(),
21
- TsconfigPathsPlugin({}),
22
- cssProcessorPlugin(),
23
- reactClientManifestPlugin({ manifest }),
24
- assetsPlugin(),
25
- stableChunkNamesAndMapsPlugin(),
26
- serverFunctionsPlugin(),
27
- esmHmrPlugin({ entryNames: ["main", "error"], changedIds, hmrEngine }),
44
+ cssProcessorPlugin({ outdir, hmrEngine }),
45
+ serverFunctionsPlugin({ serverFiles }),
46
+ reactClientManifestPlugin({ manifest, onManifestUpdated }),
47
+ assetsPlugin({ changedIds }),
48
+ stableChunkNamesAndMapsPlugin({ changedIds }),
49
+ esmHmrPlugin({ entryNames: ["main", "error"], changedIds, hmrEngine, hmrPort }),
50
+ ...(onBuildEnd
51
+ ? [
52
+ {
53
+ name: "build-end-notifier",
54
+ setup(build) {
55
+ build.onEnd(async (result) => {
56
+ if (esbuildRebuildStart > 0) {
57
+ globalThis.__ESBUILD_CORE_TIME__ = Date.now() - esbuildRebuildStart;
58
+ }
59
+ await onBuildEnd(result);
60
+ });
61
+ },
62
+ },
63
+ ]
64
+ : []),
28
65
  ];
29
66
 
30
- if (existsSync("favicons")) {
67
+ const shouldWriteToDisk = process.env.DINOU_WRITE_TO_DISK === "true";
68
+ const staticDir = fs.existsSync("public") ? "public" : (fs.existsSync("favicons") ? "favicons" : null);
69
+ if (staticDir && shouldWriteToDisk) {
70
+ let staticCopied = false;
31
71
  plugins = [
32
- copyStaticFiles({
33
- src: "favicons",
34
- dest: outdir,
35
- }),
72
+ {
73
+ name: "copy-static-files-once",
74
+ setup(build) {
75
+ build.onEnd(() => {
76
+ if (staticCopied) return;
77
+ staticCopied = true;
78
+ try {
79
+ fs.cpSync(staticDir, outdir, { recursive: true, force: true });
80
+ } catch (e) {}
81
+ });
82
+ },
83
+ },
36
84
  ...plugins,
37
85
  ];
38
86
  }
39
87
 
88
+ let userTsconfig = {};
89
+ if (fs.existsSync("tsconfig.json")) {
90
+ try {
91
+ const raw = fs.readFileSync("tsconfig.json", "utf8");
92
+ const clean = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "").replace(/,(\s*[}\]])/g, "$1");
93
+ userTsconfig = JSON.parse(clean);
94
+ } catch (e) {}
95
+ }
96
+ const compilerOptions = userTsconfig.compilerOptions || {};
97
+ const userPaths = compilerOptions.paths || {};
98
+
99
+ const paths = {};
100
+ for (const [alias, targets] of Object.entries(userPaths)) {
101
+ const list = Array.isArray(targets) ? targets : [targets];
102
+ const swcTargets = list.map((t) => {
103
+ const norm = t.replace(/^\.\//, "");
104
+ return `.dinou/swc/${norm}`;
105
+ });
106
+ paths[alias] = [...swcTargets, ...list];
107
+ }
108
+ if (!paths["@/*"]) {
109
+ paths["@/*"] = [".dinou/swc/src/*", "src/*"];
110
+ }
111
+ if (!paths["~/*"]) {
112
+ paths["~/*"] = [".dinou/swc/src/*", "src/*"];
113
+ }
114
+
115
+ const tsconfigRaw = {
116
+ ...userTsconfig,
117
+ compilerOptions: {
118
+ ...compilerOptions,
119
+ baseUrl: compilerOptions.baseUrl || ".",
120
+ paths,
121
+ },
122
+ };
123
+
124
+ const isDev = process.env.NODE_ENV !== "production";
125
+
40
126
  return {
41
127
  entryPoints,
42
128
  outdir,
43
129
  format: "esm",
44
130
  bundle: true,
45
131
  splitting: true,
46
- sourcemap: true,
132
+ sourcemap: !isDev,
133
+ treeShaking: !isDev,
134
+ tsconfigRaw,
47
135
  jsx: "automatic",
48
136
  target: "es2022",
49
137
  write: false,
@@ -15,7 +15,7 @@ import { useClientRegex, useServerRegex } from "../../constants.js";
15
15
  import { updateManifestForModule } from "./update-manifest-for-module.mjs";
16
16
 
17
17
  function hashFilePath(absPath) {
18
- return crypto.createHash("sha1").update(absPath).digest("hex").slice(0, 8);
18
+ return crypto.createHash("sha1").update(normalizePath(absPath)).digest("hex").slice(0, 8);
19
19
  }
20
20
 
21
21
  export default async function getEsbuildEntries({
@@ -27,6 +27,7 @@ export default async function getEsbuildEntries({
27
27
  const detectedCSSEntries = new Set();
28
28
  const detectedAssetEntries = new Set();
29
29
  const serverModules = new Set();
30
+ const serverFunctions = new Set();
30
31
 
31
32
  // ---------- Helpers (ported mostly verbatim) ----------
32
33
  async function getImportsAndAssetsAndCsss(
@@ -49,7 +50,8 @@ export default async function getEsbuildEntries({
49
50
  const csss = new Set();
50
51
 
51
52
  const importNodes = [];
52
- traverse.default(ast, {
53
+ const traverseFn = typeof traverse === "function" ? traverse : (traverse.default || traverse);
54
+ traverseFn(ast, {
53
55
  ImportDeclaration(nodePath) {
54
56
  importNodes.push(nodePath);
55
57
  },
@@ -88,10 +90,17 @@ export default async function getEsbuildEntries({
88
90
  if (isImportedFileClient) {
89
91
  continue; // Do not recursively process client components
90
92
  }
93
+
94
+ const isImportedFileServer = useServerRegex.test(importedCode.trim());
95
+ if (isImportedFileServer) {
96
+ serverFunctions.add(normalizePath(absImportPathWithExt));
97
+ continue;
98
+ }
91
99
  } else {
92
100
  const isImportedFileServer = useServerRegex.test(importedCode.trim());
93
101
 
94
102
  if (isImportedFileServer) {
103
+ serverFunctions.add(normalizePath(absImportPathWithExt));
95
104
  continue;
96
105
  }
97
106
  }
@@ -157,11 +166,20 @@ export default async function getEsbuildEntries({
157
166
  });
158
167
 
159
168
  // Gather client modules and update manifest entries
169
+ let fileScanCount = 0;
160
170
  for (const absPath of files) {
171
+ if (++fileScanCount % 5 === 0) {
172
+ await new Promise((resolve) => setImmediate(resolve));
173
+ }
161
174
  const code = readFileSync(absPath, "utf8");
162
175
  const isClientModule = useClientRegex.test(code.trim());
176
+ const isServerFunction = useServerRegex.test(code.trim());
163
177
  const normalizedPath = normalizePath(absPath);
164
178
 
179
+ if (isServerFunction) {
180
+ serverFunctions.add(normalizedPath);
181
+ }
182
+
165
183
  if (isClientModule) {
166
184
  const name = path.basename(absPath, path.extname(absPath));
167
185
 
@@ -170,22 +188,6 @@ export default async function getEsbuildEntries({
170
188
  absPath: normalizedPath,
171
189
  name,
172
190
  });
173
- const { imports } = await getImportsAndAssetsAndCsss(
174
- code,
175
- absPath,
176
- new Set(),
177
- true,
178
- );
179
- const clientComponentRegex = /\.(js|jsx|ts|tsx)$/i;
180
- imports.forEach((imp) => {
181
- if (clientComponentRegex.test(imp) && !imp.includes("node_modules")) {
182
- const name = path.basename(imp, path.extname(imp));
183
- detectedClientEntries.add({
184
- absPath: normalizePath(imp),
185
- name,
186
- });
187
- }
188
- });
189
191
  } else if (isPageOrLayout(absPath)) {
190
192
  serverModules.add(normalizedPath);
191
193
  try {
@@ -253,6 +255,16 @@ export default async function getEsbuildEntries({
253
255
  const outfileName = `${dCE.name}-${hash}`;
254
256
  dCE.outfile = `${outfileName}.js`;
255
257
  dCE.outfileName = outfileName;
258
+ if (manifest) {
259
+ const fileUrl = pathToFileURL(dCE.absPath).href;
260
+ const lowerFileUrl = fileUrl.toLowerCase();
261
+ for (const key in manifest) {
262
+ const kBase = key.split("#")[0].toLowerCase();
263
+ if (kBase === lowerFileUrl) {
264
+ manifest[key].id = "/" + dCE.outfile;
265
+ }
266
+ }
267
+ }
256
268
  }
257
269
 
258
270
  for (const dCSSE of detectedCSSEntries) {
@@ -273,6 +285,6 @@ export default async function getEsbuildEntries({
273
285
  detectedClientEntries,
274
286
  detectedCSSEntries,
275
287
  detectedAssetEntries,
276
- Array.from(serverModules),
288
+ Array.from(serverFunctions),
277
289
  ];
278
290
  }
@@ -0,0 +1,9 @@
1
+ export function stripNamespace(p) {
2
+ if (!p) return "";
3
+ if (/^[a-zA-Z]:[/\\]/.test(p)) {
4
+ return p;
5
+ }
6
+ return p.replace(/^[a-zA-Z0-9_-]+:/, "");
7
+ }
8
+
9
+ export default stripNamespace;
@@ -0,0 +1,225 @@
1
+ import fs from "node:fs/promises";
2
+ import fsSync from "node:fs";
3
+ import path from "node:path";
4
+ import glob from "fast-glob";
5
+ import { transform } from "@swc/core";
6
+ import { useServerRegex } from "../../constants.js";
7
+ import { extensions as assetExtensions } from "../../core/asset-extensions.js";
8
+
9
+ const styleExtensions = ["css", "scss", "less", "sass"];
10
+ const fontExtensions = ["woff", "woff2", "ttf", "eot", "otf"];
11
+ const redirectExtensions = [...styleExtensions, ...assetExtensions, ...fontExtensions];
12
+ const redirectFilter = new RegExp(`\\.(${redirectExtensions.join("|")})$`, "i");
13
+
14
+ const normPath = (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
+
23
+ export function getMirrorPath(srcPath, projectRoot = process.cwd()) {
24
+ if (!srcPath) return "";
25
+ const norm = normPath(srcPath);
26
+ const rootNorm = normPath(projectRoot);
27
+ if (!norm.startsWith(rootNorm)) return srcPath;
28
+ const rel = norm.slice(rootNorm.length + 1);
29
+ const targetRel = rel.replace(/\.[jt]sx?$/, ".js");
30
+ return path.resolve(projectRoot, ".dinou/swc", targetRel);
31
+ }
32
+
33
+ export function getOriginalPath(mirrorPath, projectRoot = process.cwd()) {
34
+ if (!mirrorPath) return "";
35
+ const norm = normPath(mirrorPath);
36
+ const rootNorm = normPath(projectRoot);
37
+ const swcPrefix = `${rootNorm}/.dinou/swc/`;
38
+ if (!norm.startsWith(swcPrefix)) return mirrorPath;
39
+ const rel = norm.slice(swcPrefix.length);
40
+ const base = path.resolve(projectRoot, rel.replace(/\.js$/, ""));
41
+ for (const ext of [".tsx", ".ts", ".jsx", ".js"]) {
42
+ const candidate = base + ext;
43
+ if (fsSync.existsSync(candidate)) return candidate;
44
+ }
45
+ return base + ".tsx";
46
+ }
47
+
48
+ export async function transformToDisk(srcPath, projectRoot = process.cwd()) {
49
+ const normSrc = path.resolve(srcPath);
50
+ if (!fsSync.existsSync(normSrc)) return null;
51
+
52
+ const raw = await fs.readFile(normSrc, "utf8");
53
+ // Skip server functions - handled by serverFunctionsPlugin proxy
54
+ if (useServerRegex.test(raw)) {
55
+ return null;
56
+ }
57
+
58
+ const targetPath = getMirrorPath(normSrc, projectRoot);
59
+
60
+ try {
61
+ const { code } = await transform(raw, {
62
+ filename: normSrc,
63
+ sourceMaps: "inline",
64
+ jsc: {
65
+ parser: { syntax: "typescript", tsx: true, dynamicImport: true },
66
+ target: "es2022",
67
+ transform: {
68
+ react: {
69
+ refresh: true,
70
+ development: true,
71
+ runtime: "automatic",
72
+ },
73
+ },
74
+ },
75
+ });
76
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
77
+ await fs.writeFile(targetPath, code, "utf8");
78
+ return targetPath;
79
+ } catch (err) {
80
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
81
+ await fs.writeFile(targetPath, raw, "utf8");
82
+ return targetPath;
83
+ }
84
+ }
85
+
86
+ export async function syncAllSwcFiles(srcDir = path.resolve("src"), projectRoot = process.cwd()) {
87
+ const files = await glob(["**/*.{js,jsx,ts,tsx}"], { cwd: srcDir, absolute: true });
88
+ const tasks = [];
89
+
90
+ for (const f of files) {
91
+ let raw = "";
92
+ try {
93
+ raw = fsSync.readFileSync(f, "utf8");
94
+ } catch (e) {
95
+ continue;
96
+ }
97
+ // Skip server functions
98
+ if (useServerRegex.test(raw)) {
99
+ continue;
100
+ }
101
+
102
+ const targetPath = getMirrorPath(f, projectRoot);
103
+ let needCompile = true;
104
+ try {
105
+ if (fsSync.existsSync(targetPath)) {
106
+ const srcStat = fsSync.statSync(f);
107
+ const tgtStat = fsSync.statSync(targetPath);
108
+ if (tgtStat.mtimeMs >= srcStat.mtimeMs) {
109
+ needCompile = false;
110
+ }
111
+ }
112
+ } catch (e) {}
113
+
114
+ if (needCompile) {
115
+ tasks.push(transformToDisk(f, projectRoot));
116
+ }
117
+ }
118
+
119
+ if (tasks.length > 0) {
120
+ await Promise.all(tasks);
121
+ }
122
+ }
123
+
124
+ export function swcRedirectPlugin({ projectRoot = process.cwd() } = {}) {
125
+ const swcDir = path.resolve(projectRoot, ".dinou/swc");
126
+ const resolveCache = new Map();
127
+
128
+ let redirTime = 0;
129
+ let redirCalls = 0;
130
+ let redirHits = 0;
131
+
132
+ const candidateExtensions = [
133
+ "",
134
+ ".js",
135
+ ".jsx",
136
+ ".ts",
137
+ ".tsx",
138
+ "/index.js",
139
+ "/index.jsx",
140
+ "/index.ts",
141
+ "/index.tsx",
142
+ ];
143
+
144
+ return {
145
+ name: "swc-disk-cache-redirect",
146
+ setup(build) {
147
+ build.onStart(() => {
148
+ resolveCache.clear();
149
+ redirTime = 0;
150
+ redirCalls = 0;
151
+ redirHits = 0;
152
+ });
153
+
154
+ // Redirect CSS, assets, and server functions / source files imported from inside .dinou/swc back to src/
155
+ build.onResolve(
156
+ { filter: /.*/ },
157
+ (args) => {
158
+ const importer = args.importer || "";
159
+ if (!importer.includes(".dinou/swc") && !importer.includes(".dinou\\swc")) {
160
+ return null;
161
+ }
162
+
163
+ // Only process relative imports or assets
164
+ const isRelative = args.path.startsWith(".");
165
+ const isAsset = redirectFilter.test(args.path);
166
+ if (!isRelative && !isAsset) {
167
+ return null;
168
+ }
169
+
170
+ const tStart = Date.now();
171
+ redirCalls++;
172
+
173
+ const cacheKey = importer + "::" + args.path;
174
+ if (resolveCache.has(cacheKey)) {
175
+ redirHits++;
176
+ redirTime += Date.now() - tStart;
177
+ return resolveCache.get(cacheKey);
178
+ }
179
+
180
+ // 1. Check if it exists directly in .dinou/swc
181
+ const candidateBase = path.resolve(path.dirname(importer), args.path);
182
+ if (fsSync.existsSync(candidateBase) && !fsSync.statSync(candidateBase).isDirectory()) {
183
+ resolveCache.set(cacheKey, null);
184
+ redirTime += Date.now() - tStart;
185
+ return null;
186
+ }
187
+
188
+ for (const ext of [".js", ".jsx", ".ts", ".tsx", "/index.js", "/index.jsx", "/index.ts", "/index.tsx"]) {
189
+ const candidate = candidateBase + ext;
190
+ if (fsSync.existsSync(candidate) && !fsSync.statSync(candidate).isDirectory()) {
191
+ resolveCache.set(cacheKey, null);
192
+ redirTime += Date.now() - tStart;
193
+ return null;
194
+ }
195
+ }
196
+
197
+ // 2. Otherwise redirect to original in src/
198
+ const relFromSwc = path.relative(swcDir, path.dirname(importer));
199
+ const originalDir = path.join(projectRoot, relFromSwc);
200
+ const targetBase = path.resolve(originalDir, args.path);
201
+
202
+ for (const ext of candidateExtensions) {
203
+ const target = targetBase + ext;
204
+ if (fsSync.existsSync(target) && !fsSync.statSync(target).isDirectory()) {
205
+ const res = { path: target };
206
+ resolveCache.set(cacheKey, res);
207
+ redirTime += Date.now() - tStart;
208
+ return res;
209
+ }
210
+ }
211
+
212
+ resolveCache.set(cacheKey, null);
213
+ redirTime += Date.now() - tStart;
214
+ return null;
215
+ }
216
+ );
217
+
218
+ build.onEnd(() => {
219
+ globalThis.__DINOU_REDIRECT_TIME__ = redirTime;
220
+ globalThis.__DINOU_REDIRECT_CALLS__ = redirCalls;
221
+ globalThis.__DINOU_REDIRECT_HITS__ = redirHits;
222
+ });
223
+ },
224
+ };
225
+ }
@@ -29,6 +29,13 @@ export function updateManifestForModule(
29
29
  chunks: expName,
30
30
  name: expName,
31
31
  };
32
+ if (expName === "default") {
33
+ manifest[`${fileUrl}#default`] = {
34
+ id: relPath,
35
+ chunks: expName,
36
+ name: expName,
37
+ };
38
+ }
32
39
  }
33
40
  }
34
41
  }
@@ -2,7 +2,20 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { regex } from "../../core/asset-extensions.js";
4
4
 
5
+ function areBuffersEqual(a, b) {
6
+ if (a === b) return true;
7
+ if (!a || !b) return false;
8
+ if (a.byteLength !== b.byteLength) return false;
9
+ return Buffer.from(a.buffer, a.byteOffset, a.byteLength).equals(
10
+ Buffer.from(b.buffer, b.byteOffset, b.byteLength)
11
+ );
12
+ }
13
+
14
+ const writtenCache = new Map();
15
+ const createdDirs = new Set();
16
+
5
17
  export default async function write(result) {
18
+ const tWriteTotal0 = Date.now();
6
19
  if (!result.metafile) {
7
20
  return;
8
21
  }
@@ -47,13 +60,109 @@ export default async function write(result) {
47
60
  }
48
61
  }
49
62
  }
63
+ // 1. Populate in-memory files cache for zero-disk latency serving
64
+ globalThis.__DINOU_MEM_FILES__ = globalThis.__DINOU_MEM_FILES__ || new Map();
65
+ const publicDir = path.resolve(process.cwd(), ".dinou/public");
66
+ for (const file of result.outputFiles) {
67
+ const fileRelPath = normalizeRel(path.relative(process.cwd(), file.path));
68
+ if (skipSet.has(fileRelPath)) continue;
69
+ const cleanPublicRel = normalizeRel(path.relative(publicDir, file.path));
70
+ const buf = Buffer.isBuffer(file.contents)
71
+ ? file.contents
72
+ : Buffer.from(file.contents.buffer, file.contents.byteOffset, file.contents.byteLength);
73
+ globalThis.__DINOU_MEM_FILES__.set(cleanPublicRel, buf);
74
+ globalThis.__DINOU_MEM_FILES__.set("/" + cleanPublicRel, buf);
75
+ const base = path.basename(cleanPublicRel);
76
+ globalThis.__DINOU_MEM_FILES__.set(base, buf);
77
+ }
78
+
79
+ // 1.b Also populate aliases for renamed chunks so old/raw names never 404
80
+ if (globalThis.__DINOU_CHUNK_RENAMES__) {
81
+ for (const [oldName, newName] of globalThis.__DINOU_CHUNK_RENAMES__.entries()) {
82
+ const cleanNew = normalizeRel(newName);
83
+ const newBase = path.basename(cleanNew);
84
+ const buf =
85
+ globalThis.__DINOU_MEM_FILES__.get(cleanNew) ||
86
+ globalThis.__DINOU_MEM_FILES__.get("/" + cleanNew) ||
87
+ globalThis.__DINOU_MEM_FILES__.get(newBase);
88
+ if (buf) {
89
+ const cleanOld = normalizeRel(oldName);
90
+ const oldBase = path.basename(cleanOld);
91
+ globalThis.__DINOU_MEM_FILES__.set(cleanOld, buf);
92
+ globalThis.__DINOU_MEM_FILES__.set("/" + cleanOld, buf);
93
+ globalThis.__DINOU_MEM_FILES__.set(oldBase, buf);
94
+ }
95
+ }
96
+ }
97
+
98
+ const isDebug =
99
+ process.env.DINOU_DEBUG === "true" ||
100
+ process.env.DINOU_DEBUG === "1" ||
101
+ process.env.DEBUG === "true" ||
102
+ process.env.DEBUG === "1";
103
+ const timelineTime = () => new Date().toTimeString().slice(0, 8) + "." + String(Date.now() % 1000).padStart(3, "0");
104
+ const timelineRel = () => globalThis.__TIMELINE_T0__ ? `[+${Date.now() - globalThis.__TIMELINE_T0__}ms]` : ``;
105
+
106
+ const isDev = process.env.DINOU_DEV === "true" || process.env.NODE_ENV === "development";
107
+ const shouldWriteToDisk = process.env.DINOU_WRITE_TO_DISK === "true" || !isDev;
108
+
109
+ if (!shouldWriteToDisk) {
110
+ globalThis.__DINOU_WRITE_TIME__ = 0;
111
+ globalThis.__DINOU_WRITE_TOTAL__ = Date.now() - tWriteTotal0;
112
+ if (isDebug) {
113
+ console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} write.mjs: in-memory cache ready (${result.outputFiles.length} file(s)), disk write skipped (0ms)`);
114
+ }
115
+ console.log(`✓ Build completed`);
116
+ return;
117
+ }
118
+
119
+ const filesToWrite = [];
50
120
  for (const file of result.outputFiles) {
51
121
  const fileRelPath = normalizeRel(path.relative(process.cwd(), file.path));
52
122
  if (skipSet.has(fileRelPath)) {
53
123
  continue;
54
124
  }
55
- await fs.mkdir(path.dirname(file.path), { recursive: true });
56
- await fs.writeFile(file.path, file.contents);
125
+ const cached = writtenCache.get(file.path);
126
+ if (cached && areBuffersEqual(cached, file.contents)) {
127
+ continue;
128
+ }
129
+ filesToWrite.push(file);
130
+ }
131
+
132
+ if (filesToWrite.length === 0) {
133
+ if (isDebug) {
134
+ console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} write.mjs: 0 files changed, disk write skipped (0ms)`);
135
+ }
136
+ return;
137
+ }
138
+
139
+ const tWrite0 = Date.now();
140
+ if (isDebug) {
141
+ console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} write.mjs: writing ${filesToWrite.length} file(s) to disk...`);
142
+ }
143
+ const uniqueDirs = new Set();
144
+ for (const f of filesToWrite) {
145
+ const dir = path.dirname(f.path);
146
+ if (!createdDirs.has(dir)) {
147
+ uniqueDirs.add(dir);
148
+ createdDirs.add(dir);
149
+ }
150
+ }
151
+ if (uniqueDirs.size > 0) {
152
+ await Promise.all(
153
+ Array.from(uniqueDirs).map((d) => fs.mkdir(d, { recursive: true }))
154
+ );
155
+ }
156
+ await Promise.all(
157
+ filesToWrite.map(async (file) => {
158
+ await fs.writeFile(file.path, file.contents);
159
+ writtenCache.set(file.path, file.contents);
160
+ })
161
+ );
162
+ globalThis.__DINOU_WRITE_TIME__ = Date.now() - tWrite0;
163
+
164
+ if (isDebug) {
165
+ console.log(`⏱️ [TIMELINE ${timelineTime()}] ${timelineRel()} write.mjs: wrote ${filesToWrite.length} file(s) in ${Date.now() - tWrite0}ms`);
57
166
  }
58
167
  console.log(`✓ Build completed`);
59
168
  }