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.
- package/CHANGELOG.md +99 -0
- package/README.md +9461 -1080
- package/cli.js +392 -169
- package/dinou/adapters/bun.js +62 -0
- package/dinou/adapters/cloudflare.js +65 -0
- package/dinou/adapters/deno.js +48 -0
- package/dinou/adapters/netlify.js +85 -0
- package/dinou/adapters/node.mjs +31 -0
- package/dinou/bun/build.mjs +1077 -0
- package/dinou/cloudflare/build.mjs +1121 -0
- package/dinou/cloudflare/shims/child_process.js +14 -0
- package/dinou/cloudflare/shims/empty.js +2 -0
- package/dinou/cloudflare/shims/fs.js +99 -0
- package/dinou/cloudflare/shims/os.js +29 -0
- package/dinou/config.js +1 -0
- package/dinou/config.mjs +2 -0
- package/dinou/constants.js +29 -4
- package/dinou/core/babel-esm-loader.js +66 -37
- package/dinou/core/bailout-proxy.js +61 -0
- package/dinou/core/build-static-pages.js +309 -157
- package/dinou/core/cache-revalidate.js +471 -144
- package/dinou/core/cache-slot.js +387 -0
- package/dinou/core/client-redirect.jsx +12 -7
- package/dinou/core/client-webpack.jsx +457 -82
- package/dinou/core/client.jsx +452 -82
- package/dinou/core/config.js +11 -0
- package/dinou/core/dinou-paths.js +52 -0
- package/dinou/core/edge-ssr.js +122 -0
- package/dinou/core/export-static.mjs +64 -0
- package/dinou/core/generate-route-types.js +252 -0
- package/dinou/core/generate-static-page.js +55 -94
- package/dinou/core/generate-static-rsc.js +47 -15
- package/dinou/core/generate-static.js +212 -10
- package/dinou/core/generating-isg.js +5 -0
- package/dinou/core/get-asset-from-manifest.js +9 -3
- package/dinou/core/get-error-jsx.js +221 -41
- package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
- package/dinou/core/get-jsx.js +172 -95
- package/dinou/core/handler.js +2296 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +181 -0
- package/dinou/core/link.jsx +18 -6
- package/dinou/core/load-dinou-config.js +60 -0
- package/dinou/core/manifest-provider.js +261 -0
- package/dinou/core/navigation-utils.js +4 -0
- package/dinou/core/navigation.js +50 -39
- package/dinou/core/parse-exports.js +207 -2
- package/dinou/core/ppr-context.js +105 -0
- package/dinou/core/ppr-runtime.js +263 -0
- package/dinou/core/redirect.jsx +20 -19
- package/dinou/core/render-jsx-to-client-jsx.js +133 -15
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +32 -0
- package/dinou/core/revalidating.js +28 -9
- package/dinou/core/route-generator.js +107 -0
- package/dinou/core/rsc-renderer.js +90 -0
- package/dinou/core/run-ssg.js +19 -0
- package/dinou/core/scan-dependency-components.js +218 -0
- package/dinou/core/server-function-proxy-webpack.js +3 -0
- package/dinou/core/server-function-proxy.js +3 -0
- package/dinou/core/slot.js +302 -0
- package/dinou/core/status-manifest.js +41 -1
- package/dinou/core/storage-adapter.js +522 -0
- package/dinou/core/url-resolver.js +16 -16
- package/dinou/core/vfs.js +139 -68
- package/dinou/deno/build.mjs +1112 -0
- package/dinou/deno/seed-kv.js +115 -0
- package/dinou/esbuild/build.mjs +51 -1
- package/dinou/esbuild/dev.mjs +290 -224
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
- package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
- package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
- package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
- package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
- package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
- package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
- package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
- package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
- package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
- package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
- package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
- package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
- package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
- package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
- package/dinou/index.d.ts +315 -11
- package/dinou/index.js +12 -6
- package/dinou/index.mjs +9 -0
- package/dinou/node/build.mjs +195 -0
- package/dinou/node/bundle-dual-engine.mjs +1012 -0
- package/dinou/node/dev.mjs +2589 -0
- package/dinou/node/port-selector.mjs +164 -0
- package/dinou/node/terminal-status.mjs +345 -0
- package/dinou/package.json +17 -14
- package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
- package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
- package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
- package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
- package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
- package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
- package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
- package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
- package/dinou/rollup/rollup.config.js +155 -43
- package/dinou/server.d.ts +114 -12
- package/dinou/server.js +35 -10
- package/dinou/server.mjs +30 -9
- package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
- package/dinou/webpack/loaders/server-functions-loader.js +22 -1
- package/dinou/webpack/loaders/swc-loader.js +76 -0
- package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
- package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
- package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
- package/dinou/webpack/webpack.config.js +227 -29
- package/eject.js +173 -94
- package/package.json +110 -107
- package/dinou/core/asset-require-hook.js +0 -58
- package/dinou/core/client-error-webpack.jsx +0 -432
- package/dinou/core/client-error.jsx +0 -432
- package/dinou/core/concurrency-manager.js +0 -48
- package/dinou/core/context-proxy.js +0 -68
- package/dinou/core/generate-static-pages.js +0 -130
- package/dinou/core/generate-static-rscs.js +0 -151
- package/dinou/core/get-ssg-metadata.js +0 -31
- package/dinou/core/register-loader.mjs +0 -11
- package/dinou/core/register-paths.js +0 -27
- package/dinou/core/render-app-to-html.js +0 -430
- package/dinou/core/render-html.js +0 -407
- package/dinou/core/server.js +0 -1542
- package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
|
@@ -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
|
|
10
|
-
import
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
reactClientManifestPlugin({ manifest }),
|
|
24
|
-
assetsPlugin(),
|
|
25
|
-
stableChunkNamesAndMapsPlugin(),
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
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
|
|
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(
|
|
288
|
+
Array.from(serverFunctions),
|
|
277
289
|
];
|
|
278
290
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
-
|
|
56
|
-
|
|
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
|
}
|