reroute-js 0.7.2 → 0.8.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/README.md +1 -0
- package/_/basic/package.json +2 -1
- package/_/blog/package.json +2 -1
- package/_/store/package.json +2 -1
- package/cli/bin.d.ts +1 -1
- package/cli/bin.js +3456 -87
- package/cli/bin.js.map +34 -7
- package/cli/index.d.ts +1 -1
- package/cli/index.js +75 -34
- package/cli/index.js.map +6 -4
- package/cli/src/cli.d.ts +1 -1
- package/cli/src/commands/analyze.d.ts +19 -0
- package/cli/src/commands/analyze.d.ts.map +1 -0
- package/cli/src/commands/boot.d.ts +1 -1
- package/cli/src/commands/build.d.ts +1 -1
- package/cli/src/commands/build.d.ts.map +1 -1
- package/cli/src/commands/dev.d.ts +1 -1
- package/cli/src/commands/gen.d.ts +1 -1
- package/cli/src/commands/gen.d.ts.map +1 -1
- package/cli/src/commands/init.d.ts +1 -1
- package/cli/src/commands/start.d.ts +1 -1
- package/cli/src/libs/command.d.ts +15 -0
- package/cli/src/libs/command.d.ts.map +1 -0
- package/cli/src/libs/index.d.ts +3 -1
- package/cli/src/libs/index.d.ts.map +1 -1
- package/cli/src/libs/log.d.ts +1 -1
- package/cli/src/libs/log.d.ts.map +1 -1
- package/cli/src/libs/markdown-processor.d.ts +1 -1
- package/cli/src/libs/markdown.d.ts +1 -1
- package/cli/src/libs/production.d.ts +34 -0
- package/cli/src/libs/production.d.ts.map +1 -0
- package/cli/src/libs/tailwind.d.ts +1 -1
- package/cli/src/libs/version.d.ts +1 -1
- package/core/index.d.ts +1 -1
- package/core/index.js +2 -2
- package/core/index.js.map +1 -1
- package/core/src/bundler/hash.d.ts +1 -1
- package/core/src/bundler/index.d.ts +1 -1
- package/core/src/bundler/transpile.d.ts +1 -1
- package/core/src/content/discovery.d.ts +1 -1
- package/core/src/content/index.d.ts +1 -1
- package/core/src/content/metadata.d.ts +1 -1
- package/core/src/content/registry.d.ts +1 -1
- package/core/src/index.d.ts +1 -1
- package/core/src/ssr/data.d.ts +1 -1
- package/core/src/ssr/index.d.ts +1 -1
- package/core/src/ssr/modules.d.ts +1 -1
- package/core/src/ssr/render.d.ts +1 -1
- package/core/src/ssr/seed.d.ts +1 -1
- package/core/src/template/html.d.ts +1 -1
- package/core/src/template/index.d.ts +1 -1
- package/core/src/types.d.ts +1 -1
- package/core/src/utils/cache.d.ts +1 -1
- package/core/src/utils/compression.d.ts +1 -1
- package/core/src/utils/index.d.ts +1 -1
- package/core/src/utils/mime.d.ts +1 -1
- package/core/src/utils/path.d.ts +1 -1
- package/elysia/index.d.ts +1 -1
- package/elysia/index.js +2 -2
- package/elysia/index.js.map +1 -1
- package/elysia/src/index.d.ts +1 -1
- package/elysia/src/libs/http.d.ts +1 -1
- package/elysia/src/libs/image.d.ts +1 -1
- package/elysia/src/plugin.d.ts +1 -1
- package/elysia/src/routes/artifacts.d.ts +1 -1
- package/elysia/src/routes/content.d.ts +1 -1
- package/elysia/src/routes/dev.d.ts +1 -1
- package/elysia/src/routes/image.d.ts +1 -1
- package/elysia/src/routes/ssr.d.ts +1 -1
- package/elysia/src/routes/static.d.ts +1 -1
- package/elysia/src/types.d.ts +1 -1
- package/package.json +3 -2
- package/react/index.d.ts +1 -1
- package/react/index.js +2 -2
- package/react/index.js.map +1 -1
- package/react/src/components/ClientOnly.d.ts +1 -1
- package/react/src/components/ContentRoute.d.ts +1 -1
- package/react/src/components/Image.d.ts +1 -1
- package/react/src/components/Link.d.ts +1 -1
- package/react/src/components/Markdown.d.ts +1 -1
- package/react/src/components/Outlet.d.ts +1 -1
- package/react/src/components/index.d.ts +1 -1
- package/react/src/hooks/index.d.ts +1 -1
- package/react/src/hooks/useContent.d.ts +1 -1
- package/react/src/hooks/useData.d.ts +1 -1
- package/react/src/hooks/useNavigate.d.ts +1 -1
- package/react/src/hooks/useParams.d.ts +1 -1
- package/react/src/hooks/useRouter.d.ts +1 -1
- package/react/src/hooks/useSearchParams.d.ts +1 -1
- package/react/src/index.d.ts +1 -1
- package/react/src/providers/ContentProvider.d.ts +1 -1
- package/react/src/providers/RerouteProvider.d.ts +1 -1
- package/react/src/providers/RouterProvider.d.ts +1 -1
- package/react/src/providers/index.d.ts +1 -1
- package/react/src/types/any.d.ts +1 -1
- package/react/src/types/index.d.ts +1 -1
- package/react/src/types/router.d.ts +1 -1
- package/react/src/utils/content.d.ts +1 -1
- package/react/src/utils/head.d.ts +1 -1
- package/react/src/utils/index.d.ts +1 -1
package/cli/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
/**
|
|
4
|
-
* reroute-js v0.
|
|
4
|
+
* reroute-js v0.8.0
|
|
5
5
|
*
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @copyright 2025 stewones <hi@stewan.io>
|
|
@@ -47,7 +47,7 @@ async function getVersionString() {
|
|
|
47
47
|
}
|
|
48
48
|
async function getVersion() {
|
|
49
49
|
if (true) {
|
|
50
|
-
return "0.
|
|
50
|
+
return "0.8.0";
|
|
51
51
|
}
|
|
52
52
|
const possiblePaths = [
|
|
53
53
|
path.join(import.meta.dir, "../../../../package.json"),
|
|
@@ -65,7 +65,7 @@ async function getVersion() {
|
|
|
65
65
|
}
|
|
66
66
|
async function getCommit() {
|
|
67
67
|
if (true) {
|
|
68
|
-
return "
|
|
68
|
+
return "b31f8e3";
|
|
69
69
|
}
|
|
70
70
|
return "dev";
|
|
71
71
|
}
|
|
@@ -649,6 +649,11 @@ function isMarkdownFile(filePath) {
|
|
|
649
649
|
}
|
|
650
650
|
var init_markdown_processor = () => {};
|
|
651
651
|
|
|
652
|
+
// packages/cli/src/libs/production.ts
|
|
653
|
+
function isProductionMode(args = []) {
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
|
|
652
657
|
// packages/cli/src/libs/tailwind.ts
|
|
653
658
|
import { spawn } from "node:child_process";
|
|
654
659
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
@@ -1030,15 +1035,16 @@ async function buildContentChunks(cwd) {
|
|
|
1030
1035
|
let code = "";
|
|
1031
1036
|
try {
|
|
1032
1037
|
console.log(`[reroute/content] build ${buildSrc}`);
|
|
1038
|
+
const isProd = isProductionMode();
|
|
1033
1039
|
const result = await Bun.build({
|
|
1034
1040
|
entrypoints: [buildSrc],
|
|
1035
1041
|
target: "browser",
|
|
1036
1042
|
format: "esm",
|
|
1037
1043
|
splitting: false,
|
|
1038
|
-
sourcemap:
|
|
1039
|
-
minify:
|
|
1044
|
+
sourcemap: isProd ? "linked" : "none",
|
|
1045
|
+
minify: isProd,
|
|
1040
1046
|
define: {
|
|
1041
|
-
"process.env.NODE_ENV":
|
|
1047
|
+
"process.env.NODE_ENV": isProd ? '"production"' : '"development"'
|
|
1042
1048
|
}
|
|
1043
1049
|
});
|
|
1044
1050
|
if (!result.success)
|
|
@@ -1445,6 +1451,7 @@ var init_log = __esm(() => {
|
|
|
1445
1451
|
"[reroute/dev]": "{green-fg}",
|
|
1446
1452
|
"[reroute/start]": "{bright-cyan-fg}",
|
|
1447
1453
|
"[reroute/build]": "{bright-magenta-fg}",
|
|
1454
|
+
"[reroute/analyze]": "{bright-green-fg}",
|
|
1448
1455
|
"[api]": "{blue-fg}",
|
|
1449
1456
|
"[app]": "{bright-green-fg}"
|
|
1450
1457
|
};
|
|
@@ -2037,7 +2044,7 @@ async function build(args) {
|
|
|
2037
2044
|
`));
|
|
2038
2045
|
process.stdout.write(`${colorizeLogPrefixAnsi(`[reroute/build] Building binary to ${outputPath}...`)}
|
|
2039
2046
|
`);
|
|
2040
|
-
const isProduction =
|
|
2047
|
+
const isProduction = isProductionMode(args) || args.includes("--minify");
|
|
2041
2048
|
const buildArgs = ["build", "src/index.ts", "--target", "bun", "--compile"];
|
|
2042
2049
|
if (isProduction) {
|
|
2043
2050
|
buildArgs.push("--minify");
|
|
@@ -2048,7 +2055,7 @@ async function build(args) {
|
|
|
2048
2055
|
return false;
|
|
2049
2056
|
}
|
|
2050
2057
|
}
|
|
2051
|
-
return arg !== "--outfile" && arg !== "-o" && arg !== "--minify" && arg !== "--no-minify";
|
|
2058
|
+
return arg !== "--outfile" && arg !== "-o" && arg !== "--minify" && arg !== "--no-minify" && arg !== "--prod";
|
|
2052
2059
|
});
|
|
2053
2060
|
buildArgs.push(...filteredArgs, "--outfile", outputPath);
|
|
2054
2061
|
await new Promise((resolve, reject) => {
|
|
@@ -2078,86 +2085,3448 @@ var init_build = __esm(() => {
|
|
|
2078
2085
|
init_log();
|
|
2079
2086
|
});
|
|
2080
2087
|
|
|
2081
|
-
// packages/
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2088
|
+
// packages/core/src/bundler/hash.ts
|
|
2089
|
+
async function generateContentHash(content) {
|
|
2090
|
+
const data = new TextEncoder().encode(content);
|
|
2091
|
+
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
2092
|
+
let hex = "";
|
|
2093
|
+
for (const b of new Uint8Array(buf))
|
|
2094
|
+
hex += b.toString(16).padStart(2, "0");
|
|
2095
|
+
return hex.slice(0, 8);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// packages/cli/src/libs/command.ts
|
|
2099
|
+
var exports_command = {};
|
|
2100
|
+
__export(exports_command, {
|
|
2101
|
+
getRerouteCommand: () => getRerouteCommand4
|
|
2102
|
+
});
|
|
2103
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2104
|
+
import { join as join10 } from "node:path";
|
|
2105
|
+
function getRerouteCommand4() {
|
|
2106
|
+
const binPath = join10(import.meta.dir, "..", "..", "bin.ts");
|
|
2107
|
+
const isBunDev = existsSync8(binPath);
|
|
2108
|
+
if (isBunDev) {
|
|
2109
|
+
return `bun ${binPath}`;
|
|
2097
2110
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2111
|
+
const cwd = process.cwd();
|
|
2112
|
+
const nodeModulesPaths = [
|
|
2113
|
+
join10(cwd, "node_modules", ".bin", "reroute"),
|
|
2114
|
+
join10(cwd, "..", "node_modules", ".bin", "reroute"),
|
|
2115
|
+
join10(cwd, "..", "..", "node_modules", ".bin", "reroute"),
|
|
2116
|
+
join10(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
|
|
2117
|
+
];
|
|
2118
|
+
for (const binPath2 of nodeModulesPaths) {
|
|
2119
|
+
if (existsSync8(binPath2)) {
|
|
2120
|
+
return binPath2;
|
|
2121
|
+
}
|
|
2101
2122
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2123
|
+
return "reroute";
|
|
2124
|
+
}
|
|
2125
|
+
var init_command = () => {};
|
|
2126
|
+
|
|
2127
|
+
// packages/cli/src/libs/index.ts
|
|
2128
|
+
var init_libs = __esm(() => {
|
|
2129
|
+
init_command();
|
|
2130
|
+
init_log();
|
|
2131
|
+
init_markdown();
|
|
2132
|
+
init_markdown_processor();
|
|
2133
|
+
init_tailwind();
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
// packages/cli/index.ts
|
|
2137
|
+
var init_cli = __esm(() => {
|
|
2138
|
+
init_libs();
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
// packages/core/src/utils/path.ts
|
|
2142
|
+
var exports_path = {};
|
|
2143
|
+
__export(exports_path, {
|
|
2144
|
+
stripStart: () => stripStart,
|
|
2145
|
+
stripEnd: () => stripEnd,
|
|
2146
|
+
join: () => join11,
|
|
2147
|
+
extname: () => extname2,
|
|
2148
|
+
basename: () => basename2
|
|
2149
|
+
});
|
|
2150
|
+
function join11(...parts) {
|
|
2151
|
+
return parts.join("/").replace(/\/+/g, "/");
|
|
2152
|
+
}
|
|
2153
|
+
function extname2(p) {
|
|
2154
|
+
const i = p.lastIndexOf(".");
|
|
2155
|
+
return i >= 0 ? p.slice(i) : "";
|
|
2156
|
+
}
|
|
2157
|
+
function basename2(p, ext) {
|
|
2158
|
+
const name = p.replace(/\\/g, "/").split("/").pop() || p;
|
|
2159
|
+
return ext && name.endsWith(ext) ? name.slice(0, -ext.length) : name;
|
|
2160
|
+
}
|
|
2161
|
+
function stripStart(p, ch) {
|
|
2162
|
+
return p.startsWith(ch) ? p.slice(ch.length) : p;
|
|
2163
|
+
}
|
|
2164
|
+
function stripEnd(p, ch) {
|
|
2165
|
+
return p.endsWith(ch) ? p.slice(0, -ch.length) : p;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// packages/core/src/bundler/transpile.ts
|
|
2169
|
+
function isWatchMode() {
|
|
2170
|
+
try {
|
|
2171
|
+
return Array.isArray(process.execArgv) && process.execArgv.includes("--watch") || Array.isArray(process.argv) && process.argv.includes("--watch");
|
|
2172
|
+
} catch {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
async function transpileFile(filePath, originalPath, options, bundleCache) {
|
|
2177
|
+
const src = await Bun.file(filePath).text();
|
|
2178
|
+
const cacheKey = `${filePath}-${await generateContentHash(src)}`;
|
|
2179
|
+
if (bundleCache.has(cacheKey)) {
|
|
2180
|
+
return bundleCache.get(cacheKey);
|
|
2181
|
+
}
|
|
2182
|
+
console.log(`[reroute] Building ${originalPath}${options.minify ? " (minified)" : ""}...`);
|
|
2183
|
+
try {
|
|
2184
|
+
if (isWatchMode()) {
|
|
2185
|
+
const cwd = typeof process !== "undefined" && process.cwd ? process.cwd() : "";
|
|
2186
|
+
if (cwd) {
|
|
2187
|
+
await buildTailwindIfConfigured(cwd);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
console.warn("[reroute/tailwind] rebuild failed:", e);
|
|
2192
|
+
}
|
|
2193
|
+
async function doBuild(sm) {
|
|
2194
|
+
return await Bun.build({
|
|
2195
|
+
entrypoints: [filePath],
|
|
2196
|
+
target: "browser",
|
|
2197
|
+
format: "esm",
|
|
2198
|
+
minify: options.minify,
|
|
2199
|
+
splitting: false,
|
|
2200
|
+
sourcemap: sm,
|
|
2201
|
+
define: {
|
|
2202
|
+
"process.env.NODE_ENV": options.minify ? '"production"' : '"development"'
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2108
2205
|
}
|
|
2206
|
+
let result;
|
|
2109
2207
|
try {
|
|
2110
|
-
|
|
2111
|
-
await module.default(args.slice(1));
|
|
2208
|
+
result = await doBuild(options.sourcemap ? "external" : "none");
|
|
2112
2209
|
} catch (error) {
|
|
2113
|
-
console.
|
|
2114
|
-
|
|
2210
|
+
console.warn(`[reroute] Build errored for ${originalPath} (sourcemap external). Retrying without sourcemap...`, error);
|
|
2211
|
+
result = await doBuild("none");
|
|
2115
2212
|
}
|
|
2213
|
+
if (!result.success) {
|
|
2214
|
+
console.error(`[reroute] Failed to build ${filePath}`);
|
|
2215
|
+
for (const log2 of result.logs)
|
|
2216
|
+
console.error(log2);
|
|
2217
|
+
throw new Error(`Failed to transpile ${filePath}`);
|
|
2218
|
+
}
|
|
2219
|
+
const output = result.outputs[0];
|
|
2220
|
+
const code = await output.text();
|
|
2221
|
+
let sourceMap;
|
|
2222
|
+
if (options.sourcemap && result.outputs.length > 1) {
|
|
2223
|
+
const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
|
|
2224
|
+
if (mapOutput) {
|
|
2225
|
+
sourceMap = await mapOutput.text();
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
const contentHash = await generateContentHash(code);
|
|
2229
|
+
const bundleInfo = {
|
|
2230
|
+
hash: contentHash,
|
|
2231
|
+
code,
|
|
2232
|
+
sourceMap
|
|
2233
|
+
};
|
|
2234
|
+
bundleCache.set(cacheKey, bundleInfo);
|
|
2235
|
+
const sizeKB = (code.length / 1024).toFixed(2);
|
|
2236
|
+
const gzippedSize = Bun.gzipSync(new TextEncoder().encode(code)).length;
|
|
2237
|
+
const gzippedKB = (gzippedSize / 1024).toFixed(2);
|
|
2238
|
+
console.log(`[reroute] Built ${originalPath} -> ${sizeKB} KB (${gzippedKB} KB gzipped)`);
|
|
2239
|
+
return bundleInfo;
|
|
2116
2240
|
}
|
|
2117
|
-
async function
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2241
|
+
async function getBundleUrlsFor(modules, clientDir, prefix, options, bundleCache) {
|
|
2242
|
+
const mods = Array.isArray(modules) ? modules : [modules];
|
|
2243
|
+
const urls = [];
|
|
2244
|
+
const { join: join12 } = await Promise.resolve().then(() => exports_path);
|
|
2245
|
+
for (const mod of mods) {
|
|
2246
|
+
const rel = mod.replace(/^\.\//, "");
|
|
2247
|
+
const fullPath = join12(clientDir, rel);
|
|
2248
|
+
try {
|
|
2249
|
+
const bundleInfo = await transpileFile(fullPath, rel, options, bundleCache);
|
|
2250
|
+
const base = basename2(rel, extname2(rel));
|
|
2251
|
+
urls.push(`${prefix}/${base}.${bundleInfo.hash}.js`);
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
console.error(`[reroute] Error getting bundle URL for ${rel}:`, error);
|
|
2254
|
+
urls.push(`${prefix}/${rel}`);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
return urls;
|
|
2258
|
+
}
|
|
2259
|
+
var init_transpile = __esm(() => {
|
|
2260
|
+
init_cli();
|
|
2261
|
+
});
|
|
2262
|
+
|
|
2263
|
+
// packages/core/src/bundler/index.ts
|
|
2264
|
+
var init_bundler = __esm(() => {
|
|
2265
|
+
init_transpile();
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
// packages/core/src/content/metadata.ts
|
|
2269
|
+
import { stat } from "node:fs/promises";
|
|
2270
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
2271
|
+
async function getContentMeta(absPath, isWatchMode2) {
|
|
2272
|
+
try {
|
|
2273
|
+
const url = pathToFileURL2(absPath).href;
|
|
2274
|
+
const mod = await import(`${url}${isWatchMode2 ? `?t=${Date.now()}` : ""}`);
|
|
2275
|
+
const meta = mod.meta || mod.frontmatter || {};
|
|
2276
|
+
if (meta && typeof meta === "object")
|
|
2277
|
+
return meta;
|
|
2278
|
+
return {};
|
|
2279
|
+
} catch {
|
|
2280
|
+
try {
|
|
2281
|
+
const s = await stat(absPath);
|
|
2282
|
+
return { date: new Date(s.mtimeMs).toISOString() };
|
|
2283
|
+
} catch {
|
|
2284
|
+
return {};
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function buildHeadFromMeta(meta) {
|
|
2289
|
+
if (!meta || typeof meta !== "object")
|
|
2290
|
+
return "";
|
|
2291
|
+
const parts = [];
|
|
2292
|
+
const title = typeof meta.title === "string" ? meta.title : undefined;
|
|
2293
|
+
const description = typeof meta.description === "string" ? meta.description : typeof meta.excerpt === "string" ? meta.excerpt : undefined;
|
|
2294
|
+
if (title)
|
|
2295
|
+
parts.push(`<title>${escapeHtml(title)}</title>`);
|
|
2296
|
+
if (description)
|
|
2297
|
+
parts.push(`<meta name="description" content="${escapeHtml(description)}" />`);
|
|
2298
|
+
return parts.length ? `
|
|
2299
|
+
${parts.join(`
|
|
2300
|
+
`)}` : "";
|
|
2301
|
+
}
|
|
2302
|
+
function escapeHtml(input) {
|
|
2303
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2304
|
+
}
|
|
2305
|
+
var init_metadata = () => {};
|
|
2306
|
+
|
|
2307
|
+
// packages/core/src/content/discovery.ts
|
|
2308
|
+
import { readdir as readdir3, stat as stat2 } from "node:fs/promises";
|
|
2309
|
+
async function listContentFiles2(dir, baseRel) {
|
|
2310
|
+
const out = [];
|
|
2311
|
+
try {
|
|
2312
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
2313
|
+
for (const entry of entries) {
|
|
2314
|
+
const full = join11(dir, entry.name);
|
|
2315
|
+
const rel = join11(baseRel, entry.name);
|
|
2316
|
+
if (entry.isDirectory()) {
|
|
2317
|
+
const nested = await listContentFiles2(full, rel);
|
|
2318
|
+
out.push(...nested);
|
|
2319
|
+
} else if (entry.isFile()) {
|
|
2320
|
+
if (/\.(tsx|ts)$/.test(entry.name) && !entry.name.startsWith("_")) {
|
|
2321
|
+
out.push(rel);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
} catch {}
|
|
2326
|
+
return out;
|
|
2327
|
+
}
|
|
2328
|
+
async function buildContentDTOs(collectionPath, clientDir, prefix, isWatchMode2) {
|
|
2329
|
+
const normalized = stripStart(stripEnd(collectionPath, "/"), "/");
|
|
2330
|
+
const contentRelDir = join11("routes", normalized, "content");
|
|
2331
|
+
const contentAbsDir = join11(clientDir, contentRelDir);
|
|
2332
|
+
const files = await listContentFiles2(contentAbsDir, "");
|
|
2333
|
+
const items = [];
|
|
2334
|
+
for (const rel of files) {
|
|
2335
|
+
const fullRelPath = join11(contentRelDir, rel);
|
|
2336
|
+
const absPath = join11(clientDir, fullRelPath);
|
|
2337
|
+
const noExt = rel.replace(/\.(tsx|ts)$/, "");
|
|
2338
|
+
const name = basename2(noExt);
|
|
2339
|
+
const moduleUrl = `${prefix}/${fullRelPath}`.replace(/\/+/g, "/");
|
|
2340
|
+
const meta = await getContentMeta(absPath, isWatchMode2);
|
|
2341
|
+
const href = `/${normalized}/${name}`.replace(/\\+/g, "/");
|
|
2342
|
+
items.push({
|
|
2343
|
+
slug: noExt,
|
|
2344
|
+
name,
|
|
2345
|
+
path: fullRelPath,
|
|
2346
|
+
module: moduleUrl,
|
|
2347
|
+
meta,
|
|
2348
|
+
href
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
return items;
|
|
2352
|
+
}
|
|
2353
|
+
async function discoverCollections(clientDir) {
|
|
2354
|
+
const root = join11(clientDir, "routes");
|
|
2355
|
+
const collections = new Set;
|
|
2356
|
+
try {
|
|
2357
|
+
const entries = await readdir3(root, { withFileTypes: true });
|
|
2358
|
+
for (const entry of entries) {
|
|
2359
|
+
if (entry.isDirectory()) {
|
|
2360
|
+
const contentDir = join11(root, entry.name, "content");
|
|
2361
|
+
try {
|
|
2362
|
+
await stat2(contentDir);
|
|
2363
|
+
collections.add(entry.name);
|
|
2364
|
+
} catch {}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
} catch {}
|
|
2368
|
+
return Array.from(collections);
|
|
2369
|
+
}
|
|
2370
|
+
var init_discovery = __esm(() => {
|
|
2371
|
+
init_metadata();
|
|
2372
|
+
});
|
|
2373
|
+
|
|
2374
|
+
// packages/core/src/content/registry.ts
|
|
2375
|
+
async function generateContentRegistry(cwd) {
|
|
2376
|
+
try {
|
|
2377
|
+
const p = join11(cwd, ".reroute", "content.ts");
|
|
2378
|
+
const exists = await Bun.file(p).exists();
|
|
2379
|
+
console.log(exists ? "[reroute] Content registry up-to-date" : "[reroute] Warning: .reroute/content.ts not found");
|
|
2380
|
+
} catch {}
|
|
2381
|
+
}
|
|
2382
|
+
var init_registry = () => {};
|
|
2383
|
+
|
|
2384
|
+
// packages/core/src/content/index.ts
|
|
2385
|
+
var init_content = __esm(() => {
|
|
2386
|
+
init_discovery();
|
|
2387
|
+
init_metadata();
|
|
2388
|
+
init_registry();
|
|
2389
|
+
});
|
|
2390
|
+
|
|
2391
|
+
// packages/core/src/ssr/modules.ts
|
|
2392
|
+
import { readdir as readdir4, stat as stat3 } from "node:fs/promises";
|
|
2393
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
2394
|
+
async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode2) {
|
|
2395
|
+
try {
|
|
2396
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2397
|
+
if (parts.length < 2)
|
|
2398
|
+
return null;
|
|
2399
|
+
const collection = parts[0];
|
|
2400
|
+
const name = parts[1];
|
|
2401
|
+
try {
|
|
2402
|
+
const registryPath = join11(cwd, ".reroute", "content.ts");
|
|
2403
|
+
const reg = await import(pathToFileURL3(registryPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2404
|
+
const get = reg?.getContentEntry;
|
|
2405
|
+
const entry = typeof get === "function" ? get(collection, name) : undefined;
|
|
2406
|
+
const moduleUrl = entry?.module;
|
|
2407
|
+
if (moduleUrl?.endsWith(".js")) {
|
|
2408
|
+
const abs = join11(cwd, moduleUrl.replace(/^\//, ""));
|
|
2409
|
+
const href = pathToFileURL3(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : "");
|
|
2410
|
+
const mod = await import(href);
|
|
2411
|
+
return mod;
|
|
2412
|
+
}
|
|
2413
|
+
} catch {}
|
|
2414
|
+
try {
|
|
2415
|
+
const chunkDir = join11(cwd, ".reroute", "chunks", collection);
|
|
2416
|
+
const files = await readdir4(chunkDir);
|
|
2417
|
+
const matches = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
|
|
2418
|
+
if (matches.length) {
|
|
2419
|
+
let latest = matches[0];
|
|
2420
|
+
let latestM = 0;
|
|
2421
|
+
for (const f of matches) {
|
|
2422
|
+
try {
|
|
2423
|
+
const s = await stat3(join11(chunkDir, f));
|
|
2424
|
+
if (s.mtimeMs >= latestM) {
|
|
2425
|
+
latestM = s.mtimeMs;
|
|
2426
|
+
latest = f;
|
|
2427
|
+
}
|
|
2428
|
+
} catch {}
|
|
2429
|
+
}
|
|
2430
|
+
const absChunk = join11(chunkDir, latest);
|
|
2431
|
+
const href = pathToFileURL3(absChunk).href + (isWatchMode2 ? `?t=${Date.now()}` : "");
|
|
2432
|
+
const mod = await import(href);
|
|
2433
|
+
return mod;
|
|
2434
|
+
}
|
|
2435
|
+
} catch {}
|
|
2436
|
+
try {
|
|
2437
|
+
const srcTsx = join11(clientDir, "routes", collection, "content", `${name}.tsx`);
|
|
2438
|
+
const srcTs = join11(clientDir, "routes", collection, "content", `${name}.ts`);
|
|
2439
|
+
let absSrc = null;
|
|
2440
|
+
if (await Bun.file(srcTsx).exists())
|
|
2441
|
+
absSrc = srcTsx;
|
|
2442
|
+
else if (await Bun.file(srcTs).exists())
|
|
2443
|
+
absSrc = srcTs;
|
|
2444
|
+
if (absSrc) {
|
|
2445
|
+
const href = pathToFileURL3(absSrc).href;
|
|
2446
|
+
const mod = await (isWatchMode2 ? import(`${href}?t=${Date.now()}`) : import(href));
|
|
2447
|
+
return mod;
|
|
2448
|
+
}
|
|
2449
|
+
} catch {}
|
|
2450
|
+
} catch {}
|
|
2451
|
+
return null;
|
|
2452
|
+
}
|
|
2453
|
+
var init_modules = () => {};
|
|
2454
|
+
|
|
2455
|
+
// packages/core/src/ssr/seed.ts
|
|
2456
|
+
async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode2) {
|
|
2457
|
+
try {
|
|
2458
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2459
|
+
if (parts.length < 2)
|
|
2460
|
+
return;
|
|
2461
|
+
const collection = parts[0];
|
|
2462
|
+
const name = parts[1];
|
|
2463
|
+
const key = `${collection}:${name}`;
|
|
2464
|
+
const mod = await importContentModuleForPath(pathname, clientDir, cwd, isWatchMode2);
|
|
2465
|
+
if (!mod)
|
|
2466
|
+
return;
|
|
2467
|
+
const C = mod.default || mod;
|
|
2468
|
+
globalThis.__REROUTE_SSR_MODULES__ = globalThis.__REROUTE_SSR_MODULES__ || {};
|
|
2469
|
+
globalThis.__REROUTE_SSR_MODULES__[key] = C;
|
|
2470
|
+
try {
|
|
2471
|
+
const pageMeta = mod.meta || mod.frontmatter || {};
|
|
2472
|
+
const pageSSR = mod.ssr || {};
|
|
2473
|
+
globalThis.__REROUTE_SSR_EXPORTS__ = globalThis.__REROUTE_SSR_EXPORTS__ || {};
|
|
2474
|
+
globalThis.__REROUTE_SSR_EXPORTS__[key] = {
|
|
2475
|
+
meta: pageMeta,
|
|
2476
|
+
ssr: pageSSR
|
|
2477
|
+
};
|
|
2478
|
+
} catch {}
|
|
2479
|
+
} catch {}
|
|
2480
|
+
}
|
|
2481
|
+
var init_seed = __esm(() => {
|
|
2482
|
+
init_modules();
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
// packages/core/src/ssr/data.ts
|
|
2486
|
+
import { pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2487
|
+
async function computeSSRDataForPath(params) {
|
|
2488
|
+
const { pathname, clientDir, cwd, isWatchMode: isWatchMode2 } = params;
|
|
2489
|
+
try {
|
|
2490
|
+
await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode2);
|
|
2491
|
+
} catch {}
|
|
2492
|
+
try {
|
|
2493
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2494
|
+
if (parts.length >= 2) {
|
|
2495
|
+
const key = `${parts[0]}:${parts[1]}`;
|
|
2496
|
+
const g = globalThis;
|
|
2497
|
+
const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
|
|
2498
|
+
const dataFn = exp?.ssr?.data;
|
|
2499
|
+
if (typeof dataFn === "function") {
|
|
2500
|
+
return await dataFn({ pathname, params: { name: parts[1] } });
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
} catch {}
|
|
2504
|
+
try {
|
|
2505
|
+
const routesPath = join11(cwd, ".reroute", "routes.ts");
|
|
2506
|
+
const m = await import(pathToFileURL4(routesPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2507
|
+
const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
|
|
2508
|
+
const r = match?.route;
|
|
2509
|
+
const paramsValue = match?.params || {};
|
|
2510
|
+
if (r && typeof r.path === "string") {
|
|
2511
|
+
try {
|
|
2512
|
+
const abs = join11(clientDir, "routes", String(r.path));
|
|
2513
|
+
const mod = await import(pathToFileURL4(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2514
|
+
const ssr = mod?.ssr;
|
|
2515
|
+
const dataFn = ssr?.data;
|
|
2516
|
+
if (typeof dataFn === "function") {
|
|
2517
|
+
return await dataFn({ pathname, params: paramsValue });
|
|
2518
|
+
}
|
|
2519
|
+
} catch {}
|
|
2520
|
+
}
|
|
2521
|
+
} catch {}
|
|
2522
|
+
return null;
|
|
2523
|
+
}
|
|
2524
|
+
var init_data = __esm(() => {
|
|
2525
|
+
init_seed();
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
// node_modules/dedent/dist/dedent.mjs
|
|
2529
|
+
function ownKeys(object, enumerableOnly) {
|
|
2530
|
+
var keys = Object.keys(object);
|
|
2531
|
+
if (Object.getOwnPropertySymbols) {
|
|
2532
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
2533
|
+
enumerableOnly && (symbols = symbols.filter(function(sym) {
|
|
2534
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
2535
|
+
})), keys.push.apply(keys, symbols);
|
|
2536
|
+
}
|
|
2537
|
+
return keys;
|
|
2538
|
+
}
|
|
2539
|
+
function _objectSpread(target) {
|
|
2540
|
+
for (var i = 1;i < arguments.length; i++) {
|
|
2541
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
2542
|
+
i % 2 ? ownKeys(Object(source), true).forEach(function(key) {
|
|
2543
|
+
_defineProperty(target, key, source[key]);
|
|
2544
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function(key) {
|
|
2545
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
return target;
|
|
2549
|
+
}
|
|
2550
|
+
function _defineProperty(obj, key, value) {
|
|
2551
|
+
key = _toPropertyKey(key);
|
|
2552
|
+
if (key in obj) {
|
|
2553
|
+
Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
|
|
2554
|
+
} else {
|
|
2555
|
+
obj[key] = value;
|
|
2556
|
+
}
|
|
2557
|
+
return obj;
|
|
2558
|
+
}
|
|
2559
|
+
function _toPropertyKey(arg) {
|
|
2560
|
+
var key = _toPrimitive(arg, "string");
|
|
2561
|
+
return typeof key === "symbol" ? key : String(key);
|
|
2562
|
+
}
|
|
2563
|
+
function _toPrimitive(input, hint) {
|
|
2564
|
+
if (typeof input !== "object" || input === null)
|
|
2565
|
+
return input;
|
|
2566
|
+
var prim = input[Symbol.toPrimitive];
|
|
2567
|
+
if (prim !== undefined) {
|
|
2568
|
+
var res = prim.call(input, hint || "default");
|
|
2569
|
+
if (typeof res !== "object")
|
|
2570
|
+
return res;
|
|
2571
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
2572
|
+
}
|
|
2573
|
+
return (hint === "string" ? String : Number)(input);
|
|
2574
|
+
}
|
|
2575
|
+
function createDedent(options) {
|
|
2576
|
+
dedent2.withOptions = (newOptions) => createDedent(_objectSpread(_objectSpread({}, options), newOptions));
|
|
2577
|
+
return dedent2;
|
|
2578
|
+
function dedent2(strings, ...values) {
|
|
2579
|
+
const raw = typeof strings === "string" ? [strings] : strings.raw;
|
|
2580
|
+
const {
|
|
2581
|
+
alignValues = false,
|
|
2582
|
+
escapeSpecialCharacters = Array.isArray(strings),
|
|
2583
|
+
trimWhitespace = true
|
|
2584
|
+
} = options;
|
|
2585
|
+
let result = "";
|
|
2586
|
+
for (let i = 0;i < raw.length; i++) {
|
|
2587
|
+
let next = raw[i];
|
|
2588
|
+
if (escapeSpecialCharacters) {
|
|
2589
|
+
next = next.replace(/\\\n[ \t]*/g, "").replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\\{/g, "{");
|
|
2590
|
+
}
|
|
2591
|
+
result += next;
|
|
2592
|
+
if (i < values.length) {
|
|
2593
|
+
const value = alignValues ? alignValue(values[i], result) : values[i];
|
|
2594
|
+
result += value;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
const lines = result.split(`
|
|
2598
|
+
`);
|
|
2599
|
+
let mindent = null;
|
|
2600
|
+
for (const l of lines) {
|
|
2601
|
+
const m = l.match(/^(\s+)\S+/);
|
|
2602
|
+
if (m) {
|
|
2603
|
+
const indent = m[1].length;
|
|
2604
|
+
if (!mindent) {
|
|
2605
|
+
mindent = indent;
|
|
2606
|
+
} else {
|
|
2607
|
+
mindent = Math.min(mindent, indent);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
if (mindent !== null) {
|
|
2612
|
+
const m = mindent;
|
|
2613
|
+
result = lines.map((l) => l[0] === " " || l[0] === "\t" ? l.slice(m) : l).join(`
|
|
2614
|
+
`);
|
|
2615
|
+
}
|
|
2616
|
+
if (trimWhitespace) {
|
|
2617
|
+
result = result.trim();
|
|
2618
|
+
}
|
|
2619
|
+
if (escapeSpecialCharacters) {
|
|
2620
|
+
result = result.replace(/\\n/g, `
|
|
2621
|
+
`);
|
|
2622
|
+
}
|
|
2623
|
+
return result;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function alignValue(value, precedingText) {
|
|
2627
|
+
if (typeof value !== "string" || !value.includes(`
|
|
2628
|
+
`)) {
|
|
2629
|
+
return value;
|
|
2630
|
+
}
|
|
2631
|
+
const currentLine = precedingText.slice(precedingText.lastIndexOf(`
|
|
2632
|
+
`) + 1);
|
|
2633
|
+
const indentMatch = currentLine.match(/^(\s+)/);
|
|
2634
|
+
if (indentMatch) {
|
|
2635
|
+
const indent = indentMatch[1];
|
|
2636
|
+
return value.replace(/\n/g, `
|
|
2637
|
+
${indent}`);
|
|
2638
|
+
}
|
|
2639
|
+
return value;
|
|
2640
|
+
}
|
|
2641
|
+
var dedent, dedent_default;
|
|
2642
|
+
var init_dedent = __esm(() => {
|
|
2643
|
+
dedent = createDedent({});
|
|
2644
|
+
dedent_default = dedent;
|
|
2645
|
+
});
|
|
2646
|
+
|
|
2647
|
+
// packages/core/src/template/html.ts
|
|
2648
|
+
async function loadIndexHtml(clientDir) {
|
|
2649
|
+
const templatePath = join11(clientDir, "index.html");
|
|
2650
|
+
try {
|
|
2651
|
+
const content = await Bun.file(templatePath).text();
|
|
2652
|
+
return content;
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
console.error(`[reroute] Failed to load template from ${templatePath}:`, error);
|
|
2655
|
+
return `<!doctype html>
|
|
2656
|
+
<html lang="en">
|
|
2657
|
+
<head>
|
|
2658
|
+
<meta charset="UTF-8" />
|
|
2659
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2660
|
+
<title>Reroute</title>
|
|
2661
|
+
</head>
|
|
2662
|
+
<body>
|
|
2663
|
+
<div id="root"></div>
|
|
2664
|
+
</body>
|
|
2665
|
+
</html>`;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
function applyIndexTemplate(templateHtml, appHtml, {
|
|
2669
|
+
head = "",
|
|
2670
|
+
hydrationScript = "",
|
|
2671
|
+
lang = "en",
|
|
2672
|
+
appId = "root"
|
|
2673
|
+
}) {
|
|
2674
|
+
let html = templateHtml;
|
|
2675
|
+
html = html.replace(/<html([^>]*)>/i, (_m, attrs) => {
|
|
2676
|
+
const hasLang = /(^|\s)lang\s*=/.test(attrs);
|
|
2677
|
+
const newAttrs = hasLang ? attrs.replace(/lang\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, `lang="${lang}"`) : `${attrs} lang="${lang}"`;
|
|
2678
|
+
return `<html${newAttrs}>`;
|
|
2679
|
+
});
|
|
2680
|
+
let headToInject = head || "";
|
|
2681
|
+
try {
|
|
2682
|
+
const titleMatch = headToInject.match(/<title[\s\S]*?<\/title>/i);
|
|
2683
|
+
if (titleMatch) {
|
|
2684
|
+
const titleTag = titleMatch[0];
|
|
2685
|
+
if (/<title[\s\S]*?<\/title>/i.test(html)) {
|
|
2686
|
+
html = html.replace(/<title[\s\S]*?<\/title>/i, titleTag);
|
|
2687
|
+
} else {}
|
|
2688
|
+
headToInject = headToInject.replace(titleTag, "");
|
|
2689
|
+
}
|
|
2690
|
+
const descRe = /<meta\s+name\s*=\s*['"]description['"][^>]*>/i;
|
|
2691
|
+
const descMatch = headToInject.match(descRe);
|
|
2692
|
+
if (descMatch) {
|
|
2693
|
+
const descTag = descMatch[0];
|
|
2694
|
+
if (descRe.test(html)) {
|
|
2695
|
+
html = html.replace(descRe, descTag);
|
|
2696
|
+
}
|
|
2697
|
+
headToInject = headToInject.replace(descTag, "");
|
|
2698
|
+
}
|
|
2699
|
+
} catch {}
|
|
2700
|
+
if (headToInject) {
|
|
2701
|
+
html = html.replace(/<\/head>/i, `${headToInject}
|
|
2702
|
+
</head>`);
|
|
2703
|
+
}
|
|
2704
|
+
html = html.replace(/<script\b[^>]*src\s*=\s*["'][^"']+\.(ts|tsx)(?:[?#][^"']*)?["'][^>]*>\s*<\/script>/gi, "");
|
|
2705
|
+
const appIdEscaped = appId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2706
|
+
const rootDivRegex = new RegExp(`(<div[^>]*\\bid=(\\"|')${appIdEscaped}\\2[^>]*>)([\\s\\S]*?)(<\\/div>)`, "i");
|
|
2707
|
+
if (rootDivRegex.test(html)) {
|
|
2708
|
+
html = html.replace(rootDivRegex, `$1${appHtml}$4`);
|
|
2709
|
+
} else {
|
|
2710
|
+
html = html.replace(/<\/body>/i, ` <div id="${appId}">${appHtml}</div>
|
|
2711
|
+
</body>`);
|
|
2712
|
+
}
|
|
2713
|
+
if (hydrationScript) {
|
|
2714
|
+
html = html.replace(/<\/body>/i, ` ${hydrationScript}
|
|
2715
|
+
</body>`);
|
|
2716
|
+
}
|
|
2717
|
+
return html;
|
|
2718
|
+
}
|
|
2719
|
+
var init_html = () => {};
|
|
2720
|
+
|
|
2721
|
+
// packages/core/src/template/index.ts
|
|
2722
|
+
var init_template = __esm(() => {
|
|
2723
|
+
init_html();
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
// packages/core/src/ssr/render.ts
|
|
2727
|
+
import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
|
|
2728
|
+
import { pathToFileURL as pathToFileURL5 } from "node:url";
|
|
2729
|
+
import { cloneElement } from "react";
|
|
2730
|
+
import { renderToString } from "react-dom/server";
|
|
2731
|
+
async function renderSSRDocument(options) {
|
|
2732
|
+
const {
|
|
2733
|
+
pathname,
|
|
2734
|
+
rootComponent,
|
|
2735
|
+
clientDir,
|
|
2736
|
+
cwd,
|
|
2737
|
+
isWatchMode: isWatchMode2,
|
|
2738
|
+
bundleUrl,
|
|
2739
|
+
head = "",
|
|
2740
|
+
lang = "en",
|
|
2741
|
+
appId = "root",
|
|
2742
|
+
minify = false
|
|
2743
|
+
} = options;
|
|
2744
|
+
const scripts = [bundleUrl];
|
|
2745
|
+
let hydrationScript = "";
|
|
2746
|
+
let extraHead = "";
|
|
2747
|
+
let statusOverride;
|
|
2748
|
+
try {
|
|
2749
|
+
globalThis.__REROUTE_SSR_ACCESSED__ = {};
|
|
2750
|
+
} catch {}
|
|
2751
|
+
try {
|
|
2752
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2753
|
+
if (parts.length >= 2) {
|
|
2754
|
+
const collection = parts[0];
|
|
2755
|
+
const name = parts[1];
|
|
2756
|
+
let modulePath = "";
|
|
2757
|
+
let isContentCollection = false;
|
|
2758
|
+
try {
|
|
2759
|
+
const maybeDir = join11(clientDir, "routes", collection, "content");
|
|
2760
|
+
const s = await stat4(maybeDir);
|
|
2761
|
+
isContentCollection = typeof s?.isDirectory === "function" ? s.isDirectory() : true;
|
|
2762
|
+
} catch {
|
|
2763
|
+
isContentCollection = false;
|
|
2764
|
+
}
|
|
2765
|
+
if (!isContentCollection) {
|
|
2766
|
+
throw new Error("skip-content-preload");
|
|
2767
|
+
}
|
|
2768
|
+
try {
|
|
2769
|
+
const registryPath = join11(cwd, ".reroute", "content.ts");
|
|
2770
|
+
const reg = await import(pathToFileURL5(registryPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2771
|
+
const get = reg?.getContentEntry;
|
|
2772
|
+
const entry = typeof get === "function" ? get(collection, name) : undefined;
|
|
2773
|
+
const moduleUrl = entry?.module;
|
|
2774
|
+
if (moduleUrl?.endsWith(".js")) {
|
|
2775
|
+
modulePath = moduleUrl;
|
|
2776
|
+
}
|
|
2777
|
+
} catch {}
|
|
2778
|
+
if (!modulePath) {
|
|
2779
|
+
try {
|
|
2780
|
+
const chunkDir = join11(cwd, ".reroute", "chunks", collection);
|
|
2781
|
+
const files = await readdir5(chunkDir);
|
|
2782
|
+
const candidates = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
|
|
2783
|
+
if (candidates.length) {
|
|
2784
|
+
let latest = candidates[0];
|
|
2785
|
+
let latestM = 0;
|
|
2786
|
+
for (const candidateName of candidates) {
|
|
2787
|
+
try {
|
|
2788
|
+
const s = await stat4(join11(chunkDir, candidateName));
|
|
2789
|
+
if (s.mtimeMs >= latestM) {
|
|
2790
|
+
latestM = s.mtimeMs;
|
|
2791
|
+
latest = candidateName;
|
|
2792
|
+
}
|
|
2793
|
+
} catch {}
|
|
2794
|
+
}
|
|
2795
|
+
modulePath = `/${join11(".reroute", "chunks", collection, latest).replace(/\\+/g, "/")}`;
|
|
2796
|
+
}
|
|
2797
|
+
} catch {}
|
|
2798
|
+
}
|
|
2799
|
+
if (!modulePath) {
|
|
2800
|
+
const tsx = join11(clientDir, "routes", collection, "content", `${name}.tsx`);
|
|
2801
|
+
const ts = join11(clientDir, "routes", collection, "content", `${name}.ts`);
|
|
2802
|
+
let srcUrl = "";
|
|
2803
|
+
if (await Bun.file(tsx).exists()) {
|
|
2804
|
+
srcUrl = `/${join11("routes", collection, "content", `${name}.tsx`).replace(/\\+/g, "/")}`;
|
|
2805
|
+
} else if (await Bun.file(ts).exists()) {
|
|
2806
|
+
srcUrl = `/${join11("routes", collection, "content", `${name}.ts`).replace(/\\+/g, "/")}`;
|
|
2807
|
+
}
|
|
2808
|
+
if (srcUrl) {
|
|
2809
|
+
modulePath = srcUrl;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
if (!modulePath) {
|
|
2813
|
+
statusOverride = 404;
|
|
2814
|
+
}
|
|
2815
|
+
if (modulePath) {
|
|
2816
|
+
const key = `${collection}:${name}`;
|
|
2817
|
+
extraHead += `
|
|
2818
|
+
<link rel="modulepreload" href="${modulePath}" />`;
|
|
2819
|
+
const qs = `src=${encodeURIComponent(modulePath)}&key=${encodeURIComponent(key)}`;
|
|
2820
|
+
hydrationScript += `<script type="module" src="/__reroute_preload?${qs}"></script>`;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
} catch {}
|
|
2824
|
+
await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode2);
|
|
2825
|
+
const __SSR_DATA__ = {};
|
|
2826
|
+
try {
|
|
2827
|
+
try {
|
|
2828
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2829
|
+
if (parts.length >= 2) {
|
|
2830
|
+
const g = globalThis;
|
|
2831
|
+
const key = `${parts[0]}:${parts[1]}`;
|
|
2832
|
+
const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
|
|
2833
|
+
const dataFn = exp?.ssr?.data;
|
|
2834
|
+
if (typeof dataFn === "function") {
|
|
2835
|
+
const out = await dataFn({
|
|
2836
|
+
pathname,
|
|
2837
|
+
params: { name: parts[1] }
|
|
2838
|
+
});
|
|
2839
|
+
__SSR_DATA__[pathname] = out;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
} catch {}
|
|
2843
|
+
try {
|
|
2844
|
+
const routesPath = join11(cwd, ".reroute", "routes.ts");
|
|
2845
|
+
const m = await import(pathToFileURL5(routesPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2846
|
+
const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
|
|
2847
|
+
const r = match?.route;
|
|
2848
|
+
const params = match?.params || {};
|
|
2849
|
+
if (r && typeof r.path === "string") {
|
|
2850
|
+
try {
|
|
2851
|
+
const abs = join11(clientDir, "routes", String(r.path));
|
|
2852
|
+
const mod = await import(pathToFileURL5(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2853
|
+
const ssr = mod?.ssr;
|
|
2854
|
+
const dataFn = ssr?.data;
|
|
2855
|
+
if (typeof dataFn === "function") {
|
|
2856
|
+
const out = await dataFn({ pathname, params });
|
|
2857
|
+
__SSR_DATA__[pathname] = out;
|
|
2858
|
+
}
|
|
2859
|
+
} catch {}
|
|
2860
|
+
}
|
|
2861
|
+
} catch {}
|
|
2862
|
+
} catch {}
|
|
2863
|
+
try {
|
|
2864
|
+
globalThis.__REROUTE_DATA__ = __SSR_DATA__;
|
|
2865
|
+
} catch {}
|
|
2866
|
+
let __byCollectionForSSR = {};
|
|
2867
|
+
try {
|
|
2868
|
+
const p = join11(cwd, ".reroute", "content.ts");
|
|
2869
|
+
const mod = await import(pathToFileURL5(p).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2870
|
+
__byCollectionForSSR = mod?.byCollection || {};
|
|
2871
|
+
try {
|
|
2872
|
+
globalThis.__REROUTE_COLLECTIONS__ = __byCollectionForSSR;
|
|
2873
|
+
} catch {}
|
|
2874
|
+
} catch {}
|
|
2875
|
+
const componentWithPathname = cloneElement(rootComponent, {
|
|
2876
|
+
pathname
|
|
2877
|
+
});
|
|
2878
|
+
const appHtml = renderToString(componentWithPathname);
|
|
2879
|
+
const baseTemplate = await loadIndexHtml(clientDir);
|
|
2880
|
+
let inlineStyleTag = "";
|
|
2881
|
+
try {
|
|
2882
|
+
const candidates = [
|
|
2883
|
+
join11(clientDir, "..", ".reroute", "theme.css"),
|
|
2884
|
+
join11(clientDir, "..", "..", ".reroute", "theme.css"),
|
|
2885
|
+
join11(clientDir, "..", "..", "..", ".reroute", "theme.css"),
|
|
2886
|
+
join11(clientDir, "..", "..", "..", "..", ".reroute", "theme.css")
|
|
2887
|
+
];
|
|
2888
|
+
let cssPath = "";
|
|
2889
|
+
for (const p of candidates) {
|
|
2890
|
+
if (await Bun.file(p).exists()) {
|
|
2891
|
+
cssPath = p;
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
if (cssPath) {
|
|
2896
|
+
let css = await Bun.file(cssPath).text();
|
|
2897
|
+
if (minify) {
|
|
2898
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").replace(/;}/g, "}").trim();
|
|
2899
|
+
}
|
|
2900
|
+
inlineStyleTag = `<style data-reroute="tailwind">${css}</style>`;
|
|
2901
|
+
}
|
|
2902
|
+
} catch {}
|
|
2903
|
+
try {
|
|
2904
|
+
const g = globalThis;
|
|
2905
|
+
const acc = g.__REROUTE_SSR_ACCESSED__;
|
|
2906
|
+
const sortByDate = (order) => (a, b) => {
|
|
2907
|
+
const da = a?.meta?.date ? Date.parse(String(a.meta.date)) : 0;
|
|
2908
|
+
const db = b?.meta?.date ? Date.parse(String(b.meta.date)) : 0;
|
|
2909
|
+
return order === "asc" ? da - db : db - da;
|
|
2910
|
+
};
|
|
2911
|
+
const usage = {};
|
|
2912
|
+
if (acc && typeof acc.forEach === "function") {
|
|
2913
|
+
acc.forEach((c) => {
|
|
2914
|
+
if (typeof c === "string")
|
|
2915
|
+
usage[c] = { limit: Number.POSITIVE_INFINITY, sort: "custom" };
|
|
2916
|
+
});
|
|
2917
|
+
} else if (acc && typeof acc === "object") {
|
|
2918
|
+
for (const [k, v] of Object.entries(acc)) {
|
|
2919
|
+
const lim = typeof v?.limit === "number" ? v.limit : Number.POSITIVE_INFINITY;
|
|
2920
|
+
const s = typeof v?.sort === "string" ? v.sort : "custom";
|
|
2921
|
+
usage[k] = { limit: lim, sort: s };
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
const collections = Object.keys(usage);
|
|
2925
|
+
if (collections.length) {
|
|
2926
|
+
const subset = {};
|
|
2927
|
+
const partial = {};
|
|
2928
|
+
for (const c of collections) {
|
|
2929
|
+
const conf = usage[c];
|
|
2930
|
+
const full = __byCollectionForSSR?.[c] || [];
|
|
2931
|
+
let arr = full;
|
|
2932
|
+
if (Number.isFinite(conf.limit) && conf.limit > 0 && conf.sort !== "custom") {
|
|
2933
|
+
if (conf.sort === "date-desc") {
|
|
2934
|
+
arr = [...full].sort(sortByDate("desc")).slice(0, conf.limit);
|
|
2935
|
+
} else if (conf.sort === "date-asc") {
|
|
2936
|
+
arr = [...full].sort(sortByDate("asc")).slice(0, conf.limit);
|
|
2937
|
+
} else if (conf.sort === "none") {
|
|
2938
|
+
arr = full.slice(0, conf.limit);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
subset[c] = arr;
|
|
2942
|
+
partial[c] = Array.isArray(arr) && Array.isArray(full) ? arr.length < full.length : false;
|
|
2943
|
+
}
|
|
2944
|
+
const subsetJson = JSON.stringify(subset);
|
|
2945
|
+
const partialJson = JSON.stringify(partial);
|
|
2946
|
+
hydrationScript += `
|
|
2947
|
+
<script>
|
|
2948
|
+
(function(){ try {
|
|
2949
|
+
var w = (typeof window!== 'undefined'? window : globalThis);
|
|
2950
|
+
w.__REROUTE_COLLECTIONS__ = w.__REROUTE_COLLECTIONS__ || {};
|
|
2951
|
+
Object.assign(w.__REROUTE_COLLECTIONS__, ${subsetJson});
|
|
2952
|
+
w.__REROUTE_COLLECTIONS_PARTIAL__ = w.__REROUTE_COLLECTIONS_PARTIAL__ || {};
|
|
2953
|
+
Object.assign(w.__REROUTE_COLLECTIONS_PARTIAL__, ${partialJson});
|
|
2954
|
+
} catch(e){} })();
|
|
2955
|
+
</script>`;
|
|
2956
|
+
}
|
|
2957
|
+
} catch {}
|
|
2958
|
+
try {
|
|
2959
|
+
const seededJson = JSON.stringify(__SSR_DATA__);
|
|
2960
|
+
hydrationScript += `
|
|
2961
|
+
<script>
|
|
2962
|
+
(function(){ try {
|
|
2963
|
+
var w = (typeof window!== 'undefined'? window : globalThis);
|
|
2964
|
+
w.__REROUTE_DATA__ = Object.assign({}, w.__REROUTE_DATA__ || {}, ${seededJson});
|
|
2965
|
+
} catch(e){} })();
|
|
2966
|
+
</script>`;
|
|
2967
|
+
} catch {}
|
|
2968
|
+
hydrationScript += scripts.map((src) => `<script type="module" src="${src}"></script>`).join("");
|
|
2969
|
+
if (isWatchMode2) {
|
|
2970
|
+
hydrationScript += `<script type="module" src="/__reroute_watch.js"></script>`;
|
|
2971
|
+
}
|
|
2972
|
+
let perPageHead = "";
|
|
2973
|
+
let pageLang;
|
|
2974
|
+
try {
|
|
2975
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2976
|
+
if (parts.length >= 2) {
|
|
2977
|
+
const key = `${parts[0]}:${parts[1]}`;
|
|
2978
|
+
const g = globalThis;
|
|
2979
|
+
const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
|
|
2980
|
+
const meta = exp?.meta;
|
|
2981
|
+
const ssr = exp?.ssr;
|
|
2982
|
+
perPageHead += buildHeadFromMeta(meta);
|
|
2983
|
+
const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
|
|
2984
|
+
`)) : "";
|
|
2985
|
+
if (ssrHead)
|
|
2986
|
+
perPageHead += `
|
|
2987
|
+
${ssrHead}`;
|
|
2988
|
+
if (typeof ssr?.lang === "string" && ssr.lang.trim()) {
|
|
2989
|
+
pageLang = ssr.lang.trim();
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
} catch {}
|
|
2993
|
+
try {
|
|
2994
|
+
const routesPath = join11(cwd, ".reroute", "routes.ts");
|
|
2995
|
+
const m = await import(pathToFileURL5(routesPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
2996
|
+
const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
|
|
2997
|
+
const r = match?.route;
|
|
2998
|
+
if (!r) {
|
|
2999
|
+
statusOverride = statusOverride || 404;
|
|
3000
|
+
}
|
|
3001
|
+
if (r && typeof r.path === "string") {
|
|
3002
|
+
try {
|
|
3003
|
+
const abs = join11(clientDir, "routes", String(r.path));
|
|
3004
|
+
const mod = await import(pathToFileURL5(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
3005
|
+
const meta = mod?.meta;
|
|
3006
|
+
const ssr = mod?.ssr;
|
|
3007
|
+
if (meta)
|
|
3008
|
+
perPageHead += buildHeadFromMeta(meta);
|
|
3009
|
+
if (ssr) {
|
|
3010
|
+
const ssrHead = typeof ssr.head === "string" ? ssr.head : Array.isArray(ssr.head) ? String(ssr.head.join(`
|
|
3011
|
+
`)) : "";
|
|
3012
|
+
if (ssrHead)
|
|
3013
|
+
perPageHead += `
|
|
3014
|
+
${ssrHead}`;
|
|
3015
|
+
if (typeof ssr.lang === "string" && ssr.lang.trim()) {
|
|
3016
|
+
pageLang = ssr.lang.trim();
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
} catch {}
|
|
3020
|
+
} else {
|
|
3021
|
+
try {
|
|
3022
|
+
const list = m?.notFoundRoutes;
|
|
3023
|
+
if (Array.isArray(list)) {
|
|
3024
|
+
let chosen;
|
|
3025
|
+
let bestLen = -1;
|
|
3026
|
+
for (const nf of list) {
|
|
3027
|
+
const pat = String(nf?.pattern || "/");
|
|
3028
|
+
if (!pathname.startsWith(pat))
|
|
3029
|
+
continue;
|
|
3030
|
+
const len = pat.split("/").filter(Boolean).length;
|
|
3031
|
+
if (len >= bestLen) {
|
|
3032
|
+
bestLen = len;
|
|
3033
|
+
chosen = nf;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
if (chosen && typeof chosen.path === "string") {
|
|
3037
|
+
try {
|
|
3038
|
+
const abs = join11(clientDir, "routes", String(chosen.path));
|
|
3039
|
+
const mod = await import(pathToFileURL5(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
|
|
3040
|
+
const meta = mod?.meta;
|
|
3041
|
+
const ssr = mod?.ssr;
|
|
3042
|
+
if (meta)
|
|
3043
|
+
perPageHead += buildHeadFromMeta(meta);
|
|
3044
|
+
const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
|
|
3045
|
+
`)) : "";
|
|
3046
|
+
if (ssrHead)
|
|
3047
|
+
perPageHead += `
|
|
3048
|
+
${ssrHead}`;
|
|
3049
|
+
if (typeof ssr?.lang === "string" && ssr.lang?.trim()) {
|
|
3050
|
+
pageLang = String(ssr.lang).trim();
|
|
3051
|
+
}
|
|
3052
|
+
} catch {}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
} catch {}
|
|
3056
|
+
}
|
|
3057
|
+
} catch {}
|
|
3058
|
+
const combinedHead = dedent_default([dedent_default(head) || "", dedent_default(extraHead), dedent_default(perPageHead)].filter(Boolean).join(`
|
|
3059
|
+
`));
|
|
3060
|
+
const html2 = applyIndexTemplate(baseTemplate, appHtml, {
|
|
3061
|
+
head: [inlineStyleTag, combinedHead].filter(Boolean).join(`
|
|
3062
|
+
`),
|
|
3063
|
+
hydrationScript,
|
|
3064
|
+
lang: pageLang || lang,
|
|
3065
|
+
appId
|
|
3066
|
+
});
|
|
3067
|
+
return {
|
|
3068
|
+
html: html2,
|
|
3069
|
+
status: statusOverride || 200
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
var init_render = __esm(() => {
|
|
3073
|
+
init_dedent();
|
|
3074
|
+
init_metadata();
|
|
3075
|
+
init_template();
|
|
3076
|
+
init_seed();
|
|
3077
|
+
});
|
|
3078
|
+
|
|
3079
|
+
// packages/core/src/ssr/index.ts
|
|
3080
|
+
var init_ssr = __esm(() => {
|
|
3081
|
+
init_data();
|
|
3082
|
+
init_render();
|
|
3083
|
+
init_seed();
|
|
3084
|
+
});
|
|
3085
|
+
// packages/core/src/utils/cache.ts
|
|
3086
|
+
class LRUCache {
|
|
3087
|
+
cache;
|
|
3088
|
+
maxSize;
|
|
3089
|
+
constructor(maxSize = 100) {
|
|
3090
|
+
this.cache = new Map;
|
|
3091
|
+
this.maxSize = maxSize;
|
|
3092
|
+
}
|
|
3093
|
+
get(key) {
|
|
3094
|
+
if (!this.cache.has(key)) {
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
const value = this.cache.get(key);
|
|
3098
|
+
this.cache.delete(key);
|
|
3099
|
+
this.cache.set(key, value);
|
|
3100
|
+
return value;
|
|
3101
|
+
}
|
|
3102
|
+
set(key, value) {
|
|
3103
|
+
if (this.cache.has(key)) {
|
|
3104
|
+
this.cache.delete(key);
|
|
3105
|
+
}
|
|
3106
|
+
this.cache.set(key, value);
|
|
3107
|
+
if (this.cache.size > this.maxSize) {
|
|
3108
|
+
const firstKey = this.cache.keys().next().value;
|
|
3109
|
+
this.cache.delete(firstKey);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
has(key) {
|
|
3113
|
+
return this.cache.has(key);
|
|
3114
|
+
}
|
|
3115
|
+
clear() {
|
|
3116
|
+
this.cache.clear();
|
|
3117
|
+
}
|
|
3118
|
+
get size() {
|
|
3119
|
+
return this.cache.size;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
var init_cache = () => {};
|
|
3123
|
+
|
|
3124
|
+
// packages/core/src/utils/mime.ts
|
|
3125
|
+
function getMimeType(filePath) {
|
|
3126
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3127
|
+
const mimeTypes = {
|
|
3128
|
+
html: "text/html",
|
|
3129
|
+
css: "text/css",
|
|
3130
|
+
js: "application/javascript",
|
|
3131
|
+
ts: "application/javascript",
|
|
3132
|
+
json: "application/json",
|
|
3133
|
+
png: "image/png",
|
|
3134
|
+
jpg: "image/jpeg",
|
|
3135
|
+
jpeg: "image/jpeg",
|
|
3136
|
+
gif: "image/gif",
|
|
3137
|
+
svg: "image/svg+xml",
|
|
3138
|
+
ico: "image/x-icon",
|
|
3139
|
+
woff: "font/woff",
|
|
3140
|
+
woff2: "font/woff2",
|
|
3141
|
+
ttf: "font/ttf"
|
|
3142
|
+
};
|
|
3143
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
3144
|
+
}
|
|
3145
|
+
function isCompressible(contentType) {
|
|
3146
|
+
if (!contentType)
|
|
3147
|
+
return false;
|
|
3148
|
+
const ct = contentType.split(";")[0].trim();
|
|
3149
|
+
return ct.startsWith("text/") || ct === "application/javascript" || ct === "application/json" || ct === "application/xml" || ct === "image/svg+xml";
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// packages/core/src/utils/compression.ts
|
|
3153
|
+
function acceptsGzip(acceptEncoding) {
|
|
3154
|
+
return Boolean(acceptEncoding && /gzip/i.test(acceptEncoding));
|
|
3155
|
+
}
|
|
3156
|
+
function toBytes(input) {
|
|
3157
|
+
return typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
3158
|
+
}
|
|
3159
|
+
function gzipIfAccepted(body, contentType, acceptEncoding) {
|
|
3160
|
+
const extraHeaders = {};
|
|
3161
|
+
if (acceptsGzip(acceptEncoding) && isCompressible(contentType)) {
|
|
3162
|
+
try {
|
|
3163
|
+
const compressed = Bun.gzipSync(toBytes(body));
|
|
3164
|
+
extraHeaders["Content-Encoding"] = "gzip";
|
|
3165
|
+
extraHeaders.Vary = "Accept-Encoding";
|
|
3166
|
+
return { body: compressed, extraHeaders };
|
|
3167
|
+
} catch {}
|
|
3168
|
+
}
|
|
3169
|
+
return { body, extraHeaders };
|
|
3170
|
+
}
|
|
3171
|
+
var init_compression = () => {};
|
|
3172
|
+
|
|
3173
|
+
// packages/core/src/utils/index.ts
|
|
3174
|
+
var init_utils = __esm(() => {
|
|
3175
|
+
init_cache();
|
|
3176
|
+
init_compression();
|
|
3177
|
+
});
|
|
3178
|
+
|
|
3179
|
+
// packages/core/src/index.ts
|
|
3180
|
+
var init_src = __esm(() => {
|
|
3181
|
+
init_bundler();
|
|
3182
|
+
init_content();
|
|
3183
|
+
init_ssr();
|
|
3184
|
+
init_template();
|
|
3185
|
+
init_utils();
|
|
3186
|
+
});
|
|
3187
|
+
|
|
3188
|
+
// packages/core/index.ts
|
|
3189
|
+
var exports_core = {};
|
|
3190
|
+
__export(exports_core, {
|
|
3191
|
+
transpileFile: () => transpileFile,
|
|
3192
|
+
toBytes: () => toBytes,
|
|
3193
|
+
stripStart: () => stripStart,
|
|
3194
|
+
stripEnd: () => stripEnd,
|
|
3195
|
+
seedSSRModuleForPath: () => seedSSRModuleForPath,
|
|
3196
|
+
renderSSRDocument: () => renderSSRDocument,
|
|
3197
|
+
loadIndexHtml: () => loadIndexHtml,
|
|
3198
|
+
listContentFiles: () => listContentFiles2,
|
|
3199
|
+
join: () => join11,
|
|
3200
|
+
isCompressible: () => isCompressible,
|
|
3201
|
+
gzipIfAccepted: () => gzipIfAccepted,
|
|
3202
|
+
getMimeType: () => getMimeType,
|
|
3203
|
+
getContentMeta: () => getContentMeta,
|
|
3204
|
+
getBundleUrlsFor: () => getBundleUrlsFor,
|
|
3205
|
+
generateContentRegistry: () => generateContentRegistry,
|
|
3206
|
+
generateContentHash: () => generateContentHash,
|
|
3207
|
+
extname: () => extname2,
|
|
3208
|
+
discoverCollections: () => discoverCollections,
|
|
3209
|
+
computeSSRDataForPath: () => computeSSRDataForPath,
|
|
3210
|
+
buildHeadFromMeta: () => buildHeadFromMeta,
|
|
3211
|
+
buildContentDTOs: () => buildContentDTOs,
|
|
3212
|
+
basename: () => basename2,
|
|
3213
|
+
applyIndexTemplate: () => applyIndexTemplate,
|
|
3214
|
+
acceptsGzip: () => acceptsGzip,
|
|
3215
|
+
LRUCache: () => LRUCache
|
|
3216
|
+
});
|
|
3217
|
+
var init_core = __esm(() => {
|
|
3218
|
+
init_src();
|
|
3219
|
+
});
|
|
3220
|
+
|
|
3221
|
+
// packages/cli/src/commands/analyze.ts
|
|
3222
|
+
var exports_analyze = {};
|
|
3223
|
+
__export(exports_analyze, {
|
|
3224
|
+
default: () => analyze
|
|
3225
|
+
});
|
|
3226
|
+
import { existsSync as existsSync9, statSync } from "node:fs";
|
|
3227
|
+
import { readdir as readdir6, readFile as readFile2, stat as stat5, writeFile as writeFile4 } from "node:fs/promises";
|
|
3228
|
+
import { basename as basename3, join as join12, relative } from "node:path";
|
|
3229
|
+
import Table from "cli-table3";
|
|
3230
|
+
async function findBundles(cwd) {
|
|
3231
|
+
const bundles = [];
|
|
3232
|
+
const chunksDir = join12(cwd, ".reroute", "chunks");
|
|
3233
|
+
if (existsSync9(chunksDir)) {
|
|
3234
|
+
const collections = await readdir6(chunksDir, { withFileTypes: true });
|
|
3235
|
+
for (const collection of collections) {
|
|
3236
|
+
if (!collection.isDirectory())
|
|
3237
|
+
continue;
|
|
3238
|
+
const collectionDir = join12(chunksDir, collection.name);
|
|
3239
|
+
const files = await readdir6(collectionDir);
|
|
3240
|
+
for (const file of files) {
|
|
3241
|
+
if (!file.endsWith(".js"))
|
|
3242
|
+
continue;
|
|
3243
|
+
const filePath = join12(collectionDir, file);
|
|
3244
|
+
const stats = await stat5(filePath);
|
|
3245
|
+
const content2 = await readFile2(filePath);
|
|
3246
|
+
const gzippedSize = Bun.gzipSync(content2).length;
|
|
3247
|
+
bundles.push({
|
|
3248
|
+
path: filePath,
|
|
3249
|
+
name: file,
|
|
3250
|
+
size: stats.size,
|
|
3251
|
+
gzippedSize,
|
|
3252
|
+
relativePath: relative(cwd, filePath),
|
|
3253
|
+
type: "chunk"
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
const distDir = join12(cwd, "dist");
|
|
3259
|
+
if (existsSync9(distDir)) {
|
|
3260
|
+
const files = await readdir6(distDir);
|
|
3261
|
+
for (const file of files) {
|
|
3262
|
+
const filePath = join12(distDir, file);
|
|
3263
|
+
const stats = statSync(filePath);
|
|
3264
|
+
if (stats.isFile() && !file.endsWith(".map") && !file.endsWith(".d.ts")) {
|
|
3265
|
+
const content2 = await readFile2(filePath);
|
|
3266
|
+
const gzippedSize = Bun.gzipSync(content2).length;
|
|
3267
|
+
bundles.push({
|
|
3268
|
+
path: filePath,
|
|
3269
|
+
name: file,
|
|
3270
|
+
size: stats.size,
|
|
3271
|
+
gzippedSize,
|
|
3272
|
+
relativePath: relative(cwd, filePath),
|
|
3273
|
+
type: "binary"
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
return bundles;
|
|
3279
|
+
}
|
|
3280
|
+
async function findBrowserEntry(cwd) {
|
|
3281
|
+
try {
|
|
3282
|
+
const clientDir = join12(cwd, "src", "client");
|
|
3283
|
+
const tsxEntry = join12(clientDir, "index.tsx");
|
|
3284
|
+
const tsEntry = join12(clientDir, "index.ts");
|
|
3285
|
+
let sourceEntry = null;
|
|
3286
|
+
if (existsSync9(tsxEntry)) {
|
|
3287
|
+
sourceEntry = tsxEntry;
|
|
3288
|
+
} else if (existsSync9(tsEntry)) {
|
|
3289
|
+
sourceEntry = tsEntry;
|
|
3290
|
+
}
|
|
3291
|
+
if (!sourceEntry)
|
|
3292
|
+
return null;
|
|
3293
|
+
const { generateContentHash: generateContentHash2 } = await Promise.resolve().then(() => (init_core(), exports_core));
|
|
3294
|
+
const isProd = isProductionMode();
|
|
3295
|
+
const result = await Bun.build({
|
|
3296
|
+
entrypoints: [sourceEntry],
|
|
3297
|
+
target: "browser",
|
|
3298
|
+
format: "esm",
|
|
3299
|
+
minify: isProd,
|
|
3300
|
+
sourcemap: "linked",
|
|
3301
|
+
external: [],
|
|
3302
|
+
define: {
|
|
3303
|
+
"process.env.NODE_ENV": isProd ? '"production"' : '"development"'
|
|
3304
|
+
}
|
|
3305
|
+
});
|
|
3306
|
+
if (!result.success) {
|
|
3307
|
+
if (result.logs && result.logs.length > 0) {
|
|
3308
|
+
console.warn("[reroute/analyze] Browser build errors:", result.logs.map((log2) => log2.message).join(`
|
|
3309
|
+
`));
|
|
3310
|
+
}
|
|
3311
|
+
return null;
|
|
3312
|
+
}
|
|
3313
|
+
if (result.outputs.length === 0) {
|
|
3314
|
+
console.warn("[reroute/analyze] Browser build produced no outputs");
|
|
3315
|
+
return null;
|
|
3316
|
+
}
|
|
3317
|
+
const tempDir = join12(cwd, ".reroute", "browser");
|
|
3318
|
+
await import("node:fs/promises").then((fs) => fs.mkdir(tempDir, { recursive: true }));
|
|
3319
|
+
const output = result.outputs[0];
|
|
3320
|
+
const code = await output.text();
|
|
3321
|
+
const actualHash = await generateContentHash2(code);
|
|
3322
|
+
const bundlePath = join12(tempDir, `index.${actualHash}.js`);
|
|
3323
|
+
await writeFile4(bundlePath, code, "utf-8");
|
|
3324
|
+
const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
|
|
3325
|
+
if (mapOutput) {
|
|
3326
|
+
const mapContent = await mapOutput.text();
|
|
3327
|
+
await writeFile4(`${bundlePath}.map`, mapContent, "utf-8");
|
|
3328
|
+
}
|
|
3329
|
+
return bundlePath;
|
|
3330
|
+
} catch (error) {
|
|
3331
|
+
console.warn("[reroute/analyze] Could not build browser entry:", error);
|
|
3332
|
+
return null;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
function formatBytes(bytes) {
|
|
3336
|
+
if (bytes === 0)
|
|
3337
|
+
return "0 B";
|
|
3338
|
+
const k = 1024;
|
|
3339
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
3340
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3341
|
+
return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`;
|
|
3342
|
+
}
|
|
3343
|
+
function calculateStats(bundles) {
|
|
3344
|
+
const byCollection = {};
|
|
3345
|
+
let totalSize = 0;
|
|
3346
|
+
let totalGzippedSize = 0;
|
|
3347
|
+
for (const bundle of bundles) {
|
|
3348
|
+
totalSize += bundle.size;
|
|
3349
|
+
totalGzippedSize += bundle.gzippedSize;
|
|
3350
|
+
if (bundle.type === "chunk") {
|
|
3351
|
+
const match = bundle.relativePath.match(/\.reroute\/chunks\/([^/]+)/);
|
|
3352
|
+
const collection = match ? match[1] : "unknown";
|
|
3353
|
+
if (!byCollection[collection]) {
|
|
3354
|
+
byCollection[collection] = [];
|
|
3355
|
+
}
|
|
3356
|
+
byCollection[collection].push(bundle);
|
|
3357
|
+
} else {
|
|
3358
|
+
if (!byCollection.binaries) {
|
|
3359
|
+
byCollection.binaries = [];
|
|
3360
|
+
}
|
|
3361
|
+
byCollection.binaries.push(bundle);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
return {
|
|
3365
|
+
totalBundles: bundles.length,
|
|
3366
|
+
totalSize,
|
|
3367
|
+
totalGzippedSize,
|
|
3368
|
+
bundles,
|
|
3369
|
+
byCollection
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
async function getInstalledPackageSizes(cwd) {
|
|
3373
|
+
const packageSizes = new Map;
|
|
3374
|
+
const nodeModulesDir = join12(cwd, "node_modules");
|
|
3375
|
+
if (!existsSync9(nodeModulesDir))
|
|
3376
|
+
return packageSizes;
|
|
3377
|
+
try {
|
|
3378
|
+
const { execSync } = await import("node:child_process");
|
|
3379
|
+
const output = execSync(`du -sk ${nodeModulesDir}/* 2>/dev/null || true`, {
|
|
3380
|
+
encoding: "utf8",
|
|
3381
|
+
maxBuffer: 10485760
|
|
3382
|
+
});
|
|
3383
|
+
const lines = output.trim().split(`
|
|
3384
|
+
`);
|
|
3385
|
+
for (const line of lines) {
|
|
3386
|
+
const [sizeKB, path3] = line.split("\t");
|
|
3387
|
+
if (!path3)
|
|
3388
|
+
continue;
|
|
3389
|
+
const pkgName = basename3(path3);
|
|
3390
|
+
if (pkgName.startsWith("@")) {
|
|
3391
|
+
const scopeDir = path3;
|
|
3392
|
+
const scopedOutput = execSync(`du -sk ${scopeDir}/* 2>/dev/null || true`, {
|
|
3393
|
+
encoding: "utf8"
|
|
3394
|
+
});
|
|
3395
|
+
const scopedLines = scopedOutput.trim().split(`
|
|
3396
|
+
`);
|
|
3397
|
+
for (const scopedLine of scopedLines) {
|
|
3398
|
+
const [scopedSizeKB, scopedPath] = scopedLine.split("\t");
|
|
3399
|
+
if (!scopedPath)
|
|
3400
|
+
continue;
|
|
3401
|
+
const fullPkgName = `${pkgName}/${basename3(scopedPath)}`;
|
|
3402
|
+
packageSizes.set(fullPkgName, Number.parseInt(scopedSizeKB, 10) * 1024);
|
|
3403
|
+
}
|
|
3404
|
+
} else {
|
|
3405
|
+
packageSizes.set(pkgName, Number.parseInt(sizeKB, 10) * 1024);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
} catch (_error) {}
|
|
3409
|
+
return packageSizes;
|
|
3410
|
+
}
|
|
3411
|
+
async function buildDependencyTree(entryPath, cwd) {
|
|
3412
|
+
try {
|
|
3413
|
+
if (!existsSync9(entryPath))
|
|
3414
|
+
return null;
|
|
3415
|
+
const root = {
|
|
3416
|
+
name: basename3(entryPath),
|
|
3417
|
+
path: entryPath,
|
|
3418
|
+
size: 0,
|
|
3419
|
+
children: []
|
|
3420
|
+
};
|
|
3421
|
+
const fromMap = await buildTreeFromSourceMap(entryPath, cwd);
|
|
3422
|
+
if (fromMap)
|
|
3423
|
+
return fromMap;
|
|
3424
|
+
try {
|
|
3425
|
+
let place = function(filePath, size) {
|
|
3426
|
+
if (filePath.includes("node_modules")) {
|
|
3427
|
+
let pkgName = filePath.slice(filePath.indexOf("node_modules") + "node_modules".length + 1);
|
|
3428
|
+
pkgName = pkgName.startsWith("@") ? pkgName.split("/").slice(0, 2).join("/") : pkgName.split("/")[0];
|
|
3429
|
+
let pkgNode = nodeModulesNode.children?.find((c) => c.name === pkgName);
|
|
3430
|
+
if (!pkgNode) {
|
|
3431
|
+
pkgNode = {
|
|
3432
|
+
name: pkgName || "unknown",
|
|
3433
|
+
path: join12(cwd, "node_modules", pkgName || ""),
|
|
3434
|
+
size: 0,
|
|
3435
|
+
children: []
|
|
3436
|
+
};
|
|
3437
|
+
nodeModulesNode.children?.push(pkgNode);
|
|
3438
|
+
}
|
|
3439
|
+
pkgNode.children?.push({
|
|
3440
|
+
name: basename3(filePath),
|
|
3441
|
+
path: filePath,
|
|
3442
|
+
size
|
|
3443
|
+
});
|
|
3444
|
+
} else {
|
|
3445
|
+
const rel = relative(cwd, filePath);
|
|
3446
|
+
const parts = rel.split("/").filter(Boolean);
|
|
3447
|
+
let current = srcNode;
|
|
3448
|
+
for (let i = 0;i < parts.length - 1; i++) {
|
|
3449
|
+
const part = parts[i];
|
|
3450
|
+
let child = current.children?.find((c) => c.name === part);
|
|
3451
|
+
if (!child) {
|
|
3452
|
+
child = {
|
|
3453
|
+
name: part,
|
|
3454
|
+
path: join12(cwd, ...parts.slice(0, i + 1)),
|
|
3455
|
+
size: 0,
|
|
3456
|
+
children: []
|
|
3457
|
+
};
|
|
3458
|
+
current.children?.push(child);
|
|
3459
|
+
}
|
|
3460
|
+
current = child;
|
|
3461
|
+
}
|
|
3462
|
+
current.children?.push({
|
|
3463
|
+
name: parts[parts.length - 1] || basename3(filePath),
|
|
3464
|
+
path: filePath,
|
|
3465
|
+
size
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
}, compute = function(node) {
|
|
3469
|
+
if (node.children && node.children.length > 0) {
|
|
3470
|
+
node.size = node.children.reduce((s, c) => s + compute(c), 0);
|
|
3471
|
+
}
|
|
3472
|
+
return node.size;
|
|
3473
|
+
};
|
|
3474
|
+
const content2 = await readFile2(entryPath, "utf-8");
|
|
3475
|
+
const modulePattern = /\/\/\s*(.+?\.(?:ts|tsx|js|jsx|mjs|cjs))\s*$/gm;
|
|
3476
|
+
const nodeModulesNode = {
|
|
3477
|
+
name: "node_modules",
|
|
3478
|
+
path: join12(cwd, "node_modules"),
|
|
3479
|
+
size: 0,
|
|
3480
|
+
children: []
|
|
3481
|
+
};
|
|
3482
|
+
const srcNode = {
|
|
3483
|
+
name: "src",
|
|
3484
|
+
path: cwd,
|
|
3485
|
+
size: 0,
|
|
3486
|
+
children: []
|
|
3487
|
+
};
|
|
3488
|
+
root.children?.push(nodeModulesNode, srcNode);
|
|
3489
|
+
let match;
|
|
3490
|
+
let lastPath = "";
|
|
3491
|
+
let lastPos = 0;
|
|
3492
|
+
match = modulePattern.exec(content2);
|
|
3493
|
+
while (match !== null) {
|
|
3494
|
+
if (lastPath) {
|
|
3495
|
+
const size = match.index - lastPos;
|
|
3496
|
+
if (size > 0)
|
|
3497
|
+
place(lastPath, size);
|
|
3498
|
+
}
|
|
3499
|
+
lastPath = match[1];
|
|
3500
|
+
lastPos = match.index + match[0].length;
|
|
3501
|
+
match = modulePattern.exec(content2);
|
|
3502
|
+
}
|
|
3503
|
+
if (lastPath) {
|
|
3504
|
+
const size = content2.length - lastPos;
|
|
3505
|
+
if (size > 0)
|
|
3506
|
+
place(lastPath, size);
|
|
3507
|
+
}
|
|
3508
|
+
compute(root);
|
|
3509
|
+
return {
|
|
3510
|
+
entry: entryPath,
|
|
3511
|
+
totalSize: root.size,
|
|
3512
|
+
root
|
|
3513
|
+
};
|
|
3514
|
+
} catch {
|
|
3515
|
+
return null;
|
|
3516
|
+
}
|
|
3517
|
+
} catch {
|
|
3518
|
+
return null;
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
async function buildTreeFromSourceMap(bundlePath, cwd) {
|
|
3522
|
+
try {
|
|
3523
|
+
let decodeVLQ = function(str, indexRef) {
|
|
3524
|
+
let result = 0;
|
|
3525
|
+
let shift = 0;
|
|
3526
|
+
let continuation;
|
|
3527
|
+
do {
|
|
3528
|
+
const ch = str[indexRef.i++];
|
|
3529
|
+
if (ch == null)
|
|
3530
|
+
return 0;
|
|
3531
|
+
const val = charToInt.get(ch) ?? 0;
|
|
3532
|
+
continuation = (val & 32) !== 0;
|
|
3533
|
+
const digit = val & 31;
|
|
3534
|
+
result += digit << shift;
|
|
3535
|
+
shift += 5;
|
|
3536
|
+
} while (continuation);
|
|
3537
|
+
const negate = result & 1;
|
|
3538
|
+
result >>= 1;
|
|
3539
|
+
return negate ? -result : result;
|
|
3540
|
+
}, absFor = function(sourcePath) {
|
|
3541
|
+
if (sourcePath.startsWith("/") || sourcePath.match(/^[A-Za-z]:\\/))
|
|
3542
|
+
return sourcePath;
|
|
3543
|
+
const bundleDir = bundlePath.substring(0, bundlePath.lastIndexOf("/"));
|
|
3544
|
+
return join12(bundleDir, sourceRoot || "", sourcePath);
|
|
3545
|
+
}, computeSizes = function(node) {
|
|
3546
|
+
if (node.children && node.children.length > 0) {
|
|
3547
|
+
node.size = node.children.reduce((s, c) => s + computeSizes(c), 0);
|
|
3548
|
+
}
|
|
3549
|
+
return node.size;
|
|
3550
|
+
};
|
|
3551
|
+
const mapPath = `${bundlePath}.map`;
|
|
3552
|
+
if (!existsSync9(mapPath))
|
|
3553
|
+
return null;
|
|
3554
|
+
const [bundle, mapJson] = await Promise.all([
|
|
3555
|
+
readFile2(bundlePath, "utf-8"),
|
|
3556
|
+
readFile2(mapPath, "utf-8")
|
|
3557
|
+
]);
|
|
3558
|
+
const map = JSON.parse(mapJson);
|
|
3559
|
+
const sourceRoot = map.sourceRoot || "";
|
|
3560
|
+
const genLines = bundle.split(`
|
|
3561
|
+
`).map((l) => l.length);
|
|
3562
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3563
|
+
const charToInt = new Map;
|
|
3564
|
+
for (let i = 0;i < chars.length; i++)
|
|
3565
|
+
charToInt.set(chars[i], i);
|
|
3566
|
+
const perSource = new Array(map.sources.length).fill(0);
|
|
3567
|
+
const lines = map.mappings.split(";");
|
|
3568
|
+
for (let line = 0;line < lines.length; line++) {
|
|
3569
|
+
const segs = lines[line];
|
|
3570
|
+
if (!segs)
|
|
3571
|
+
continue;
|
|
3572
|
+
let genCol = 0;
|
|
3573
|
+
let src2 = 0;
|
|
3574
|
+
let _srcLine = 0;
|
|
3575
|
+
let _srcCol = 0;
|
|
3576
|
+
const decoded = [];
|
|
3577
|
+
const idx = { i: 0 };
|
|
3578
|
+
while (idx.i < segs.length) {
|
|
3579
|
+
const start2 = idx.i;
|
|
3580
|
+
genCol += decodeVLQ(segs, idx);
|
|
3581
|
+
let srcIndex = null;
|
|
3582
|
+
if (idx.i < segs.length && segs[idx.i] !== ",") {
|
|
3583
|
+
src2 += decodeVLQ(segs, idx);
|
|
3584
|
+
_srcLine += decodeVLQ(segs, idx);
|
|
3585
|
+
_srcCol += decodeVLQ(segs, idx);
|
|
3586
|
+
if (idx.i < segs.length && segs[idx.i] !== ",") {
|
|
3587
|
+
decodeVLQ(segs, idx);
|
|
3588
|
+
}
|
|
3589
|
+
srcIndex = src2;
|
|
3590
|
+
}
|
|
3591
|
+
decoded.push({ gen: genCol, src: srcIndex });
|
|
3592
|
+
if (segs[idx.i] === ",")
|
|
3593
|
+
idx.i++;
|
|
3594
|
+
if (idx.i === start2)
|
|
3595
|
+
idx.i++;
|
|
3596
|
+
}
|
|
3597
|
+
if (decoded.length === 0)
|
|
3598
|
+
continue;
|
|
3599
|
+
for (let i = 0;i < decoded.length; i++) {
|
|
3600
|
+
const curr = decoded[i];
|
|
3601
|
+
const nextGen = i + 1 < decoded.length ? decoded[i + 1].gen : genLines[line] ?? 0;
|
|
3602
|
+
const span = Math.max(0, nextGen - curr.gen);
|
|
3603
|
+
if (span > 0 && curr.src != null && perSource[curr.src] != null) {
|
|
3604
|
+
perSource[curr.src] += span;
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
const root = {
|
|
3609
|
+
name: basename3(bundlePath),
|
|
3610
|
+
path: bundlePath,
|
|
3611
|
+
size: 0,
|
|
3612
|
+
children: []
|
|
3613
|
+
};
|
|
3614
|
+
const nodeModulesNode = {
|
|
3615
|
+
name: "node_modules",
|
|
3616
|
+
path: join12(cwd, "node_modules"),
|
|
3617
|
+
size: 0,
|
|
3618
|
+
children: []
|
|
3619
|
+
};
|
|
3620
|
+
const srcNode = {
|
|
3621
|
+
name: "src",
|
|
3622
|
+
path: cwd,
|
|
3623
|
+
size: 0,
|
|
3624
|
+
children: []
|
|
3625
|
+
};
|
|
3626
|
+
root.children?.push(nodeModulesNode, srcNode);
|
|
3627
|
+
for (let i = 0;i < map.sources.length; i++) {
|
|
3628
|
+
const size = perSource[i];
|
|
3629
|
+
if (size <= 0)
|
|
3630
|
+
continue;
|
|
3631
|
+
const srcPath = absFor(map.sources[i]);
|
|
3632
|
+
if (srcPath.includes("node_modules")) {
|
|
3633
|
+
let pkgName;
|
|
3634
|
+
if (srcPath.includes(".bun/")) {
|
|
3635
|
+
const bunMatch = srcPath.match(/\.bun\/([^/]+)@[^/]+\/node_modules\/([^/]+)\//);
|
|
3636
|
+
if (bunMatch) {
|
|
3637
|
+
const innerPkg = bunMatch[2];
|
|
3638
|
+
if (innerPkg === "@borewit" || innerPkg.startsWith("@")) {
|
|
3639
|
+
const scopeMatch = srcPath.match(/\.bun\/[^/]+@[^/]+\/node_modules\/((@[^/]+\/[^/]+))/);
|
|
3640
|
+
pkgName = scopeMatch ? scopeMatch[1] : innerPkg;
|
|
3641
|
+
} else {
|
|
3642
|
+
pkgName = innerPkg;
|
|
3643
|
+
}
|
|
3644
|
+
} else {
|
|
3645
|
+
const fallbackMatch = srcPath.match(/\.bun\/([^@/]+)/);
|
|
3646
|
+
pkgName = fallbackMatch ? fallbackMatch[1] : "unknown";
|
|
3647
|
+
}
|
|
3648
|
+
} else {
|
|
3649
|
+
const afterNm = srcPath.slice(srcPath.lastIndexOf("node_modules") + "node_modules".length + 1);
|
|
3650
|
+
pkgName = afterNm.startsWith("@") ? afterNm.split("/").slice(0, 2).join("/") : afterNm.split("/")[0];
|
|
3651
|
+
}
|
|
3652
|
+
let pkgNode = nodeModulesNode.children?.find((c) => c.name === pkgName);
|
|
3653
|
+
if (!pkgNode) {
|
|
3654
|
+
pkgNode = {
|
|
3655
|
+
name: pkgName || "unknown",
|
|
3656
|
+
path: join12(cwd, "node_modules", pkgName || ""),
|
|
3657
|
+
size: 0,
|
|
3658
|
+
children: []
|
|
3659
|
+
};
|
|
3660
|
+
nodeModulesNode.children?.push(pkgNode);
|
|
3661
|
+
}
|
|
3662
|
+
pkgNode.children?.push({
|
|
3663
|
+
name: basename3(srcPath),
|
|
3664
|
+
path: srcPath,
|
|
3665
|
+
size
|
|
3666
|
+
});
|
|
3667
|
+
} else {
|
|
3668
|
+
const rel = relative(cwd, srcPath);
|
|
3669
|
+
const parts = rel.split("/").filter(Boolean);
|
|
3670
|
+
let current = srcNode;
|
|
3671
|
+
for (let i2 = 0;i2 < parts.length - 1; i2++) {
|
|
3672
|
+
const part = parts[i2];
|
|
3673
|
+
let child = current.children?.find((c) => c.name === part);
|
|
3674
|
+
if (!child) {
|
|
3675
|
+
child = {
|
|
3676
|
+
name: part,
|
|
3677
|
+
path: join12(cwd, ...parts.slice(0, i2 + 1)),
|
|
3678
|
+
size: 0,
|
|
3679
|
+
children: []
|
|
3680
|
+
};
|
|
3681
|
+
current.children?.push(child);
|
|
3682
|
+
}
|
|
3683
|
+
current = child;
|
|
3684
|
+
}
|
|
3685
|
+
current.children?.push({
|
|
3686
|
+
name: parts[parts.length - 1] || basename3(srcPath),
|
|
3687
|
+
path: srcPath,
|
|
3688
|
+
size
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
computeSizes(root);
|
|
3693
|
+
return {
|
|
3694
|
+
entry: bundlePath,
|
|
3695
|
+
totalSize: root.size,
|
|
3696
|
+
root
|
|
3697
|
+
};
|
|
3698
|
+
} catch {
|
|
3699
|
+
return null;
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
async function generateHTMLReport(stats, depTree, mainInfo, browserDepTree, browserInfo, initialSearch) {
|
|
3703
|
+
const bundles = stats.bundles.sort((a, b) => b.size - a.size);
|
|
3704
|
+
let actualServerBinarySize = null;
|
|
3705
|
+
let actualServerBinaryGzipped = null;
|
|
3706
|
+
try {
|
|
3707
|
+
const distDir = join12(process.cwd(), "dist");
|
|
3708
|
+
if (existsSync9(distDir)) {
|
|
3709
|
+
const files = await readdir6(distDir);
|
|
3710
|
+
for (const file of files) {
|
|
3711
|
+
const filePath = join12(distDir, file);
|
|
3712
|
+
const stats2 = statSync(filePath);
|
|
3713
|
+
if (stats2.isFile() && !file.includes(".")) {
|
|
3714
|
+
actualServerBinarySize = stats2.size;
|
|
3715
|
+
const content2 = await readFile2(filePath);
|
|
3716
|
+
actualServerBinaryGzipped = Bun.gzipSync(content2).length;
|
|
3717
|
+
break;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
} catch {}
|
|
3722
|
+
const icon = (name, color = "#0f0", size = 16) => {
|
|
3723
|
+
const icons = {
|
|
3724
|
+
chart: '<path d="M3 3v18h18"/><path d="M7 12l4-4 4 4 6-6"/>',
|
|
3725
|
+
world: '<path d="M21 12a9 9 0 1 1-6.219-8.56"/><path d="M3 9v6a6 6 0 0 0 6 6h6"/><path d="M21 9v6a6 6 0 0 1-6 6H9"/>',
|
|
3726
|
+
search: '<circle cx="10" cy="10" r="7"/><path d="m21 21-6-6"/>',
|
|
3727
|
+
treemap: '<path d="M3 3v18h18"/><path d="M3 9h8v12H3z"/><path d="M13 9h8v6h-8z"/><path d="M13 17h8v4h-8z"/>',
|
|
3728
|
+
package: '<path d="M12 3v20"/><path d="M2 7h20v14H2z"/><path d="M2 7l10-4 10 4"/>',
|
|
3729
|
+
server: '<path d="M4 5h16"/><path d="M4 9h16"/><path d="M4 13h16"/><path d="M4 17h16"/><path d="M6 3v18"/><path d="M18 3v18"/>'
|
|
3730
|
+
};
|
|
3731
|
+
const path3 = icons[name] || icons.chart;
|
|
3732
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 0.4rem;">${path3}</svg>`;
|
|
3733
|
+
};
|
|
3734
|
+
const html2 = `<!DOCTYPE html>
|
|
3735
|
+
<html lang="en">
|
|
3736
|
+
<head>
|
|
3737
|
+
<meta charset="UTF-8">
|
|
3738
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3739
|
+
<title>Reroute Bundle Analysis</title>
|
|
3740
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
3741
|
+
<style>
|
|
3742
|
+
* {
|
|
3743
|
+
margin: 0;
|
|
3744
|
+
padding: 0;
|
|
3745
|
+
box-sizing: border-box;
|
|
3746
|
+
}
|
|
3747
|
+
body {
|
|
3748
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', monospace;
|
|
3749
|
+
background: #000;
|
|
3750
|
+
color: #0f0;
|
|
3751
|
+
padding: 1.5rem;
|
|
3752
|
+
line-height: 1.5;
|
|
3753
|
+
font-size: 13px;
|
|
3754
|
+
}
|
|
3755
|
+
.container {
|
|
3756
|
+
max-width: 1600px;
|
|
3757
|
+
margin: 0 auto;
|
|
3758
|
+
}
|
|
3759
|
+
h1 {
|
|
3760
|
+
color: #0f0;
|
|
3761
|
+
margin-bottom: 0.5rem;
|
|
3762
|
+
font-size: 1.25rem;
|
|
3763
|
+
font-weight: 600;
|
|
3764
|
+
letter-spacing: 0.5px;
|
|
3765
|
+
}
|
|
3766
|
+
h1::before {
|
|
3767
|
+
content: '$ ';
|
|
3768
|
+
color: #666;
|
|
3769
|
+
}
|
|
3770
|
+
.sticky-search {
|
|
3771
|
+
position: sticky;
|
|
3772
|
+
top: 0;
|
|
3773
|
+
background: #000;
|
|
3774
|
+
z-index: 100;
|
|
3775
|
+
padding: 1rem 0;
|
|
3776
|
+
margin: 0;
|
|
3777
|
+
}
|
|
3778
|
+
.search-row {
|
|
3779
|
+
display: grid;
|
|
3780
|
+
grid-template-columns: 1fr auto;
|
|
3781
|
+
gap: 0.75rem;
|
|
3782
|
+
align-items: start;
|
|
3783
|
+
height: 33px;
|
|
3784
|
+
}
|
|
3785
|
+
.search-container {
|
|
3786
|
+
position: relative;
|
|
3787
|
+
display: flex;
|
|
3788
|
+
flex-direction: column;
|
|
3789
|
+
gap: 0.5rem;
|
|
3790
|
+
}
|
|
3791
|
+
.search-input-wrapper {
|
|
3792
|
+
position: relative;
|
|
3793
|
+
}
|
|
3794
|
+
.search-clear {
|
|
3795
|
+
position: absolute;
|
|
3796
|
+
right: 0.5rem;
|
|
3797
|
+
top: 50%;
|
|
3798
|
+
transform: translateY(-50%);
|
|
3799
|
+
background: none;
|
|
3800
|
+
border: none;
|
|
3801
|
+
color: #666;
|
|
3802
|
+
cursor: pointer;
|
|
3803
|
+
padding: 0.25rem;
|
|
3804
|
+
display: none;
|
|
3805
|
+
align-items: center;
|
|
3806
|
+
justify-content: center;
|
|
3807
|
+
font-size: 17px;
|
|
3808
|
+
line-height: 1;
|
|
3809
|
+
}
|
|
3810
|
+
.search-clear:hover {
|
|
3811
|
+
color: #0f0;
|
|
3812
|
+
}
|
|
3813
|
+
.search-clear.visible {
|
|
3814
|
+
display: flex;
|
|
3815
|
+
}
|
|
3816
|
+
.search-input {
|
|
3817
|
+
width: 100%;
|
|
3818
|
+
padding: 0.5rem 2rem 0.5rem 0.75rem;
|
|
3819
|
+
background: #111;
|
|
3820
|
+
border: 1px solid #333;
|
|
3821
|
+
color: #0f0;
|
|
3822
|
+
font-size: 13px;
|
|
3823
|
+
font-family: inherit;
|
|
3824
|
+
}
|
|
3825
|
+
.note {
|
|
3826
|
+
color: #666;
|
|
3827
|
+
font-size: 12px;
|
|
3828
|
+
padding: 0.75rem;
|
|
3829
|
+
background: #111;
|
|
3830
|
+
border-left: 2px solid #333;
|
|
3831
|
+
}
|
|
3832
|
+
.note strong {
|
|
3833
|
+
color: #0f0;
|
|
3834
|
+
}
|
|
3835
|
+
@media (max-width: 640px) {
|
|
3836
|
+
.search-row {
|
|
3837
|
+
grid-template-columns: 1fr;
|
|
3838
|
+
gap: 0.5rem;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
.search-input:focus {
|
|
3842
|
+
outline: none;
|
|
3843
|
+
border-color: #0f0;
|
|
3844
|
+
}
|
|
3845
|
+
.search-input::placeholder {
|
|
3846
|
+
color: #666;
|
|
3847
|
+
}
|
|
3848
|
+
.search-stats {
|
|
3849
|
+
margin-bottom: 1rem;
|
|
3850
|
+
font-size: 12px;
|
|
3851
|
+
color: #666;
|
|
3852
|
+
}
|
|
3853
|
+
.search-actions {
|
|
3854
|
+
display: flex;
|
|
3855
|
+
gap: 0.5rem;
|
|
3856
|
+
align-items: stretch;
|
|
3857
|
+
}
|
|
3858
|
+
@media (max-width: 640px) {
|
|
3859
|
+
.search-actions {
|
|
3860
|
+
width: 100%;
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
.checkbox-label {
|
|
3864
|
+
padding: 0.5rem 0.75rem;
|
|
3865
|
+
background: #111;
|
|
3866
|
+
border: 1px solid #333;
|
|
3867
|
+
color: #0f0;
|
|
3868
|
+
font-size: 13px;
|
|
3869
|
+
font-family: inherit;
|
|
3870
|
+
cursor: pointer;
|
|
3871
|
+
white-space: nowrap;
|
|
3872
|
+
display: flex;
|
|
3873
|
+
align-items: center;
|
|
3874
|
+
gap: 0.5rem;
|
|
3875
|
+
user-select: none;
|
|
3876
|
+
}
|
|
3877
|
+
.checkbox-label:hover {
|
|
3878
|
+
background: #1a1a1a;
|
|
3879
|
+
border-color: #0f0;
|
|
3880
|
+
}
|
|
3881
|
+
.checkbox-label input[type="checkbox"] {
|
|
3882
|
+
cursor: pointer;
|
|
3883
|
+
width: 14px;
|
|
3884
|
+
height: 14px;
|
|
3885
|
+
accent-color: #0f0;
|
|
3886
|
+
}
|
|
3887
|
+
@media (max-width: 640px) {
|
|
3888
|
+
.checkbox-label {
|
|
3889
|
+
flex: 1;
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
.btn-toggle {
|
|
3893
|
+
padding: 0.5rem 0.75rem;
|
|
3894
|
+
background: #111;
|
|
3895
|
+
border: 1px solid #333;
|
|
3896
|
+
color: #0f0;
|
|
3897
|
+
font-size: 13px;
|
|
3898
|
+
font-family: inherit;
|
|
3899
|
+
cursor: pointer;
|
|
3900
|
+
white-space: nowrap;
|
|
3901
|
+
display: flex;
|
|
3902
|
+
align-items: center;
|
|
3903
|
+
box-sizing: border-box;
|
|
3904
|
+
}
|
|
3905
|
+
.btn-toggle:hover {
|
|
3906
|
+
background: #1a1a1a;
|
|
3907
|
+
border-color: #0f0;
|
|
3908
|
+
}
|
|
3909
|
+
@media (max-width: 640px) {
|
|
3910
|
+
.btn-toggle {
|
|
3911
|
+
flex: 1;
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
.hidden {
|
|
3915
|
+
display: none !important;
|
|
3916
|
+
}
|
|
3917
|
+
.section {
|
|
3918
|
+
margin-bottom: 1.5rem;
|
|
3919
|
+
border: 1px solid #333;
|
|
3920
|
+
background: #0a0a0a;
|
|
3921
|
+
}
|
|
3922
|
+
.section-header {
|
|
3923
|
+
padding: 0.75rem 1rem;
|
|
3924
|
+
background: #111;
|
|
3925
|
+
border-bottom: 1px solid #333;
|
|
3926
|
+
cursor: pointer;
|
|
3927
|
+
user-select: none;
|
|
3928
|
+
display: flex;
|
|
3929
|
+
justify-content: space-between;
|
|
3930
|
+
align-items: center;
|
|
3931
|
+
}
|
|
3932
|
+
.section-header:hover {
|
|
3933
|
+
background: #151515;
|
|
3934
|
+
}
|
|
3935
|
+
.section-title {
|
|
3936
|
+
font-size: 14px;
|
|
3937
|
+
font-weight: 600;
|
|
3938
|
+
color: #0f0;
|
|
3939
|
+
}
|
|
3940
|
+
.section-title.server {
|
|
3941
|
+
color: #666;
|
|
3942
|
+
}
|
|
3943
|
+
.section-meta {
|
|
3944
|
+
color: #666;
|
|
3945
|
+
font-size: 12px;
|
|
3946
|
+
}
|
|
3947
|
+
.section-toggle {
|
|
3948
|
+
color: #666;
|
|
3949
|
+
font-size: 12px;
|
|
3950
|
+
}
|
|
3951
|
+
.section-content {
|
|
3952
|
+
padding: 1rem;
|
|
3953
|
+
}
|
|
3954
|
+
.section.collapsed .section-content {
|
|
3955
|
+
display: none;
|
|
3956
|
+
}
|
|
3957
|
+
.section.collapsed .section-toggle::before {
|
|
3958
|
+
content: '▶ ';
|
|
3959
|
+
}
|
|
3960
|
+
.section-toggle::before {
|
|
3961
|
+
content: '▼ ';
|
|
3962
|
+
}
|
|
3963
|
+
.stats-grid {
|
|
3964
|
+
display: grid;
|
|
3965
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
3966
|
+
gap: 0.5rem;
|
|
3967
|
+
margin-bottom: 1rem;
|
|
3968
|
+
}
|
|
3969
|
+
.stat {
|
|
3970
|
+
background: #111;
|
|
3971
|
+
padding: 0.5rem 0.75rem;
|
|
3972
|
+
border: 1px solid #222;
|
|
3973
|
+
}
|
|
3974
|
+
.stat-label {
|
|
3975
|
+
font-size: 11px;
|
|
3976
|
+
color: #666;
|
|
3977
|
+
margin-bottom: 0.25rem;
|
|
3978
|
+
}
|
|
3979
|
+
.stat-value {
|
|
3980
|
+
font-size: 16px;
|
|
3981
|
+
font-weight: 600;
|
|
3982
|
+
color: #0f0;
|
|
3983
|
+
}
|
|
3984
|
+
.stat-value.server {
|
|
3985
|
+
color: #666;
|
|
3986
|
+
}
|
|
3987
|
+
.dep-list {
|
|
3988
|
+
list-style: none;
|
|
3989
|
+
margin: 0;
|
|
3990
|
+
padding: 0;
|
|
3991
|
+
}
|
|
3992
|
+
.dep-item {
|
|
3993
|
+
padding: 0.4rem 0;
|
|
3994
|
+
border-bottom: 1px solid #1a1a1a;
|
|
3995
|
+
display: flex;
|
|
3996
|
+
justify-content: space-between;
|
|
3997
|
+
align-items: center;
|
|
3998
|
+
font-size: 12px;
|
|
3999
|
+
}
|
|
4000
|
+
.dep-item:last-child {
|
|
4001
|
+
border-bottom: none;
|
|
4002
|
+
}
|
|
4003
|
+
.dep-name {
|
|
4004
|
+
color: #0f0;
|
|
4005
|
+
font-weight: 500;
|
|
4006
|
+
}
|
|
4007
|
+
.dep-name.server {
|
|
4008
|
+
color: #666;
|
|
4009
|
+
}
|
|
4010
|
+
.dep-size {
|
|
4011
|
+
color: #666;
|
|
4012
|
+
font-size: 11px;
|
|
4013
|
+
}
|
|
4014
|
+
.dep-children {
|
|
4015
|
+
margin-left: 1.5rem;
|
|
4016
|
+
margin-top: 0.25rem;
|
|
4017
|
+
}
|
|
4018
|
+
.dep-child {
|
|
4019
|
+
color: #666;
|
|
4020
|
+
font-size: 11px;
|
|
4021
|
+
padding: 0.2rem 0;
|
|
4022
|
+
}
|
|
4023
|
+
.load-more {
|
|
4024
|
+
margin-top: 0.75rem;
|
|
4025
|
+
padding: 0.4rem 0.75rem;
|
|
4026
|
+
background: #111;
|
|
4027
|
+
border: 1px solid #333;
|
|
4028
|
+
color: #0f0;
|
|
4029
|
+
cursor: pointer;
|
|
4030
|
+
font-family: inherit;
|
|
4031
|
+
font-size: 12px;
|
|
4032
|
+
width: 100%;
|
|
4033
|
+
}
|
|
4034
|
+
.load-more:hover {
|
|
4035
|
+
background: #1a1a1a;
|
|
4036
|
+
border-color: #0f0;
|
|
4037
|
+
}
|
|
4038
|
+
.treemap-container {
|
|
4039
|
+
width: 100%;
|
|
4040
|
+
overflow: visible;
|
|
4041
|
+
}
|
|
4042
|
+
.empty {
|
|
4043
|
+
text-align: center;
|
|
4044
|
+
padding: 2rem;
|
|
4045
|
+
color: #666;
|
|
4046
|
+
font-size: 12px;
|
|
4047
|
+
}
|
|
4048
|
+
.treemap-cell {
|
|
4049
|
+
stroke: #111;
|
|
4050
|
+
stroke-width: 1px;
|
|
4051
|
+
cursor: pointer;
|
|
4052
|
+
transition: all 0.2s;
|
|
4053
|
+
}
|
|
4054
|
+
.treemap-cell:hover {
|
|
4055
|
+
stroke: #0f0;
|
|
4056
|
+
stroke-width: 2px;
|
|
4057
|
+
}
|
|
4058
|
+
.treemap-label {
|
|
4059
|
+
fill: #0f0;
|
|
4060
|
+
font-size: 10px;
|
|
4061
|
+
pointer-events: none;
|
|
4062
|
+
text-anchor: middle;
|
|
4063
|
+
font-weight: 500;
|
|
4064
|
+
}
|
|
4065
|
+
.treemap-tooltip {
|
|
4066
|
+
position: absolute;
|
|
4067
|
+
background: #000;
|
|
4068
|
+
border: 1px solid #0f0;
|
|
4069
|
+
padding: 0.5rem 0.75rem;
|
|
4070
|
+
pointer-events: none;
|
|
4071
|
+
opacity: 0;
|
|
4072
|
+
transition: opacity 0.15s;
|
|
4073
|
+
z-index: 1000;
|
|
4074
|
+
font-size: 11px;
|
|
4075
|
+
}
|
|
4076
|
+
.treemap-tooltip.visible {
|
|
4077
|
+
opacity: 1;
|
|
4078
|
+
}
|
|
4079
|
+
.tooltip-name {
|
|
4080
|
+
font-weight: 600;
|
|
4081
|
+
color: #0f0;
|
|
4082
|
+
margin-bottom: 0.25rem;
|
|
4083
|
+
}
|
|
4084
|
+
.tooltip-path {
|
|
4085
|
+
color: #666;
|
|
4086
|
+
font-size: 10px;
|
|
4087
|
+
margin-bottom: 0.25rem;
|
|
4088
|
+
word-break: break-all;
|
|
4089
|
+
}
|
|
4090
|
+
.tooltip-size {
|
|
4091
|
+
color: #0f0;
|
|
4092
|
+
font-size: 11px;
|
|
4093
|
+
}
|
|
4094
|
+
</style>
|
|
4095
|
+
</head>
|
|
4096
|
+
<body>
|
|
4097
|
+
<div class="container">
|
|
4098
|
+
<h1>reroute analyze</h1>
|
|
4099
|
+
|
|
4100
|
+
<div class="sticky-search">
|
|
4101
|
+
<div class="search-row">
|
|
4102
|
+
<div class="search-container">
|
|
4103
|
+
<div class="search-input-wrapper">
|
|
4104
|
+
<input
|
|
4105
|
+
type="text"
|
|
4106
|
+
id="search-input"
|
|
4107
|
+
class="search-input"
|
|
4108
|
+
placeholder="search dependencies (e.g., 'react', 'scheduler', 'sharp')..."
|
|
4109
|
+
/>
|
|
4110
|
+
<button class="search-clear" id="search-clear" type="button" aria-label="Clear search">×</button>
|
|
4111
|
+
</div>
|
|
4112
|
+
</div>
|
|
4113
|
+
<div class="search-actions">
|
|
4114
|
+
<button class="btn-toggle" id="expand-all">expand</button>
|
|
4115
|
+
<button class="btn-toggle" id="collapse-all">collapse</button>
|
|
4116
|
+
</div>
|
|
4117
|
+
</div>
|
|
4118
|
+
</div>
|
|
4119
|
+
|
|
4120
|
+
<div class="section">
|
|
4121
|
+
<div class="section-header">
|
|
4122
|
+
<div>
|
|
4123
|
+
<div class="section-title">${icon("chart")}Summary</div>
|
|
4124
|
+
</div>
|
|
4125
|
+
</div>
|
|
4126
|
+
<div class="section-content">
|
|
4127
|
+
<div class="stats-grid">
|
|
4128
|
+
<div class="stat">
|
|
4129
|
+
<div class="stat-label">browser dependencies</div>
|
|
4130
|
+
<div class="stat-value">${(() => {
|
|
4131
|
+
const nodeModules = browserDepTree?.root.children?.find((c) => c.name === "node_modules");
|
|
4132
|
+
return nodeModules?.children?.length || 0;
|
|
4133
|
+
})()}</div>
|
|
4134
|
+
</div>
|
|
4135
|
+
<div class="stat">
|
|
4136
|
+
<div class="stat-label">browser bundle</div>
|
|
4137
|
+
<div class="stat-value">${browserInfo ? formatBytes(browserInfo.size) : "N/A"}</div>
|
|
4138
|
+
</div>
|
|
4139
|
+
<div class="stat">
|
|
4140
|
+
<div class="stat-label">server dependencies</div>
|
|
4141
|
+
<div class="stat-value">${(() => {
|
|
4142
|
+
const nodeModules = depTree?.root.children?.find((c) => c.name === "node_modules");
|
|
4143
|
+
return nodeModules?.children?.length || 0;
|
|
4144
|
+
})()}</div>
|
|
4145
|
+
</div>
|
|
4146
|
+
<div class="stat">
|
|
4147
|
+
<div class="stat-label">final server binary</div>
|
|
4148
|
+
<div class="stat-value">${actualServerBinarySize ? formatBytes(actualServerBinarySize) : "N/A"}</div>
|
|
4149
|
+
</div>
|
|
4150
|
+
</div>
|
|
4151
|
+
</div>
|
|
4152
|
+
</div>
|
|
4153
|
+
|
|
4154
|
+
<!-- ═══════════════════════════════════════════════════════════ -->
|
|
4155
|
+
<!-- BROWSER SECTION (what users download) -->
|
|
4156
|
+
<!-- ═══════════════════════════════════════════════════════════ -->
|
|
4157
|
+
|
|
4158
|
+
${browserInfo ? `
|
|
4159
|
+
<div class="section">
|
|
4160
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4161
|
+
<div>
|
|
4162
|
+
<div class="section-title">${icon("world")}browser bundle</div>
|
|
4163
|
+
<div class="section-meta">${browserInfo.name} · ${formatBytes(browserInfo.size)} raw · ${formatBytes(browserInfo.gzippedSize)} gzipped</div>
|
|
4164
|
+
</div>
|
|
4165
|
+
<div class="section-toggle"></div>
|
|
4166
|
+
</div>
|
|
4167
|
+
<div class="section-content">
|
|
4168
|
+
<div class="stats-grid">
|
|
4169
|
+
<div class="stat">
|
|
4170
|
+
<div class="stat-label">raw size</div>
|
|
4171
|
+
<div class="stat-value">${formatBytes(browserInfo.size)}</div>
|
|
4172
|
+
</div>
|
|
4173
|
+
<div class="stat">
|
|
4174
|
+
<div class="stat-label">gzipped</div>
|
|
4175
|
+
<div class="stat-value">${formatBytes(browserInfo.gzippedSize)}</div>
|
|
4176
|
+
</div>
|
|
4177
|
+
<div class="stat">
|
|
4178
|
+
<div class="stat-label">compression</div>
|
|
4179
|
+
<div class="stat-value">${((1 - browserInfo.gzippedSize / browserInfo.size) * 100).toFixed(1)}%</div>
|
|
4180
|
+
</div>
|
|
4181
|
+
<div class="stat">
|
|
4182
|
+
<div class="stat-label">3G transfer</div>
|
|
4183
|
+
<div class="stat-value">${(() => {
|
|
4184
|
+
const t = browserInfo.gzippedSize / 1024 / 204.8;
|
|
4185
|
+
return t >= 1 ? `${t.toFixed(1)}s` : `${(t * 1000).toFixed(0)}ms`;
|
|
4186
|
+
})()}</div>
|
|
4187
|
+
</div>
|
|
4188
|
+
<div class="stat">
|
|
4189
|
+
<div class="stat-label">4G transfer</div>
|
|
4190
|
+
<div class="stat-value">${(() => {
|
|
4191
|
+
const t = browserInfo.gzippedSize / 1024 / 1152;
|
|
4192
|
+
return t >= 1 ? `${t.toFixed(1)}s` : `${(t * 1000).toFixed(0)}ms`;
|
|
4193
|
+
})()}</div>
|
|
4194
|
+
</div>
|
|
4195
|
+
<div class="stat">
|
|
4196
|
+
<div class="stat-label">5G transfer</div>
|
|
4197
|
+
<div class="stat-value">${(() => {
|
|
4198
|
+
const t = browserInfo.gzippedSize / 1024 / 6400;
|
|
4199
|
+
return t >= 1 ? `${t.toFixed(1)}s` : `${(t * 1000).toFixed(0)}ms`;
|
|
4200
|
+
})()}</div>
|
|
4201
|
+
</div>
|
|
4202
|
+
</div>
|
|
4203
|
+
</div>
|
|
4204
|
+
</div>
|
|
4205
|
+
` : ""}
|
|
4206
|
+
${browserDepTree ? `
|
|
4207
|
+
<div class="section collapsed">
|
|
4208
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4209
|
+
<div>
|
|
4210
|
+
<div class="section-title">${icon("search")}browser dependencies</div>
|
|
4211
|
+
<div class="section-meta">dependency breakdown</div>
|
|
4212
|
+
</div>
|
|
4213
|
+
<div class="section-toggle"></div>
|
|
4214
|
+
</div>
|
|
4215
|
+
<div class="section-content">
|
|
4216
|
+
<div id="browser-deps-list"></div>
|
|
4217
|
+
</div>
|
|
4218
|
+
</div>
|
|
4219
|
+
|
|
4220
|
+
<div class="section collapsed">
|
|
4221
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4222
|
+
<div>
|
|
4223
|
+
<div class="section-title">${icon("treemap")}browser treemap</div>
|
|
4224
|
+
<div class="section-meta">visual dependency breakdown</div>
|
|
4225
|
+
</div>
|
|
4226
|
+
<div class="section-toggle"></div>
|
|
4227
|
+
</div>
|
|
4228
|
+
<div class="section-content">
|
|
4229
|
+
<div class="treemap-container" id="browser-treemap"></div>
|
|
4230
|
+
</div>
|
|
4231
|
+
</div>
|
|
4232
|
+
` : ""}
|
|
4233
|
+
|
|
4234
|
+
${Object.entries(stats.byCollection).filter(([collection]) => collection !== "binaries").map(([collection, collectionBundles]) => {
|
|
4235
|
+
const collectionTotal = collectionBundles.reduce((sum, b) => sum + b.size, 0);
|
|
4236
|
+
const collectionGzipped = collectionBundles.reduce((sum, b) => sum + b.gzippedSize, 0);
|
|
4237
|
+
return `
|
|
4238
|
+
<div class="section collapsed">
|
|
4239
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4240
|
+
<div>
|
|
4241
|
+
<div class="section-title">${icon("package")}${collection} chunks</div>
|
|
4242
|
+
<div class="section-meta">${collectionBundles.length} files · ${formatBytes(collectionTotal)} raw · ${formatBytes(collectionGzipped)} gzipped</div>
|
|
4243
|
+
</div>
|
|
4244
|
+
<div class="section-toggle"></div>
|
|
4245
|
+
</div>
|
|
4246
|
+
<div class="section-content">
|
|
4247
|
+
<ul class="dep-list" id="${collection}-chunks-list">
|
|
4248
|
+
${collectionBundles.sort((a, b) => b.size - a.size).slice(0, 10).map((bundle) => `
|
|
4249
|
+
<li class="dep-item">
|
|
4250
|
+
<span class="dep-name">${bundle.name}</span>
|
|
4251
|
+
<span class="dep-size">${formatBytes(bundle.size)} · ${formatBytes(bundle.gzippedSize)} gz</span>
|
|
4252
|
+
</li>
|
|
4253
|
+
`).join("")}
|
|
4254
|
+
</ul>
|
|
4255
|
+
${collectionBundles.length > 10 ? `
|
|
4256
|
+
<button class="load-more" onclick="loadMore${collection}()">load ${collectionBundles.length - 10} more...</button>
|
|
4257
|
+
<script>
|
|
4258
|
+
let ${collection}Loaded = false;
|
|
4259
|
+
function loadMore${collection}() {
|
|
4260
|
+
if (${collection}Loaded) return;
|
|
4261
|
+
${collection}Loaded = true;
|
|
4262
|
+
const list = document.getElementById('${collection}-chunks-list');
|
|
4263
|
+
const remaining = ${JSON.stringify(collectionBundles.slice(10).map((b) => ({ name: b.name, size: b.size, gzippedSize: b.gzippedSize })))};
|
|
4264
|
+
for (const bundle of remaining) {
|
|
4265
|
+
const li = document.createElement('li');
|
|
4266
|
+
li.className = 'dep-item';
|
|
4267
|
+
li.innerHTML = '<span class="dep-name">' + bundle.name + '</span><span class="dep-size">' + formatBytes(bundle.size) + ' · ' + formatBytes(bundle.gzippedSize) + ' gz</span>';
|
|
4268
|
+
list.appendChild(li);
|
|
4269
|
+
}
|
|
4270
|
+
event.target.remove();
|
|
4271
|
+
}
|
|
4272
|
+
</script>
|
|
4273
|
+
` : ""}
|
|
4274
|
+
</div>
|
|
4275
|
+
</div>
|
|
4276
|
+
`;
|
|
4277
|
+
}).join("")}
|
|
4278
|
+
|
|
4279
|
+
<!-- ═══════════════════════════════════════════════════════════ -->
|
|
4280
|
+
<!-- SERVER SECTION (not shipped to browser) -->
|
|
4281
|
+
<!-- ═══════════════════════════════════════════════════════════ -->
|
|
4282
|
+
|
|
4283
|
+
${actualServerBinarySize ? `
|
|
4284
|
+
<div class="section collapsed">
|
|
4285
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4286
|
+
<div>
|
|
4287
|
+
<div class="section-title server">${icon("server", "#666")}final server binary</div>
|
|
4288
|
+
<div class="section-meta">standalone executable · ${formatBytes(actualServerBinarySize)} · not shipped to browser</div>
|
|
4289
|
+
</div>
|
|
4290
|
+
<div class="section-toggle"></div>
|
|
4291
|
+
</div>
|
|
4292
|
+
<div class="section-content">
|
|
4293
|
+
<div class="stats-grid">
|
|
4294
|
+
<div class="stat">
|
|
4295
|
+
<div class="stat-label">binary size</div>
|
|
4296
|
+
<div class="stat-value server">${formatBytes(actualServerBinarySize)}</div>
|
|
4297
|
+
</div>
|
|
4298
|
+
${actualServerBinaryGzipped ? `
|
|
4299
|
+
<div class="stat">
|
|
4300
|
+
<div class="stat-label">gzipped</div>
|
|
4301
|
+
<div class="stat-value server">${formatBytes(actualServerBinaryGzipped)}</div>
|
|
4302
|
+
</div>
|
|
4303
|
+
<div class="stat">
|
|
4304
|
+
<div class="stat-label">compression</div>
|
|
4305
|
+
<div class="stat-value server">${((1 - actualServerBinaryGzipped / actualServerBinarySize) * 100).toFixed(1)}%</div>
|
|
4306
|
+
</div>
|
|
4307
|
+
` : ""}
|
|
4308
|
+
</div>
|
|
4309
|
+
</div>
|
|
4310
|
+
</div>
|
|
4311
|
+
` : ""}
|
|
4312
|
+
${depTree ? `
|
|
4313
|
+
<div class="section collapsed">
|
|
4314
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4315
|
+
<div>
|
|
4316
|
+
<div class="section-title server">${icon("search", "#666")}server dependencies</div>
|
|
4317
|
+
<div class="section-meta">server-side only · not shipped to browser</div>
|
|
4318
|
+
</div>
|
|
4319
|
+
<div class="section-toggle"></div>
|
|
4320
|
+
</div>
|
|
4321
|
+
<div class="section-content">
|
|
4322
|
+
<div id="server-deps-list"></div>
|
|
4323
|
+
</div>
|
|
4324
|
+
</div>
|
|
4325
|
+
|
|
4326
|
+
<div class="section collapsed">
|
|
4327
|
+
<div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
4328
|
+
<div>
|
|
4329
|
+
<div class="section-title server">${icon("treemap", "#666")}server treemap</div>
|
|
4330
|
+
<div class="section-meta">visual dependency breakdown</div>
|
|
4331
|
+
</div>
|
|
4332
|
+
<div class="section-toggle"></div>
|
|
4333
|
+
</div>
|
|
4334
|
+
<div class="section-content">
|
|
4335
|
+
<div class="treemap-container" id="server-treemap"></div>
|
|
4336
|
+
</div>
|
|
4337
|
+
</div>
|
|
4338
|
+
` : ""}
|
|
4339
|
+
|
|
4340
|
+
${bundles.length === 0 ? '<div class="empty">no bundles found. run <code>reroute gen</code> or <code>reroute build</code> first.</div>' : ""}
|
|
4341
|
+
</div>
|
|
4342
|
+
<div class="treemap-tooltip" id="tooltip">
|
|
4343
|
+
<div class="tooltip-name" id="tooltip-name"></div>
|
|
4344
|
+
<div class="tooltip-path" id="tooltip-path"></div>
|
|
4345
|
+
<div class="tooltip-size" id="tooltip-size"></div>
|
|
4346
|
+
</div>
|
|
4347
|
+
${browserDepTree || depTree ? `
|
|
4348
|
+
<script>
|
|
4349
|
+
const browserDepTree = ${browserDepTree ? JSON.stringify(browserDepTree) : "null"};
|
|
4350
|
+
const serverDepTree = ${depTree ? JSON.stringify(depTree) : "null"};
|
|
4351
|
+
const formatBytes = ${formatBytes.toString()};
|
|
4352
|
+
|
|
4353
|
+
function cleanPath(path) {
|
|
4354
|
+
if (!path) return '';
|
|
4355
|
+
const nodeModulesIdx = path.indexOf('node_modules');
|
|
4356
|
+
if (nodeModulesIdx === -1) return path;
|
|
4357
|
+
return path.substring(nodeModulesIdx);
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
function convertNode(node) {
|
|
4361
|
+
return {
|
|
4362
|
+
name: node.name,
|
|
4363
|
+
path: node.path,
|
|
4364
|
+
size: node.size,
|
|
4365
|
+
children: node.children && node.children.length > 0 ? node.children.map(convertNode) : null
|
|
4366
|
+
};
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
function renderDepTreemap(tree, containerId) {
|
|
4370
|
+
const container = d3.select(containerId);
|
|
4371
|
+
container.selectAll("*").remove();
|
|
4372
|
+
const width = container.node()?.clientWidth || 1200;
|
|
4373
|
+
|
|
4374
|
+
const tooltip = d3.select("#tooltip");
|
|
4375
|
+
|
|
4376
|
+
const root = d3.hierarchy(convertNode(tree.root))
|
|
4377
|
+
.sum(d => d.size || 0)
|
|
4378
|
+
.sort((a, b) => (b.value || 0) - (a.value || 0));
|
|
4379
|
+
|
|
4380
|
+
// Calculate height based on data - use aspect ratio that works well for treemaps
|
|
4381
|
+
// More items = taller treemap, but cap at reasonable max
|
|
4382
|
+
const leafCount = root.leaves().length;
|
|
4383
|
+
const baseHeight = Math.max(400, Math.min(1200, width * 0.6));
|
|
4384
|
+
const height = Math.min(baseHeight + (leafCount * 2), width * 1.2);
|
|
4385
|
+
|
|
4386
|
+
const svg = container.append("svg")
|
|
4387
|
+
.attr("width", width)
|
|
4388
|
+
.attr("height", height);
|
|
4389
|
+
|
|
4390
|
+
const treemap = d3.treemap()
|
|
4391
|
+
.size([width, height])
|
|
4392
|
+
.padding(2)
|
|
4393
|
+
.round(true);
|
|
4394
|
+
|
|
4395
|
+
treemap(root);
|
|
4396
|
+
|
|
4397
|
+
function hashString(str) {
|
|
4398
|
+
let h = 0;
|
|
4399
|
+
for (let i = 0; i < str.length; i++) {
|
|
4400
|
+
h = ((h << 5) - h + str.charCodeAt(i)) | 0;
|
|
4401
|
+
}
|
|
4402
|
+
return Math.abs(h);
|
|
4403
|
+
}
|
|
4404
|
+
function getPackageName(d) {
|
|
4405
|
+
const p = d.data.path || '';
|
|
4406
|
+
if (!p.includes('node_modules')) return null;
|
|
4407
|
+
const idx = p.lastIndexOf('node_modules/');
|
|
4408
|
+
if (idx === -1) return null;
|
|
4409
|
+
const after = p.slice(idx + 'node_modules/'.length);
|
|
4410
|
+
// Handle @scope/package or package
|
|
4411
|
+
if (after.startsWith('@')) {
|
|
4412
|
+
const parts = after.split('/');
|
|
4413
|
+
return parts.length >= 2 ? parts[0] + '/' + parts[1] : after;
|
|
4414
|
+
}
|
|
4415
|
+
return after.split('/')[0];
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
// Terminal-style colors
|
|
4419
|
+
const colorCache = {};
|
|
4420
|
+
function colorFor(d) {
|
|
4421
|
+
const p = d.data.path || '';
|
|
4422
|
+
if (p.includes('node_modules')) {
|
|
4423
|
+
const pkg = getPackageName(d) || 'unknown';
|
|
4424
|
+
|
|
4425
|
+
if (!colorCache[pkg]) {
|
|
4426
|
+
const hash = hashString(pkg);
|
|
4427
|
+
// Shades of green for terminal aesthetic
|
|
4428
|
+
const brightness = 40 + (hash % 40);
|
|
4429
|
+
colorCache[pkg] = 'rgb(0, ' + brightness + ', 0)';
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
return colorCache[pkg];
|
|
4433
|
+
}
|
|
4434
|
+
// Brighter green for local src files
|
|
4435
|
+
const depth = Math.min(d.depth, 4);
|
|
4436
|
+
const brightness = 80 + depth * 30;
|
|
4437
|
+
return 'rgb(0, ' + Math.min(255, brightness) + ', 0)';
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
const cell = svg.selectAll("g")
|
|
4441
|
+
.data(root.leaves())
|
|
4442
|
+
.enter()
|
|
4443
|
+
.append("g")
|
|
4444
|
+
.attr("transform", d => \`translate(\${d.x0},\${d.y0})\`);
|
|
4445
|
+
|
|
4446
|
+
cell.append("rect")
|
|
4447
|
+
.attr("class", "treemap-cell")
|
|
4448
|
+
.attr("width", d => d.x1 - d.x0)
|
|
4449
|
+
.attr("height", d => d.y1 - d.y0)
|
|
4450
|
+
.attr("fill", colorFor)
|
|
4451
|
+
.on("mouseover", function(event, d) {
|
|
4452
|
+
d3.select(this).attr("stroke", "#0f0").attr("stroke-width", 2);
|
|
4453
|
+
tooltip.select("#tooltip-name").text(d.data.name);
|
|
4454
|
+
tooltip.select("#tooltip-path").text(cleanPath(d.data.path || ''));
|
|
4455
|
+
tooltip.select("#tooltip-size").text(formatBytes(d.value));
|
|
4456
|
+
tooltip.classed("visible", true);
|
|
4457
|
+
})
|
|
4458
|
+
.on("mousemove", function(event) {
|
|
4459
|
+
tooltip.style("left", (event.pageX + 10) + "px")
|
|
4460
|
+
.style("top", (event.pageY - 10) + "px");
|
|
4461
|
+
})
|
|
4462
|
+
.on("mouseout", function() {
|
|
4463
|
+
d3.select(this).attr("stroke", "#111").attr("stroke-width", 1);
|
|
4464
|
+
tooltip.classed("visible", false);
|
|
4465
|
+
});
|
|
4466
|
+
|
|
4467
|
+
// Labels on cells - show first letter for small cells, truncated text for larger ones
|
|
4468
|
+
const labels = cell.filter(d => {
|
|
4469
|
+
const w = d.x1 - d.x0;
|
|
4470
|
+
const h = d.y1 - d.y0;
|
|
4471
|
+
// Show label if cell is at least 10px wide and 10px tall (enough for first letter)
|
|
4472
|
+
return w > 10 && h > 10;
|
|
4473
|
+
})
|
|
4474
|
+
.append("text")
|
|
4475
|
+
.attr("class", "treemap-label")
|
|
4476
|
+
.attr("x", d => (d.x1 - d.x0) / 2)
|
|
4477
|
+
.attr("y", d => (d.y1 - d.y0) / 2)
|
|
4478
|
+
.style("text-anchor", "middle")
|
|
4479
|
+
.style("dominant-baseline", "middle");
|
|
4480
|
+
|
|
4481
|
+
labels.each(function(d) {
|
|
4482
|
+
const name = d.data.name;
|
|
4483
|
+
const width = d.x1 - d.x0;
|
|
4484
|
+
const height = d.y1 - d.y0;
|
|
4485
|
+
const text = d3.select(this);
|
|
4486
|
+
|
|
4487
|
+
// Estimate character width: ~6px per character for 10px monospace font
|
|
4488
|
+
const charWidth = 6;
|
|
4489
|
+
const maxChars = Math.floor((width - 8) / charWidth); // 8px padding
|
|
4490
|
+
const minSize = Math.min(width, height);
|
|
4491
|
+
|
|
4492
|
+
let labelText = '';
|
|
4493
|
+
let fontSize = 10;
|
|
4494
|
+
|
|
4495
|
+
if (width < 20 || height < 14 || maxChars < 1) {
|
|
4496
|
+
// Very small cell - show first letter only, ensure it's readable
|
|
4497
|
+
labelText = name.charAt(0).toUpperCase();
|
|
4498
|
+
// Scale font to fit but ensure minimum 8px for readability
|
|
4499
|
+
fontSize = Math.max(8, Math.min(12, minSize * 0.65));
|
|
4500
|
+
} else if (width < 30 || maxChars < 2) {
|
|
4501
|
+
// Small cell - show first letter (or 2 chars if fits)
|
|
4502
|
+
labelText = name.substring(0, Math.min(1, name.length)).toUpperCase();
|
|
4503
|
+
fontSize = Math.max(8, Math.min(10, minSize * 0.5));
|
|
4504
|
+
} else if (width < 40 || maxChars < 3) {
|
|
4505
|
+
// Small-medium cell - show first 2-3 characters
|
|
4506
|
+
labelText = name.substring(0, Math.min(2, name.length));
|
|
4507
|
+
fontSize = 9;
|
|
4508
|
+
} else if (maxChars >= name.length) {
|
|
4509
|
+
// Large enough - show full name
|
|
4510
|
+
labelText = name;
|
|
4511
|
+
fontSize = 10;
|
|
4512
|
+
} else {
|
|
4513
|
+
// Medium cell - truncate based on available width
|
|
4514
|
+
const truncateLength = Math.max(3, maxChars - 3); // Leave room for "..."
|
|
4515
|
+
labelText = name.substring(0, truncateLength) + '...';
|
|
4516
|
+
fontSize = 10;
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
text.style("font-size", fontSize + "px")
|
|
4520
|
+
.text(labelText);
|
|
4521
|
+
|
|
4522
|
+
// Show size for larger cells
|
|
4523
|
+
if (width > 90 && height > 32) {
|
|
4524
|
+
text.append("tspan")
|
|
4525
|
+
.attr("x", width / 2)
|
|
4526
|
+
.attr("dy", "14")
|
|
4527
|
+
.text(formatBytes(d.value));
|
|
4528
|
+
}
|
|
4529
|
+
});
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
let currentSearchTerm = '';
|
|
4533
|
+
|
|
4534
|
+
function updateSearchStats(visibleCount, totalCount) {
|
|
4535
|
+
const stats = document.getElementById('search-stats');
|
|
4536
|
+
if (stats) {
|
|
4537
|
+
if (currentSearchTerm) {
|
|
4538
|
+
stats.textContent = 'Showing ' + visibleCount + ' of ' + totalCount + ' items matching "' + currentSearchTerm + '"';
|
|
4539
|
+
} else {
|
|
4540
|
+
stats.textContent = '';
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
function filterTreemap(searchTerm) {
|
|
4546
|
+
currentSearchTerm = searchTerm.toLowerCase();
|
|
4547
|
+
if (!searchTerm) {
|
|
4548
|
+
if (browserDepTree) renderDepTreemap(browserDepTree, "#browser-treemap");
|
|
4549
|
+
if (serverDepTree) renderDepTreemap(serverDepTree, "#server-treemap");
|
|
4550
|
+
updateSearchStats(0, 0);
|
|
4551
|
+
|
|
4552
|
+
// Show all elements
|
|
4553
|
+
document.querySelectorAll('.section').forEach(el => el.classList.remove('hidden'));
|
|
4554
|
+
document.querySelectorAll('tbody tr').forEach(el => el.classList.remove('hidden'));
|
|
4555
|
+
document.querySelectorAll('.dep-item').forEach(el => el.classList.remove('hidden'));
|
|
4556
|
+
document.querySelectorAll('.stat').forEach(el => el.classList.remove('hidden'));
|
|
4557
|
+
return;
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4560
|
+
let totalMatches = 0;
|
|
4561
|
+
let totalItems = 0;
|
|
4562
|
+
|
|
4563
|
+
// Filter treemap data
|
|
4564
|
+
function filterNode(node) {
|
|
4565
|
+
// Only match against the package/file name, not the full path
|
|
4566
|
+
// to avoid matching build directories like .reroute/
|
|
4567
|
+
const matches = node.name.toLowerCase().includes(searchTerm);
|
|
4568
|
+
|
|
4569
|
+
if (!node.children || node.children.length === 0) {
|
|
4570
|
+
return matches ? { ...node } : null;
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
const filteredChildren = node.children
|
|
4574
|
+
.map(child => filterNode(child))
|
|
4575
|
+
.filter(child => child !== null);
|
|
4576
|
+
|
|
4577
|
+
if (filteredChildren.length > 0 || matches) {
|
|
4578
|
+
return {
|
|
4579
|
+
...node,
|
|
4580
|
+
children: filteredChildren.length > 0 ? filteredChildren : undefined
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4584
|
+
return null;
|
|
4585
|
+
}
|
|
4586
|
+
|
|
4587
|
+
function countLeaves(node) {
|
|
4588
|
+
if (!node.children || node.children.length === 0) return 1;
|
|
4589
|
+
return node.children.reduce((sum, child) => sum + countLeaves(child), 0);
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
// Filter browser treemap
|
|
4593
|
+
if (browserDepTree) {
|
|
4594
|
+
const filtered = filterNode(browserDepTree.root);
|
|
4595
|
+
if (filtered) {
|
|
4596
|
+
const totalLeaves = countLeaves(browserDepTree.root);
|
|
4597
|
+
const visibleLeaves = countLeaves(filtered);
|
|
4598
|
+
totalItems += totalLeaves;
|
|
4599
|
+
totalMatches += visibleLeaves;
|
|
4600
|
+
renderDepTreemap({ ...browserDepTree, root: filtered }, "#browser-treemap");
|
|
4601
|
+
} else {
|
|
4602
|
+
const elem = document.getElementById('browser-treemap');
|
|
4603
|
+
if (elem) elem.innerHTML = '<div class="empty">No matches found</div>';
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
// Filter server treemap
|
|
4608
|
+
if (serverDepTree) {
|
|
4609
|
+
const filtered = filterNode(serverDepTree.root);
|
|
4610
|
+
if (filtered) {
|
|
4611
|
+
renderDepTreemap({ ...serverDepTree, root: filtered }, "#server-treemap");
|
|
4612
|
+
} else {
|
|
4613
|
+
const elem = document.getElementById('server-treemap');
|
|
4614
|
+
if (elem) elem.innerHTML = '<div class="empty">No matches found</div>';
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
// Render matching items that aren't loaded yet
|
|
4619
|
+
if (searchTerm) {
|
|
4620
|
+
renderMatchingDeps('browser-deps-list', searchTerm);
|
|
4621
|
+
renderMatchingDeps('server-deps-list', searchTerm);
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
// Filter dependency lists
|
|
4625
|
+
document.querySelectorAll('.dep-item').forEach(item => {
|
|
4626
|
+
const packageName = item.getAttribute('data-package-name') || '';
|
|
4627
|
+
if (packageName.includes(searchTerm)) {
|
|
4628
|
+
item.classList.remove('hidden');
|
|
4629
|
+
totalMatches++;
|
|
4630
|
+
} else {
|
|
4631
|
+
item.classList.add('hidden');
|
|
4632
|
+
}
|
|
4633
|
+
totalItems++;
|
|
4634
|
+
});
|
|
4635
|
+
|
|
4636
|
+
// Filter bundle/chunk tables
|
|
4637
|
+
document.querySelectorAll('tbody tr').forEach(row => {
|
|
4638
|
+
const text = row.textContent.toLowerCase();
|
|
4639
|
+
if (text.includes(searchTerm)) {
|
|
4640
|
+
row.classList.remove('hidden');
|
|
4641
|
+
totalMatches++;
|
|
4642
|
+
} else {
|
|
4643
|
+
row.classList.add('hidden');
|
|
4644
|
+
}
|
|
4645
|
+
totalItems++;
|
|
4646
|
+
});
|
|
4647
|
+
|
|
4648
|
+
// Filter section headers and content
|
|
4649
|
+
document.querySelectorAll('.section').forEach(section => {
|
|
4650
|
+
// Check if section has visible content
|
|
4651
|
+
const tbody = section.querySelector('tbody');
|
|
4652
|
+
const depList = section.querySelector('.dep-list');
|
|
4653
|
+
const treemapContainer = section.querySelector('.treemap-container');
|
|
4654
|
+
const stats = section.querySelectorAll('.stat');
|
|
4655
|
+
const sectionTitle = section.querySelector('.section-title');
|
|
4656
|
+
|
|
4657
|
+
let hasVisibleContent = false;
|
|
4658
|
+
|
|
4659
|
+
if (tbody) {
|
|
4660
|
+
const visibleRows = tbody.querySelectorAll('tr:not(.hidden)');
|
|
4661
|
+
hasVisibleContent = visibleRows.length > 0;
|
|
4662
|
+
} else if (depList) {
|
|
4663
|
+
const visibleItems = depList.querySelectorAll('.dep-item:not(.hidden)');
|
|
4664
|
+
hasVisibleContent = visibleItems.length > 0;
|
|
4665
|
+
} else if (treemapContainer) {
|
|
4666
|
+
hasVisibleContent = !treemapContainer.querySelector('.empty');
|
|
4667
|
+
} else if (stats.length > 0) {
|
|
4668
|
+
// Stat sections: show if any stat matches
|
|
4669
|
+
const statMatches = Array.from(stats).some(stat =>
|
|
4670
|
+
stat.textContent.toLowerCase().includes(searchTerm)
|
|
4671
|
+
);
|
|
4672
|
+
hasVisibleContent = statMatches;
|
|
4673
|
+
} else if (sectionTitle && sectionTitle.textContent.toLowerCase().includes(searchTerm)) {
|
|
4674
|
+
// Section title matches
|
|
4675
|
+
hasVisibleContent = true;
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
if (hasVisibleContent) {
|
|
4679
|
+
section.classList.remove('hidden');
|
|
4680
|
+
} else {
|
|
4681
|
+
section.classList.add('hidden');
|
|
4682
|
+
}
|
|
4683
|
+
});
|
|
4684
|
+
|
|
4685
|
+
updateSearchStats(totalMatches, totalItems);
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
// Setup search
|
|
4689
|
+
const searchInput = document.getElementById('search-input');
|
|
4690
|
+
const searchClear = document.getElementById('search-clear');
|
|
4691
|
+
|
|
4692
|
+
function updateClearButton() {
|
|
4693
|
+
if (searchClear && searchInput) {
|
|
4694
|
+
if (searchInput.value.trim().length > 0) {
|
|
4695
|
+
searchClear.classList.add('visible');
|
|
4696
|
+
} else {
|
|
4697
|
+
searchClear.classList.remove('visible');
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
if (searchInput) {
|
|
4703
|
+
searchInput.addEventListener('input', (e) => {
|
|
4704
|
+
filterTreemap(e.target.value);
|
|
4705
|
+
updateClearButton();
|
|
4706
|
+
});
|
|
4707
|
+
// Check initial state
|
|
4708
|
+
updateClearButton();
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
if (searchClear) {
|
|
4712
|
+
searchClear.addEventListener('click', () => {
|
|
4713
|
+
if (searchInput) {
|
|
4714
|
+
searchInput.value = '';
|
|
4715
|
+
searchInput.focus();
|
|
4716
|
+
filterTreemap('');
|
|
4717
|
+
updateClearButton();
|
|
4718
|
+
}
|
|
4719
|
+
});
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
// Expand/collapse all handlers
|
|
4723
|
+
const expandAllBtn = document.getElementById('expand-all');
|
|
4724
|
+
const collapseAllBtn = document.getElementById('collapse-all');
|
|
4725
|
+
|
|
4726
|
+
if (expandAllBtn) {
|
|
4727
|
+
expandAllBtn.addEventListener('click', () => {
|
|
4728
|
+
document.querySelectorAll('.section.collapsed').forEach(section => {
|
|
4729
|
+
section.classList.remove('collapsed');
|
|
4730
|
+
});
|
|
4731
|
+
});
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
if (collapseAllBtn) {
|
|
4735
|
+
collapseAllBtn.addEventListener('click', () => {
|
|
4736
|
+
document.querySelectorAll('.section:not(.collapsed)').forEach(section => {
|
|
4737
|
+
// Don't collapse the Summary section
|
|
4738
|
+
const title = section.querySelector('.section-title');
|
|
4739
|
+
if (title && !title.textContent.includes('Summary')) {
|
|
4740
|
+
section.classList.add('collapsed');
|
|
4741
|
+
}
|
|
4742
|
+
});
|
|
4743
|
+
});
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
// Store full packages data for search
|
|
4747
|
+
const depListData = new Map();
|
|
4748
|
+
|
|
4749
|
+
// Helper to create a dep item element
|
|
4750
|
+
function createDepItem(pkg, isServer, totalSize, bundleInfo) {
|
|
4751
|
+
const li = document.createElement('li');
|
|
4752
|
+
li.className = 'dep-item';
|
|
4753
|
+
li.setAttribute('data-package-name', pkg.name.toLowerCase());
|
|
4754
|
+
|
|
4755
|
+
const nameSpan = document.createElement('span');
|
|
4756
|
+
nameSpan.className = isServer ? 'dep-name server' : 'dep-name';
|
|
4757
|
+
nameSpan.textContent = pkg.name;
|
|
4758
|
+
|
|
4759
|
+
const sizeSpan = document.createElement('span');
|
|
4760
|
+
sizeSpan.className = 'dep-size';
|
|
4761
|
+
|
|
4762
|
+
// Estimate gzipped size using compression ratio
|
|
4763
|
+
let sizeText = formatBytes(pkg.size);
|
|
4764
|
+
if (bundleInfo && bundleInfo.size > 0) {
|
|
4765
|
+
const compressionRatio = bundleInfo.gzippedSize / bundleInfo.size;
|
|
4766
|
+
const estimatedGz = Math.round(pkg.size * compressionRatio);
|
|
4767
|
+
sizeText += ' (' + formatBytes(estimatedGz) + ' gz)';
|
|
4768
|
+
}
|
|
4769
|
+
sizeText += ' · ' + ((pkg.size / totalSize) * 100).toFixed(1) + '%';
|
|
4770
|
+
sizeSpan.textContent = sizeText;
|
|
4771
|
+
|
|
4772
|
+
li.appendChild(nameSpan);
|
|
4773
|
+
li.appendChild(sizeSpan);
|
|
4774
|
+
return li;
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4777
|
+
// Populate dependency lists
|
|
4778
|
+
function populateDepList(tree, containerId, isServer, bundleInfo) {
|
|
4779
|
+
const container = document.getElementById(containerId);
|
|
4780
|
+
if (!container || !tree) return;
|
|
4781
|
+
|
|
4782
|
+
const nodeModules = tree.root.children?.find(c => c.name === 'node_modules');
|
|
4783
|
+
if (!nodeModules || !nodeModules.children) return;
|
|
4784
|
+
|
|
4785
|
+
const packages = nodeModules.children
|
|
4786
|
+
.filter(pkg => pkg.size > 0)
|
|
4787
|
+
.sort((a, b) => b.size - a.size);
|
|
4788
|
+
|
|
4789
|
+
// Store full packages data for this container
|
|
4790
|
+
depListData.set(containerId, {
|
|
4791
|
+
packages: packages,
|
|
4792
|
+
isServer: isServer,
|
|
4793
|
+
totalSize: tree.totalSize,
|
|
4794
|
+
bundleInfo: bundleInfo,
|
|
4795
|
+
loadedCount: 0
|
|
4796
|
+
});
|
|
4797
|
+
|
|
4798
|
+
const ul = document.createElement('ul');
|
|
4799
|
+
ul.className = 'dep-list';
|
|
4800
|
+
|
|
4801
|
+
const initialCount = 10;
|
|
4802
|
+
for (let i = 0; i < Math.min(initialCount, packages.length); i++) {
|
|
4803
|
+
const li = createDepItem(packages[i], isServer, tree.totalSize, bundleInfo);
|
|
4804
|
+
ul.appendChild(li);
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
depListData.get(containerId).loadedCount = Math.min(initialCount, packages.length);
|
|
4808
|
+
|
|
4809
|
+
container.appendChild(ul);
|
|
4810
|
+
|
|
4811
|
+
if (packages.length > initialCount) {
|
|
4812
|
+
const btn = document.createElement('button');
|
|
4813
|
+
btn.className = 'load-more';
|
|
4814
|
+
btn.textContent = 'load ' + (packages.length - initialCount) + ' more...';
|
|
4815
|
+
btn.onclick = function() {
|
|
4816
|
+
const data = depListData.get(containerId);
|
|
4817
|
+
const existingItems = new Set();
|
|
4818
|
+
ul.querySelectorAll('.dep-item').forEach(item => {
|
|
4819
|
+
existingItems.add(item.getAttribute('data-package-name'));
|
|
4820
|
+
});
|
|
4821
|
+
|
|
4822
|
+
let loaded = 0;
|
|
4823
|
+
for (let i = 0; i < packages.length; i++) {
|
|
4824
|
+
const pkg = packages[i];
|
|
4825
|
+
const nameLower = pkg.name.toLowerCase();
|
|
4826
|
+
if (!existingItems.has(nameLower)) {
|
|
4827
|
+
const li = createDepItem(pkg, isServer, tree.totalSize, bundleInfo);
|
|
4828
|
+
ul.appendChild(li);
|
|
4829
|
+
loaded++;
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
data.loadedCount = packages.length;
|
|
4833
|
+
btn.remove();
|
|
4834
|
+
};
|
|
4835
|
+
container.appendChild(btn);
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
// Render matching items that aren't loaded yet
|
|
4840
|
+
function renderMatchingDeps(containerId, searchTerm) {
|
|
4841
|
+
const data = depListData.get(containerId);
|
|
4842
|
+
if (!data || !searchTerm) return;
|
|
4843
|
+
|
|
4844
|
+
const container = document.getElementById(containerId);
|
|
4845
|
+
if (!container) return;
|
|
4846
|
+
|
|
4847
|
+
const ul = container.querySelector('.dep-list');
|
|
4848
|
+
if (!ul) return;
|
|
4849
|
+
|
|
4850
|
+
const searchLower = searchTerm.toLowerCase();
|
|
4851
|
+
const existingItems = new Set();
|
|
4852
|
+
ul.querySelectorAll('.dep-item').forEach(item => {
|
|
4853
|
+
existingItems.add(item.getAttribute('data-package-name'));
|
|
4854
|
+
});
|
|
4855
|
+
|
|
4856
|
+
// Find matching packages that aren't rendered yet
|
|
4857
|
+
const matchingPackages = data.packages.filter((pkg) => {
|
|
4858
|
+
const nameLower = pkg.name.toLowerCase();
|
|
4859
|
+
const matches = nameLower.includes(searchLower);
|
|
4860
|
+
const notRendered = !existingItems.has(nameLower);
|
|
4861
|
+
return matches && notRendered;
|
|
4862
|
+
});
|
|
4863
|
+
|
|
4864
|
+
// Render matching items
|
|
4865
|
+
matchingPackages.forEach(pkg => {
|
|
4866
|
+
const li = createDepItem(pkg, data.isServer, data.totalSize, data.bundleInfo);
|
|
4867
|
+
ul.appendChild(li);
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
// Initial render
|
|
4872
|
+
const browserInfo = ${JSON.stringify(browserInfo)};
|
|
4873
|
+
const serverInfo = ${JSON.stringify(mainInfo)};
|
|
4874
|
+
|
|
4875
|
+
if (browserDepTree) {
|
|
4876
|
+
populateDepList(browserDepTree, 'browser-deps-list', false, browserInfo);
|
|
4877
|
+
renderDepTreemap(browserDepTree, "#browser-treemap");
|
|
4878
|
+
}
|
|
4879
|
+
if (serverDepTree) {
|
|
4880
|
+
populateDepList(serverDepTree, 'server-deps-list', true, serverInfo);
|
|
4881
|
+
renderDepTreemap(serverDepTree, "#server-treemap");
|
|
4882
|
+
}
|
|
4883
|
+
|
|
4884
|
+
window.addEventListener("resize", () => {
|
|
4885
|
+
if (currentSearchTerm) {
|
|
4886
|
+
filterTreemap(currentSearchTerm);
|
|
4887
|
+
} else {
|
|
4888
|
+
if (browserDepTree) renderDepTreemap(browserDepTree, "#browser-treemap");
|
|
4889
|
+
if (serverDepTree) renderDepTreemap(serverDepTree, "#server-treemap");
|
|
4890
|
+
}
|
|
4891
|
+
});
|
|
4892
|
+
|
|
4893
|
+
// Apply initial search if provided
|
|
4894
|
+
const initialSearchTerm = ${JSON.stringify(initialSearch || "")};
|
|
4895
|
+
if (initialSearchTerm && searchInput) {
|
|
4896
|
+
searchInput.value = initialSearchTerm;
|
|
4897
|
+
filterTreemap(initialSearchTerm);
|
|
4898
|
+
updateClearButton();
|
|
4899
|
+
}
|
|
4900
|
+
</script>
|
|
4901
|
+
` : ""}
|
|
4902
|
+
|
|
4903
|
+
<div class="footer">
|
|
4904
|
+
<div class="search-stats" id="search-stats"></div>
|
|
4905
|
+
<div class="note">
|
|
4906
|
+
<strong>Browser</strong> bundles are shipped to users. <strong>Server</strong> binaries run on your server only.
|
|
4907
|
+
</div>
|
|
4908
|
+
${mainInfo ? `
|
|
4909
|
+
<div class="note" style="margin-top: 0.5rem;">
|
|
4910
|
+
<strong>Final Server Binary</strong> is a standalone executable that includes the Bun runtime, your application code, and all dependencies. This allows it to run without requiring Node.js or Bun to be installed on the deployment server.
|
|
4911
|
+
</div>
|
|
4912
|
+
` : ""}
|
|
4913
|
+
</div>
|
|
4914
|
+
</div>
|
|
4915
|
+
</body>
|
|
4916
|
+
</html>`;
|
|
4917
|
+
return html2;
|
|
4918
|
+
}
|
|
4919
|
+
async function printConsoleReport(stats, depTree, mainInfo, browserDepTree, browserInfo, fullOutput = false) {
|
|
4920
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Bundle Analysis Report
|
|
4921
|
+
`));
|
|
4922
|
+
if (mainInfo || browserInfo) {
|
|
4923
|
+
console.log(`\x1B[90mNote: Browser bundles are shipped to users. Server binaries run on your server only.\x1B[0m
|
|
4924
|
+
`);
|
|
4925
|
+
}
|
|
4926
|
+
if (browserInfo) {
|
|
4927
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
|
|
4928
|
+
console.log(`\x1B[1m\x1B[36mBrowser Bundle: ${browserInfo.name}\x1B[0m \x1B[32m(shipped to browser)\x1B[0m`);
|
|
4929
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
|
|
4930
|
+
`);
|
|
4931
|
+
const browserTable = new Table({
|
|
4932
|
+
head: ["Metric", "Value"],
|
|
4933
|
+
colWidths: [25, 35],
|
|
4934
|
+
style: {
|
|
4935
|
+
head: ["cyan", "bold"],
|
|
4936
|
+
border: ["gray"]
|
|
4937
|
+
},
|
|
4938
|
+
chars: ASCII_TABLE_CHARS
|
|
4939
|
+
});
|
|
4940
|
+
browserTable.push(["Raw Size", formatBytes(browserInfo.size)], ["Gzipped", formatBytes(browserInfo.gzippedSize)], [
|
|
4941
|
+
"Compression Ratio",
|
|
4942
|
+
`${((1 - browserInfo.gzippedSize / browserInfo.size) * 100).toFixed(1)}%`
|
|
4943
|
+
]);
|
|
4944
|
+
console.log(browserTable.toString());
|
|
4945
|
+
console.log("");
|
|
4946
|
+
const gzipKB = browserInfo.gzippedSize / 1024;
|
|
4947
|
+
const time3G = gzipKB / 204.8;
|
|
4948
|
+
const time4G = gzipKB / 1152;
|
|
4949
|
+
const time5G = gzipKB / 6400;
|
|
4950
|
+
const timeWiFi = gzipKB / 12800;
|
|
4951
|
+
const transferTable = new Table({
|
|
4952
|
+
head: ["Network", "Throughput", "Transfer Time"],
|
|
4953
|
+
colWidths: [15, 15, 20],
|
|
4954
|
+
style: {
|
|
4955
|
+
head: ["cyan", "bold"],
|
|
4956
|
+
border: ["gray"]
|
|
4957
|
+
},
|
|
4958
|
+
chars: ASCII_TABLE_CHARS
|
|
4959
|
+
});
|
|
4960
|
+
transferTable.push([
|
|
4961
|
+
"3G Fast",
|
|
4962
|
+
"1.6 Mbps",
|
|
4963
|
+
time3G >= 1 ? `~${time3G.toFixed(2)}s` : `~${(time3G * 1000).toFixed(0)}ms`
|
|
4964
|
+
], [
|
|
4965
|
+
"4G",
|
|
4966
|
+
"9 Mbps",
|
|
4967
|
+
time4G >= 1 ? `~${time4G.toFixed(2)}s` : `~${(time4G * 1000).toFixed(0)}ms`
|
|
4968
|
+
], [
|
|
4969
|
+
"5G",
|
|
4970
|
+
"50 Mbps",
|
|
4971
|
+
time5G >= 1 ? `~${time5G.toFixed(2)}s` : `~${(time5G * 1000).toFixed(0)}ms`
|
|
4972
|
+
], [
|
|
4973
|
+
"Wi-Fi 100",
|
|
4974
|
+
"100 Mbps",
|
|
4975
|
+
timeWiFi >= 1 ? `~${timeWiFi.toFixed(2)}s` : `~${(timeWiFi * 1000).toFixed(0)}ms`
|
|
4976
|
+
]);
|
|
4977
|
+
console.log(transferTable.toString());
|
|
4978
|
+
console.log(`\x1B[90m Note: Estimates use gzipped size, ignore latency/HTTP overhead\x1B[0m
|
|
4979
|
+
`);
|
|
4980
|
+
}
|
|
4981
|
+
if (browserDepTree) {
|
|
4982
|
+
const nodeModules = browserDepTree.root.children?.find((c) => c.name === "node_modules");
|
|
4983
|
+
const src2 = browserDepTree.root.children?.find((c) => c.name === "src");
|
|
4984
|
+
if (nodeModules || src2) {
|
|
4985
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
|
|
4986
|
+
console.log("\x1B[1m\x1B[36mBrowser Dependency Breakdown\x1B[0m \x1B[32m(shipped to browser)\x1B[0m");
|
|
4987
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
|
|
4988
|
+
`);
|
|
4989
|
+
if (nodeModules && nodeModules.size > 0) {
|
|
4990
|
+
const nmPercent = (nodeModules.size / browserDepTree.totalSize * 100).toFixed(1);
|
|
4991
|
+
console.log(`\x1B[1mnode_modules: ${formatBytes(nodeModules.size)} (${nmPercent}%)\x1B[0m
|
|
4992
|
+
`);
|
|
4993
|
+
const packages = (nodeModules.children || []).filter((pkg) => pkg.size > 0).sort((a, b) => b.size - a.size);
|
|
4994
|
+
if (packages.length > 0) {
|
|
4995
|
+
const pkgTable = new Table({
|
|
4996
|
+
head: ["Package / File", "Raw Size", "Gzipped", "% of Total"],
|
|
4997
|
+
colWidths: [40, 15, 15, 12],
|
|
4998
|
+
style: {
|
|
4999
|
+
head: ["green", "bold"],
|
|
5000
|
+
border: ["gray"]
|
|
5001
|
+
},
|
|
5002
|
+
chars: ASCII_TABLE_CHARS
|
|
5003
|
+
});
|
|
5004
|
+
const packagesToShow = fullOutput ? packages : packages.filter((pkg) => pkg.size / browserDepTree.totalSize >= 0.01);
|
|
5005
|
+
for (const pkg of packagesToShow) {
|
|
5006
|
+
const percentage = (pkg.size / browserDepTree.totalSize * 100).toFixed(1);
|
|
5007
|
+
const pkgEstimatedGz = Math.round(pkg.size * (browserInfo?.gzippedSize || 0) / (browserInfo?.size || 1));
|
|
5008
|
+
pkgTable.push([
|
|
5009
|
+
`\x1B[1m${pkg.name}\x1B[0m`,
|
|
5010
|
+
formatBytes(pkg.size),
|
|
5011
|
+
formatBytes(pkgEstimatedGz),
|
|
5012
|
+
`${percentage}%`
|
|
5013
|
+
]);
|
|
5014
|
+
const files = (pkg.children || []).filter((f) => f.size > 0).sort((a, b) => b.size - a.size);
|
|
5015
|
+
const filesToShow = fullOutput ? files : files.slice(0, 10);
|
|
5016
|
+
for (const file of filesToShow) {
|
|
5017
|
+
const filePercent = (file.size / browserDepTree.totalSize * 100).toFixed(1);
|
|
5018
|
+
const estimatedGz = Math.round(file.size * (browserInfo?.gzippedSize || 0) / (browserInfo?.size || 1));
|
|
5019
|
+
pkgTable.push([
|
|
5020
|
+
` ${String.fromCharCode(8627)} ${file.name}`,
|
|
5021
|
+
formatBytes(file.size),
|
|
5022
|
+
formatBytes(estimatedGz),
|
|
5023
|
+
`${filePercent}%`
|
|
5024
|
+
]);
|
|
5025
|
+
}
|
|
5026
|
+
if (!fullOutput && files.length > 10) {
|
|
5027
|
+
pkgTable.push([
|
|
5028
|
+
` ... ${files.length - 10} more files`,
|
|
5029
|
+
"",
|
|
5030
|
+
"",
|
|
5031
|
+
""
|
|
5032
|
+
]);
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
console.log(pkgTable.toString());
|
|
5036
|
+
console.log("");
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
if (src2 && src2.size > 0) {
|
|
5040
|
+
const srcPercent = (src2.size / browserDepTree.totalSize * 100).toFixed(1);
|
|
5041
|
+
console.log(`\x1B[1msrc: ${formatBytes(src2.size)} (${srcPercent}%)\x1B[0m
|
|
5042
|
+
`);
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
const browserChunks = Object.entries(stats.byCollection).filter(([collection]) => collection !== "binaries");
|
|
5047
|
+
if (browserChunks.length > 0) {
|
|
5048
|
+
for (const [collection, bundles] of browserChunks) {
|
|
5049
|
+
const collectionTotal = bundles.reduce((sum, b) => sum + b.size, 0);
|
|
5050
|
+
const collectionGzipped = bundles.reduce((sum, b) => sum + b.gzippedSize, 0);
|
|
5051
|
+
console.log(`\x1B[1mBrowser Chunks: ${collection}\x1B[0m \x1B[32m(shipped to browser)\x1B[0m`);
|
|
5052
|
+
console.log(` Total: ${formatBytes(collectionTotal)} (${formatBytes(collectionGzipped)} gzipped)
|
|
5053
|
+
`);
|
|
5054
|
+
const sorted = bundles.sort((a, b) => b.size - a.size);
|
|
5055
|
+
const bundleTable = new Table({
|
|
5056
|
+
head: ["Bundle", "Size", "Gzipped", "% of Total"],
|
|
5057
|
+
colWidths: [40, 12, 12, 12],
|
|
5058
|
+
style: {
|
|
5059
|
+
head: ["green", "bold"],
|
|
5060
|
+
border: ["gray"]
|
|
5061
|
+
},
|
|
5062
|
+
chars: ASCII_TABLE_CHARS
|
|
5063
|
+
});
|
|
5064
|
+
for (const bundle of sorted) {
|
|
5065
|
+
const percentage = (bundle.size / stats.totalSize * 100).toFixed(1);
|
|
5066
|
+
bundleTable.push([
|
|
5067
|
+
bundle.name,
|
|
5068
|
+
formatBytes(bundle.size),
|
|
5069
|
+
formatBytes(bundle.gzippedSize),
|
|
5070
|
+
`${percentage}%`
|
|
5071
|
+
]);
|
|
5072
|
+
}
|
|
5073
|
+
console.log(bundleTable.toString());
|
|
5074
|
+
console.log("");
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
if (mainInfo) {
|
|
5078
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
|
|
5079
|
+
console.log(`\x1B[1m\x1B[36mServer Binary: ${mainInfo.name}\x1B[0m \x1B[90m(not shipped to browser)\x1B[0m`);
|
|
5080
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
|
|
5081
|
+
`);
|
|
5082
|
+
const mainTable = new Table({
|
|
5083
|
+
head: ["Metric", "Value"],
|
|
5084
|
+
colWidths: [25, 35],
|
|
5085
|
+
style: {
|
|
5086
|
+
head: ["cyan", "bold"],
|
|
5087
|
+
border: ["gray"]
|
|
5088
|
+
},
|
|
5089
|
+
chars: ASCII_TABLE_CHARS
|
|
5090
|
+
});
|
|
5091
|
+
mainTable.push(["Binary Size", formatBytes(mainInfo.size)]);
|
|
5092
|
+
console.log(mainTable.toString());
|
|
5093
|
+
console.log("");
|
|
5094
|
+
}
|
|
5095
|
+
if (depTree) {
|
|
5096
|
+
const nodeModules = depTree.root.children?.find((c) => c.name === "node_modules");
|
|
5097
|
+
const src2 = depTree.root.children?.find((c) => c.name === "src");
|
|
5098
|
+
if (nodeModules || src2) {
|
|
5099
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
|
|
5100
|
+
console.log("\x1B[1m\x1B[36mServer Dependency Breakdown\x1B[0m \x1B[90m(server-side only)\x1B[0m");
|
|
5101
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
|
|
5102
|
+
`);
|
|
5103
|
+
if (nodeModules && nodeModules.size > 0) {
|
|
5104
|
+
const nmPercent = (nodeModules.size / depTree.totalSize * 100).toFixed(1);
|
|
5105
|
+
console.log(`\x1B[1mnode_modules: ${formatBytes(nodeModules.size)} (${nmPercent}%)\x1B[0m
|
|
5106
|
+
`);
|
|
5107
|
+
const packages = (nodeModules.children || []).filter((pkg) => pkg.size > 0).sort((a, b) => b.size - a.size);
|
|
5108
|
+
if (packages.length > 0) {
|
|
5109
|
+
const pkgTable = new Table({
|
|
5110
|
+
head: ["Package / File", "Size", "% of Total"],
|
|
5111
|
+
colWidths: [50, 20, 12],
|
|
5112
|
+
style: {
|
|
5113
|
+
head: ["red", "bold"],
|
|
5114
|
+
border: ["gray"]
|
|
5115
|
+
},
|
|
5116
|
+
chars: ASCII_TABLE_CHARS
|
|
5117
|
+
});
|
|
5118
|
+
const packagesToShow = fullOutput ? packages : packages.filter((pkg) => pkg.size / depTree.totalSize >= 0.01);
|
|
5119
|
+
for (const pkg of packagesToShow) {
|
|
5120
|
+
const percentage = (pkg.size / depTree.totalSize * 100).toFixed(1);
|
|
5121
|
+
pkgTable.push([
|
|
5122
|
+
`\x1B[1m${pkg.name}\x1B[0m`,
|
|
5123
|
+
formatBytes(pkg.size),
|
|
5124
|
+
`${percentage}%`
|
|
5125
|
+
]);
|
|
5126
|
+
const files = (pkg.children || []).filter((f) => f.size > 0).sort((a, b) => b.size - a.size);
|
|
5127
|
+
const filesToShow = fullOutput ? files : files.slice(0, 10);
|
|
5128
|
+
for (const file of filesToShow) {
|
|
5129
|
+
const filePercent = (file.size / depTree.totalSize * 100).toFixed(1);
|
|
5130
|
+
pkgTable.push([
|
|
5131
|
+
` ${String.fromCharCode(8627)} ${file.name}`,
|
|
5132
|
+
formatBytes(file.size),
|
|
5133
|
+
`${filePercent}%`
|
|
5134
|
+
]);
|
|
5135
|
+
}
|
|
5136
|
+
if (!fullOutput && files.length > 10) {
|
|
5137
|
+
pkgTable.push([` ... ${files.length - 10} more files`, "", ""]);
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
5140
|
+
console.log(pkgTable.toString());
|
|
5141
|
+
console.log("");
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
if (src2 && src2.size > 0) {
|
|
5145
|
+
const srcPercent = (src2.size / depTree.totalSize * 100).toFixed(1);
|
|
5146
|
+
console.log(`\x1B[1msrc: ${formatBytes(src2.size)} (${srcPercent}%)\x1B[0m
|
|
5147
|
+
`);
|
|
5148
|
+
const srcItems = (src2.children || []).filter((item) => item.size > 0).sort((a, b) => b.size - a.size);
|
|
5149
|
+
if (srcItems.length > 0) {
|
|
5150
|
+
const srcTable = new Table({
|
|
5151
|
+
head: ["Source", "Size", "% of Total"],
|
|
5152
|
+
colWidths: [45, 12, 12],
|
|
5153
|
+
style: {
|
|
5154
|
+
head: ["green", "bold"],
|
|
5155
|
+
border: ["gray"]
|
|
5156
|
+
},
|
|
5157
|
+
chars: ASCII_TABLE_CHARS
|
|
5158
|
+
});
|
|
5159
|
+
for (const item of srcItems) {
|
|
5160
|
+
const percentage = (item.size / depTree.totalSize * 100).toFixed(1);
|
|
5161
|
+
srcTable.push([
|
|
5162
|
+
item.name,
|
|
5163
|
+
formatBytes(item.size),
|
|
5164
|
+
`${percentage}%`
|
|
5165
|
+
]);
|
|
5166
|
+
}
|
|
5167
|
+
console.log(srcTable.toString());
|
|
5168
|
+
console.log("");
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
|
|
5174
|
+
console.log("\x1B[1m\x1B[36mSummary\x1B[0m");
|
|
5175
|
+
console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
|
|
5176
|
+
`);
|
|
5177
|
+
const summaryTable = new Table({
|
|
5178
|
+
head: ["Metric", "Value"],
|
|
5179
|
+
colWidths: [25, 35],
|
|
5180
|
+
style: {
|
|
5181
|
+
head: ["cyan", "bold"],
|
|
5182
|
+
border: ["gray"]
|
|
5183
|
+
},
|
|
5184
|
+
chars: ASCII_TABLE_CHARS
|
|
5185
|
+
});
|
|
5186
|
+
const browserNodeModules = browserDepTree?.root.children?.find((c) => c.name === "node_modules");
|
|
5187
|
+
const browserDepCount = browserNodeModules?.children?.length || 0;
|
|
5188
|
+
const serverNodeModules = depTree?.root.children?.find((c) => c.name === "node_modules");
|
|
5189
|
+
const serverDepCount = serverNodeModules?.children?.length || 0;
|
|
5190
|
+
let actualServerBinarySize = null;
|
|
5191
|
+
let actualServerBinaryGzipped = null;
|
|
5192
|
+
try {
|
|
5193
|
+
const distDir = join12(process.cwd(), "dist");
|
|
5194
|
+
if (existsSync9(distDir)) {
|
|
5195
|
+
const files = await readdir6(distDir);
|
|
5196
|
+
for (const file of files) {
|
|
5197
|
+
const filePath = join12(distDir, file);
|
|
5198
|
+
const stats2 = statSync(filePath);
|
|
5199
|
+
if (stats2.isFile() && !file.includes(".")) {
|
|
5200
|
+
actualServerBinarySize = stats2.size;
|
|
5201
|
+
const content2 = await readFile2(filePath);
|
|
5202
|
+
actualServerBinaryGzipped = Bun.gzipSync(content2).length;
|
|
5203
|
+
break;
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
} catch {}
|
|
5208
|
+
const browserBundleDisplay = browserInfo ? `${formatBytes(browserInfo.size)} (${formatBytes(browserInfo.gzippedSize)} gz)` : "N/A";
|
|
5209
|
+
const serverBinaryDisplay = actualServerBinarySize ? `${formatBytes(actualServerBinarySize)}${actualServerBinaryGzipped ? ` (${formatBytes(actualServerBinaryGzipped)} gz)` : ""}` : mainInfo ? formatBytes(mainInfo.size) : "N/A";
|
|
5210
|
+
summaryTable.push(["Browser Dependencies", browserDepCount.toString()], ["Browser Bundle", browserBundleDisplay], ["Server Dependencies", serverDepCount.toString()], ["Final Server Binary", serverBinaryDisplay]);
|
|
5211
|
+
console.log(summaryTable.toString());
|
|
5212
|
+
console.log("");
|
|
5213
|
+
if (actualServerBinarySize) {
|
|
5214
|
+
console.log("\x1B[90mNote: Final Server Binary is a standalone executable that includes the Bun runtime,\x1B[0m");
|
|
5215
|
+
console.log("\x1B[90m your application code, and all dependencies. This allows it to run without\x1B[0m");
|
|
5216
|
+
console.log("\x1B[90m requiring Node.js or Bun to be installed on the deployment server.\x1B[0m");
|
|
5217
|
+
console.log("");
|
|
5218
|
+
}
|
|
5219
|
+
if (stats.bundles.length === 0) {
|
|
5220
|
+
console.log(colorizeLogPrefixAnsi("[reroute/analyze] No bundles found. Run `reroute build` first. For production builds, use NODE_ENV=production or --prod flag.\n"));
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
async function analyze(args) {
|
|
5224
|
+
try {
|
|
5225
|
+
const cwd = process.cwd();
|
|
5226
|
+
const outputHtml = args.includes("--html");
|
|
5227
|
+
const openBrowser = args.includes("--open") || args.includes("-o");
|
|
5228
|
+
const isProd = isProductionMode(args);
|
|
5229
|
+
const searchIndex = args.indexOf("--search");
|
|
5230
|
+
const initialSearch = searchIndex !== -1 && args[searchIndex + 1] ? args[searchIndex + 1] : "";
|
|
5231
|
+
const rerouteIndexPath = join12(cwd, ".reroute", "index.ts");
|
|
5232
|
+
if (!existsSync9(rerouteIndexPath)) {
|
|
5233
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] .reroute artifacts not found, running build...
|
|
5234
|
+
`));
|
|
5235
|
+
const { spawn: spawn5 } = await import("node:child_process");
|
|
5236
|
+
const { getRerouteCommand: getRerouteCommand5 } = await Promise.resolve().then(() => (init_command(), exports_command));
|
|
5237
|
+
const rerouteCmd = getRerouteCommand5();
|
|
5238
|
+
const useShell = rerouteCmd.includes(" ");
|
|
5239
|
+
const buildCommand = useShell ? `${rerouteCmd} build` : rerouteCmd;
|
|
5240
|
+
const buildArgs = useShell ? [] : ["build"];
|
|
5241
|
+
await new Promise((resolve, reject) => {
|
|
5242
|
+
const buildProcess = spawn5(buildCommand, buildArgs, {
|
|
5243
|
+
stdio: "inherit",
|
|
5244
|
+
shell: useShell
|
|
5245
|
+
});
|
|
5246
|
+
buildProcess.on("exit", (code) => {
|
|
5247
|
+
if (code !== null && code !== 0) {
|
|
5248
|
+
reject(new Error(`Build exited with code ${code}`));
|
|
5249
|
+
} else {
|
|
5250
|
+
resolve();
|
|
5251
|
+
}
|
|
5252
|
+
});
|
|
5253
|
+
buildProcess.on("error", (error) => {
|
|
5254
|
+
reject(error);
|
|
5255
|
+
});
|
|
5256
|
+
});
|
|
5257
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Build complete
|
|
5258
|
+
`));
|
|
5259
|
+
}
|
|
5260
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Scanning for bundles...
|
|
5261
|
+
`));
|
|
5262
|
+
const bundles = await findBundles(cwd);
|
|
5263
|
+
const stats = calculateStats(bundles);
|
|
5264
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building browser entrypoint for analysis...
|
|
5265
|
+
`));
|
|
5266
|
+
const browserEntry = await findBrowserEntry(cwd);
|
|
5267
|
+
let browserDepTree = null;
|
|
5268
|
+
let browserInfo = null;
|
|
5269
|
+
if (browserEntry) {
|
|
5270
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Analyzing browser bundle: ${relative(cwd, browserEntry)}
|
|
5271
|
+
`));
|
|
5272
|
+
browserDepTree = await buildDependencyTree(browserEntry, cwd);
|
|
5273
|
+
if (!browserDepTree) {
|
|
5274
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Could not build dependency tree from browser bundle. Sourcemap may be missing or invalid.
|
|
5275
|
+
`));
|
|
5276
|
+
}
|
|
5277
|
+
try {
|
|
5278
|
+
const browserStat = await stat5(browserEntry);
|
|
5279
|
+
const browserContent = await readFile2(browserEntry);
|
|
5280
|
+
const browserGzipped = Bun.gzipSync(browserContent).length;
|
|
5281
|
+
browserInfo = {
|
|
5282
|
+
name: basename3(browserEntry),
|
|
5283
|
+
size: browserStat.size,
|
|
5284
|
+
gzippedSize: browserGzipped
|
|
5285
|
+
};
|
|
5286
|
+
} catch {}
|
|
5287
|
+
} else {
|
|
5288
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Could not build browser entrypoint for analysis. Browser dependency breakdown will not be available.
|
|
5289
|
+
`));
|
|
5290
|
+
}
|
|
5291
|
+
let entry = null;
|
|
5292
|
+
let depTree = null;
|
|
5293
|
+
let mainInfo = null;
|
|
5294
|
+
const srcEntry = join12(cwd, "src", "index.ts");
|
|
5295
|
+
if (existsSync9(srcEntry)) {
|
|
5296
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building server binary for analysis...
|
|
5297
|
+
`));
|
|
5298
|
+
const { mkdir: mkdir4 } = await import("node:fs/promises");
|
|
5299
|
+
const serverAnalysisDir = join12(cwd, ".reroute", "server");
|
|
5300
|
+
await mkdir4(serverAnalysisDir, { recursive: true });
|
|
5301
|
+
const result = await Bun.build({
|
|
5302
|
+
entrypoints: [srcEntry],
|
|
5303
|
+
target: "bun",
|
|
5304
|
+
minify: isProd,
|
|
5305
|
+
sourcemap: "linked",
|
|
5306
|
+
external: [],
|
|
5307
|
+
splitting: false
|
|
5308
|
+
});
|
|
5309
|
+
if (result.success && result.outputs.length > 0) {
|
|
5310
|
+
const output = result.outputs[0];
|
|
5311
|
+
const code = await output.text();
|
|
5312
|
+
const analysisEntry = join12(serverAnalysisDir, "index.js");
|
|
5313
|
+
await writeFile4(analysisEntry, code, "utf-8");
|
|
5314
|
+
const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
|
|
5315
|
+
if (mapOutput) {
|
|
5316
|
+
const mapContent = await mapOutput.text();
|
|
5317
|
+
await writeFile4(`${analysisEntry}.map`, mapContent, "utf-8");
|
|
5318
|
+
}
|
|
5319
|
+
entry = analysisEntry;
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
if (entry) {
|
|
5323
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building dependency graph for ${relative(cwd, entry)}
|
|
5324
|
+
`));
|
|
5325
|
+
depTree = await buildDependencyTree(entry, cwd);
|
|
5326
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Checking installed package sizes...
|
|
5327
|
+
`));
|
|
5328
|
+
const installedSizes = await getInstalledPackageSizes(cwd);
|
|
5329
|
+
if (depTree) {
|
|
5330
|
+
const packageJsonPath = join12(cwd, "package.json");
|
|
5331
|
+
if (existsSync9(packageJsonPath)) {
|
|
5332
|
+
try {
|
|
5333
|
+
const packageJson = JSON.parse(await readFile2(packageJsonPath, "utf-8"));
|
|
5334
|
+
const runtimeDeps = packageJson.dependencies || {};
|
|
5335
|
+
const nodeModulesNode = depTree.root.children?.find((c) => c.name === "node_modules");
|
|
5336
|
+
if (nodeModulesNode) {
|
|
5337
|
+
const trackedPkgs = new Set(nodeModulesNode.children?.map((c) => c.name) || []);
|
|
5338
|
+
for (const pkgName of Object.keys(runtimeDeps)) {
|
|
5339
|
+
if (!trackedPkgs.has(pkgName) && installedSizes.has(pkgName)) {
|
|
5340
|
+
nodeModulesNode.children?.push({
|
|
5341
|
+
name: pkgName,
|
|
5342
|
+
path: join12(cwd, "node_modules", pkgName),
|
|
5343
|
+
size: installedSizes.get(pkgName) || 0
|
|
5344
|
+
});
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
nodeModulesNode.size = nodeModulesNode.children?.reduce((sum, c) => {
|
|
5348
|
+
if (c.children && c.children.length > 0) {
|
|
5349
|
+
c.size = c.children.reduce((s, child) => s + child.size, 0);
|
|
5350
|
+
}
|
|
5351
|
+
return sum + c.size;
|
|
5352
|
+
}, 0) || 0;
|
|
5353
|
+
depTree.root.size = depTree.root.children?.reduce((sum, c) => sum + c.size, 0) || 0;
|
|
5354
|
+
depTree.totalSize = depTree.root.size;
|
|
5355
|
+
}
|
|
5356
|
+
} catch (_e) {}
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
try {
|
|
5360
|
+
const entryStat = await stat5(entry);
|
|
5361
|
+
const entryContent = await readFile2(entry);
|
|
5362
|
+
const entryGzipped = Bun.gzipSync(entryContent).length;
|
|
5363
|
+
mainInfo = {
|
|
5364
|
+
name: basename3(entry),
|
|
5365
|
+
size: entryStat.size,
|
|
5366
|
+
gzippedSize: entryGzipped
|
|
5367
|
+
};
|
|
5368
|
+
} catch {}
|
|
5369
|
+
}
|
|
5370
|
+
const fullOutput = args.includes("--full");
|
|
5371
|
+
await printConsoleReport(stats, depTree, mainInfo, browserDepTree, browserInfo, fullOutput);
|
|
5372
|
+
if (outputHtml || openBrowser) {
|
|
5373
|
+
const htmlPath = join12(cwd, ".reroute", "analysis.html");
|
|
5374
|
+
const html2 = await generateHTMLReport(stats, depTree, mainInfo, browserDepTree, browserInfo, initialSearch);
|
|
5375
|
+
const { mkdir: mkdir4 } = await import("node:fs/promises");
|
|
5376
|
+
await mkdir4(join12(cwd, ".reroute"), { recursive: true });
|
|
5377
|
+
await writeFile4(htmlPath, html2, "utf-8");
|
|
5378
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] HTML report generated: ${relative(cwd, htmlPath)}
|
|
5379
|
+
`));
|
|
5380
|
+
if (openBrowser) {
|
|
5381
|
+
try {
|
|
5382
|
+
const { spawn: spawn5 } = await import("node:child_process");
|
|
5383
|
+
const platform = process.platform;
|
|
5384
|
+
let command2;
|
|
5385
|
+
let commandArgs;
|
|
5386
|
+
if (platform === "darwin") {
|
|
5387
|
+
command2 = "open";
|
|
5388
|
+
commandArgs = [htmlPath];
|
|
5389
|
+
} else if (platform === "win32") {
|
|
5390
|
+
command2 = "cmd";
|
|
5391
|
+
commandArgs = ["/c", "start", "", htmlPath];
|
|
5392
|
+
} else {
|
|
5393
|
+
command2 = "xdg-open";
|
|
5394
|
+
commandArgs = [htmlPath];
|
|
5395
|
+
}
|
|
5396
|
+
spawn5(command2, commandArgs, { detached: true, stdio: "ignore" });
|
|
5397
|
+
console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Opened report in browser
|
|
5398
|
+
`));
|
|
5399
|
+
} catch {
|
|
5400
|
+
console.warn(colorizeLogPrefixAnsi(`[reroute/analyze] Could not open browser automatically. Please open ${htmlPath} manually.
|
|
5401
|
+
`));
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
} catch (error) {
|
|
5406
|
+
console.error(colorizeLogPrefixAnsi(`[reroute/analyze] Error: ${String(error)}
|
|
5407
|
+
`));
|
|
5408
|
+
throw error;
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
var ASCII_TABLE_CHARS;
|
|
5412
|
+
var init_analyze = __esm(() => {
|
|
5413
|
+
init_log();
|
|
5414
|
+
ASCII_TABLE_CHARS = {
|
|
5415
|
+
top: "-",
|
|
5416
|
+
"top-mid": "+",
|
|
5417
|
+
"top-left": "+",
|
|
5418
|
+
"top-right": "+",
|
|
5419
|
+
bottom: "-",
|
|
5420
|
+
"bottom-mid": "+",
|
|
5421
|
+
"bottom-left": "+",
|
|
5422
|
+
"bottom-right": "+",
|
|
5423
|
+
left: "|",
|
|
5424
|
+
"left-mid": "+",
|
|
5425
|
+
mid: "-",
|
|
5426
|
+
"mid-mid": "+",
|
|
5427
|
+
right: "|",
|
|
5428
|
+
"right-mid": "+",
|
|
5429
|
+
middle: "|"
|
|
5430
|
+
};
|
|
5431
|
+
});
|
|
5432
|
+
|
|
5433
|
+
// packages/cli/src/cli.ts
|
|
5434
|
+
import path3 from "node:path";
|
|
5435
|
+
var commands = {
|
|
5436
|
+
init: () => Promise.resolve().then(() => (init_init(), exports_init)),
|
|
5437
|
+
gen: () => Promise.resolve().then(() => (init_gen(), exports_gen)),
|
|
5438
|
+
boot: () => Promise.resolve().then(() => (init_boot(), exports_boot)),
|
|
5439
|
+
dev: () => Promise.resolve().then(() => (init_dev(), exports_dev)),
|
|
5440
|
+
start: () => Promise.resolve().then(() => (init_start(), exports_start)),
|
|
5441
|
+
build: () => Promise.resolve().then(() => (init_build(), exports_build)),
|
|
5442
|
+
analyze: () => Promise.resolve().then(() => (init_analyze(), exports_analyze))
|
|
5443
|
+
};
|
|
5444
|
+
async function main() {
|
|
5445
|
+
const args = process.argv.slice(2);
|
|
5446
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
5447
|
+
const version = await getVersionString2();
|
|
5448
|
+
console.log(version);
|
|
5449
|
+
process.exit(0);
|
|
5450
|
+
}
|
|
5451
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
5452
|
+
await printHelp2();
|
|
5453
|
+
process.exit(0);
|
|
5454
|
+
}
|
|
5455
|
+
const command2 = args[0];
|
|
5456
|
+
if (!commands[command2]) {
|
|
5457
|
+
console.error(`Unknown command: ${command2}`);
|
|
5458
|
+
console.error("");
|
|
5459
|
+
await printHelp2();
|
|
5460
|
+
process.exit(1);
|
|
5461
|
+
}
|
|
5462
|
+
try {
|
|
5463
|
+
const commandArgs = args.slice(1);
|
|
5464
|
+
if (commandArgs.includes("--prod")) {
|
|
5465
|
+
process.env.NODE_ENV = "production";
|
|
5466
|
+
if (typeof Bun !== "undefined") {
|
|
5467
|
+
Bun.env.NODE_ENV = "production";
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
const module = await commands[command2]();
|
|
5471
|
+
await module.default(commandArgs);
|
|
5472
|
+
} catch (error) {
|
|
5473
|
+
console.error("Error executing command:", error);
|
|
5474
|
+
process.exit(1);
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
async function printHelp2() {
|
|
5478
|
+
console.log("");
|
|
5479
|
+
console.log(await getVersionString2());
|
|
5480
|
+
console.log("");
|
|
5481
|
+
console.log("");
|
|
5482
|
+
console.log("Usage:");
|
|
5483
|
+
console.log("");
|
|
5484
|
+
console.log(" reroute <command> [options]");
|
|
5485
|
+
console.log("");
|
|
5486
|
+
console.log("");
|
|
5487
|
+
console.log("Commands:");
|
|
5488
|
+
console.log("");
|
|
5489
|
+
console.log(" init Scaffold a new Reroute project");
|
|
5490
|
+
console.log(" boot Initialize .reroute directory with stub files");
|
|
5491
|
+
console.log(" gen Generate content registry and static assets");
|
|
5492
|
+
console.log(" dev Start development environment");
|
|
5493
|
+
console.log(" start Run production-like server locally");
|
|
5494
|
+
console.log(" build Build production binary");
|
|
5495
|
+
console.log(" analyze Analyze bundle sizes and generate report");
|
|
5496
|
+
console.log("");
|
|
5497
|
+
console.log("");
|
|
5498
|
+
console.log("Options:");
|
|
5499
|
+
console.log("");
|
|
5500
|
+
console.log(" -h, --help Show help");
|
|
5501
|
+
console.log(" -v, --version Show version");
|
|
5502
|
+
console.log(" --prod Enable production mode (same as NODE_ENV=production)");
|
|
5503
|
+
console.log("");
|
|
5504
|
+
console.log("");
|
|
5505
|
+
console.log("Examples:");
|
|
5506
|
+
console.log("");
|
|
5507
|
+
console.log(" reroute init my-app");
|
|
5508
|
+
console.log(" reroute boot");
|
|
5509
|
+
console.log("");
|
|
5510
|
+
console.log(" reroute gen");
|
|
5511
|
+
console.log(" reroute gen -w");
|
|
5512
|
+
console.log(" reroute gen --watch");
|
|
5513
|
+
console.log("");
|
|
5514
|
+
console.log(" reroute dev");
|
|
5515
|
+
console.log(" reroute dev -s");
|
|
5516
|
+
console.log(" reroute dev --single");
|
|
5517
|
+
console.log("");
|
|
5518
|
+
console.log(" reroute start");
|
|
5519
|
+
console.log("");
|
|
5520
|
+
console.log(" reroute build");
|
|
5521
|
+
console.log(" reroute build --prod");
|
|
5522
|
+
console.log(" reroute build -o ./dist/myapp");
|
|
5523
|
+
console.log(" reroute build --outfile ./dist/myapp");
|
|
5524
|
+
console.log("");
|
|
5525
|
+
console.log(" reroute analyze");
|
|
5526
|
+
console.log(" reroute analyze --prod");
|
|
5527
|
+
console.log(" reroute analyze --full");
|
|
5528
|
+
console.log(" reroute analyze --html");
|
|
5529
|
+
console.log(" reroute analyze --open");
|
|
2161
5530
|
}
|
|
2162
5531
|
async function getVersionString2() {
|
|
2163
5532
|
const version = await getVersion2();
|
|
@@ -2166,12 +5535,12 @@ async function getVersionString2() {
|
|
|
2166
5535
|
}
|
|
2167
5536
|
async function getVersion2() {
|
|
2168
5537
|
if (true) {
|
|
2169
|
-
return "0.
|
|
5538
|
+
return "0.8.0";
|
|
2170
5539
|
}
|
|
2171
5540
|
const possiblePaths = [
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
5541
|
+
path3.join(import.meta.dir, "../../../package.json"),
|
|
5542
|
+
path3.join(import.meta.dir, "../../package.json"),
|
|
5543
|
+
path3.join(import.meta.dir, "../package.json")
|
|
2175
5544
|
];
|
|
2176
5545
|
for (const pkgPath of possiblePaths) {
|
|
2177
5546
|
try {
|
|
@@ -2183,10 +5552,10 @@ async function getVersion2() {
|
|
|
2183
5552
|
}
|
|
2184
5553
|
async function getCommit2() {
|
|
2185
5554
|
if (true) {
|
|
2186
|
-
return "
|
|
5555
|
+
return "b31f8e3";
|
|
2187
5556
|
}
|
|
2188
5557
|
return "dev";
|
|
2189
5558
|
}
|
|
2190
5559
|
main();
|
|
2191
5560
|
|
|
2192
|
-
//# debugId=
|
|
5561
|
+
//# debugId=8F50A077EDCF534864756E2164756E21
|