@junejs/server 0.0.31 → 0.0.32
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 +3 -2
- package/src/island-registry.ts +72 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,10 +30,11 @@
|
|
|
30
30
|
"typecheck": "tsc --noEmit"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@junejs/core": "0.0.
|
|
33
|
+
"@junejs/core": "0.0.33",
|
|
34
34
|
"@junejs/db": "0.0.28",
|
|
35
35
|
"lightningcss": "^1.27.0",
|
|
36
36
|
"marked": "^18.0.5",
|
|
37
|
+
"oxc-parser": "^0.137.0",
|
|
37
38
|
"rolldown": "^1.1.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
package/src/island-registry.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
24
24
|
import { join, relative } from "node:path";
|
|
25
25
|
|
|
26
|
+
import { parseSync } from "oxc-parser";
|
|
27
|
+
|
|
26
28
|
export const ISLAND_REGISTRY_FILE = "_islands.gen.ts";
|
|
27
29
|
|
|
28
30
|
// Shared with rsc-manifest.ts (the RSC client-reference codegen).
|
|
@@ -62,27 +64,83 @@ export function exportNames(src: string): string[] {
|
|
|
62
64
|
return names;
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
// The island NAME an `island(...)` call registers under — the SAME value the
|
|
68
|
+
// runtime derives (options.name ?? the component's function/identifier name).
|
|
69
|
+
// Keying the loader by THIS (not the export name) is what kills the desync
|
|
70
|
+
// (P1-1): the marker, the registry, and the loader all agree by construction.
|
|
71
|
+
// Returns null for an anonymous component with no { name } — island() throws on
|
|
72
|
+
// that at runtime, so we leave it for the runtime to report.
|
|
73
|
+
function islandNameOfCall(init: unknown): string | null {
|
|
74
|
+
const call = init as { type?: string; callee?: { type?: string; name?: string }; arguments?: any[] };
|
|
75
|
+
if (call?.type !== "CallExpression") return null;
|
|
76
|
+
if (call.callee?.type !== "Identifier" || call.callee.name !== "island") return null;
|
|
77
|
+
const [a0, a1] = call.arguments ?? [];
|
|
78
|
+
if (a1?.type === "ObjectExpression") {
|
|
79
|
+
for (const p of a1.properties ?? []) {
|
|
80
|
+
const key = p.key?.name ?? p.key?.value;
|
|
81
|
+
if (key === "name" && typeof p.value?.value === "string") return p.value.value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (a0?.type === "FunctionExpression") return a0.id?.name ?? null;
|
|
85
|
+
if (a0?.type === "Identifier") return a0.name ?? null;
|
|
86
|
+
return null; // arrow/anonymous without { name } → runtime island() errors
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// True iff the module imports `island` from @junejs/core/islands (so a local
|
|
90
|
+
// function happening to be named `island` is never mistaken for the API).
|
|
91
|
+
function importsIslandApi(program: { body: any[] }): boolean {
|
|
92
|
+
return program.body.some(
|
|
93
|
+
(n) =>
|
|
94
|
+
n.type === "ImportDeclaration" &&
|
|
95
|
+
n.source?.value === "@junejs/core/islands" &&
|
|
96
|
+
(n.specifiers ?? []).some((s: any) => s.imported?.name === "island"),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
65
100
|
// Write app/_islands.gen.ts. Returns the number of island loaders emitted.
|
|
101
|
+
//
|
|
102
|
+
// AST-based (oxc-parser) — robust against multi-line exports / re-exports that a
|
|
103
|
+
// line regex would miss (P1-2), keyed by the island's runtime name (P1-1), and
|
|
104
|
+
// it throws on a duplicate name across modules (P2-1) instead of silently
|
|
105
|
+
// overwriting.
|
|
66
106
|
export function generateIslandRegistry(appDir: string): number {
|
|
67
|
-
const
|
|
68
|
-
.filter((f) => /\.(tsx|ts|jsx)$/.test(f) && !f.endsWith(ISLAND_REGISTRY_FILE))
|
|
69
|
-
.map((f) => ({ f, src: readFileSync(f, "utf8") }))
|
|
70
|
-
.filter(
|
|
71
|
-
({ src }) =>
|
|
72
|
-
firstStatementIsDirective(src, "use client") &&
|
|
73
|
-
/@junejs\/core\/islands/.test(src) &&
|
|
74
|
-
/\bisland\s*\(/.test(src),
|
|
75
|
-
);
|
|
76
|
-
|
|
107
|
+
const seen = new Map<string, string>(); // island name → absolute file (dup guard)
|
|
77
108
|
const entries: string[] = [];
|
|
78
|
-
|
|
109
|
+
|
|
110
|
+
for (const f of walk(appDir)) {
|
|
111
|
+
if (!/\.(tsx|ts|jsx)$/.test(f) || f.endsWith(ISLAND_REGISTRY_FILE)) continue;
|
|
112
|
+
const src = readFileSync(f, "utf8");
|
|
113
|
+
if (!firstStatementIsDirective(src, "use client")) continue;
|
|
114
|
+
const { program } = parseSync(f, src);
|
|
115
|
+
if (!importsIslandApi(program as { body: any[] })) continue;
|
|
116
|
+
|
|
79
117
|
const rel = "./" + relative(appDir, f).replace(/\.(tsx|ts|jsx)$/, "");
|
|
80
|
-
for (const
|
|
81
|
-
|
|
82
|
-
|
|
118
|
+
for (const node of (program as { body: any[] }).body) {
|
|
119
|
+
const varDecl =
|
|
120
|
+
node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration"
|
|
121
|
+
? node.declaration
|
|
122
|
+
: node.type === "VariableDeclaration"
|
|
123
|
+
? node
|
|
124
|
+
: null;
|
|
125
|
+
if (!varDecl) continue;
|
|
126
|
+
for (const d of varDecl.declarations ?? []) {
|
|
127
|
+
const name = islandNameOfCall(d.init);
|
|
128
|
+
if (!name) continue;
|
|
129
|
+
const prev = seen.get(name);
|
|
130
|
+
if (prev && prev !== f) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`[june] duplicate island name "${name}": defined in ${rel} and ` +
|
|
133
|
+
`./${relative(appDir, prev).replace(/\.(tsx|ts|jsx)$/, "")}. Island names must be unique.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (seen.has(name)) continue;
|
|
137
|
+
seen.set(name, f);
|
|
138
|
+
entries.push(` ${JSON.stringify(name)}: () => import(${JSON.stringify(rel)}),`);
|
|
139
|
+
}
|
|
83
140
|
}
|
|
84
141
|
}
|
|
85
142
|
|
|
143
|
+
entries.sort(); // deterministic output
|
|
86
144
|
const body = entries.length ? entries.join("\n") + "\n" : "";
|
|
87
145
|
const out =
|
|
88
146
|
"// AUTO-GENERATED by June — do not edit. Lazy loaders for island() modules.\n" +
|