@mandujs/core 0.19.2 → 0.20.1
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/package.json +1 -1
- package/src/bundler/build.ts +94 -2
- package/src/bundler/css.ts +323 -353
- package/src/bundler/types.ts +20 -0
- package/src/devtools/client/components/mandu-character.tsx +77 -53
- package/src/devtools/client/components/panel/errors-panel.tsx +2 -2
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -31
- package/src/devtools/client/components/panel/islands-panel.tsx +30 -14
- package/src/devtools/client/components/panel/network-panel.tsx +2 -3
- package/src/devtools/client/components/panel/panel-container.tsx +485 -332
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -22
- package/src/devtools/init.ts +1 -1
- package/src/devtools/types.ts +35 -35
- package/src/filling/filling.ts +66 -66
- package/src/index.ts +1 -0
- package/src/kitchen/kitchen-handler.ts +80 -1
- package/src/observability/event-bus.ts +79 -0
- package/src/observability/index.ts +8 -0
- package/src/observability/logger-adapter.ts +36 -0
- package/src/runtime/index.ts +9 -8
- package/src/runtime/ppr.ts +74 -0
- package/src/runtime/server.ts +203 -147
- package/src/runtime/ssr.ts +17 -4
- package/src/runtime/streaming-ssr.ts +55 -36
- package/src/testing/index.ts +45 -0
package/package.json
CHANGED
package/src/bundler/build.ts
CHANGED
|
@@ -11,11 +11,67 @@ import type {
|
|
|
11
11
|
BundleManifest,
|
|
12
12
|
BundleStats,
|
|
13
13
|
BundlerOptions,
|
|
14
|
+
IslandFileEntry,
|
|
14
15
|
} from "./types";
|
|
15
16
|
import { HYDRATION } from "../constants";
|
|
16
17
|
import path from "path";
|
|
17
18
|
import fs from "fs/promises";
|
|
18
19
|
|
|
20
|
+
/** Scan for *.island.tsx / *.island.ts files across hydrated route directories. */
|
|
21
|
+
async function scanIslandFiles(routes: RouteSpec[], rootDir: string): Promise<IslandFileEntry[]> {
|
|
22
|
+
const entries: IslandFileEntry[] = [];
|
|
23
|
+
const seenDirs = new Set<string>();
|
|
24
|
+
|
|
25
|
+
for (const route of routes) {
|
|
26
|
+
const dir = path.dirname(path.join(rootDir, route.componentModule ?? route.module));
|
|
27
|
+
if (seenDirs.has(dir)) continue;
|
|
28
|
+
seenDirs.add(dir);
|
|
29
|
+
|
|
30
|
+
let files: string[];
|
|
31
|
+
try { files = await fs.readdir(dir); } catch { continue; }
|
|
32
|
+
|
|
33
|
+
const priority = getRouteHydration(route)?.priority || HYDRATION.DEFAULT_PRIORITY;
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
if (/\.island\.tsx?$/.test(file)) {
|
|
36
|
+
entries.push({
|
|
37
|
+
name: file.replace(/\.island\.tsx?$/, ""),
|
|
38
|
+
filePath: path.join(dir, file),
|
|
39
|
+
routeId: route.id,
|
|
40
|
+
priority,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Build a single per-island bundle. */
|
|
49
|
+
async function buildPerIslandBundle(
|
|
50
|
+
entry: IslandFileEntry, outDir: string, options: BundlerOptions
|
|
51
|
+
): Promise<{ name: string; js: string; route: string; priority: IslandFileEntry["priority"] }> {
|
|
52
|
+
const entryPath = path.join(outDir, `_entry_island_${entry.name}.js`);
|
|
53
|
+
const outputName = `${entry.name}.island.js`;
|
|
54
|
+
try {
|
|
55
|
+
await Bun.write(entryPath, generateIslandEntry(entry.name, entry.filePath));
|
|
56
|
+
const result = await Bun.build({
|
|
57
|
+
entrypoints: [entryPath],
|
|
58
|
+
outdir: outDir,
|
|
59
|
+
naming: outputName,
|
|
60
|
+
minify: options.minify ?? process.env.NODE_ENV === "production",
|
|
61
|
+
sourcemap: options.sourcemap ? "external" : "none",
|
|
62
|
+
target: "browser",
|
|
63
|
+
external: ["react", "react-dom", "react-dom/client", ...(options.external || [])],
|
|
64
|
+
define: { "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || "development"), ...options.define },
|
|
65
|
+
});
|
|
66
|
+
await fs.unlink(entryPath).catch(() => {});
|
|
67
|
+
if (!result.success) throw new Error(result.logs.map((l) => l.message).join("\n"));
|
|
68
|
+
return { name: entry.name, js: `/.mandu/client/${outputName}`, route: entry.routeId, priority: entry.priority };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
await fs.unlink(entryPath).catch(() => {});
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
19
75
|
/**
|
|
20
76
|
* 빈 매니페스트 생성
|
|
21
77
|
*/
|
|
@@ -1292,7 +1348,8 @@ function createBundleManifest(
|
|
|
1292
1348
|
runtimePath: string,
|
|
1293
1349
|
vendorResult: VendorBuildResult,
|
|
1294
1350
|
routerPath: string,
|
|
1295
|
-
env: "development" | "production"
|
|
1351
|
+
env: "development" | "production",
|
|
1352
|
+
islandBundles?: Array<{ name: string; js: string; route: string; priority: IslandFileEntry["priority"] }>
|
|
1296
1353
|
): BundleManifest {
|
|
1297
1354
|
const bundles: BundleManifest["bundles"] = {};
|
|
1298
1355
|
|
|
@@ -1307,11 +1364,25 @@ function createBundleManifest(
|
|
|
1307
1364
|
};
|
|
1308
1365
|
}
|
|
1309
1366
|
|
|
1367
|
+
// Per-island bundles (code splitting)
|
|
1368
|
+
let islands: BundleManifest["islands"];
|
|
1369
|
+
if (islandBundles && islandBundles.length > 0) {
|
|
1370
|
+
islands = {};
|
|
1371
|
+
for (const ib of islandBundles) {
|
|
1372
|
+
islands[ib.name] = {
|
|
1373
|
+
js: ib.js,
|
|
1374
|
+
route: ib.route,
|
|
1375
|
+
priority: ib.priority,
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1310
1380
|
return {
|
|
1311
1381
|
version: 1,
|
|
1312
1382
|
buildTime: new Date().toISOString(),
|
|
1313
1383
|
env,
|
|
1314
1384
|
bundles,
|
|
1385
|
+
...(islands ? { islands } : {}),
|
|
1315
1386
|
shared: {
|
|
1316
1387
|
runtime: runtimePath,
|
|
1317
1388
|
vendor: vendorResult.react, // primary vendor for backwards compatibility
|
|
@@ -1544,6 +1615,26 @@ export async function buildClientBundles(
|
|
|
1544
1615
|
}
|
|
1545
1616
|
}
|
|
1546
1617
|
|
|
1618
|
+
// 5.5. Per-island code splitting: scan and build individual island bundles
|
|
1619
|
+
const islandFiles = await scanIslandFiles(hydratedRoutes, rootDir);
|
|
1620
|
+
const islandBundles: Array<{ name: string; js: string; route: string; priority: IslandFileEntry["priority"] }> = [];
|
|
1621
|
+
|
|
1622
|
+
if (islandFiles.length > 0) {
|
|
1623
|
+
const islandResults = await Promise.all(
|
|
1624
|
+
islandFiles.map(async (entry) => {
|
|
1625
|
+
try {
|
|
1626
|
+
return await buildPerIslandBundle(entry, outDir, options);
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
errors.push(`[island:${entry.name}] ${String(error)}`);
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
})
|
|
1632
|
+
);
|
|
1633
|
+
for (const result of islandResults) {
|
|
1634
|
+
if (result) islandBundles.push(result);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1547
1638
|
// 6. 번들 매니페스트 생성
|
|
1548
1639
|
const bundleManifest = createBundleManifest(
|
|
1549
1640
|
outputs,
|
|
@@ -1551,7 +1642,8 @@ export async function buildClientBundles(
|
|
|
1551
1642
|
runtimeResult.outputPath,
|
|
1552
1643
|
vendorResult,
|
|
1553
1644
|
routerResult.outputPath,
|
|
1554
|
-
env
|
|
1645
|
+
env,
|
|
1646
|
+
islandBundles
|
|
1555
1647
|
);
|
|
1556
1648
|
|
|
1557
1649
|
await fs.writeFile(
|