@sigil-dev/grimoire 0.7.5 → 0.7.7
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/.grimoire/_routes.dom.js +8 -0
- package/.grimoire/_routes.hydrate.js +8 -0
- package/.grimoire/tsconfig.generated.json +11 -0
- package/.grimoire/types/ambient.d.ts +59 -0
- package/.grimoire/types/api/hello/$types.d.ts +50 -0
- package/.grimoire/types/api/items/$types.d.ts +50 -0
- package/.grimoire/types/echo/$types.d.ts +50 -0
- package/.grimoire/types/env-private.d.ts +5 -0
- package/.grimoire/types/env-public.d.ts +5 -0
- package/.grimoire/types/mixed/$types.d.ts +50 -0
- package/.grimoire/types/params/[docId]/$types.d.ts +52 -0
- package/.grimoire/types/reject/$types.d.ts +50 -0
- package/index.ts +21 -20
- package/package.json +13 -7
- package/preload.js +3 -0
- package/public/__grimoire__/hydrate.js +585 -0
- package/public/__grimoire__/index.js +490 -0
- package/server.ts +13 -13
- package/src/client/head.ts +29 -0
- package/src/client/router.ts +254 -40
- package/src/dev/compile-module.ts +173 -0
- package/src/dev/effect-registry.ts +23 -0
- package/src/dev/graph.ts +114 -0
- package/src/dev/hmr-client.ts +158 -0
- package/src/dev/hmr-server.ts +187 -0
- package/src/dev/loader.ts +47 -0
- package/src/dev/paths.ts +14 -0
- package/src/dev/runtime-bundle.ts +49 -0
- package/src/dev/watcher.ts +44 -0
- package/src/env/index.ts +25 -0
- package/src/env/plugin.ts +13 -0
- package/src/env/private.ts +5 -0
- package/src/env/public.ts +7 -0
- package/src/env/typegen.ts +51 -0
- package/src/integrations/vite.ts +1 -0
- package/src/rendering/head.ts +22 -2
- package/src/rendering/hydrate.ts +111 -18
- package/src/rendering/index.ts +263 -153
- package/src/rendering/ssrPlugin.ts +59 -39
- package/src/routing/manifest-gen.ts +18 -2
- package/src/routing/router.ts +94 -83
- package/src/routing/scanner.ts +26 -14
- package/src/routing/transform-routes.ts +68 -68
- package/src/server/build.ts +225 -76
- package/src/server/coordinator.ts +9 -0
- package/src/server/hooks.ts +24 -3
- package/src/server/index.ts +388 -104
- package/src/typegen/index.ts +30 -14
- package/src/types.ts +12 -2
- package/test/middleware.test.ts +6 -4
- package/test/rendering.test.ts +510 -356
- package/test/routing.test.ts +36 -0
- package/test/scanning.test.ts +39 -8
- package/test/scope.test.ts +24 -8
- package/test/server.test.ts +27 -7
- package/test/streaming.test.ts +117 -98
- package/test/typegen.test.ts +52 -24
- package/tsconfig.json +1 -0
|
@@ -1,47 +1,67 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { transformSync } from "@babel/core";
|
|
2
3
|
import sigilPlugin from "@sigil-dev/compiler/babel";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
4
|
import type { GrimoirePlugin } from "../types";
|
|
5
5
|
|
|
6
6
|
let registered = false;
|
|
7
7
|
|
|
8
8
|
export function registerSSRPlugin(plugins: GrimoirePlugin[] = []) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
9
|
+
if (registered) return;
|
|
10
|
+
registered = true;
|
|
11
|
+
|
|
12
|
+
Bun.plugin({
|
|
13
|
+
name: "sigil-ssr",
|
|
14
|
+
|
|
15
|
+
setup(build) {
|
|
16
|
+
// loader: "ts" not "tsx" — Babel already consumed the JSX,
|
|
17
|
+
// Bun only needs to strip remaining TypeScript types
|
|
18
|
+
const transpiler = new Bun.Transpiler({ loader: "ts", target: "bun" });
|
|
19
|
+
build.onResolve({ filter: /^@sigil-dev\/runtime($|\/)/ }, (args) => {
|
|
20
|
+
try {
|
|
21
|
+
const resolved = Bun.resolveSync(args.path, process.cwd());
|
|
22
|
+
return { path: resolved.replaceAll("\\", "/") };
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
build.onLoad({ filter: /\.tsx?$/ }, async ({ path }) => {
|
|
29
|
+
if (path.includes("index.tsx"))
|
|
30
|
+
console.log("[ssr-plugin] onLoad:", path);
|
|
31
|
+
const source = await Bun.file(path).text();
|
|
32
|
+
|
|
33
|
+
// node_modules and .grimoire files are plain TypeScript (no sigil JSX).
|
|
34
|
+
// Still must return { contents, loader } — never return undefined from onLoad.
|
|
35
|
+
if (path.includes("node_modules") || path.includes(".grimoire")) {
|
|
36
|
+
return {
|
|
37
|
+
contents: transpiler.transformSync(source),
|
|
38
|
+
loader: "js" as const,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (process.env.SIGIL_VERBOSE) {
|
|
43
|
+
console.log("[sigil-ssr] XFORM:", path);
|
|
44
|
+
}
|
|
45
|
+
const hash = createHash("md5").update(path).digest("hex").slice(0, 8);
|
|
46
|
+
const result = transformSync(source, {
|
|
47
|
+
configFile: false,
|
|
48
|
+
babelrc: false,
|
|
49
|
+
parserOpts: {
|
|
50
|
+
plugins: ["typescript", "jsx"],
|
|
51
|
+
},
|
|
52
|
+
plugins: [[sigilPlugin, { mode: "ssr", hash }]],
|
|
53
|
+
filename: path,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let contents = transpiler.transformSync(result?.code ?? "");
|
|
57
|
+
|
|
58
|
+
for (const plugin of plugins) {
|
|
59
|
+
if (plugin.transform)
|
|
60
|
+
contents = (await plugin.transform(contents, path)) ?? contents;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { contents, loader: "js" as const };
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
});
|
|
47
67
|
}
|
|
@@ -13,14 +13,30 @@ export function generateManifest(
|
|
|
13
13
|
fileMap?: Map<string, string>,
|
|
14
14
|
): string {
|
|
15
15
|
const pages = routes.filter((r) => r.type === "page" || r.type === "simple");
|
|
16
|
-
const
|
|
16
|
+
const layouts = routes.filter((r) => r.type === "layout");
|
|
17
|
+
|
|
18
|
+
const pageImports = pages
|
|
17
19
|
.map((r, i) => {
|
|
18
20
|
const path = fileMap?.get(r.filePath) ?? r.filePath;
|
|
19
21
|
return `import __Page${i} from ${JSON.stringify(path)};`;
|
|
20
22
|
})
|
|
21
23
|
.join("\n");
|
|
24
|
+
const layoutImports = layouts
|
|
25
|
+
.map((r, i) => {
|
|
26
|
+
const path = fileMap?.get(r.filePath) ?? r.filePath;
|
|
27
|
+
return `import __Layout${i} from ${JSON.stringify(path)};`;
|
|
28
|
+
})
|
|
29
|
+
.join("\n");
|
|
30
|
+
|
|
22
31
|
const map = pages
|
|
23
32
|
.map((r, i) => ` ${JSON.stringify(r.path)}: __Page${i},`)
|
|
24
33
|
.join("\n");
|
|
25
|
-
|
|
34
|
+
const layoutMap = layouts
|
|
35
|
+
.map(
|
|
36
|
+
(r, i) =>
|
|
37
|
+
` { path: ${JSON.stringify(r.path)}, component: __Layout${i}, load: __Layout${i}.load },`,
|
|
38
|
+
)
|
|
39
|
+
.join("\n");
|
|
40
|
+
|
|
41
|
+
return `${pageImports}\n${layoutImports}\nexport const routes = {\n${map}\n};\nexport const layouts = [\n${layoutMap}\n];\n`;
|
|
26
42
|
}
|
package/src/routing/router.ts
CHANGED
|
@@ -1,98 +1,109 @@
|
|
|
1
1
|
import type { RouteFile, RouteTree } from "./scanner";
|
|
2
2
|
|
|
3
3
|
export interface MatchedRoute {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
route: RouteFile;
|
|
5
|
+
params: Record<string, string>;
|
|
6
|
+
layouts: RouteFile[]; // was: layout?: RouteFile
|
|
7
|
+
layoutServers: RouteFile[]; // was: layoutServer?: RouteFile
|
|
8
|
+
pageServer?: RouteFile;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function matchRoute(tree: RouteTree, url: URL): MatchedRoute | null {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return null;
|
|
12
|
+
const pathname = url.pathname;
|
|
13
|
+
|
|
14
|
+
// check server routes first
|
|
15
|
+
for (const server of tree.servers) {
|
|
16
|
+
const params = matchPattern(server.path, pathname);
|
|
17
|
+
if (params !== null)
|
|
18
|
+
return {
|
|
19
|
+
route: server,
|
|
20
|
+
params,
|
|
21
|
+
layouts: [],
|
|
22
|
+
layoutServers: [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// then pages
|
|
27
|
+
for (const route of tree.routes) {
|
|
28
|
+
if (route.type === "pageServer") continue;
|
|
29
|
+
const params = matchPattern(route.path, pathname);
|
|
30
|
+
if (params === null) continue;
|
|
31
|
+
|
|
32
|
+
// find applicable layouts by matching route pattern prefix, not URL pathname.
|
|
33
|
+
// pathname.startsWith(l.path) would fail for parameterized layouts like /w/:workspace.
|
|
34
|
+
const layoutMatches = (l: RouteFile) =>
|
|
35
|
+
l.path === "/" ||
|
|
36
|
+
route.path === l.path ||
|
|
37
|
+
route.path.startsWith(l.path + "/");
|
|
38
|
+
|
|
39
|
+
const layouts = tree.layouts
|
|
40
|
+
.filter((l) => l.type === "layout" && layoutMatches(l))
|
|
41
|
+
.sort((a, b) => a.path.length - b.path.length); // root first, innermost last
|
|
42
|
+
|
|
43
|
+
const layoutServers = tree.layouts
|
|
44
|
+
.filter((l) => l.type === "layoutServer" && layoutMatches(l))
|
|
45
|
+
.sort((a, b) => a.path.length - b.path.length); // root first, innermost last
|
|
46
|
+
|
|
47
|
+
const pageServer = tree.routes.find(
|
|
48
|
+
(r) => r.type === "pageServer" && r.path === route.path,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return { route, params, layouts, layoutServers, pageServer };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
export function findClosestError(
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
errors: RouteFile[],
|
|
59
|
+
pathname: string,
|
|
61
60
|
): RouteFile | null {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
62
|
+
|
|
63
|
+
// walk from most specific to root
|
|
64
|
+
while (segments.length >= 0) {
|
|
65
|
+
const prefix = `/${segments.join("/")}`;
|
|
66
|
+
const error = errors.find(
|
|
67
|
+
(e) => e.path === prefix || e.path === prefix + "/",
|
|
68
|
+
);
|
|
69
|
+
if (error) return error;
|
|
70
|
+
if (segments.length === 0) break;
|
|
71
|
+
segments.pop();
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
function matchPattern(
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
pattern: string,
|
|
78
|
+
pathname: string,
|
|
80
79
|
): Record<string, string> | null {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
81
|
+
const pathParts = pathname.split("/").filter(Boolean);
|
|
82
|
+
|
|
83
|
+
const params: Record<string, string> = {};
|
|
84
|
+
let pi = 0;
|
|
85
|
+
let pj = 0;
|
|
86
|
+
while (pi < patternParts.length) {
|
|
87
|
+
const patternPart = patternParts[pi]!;
|
|
88
|
+
|
|
89
|
+
if (patternPart.startsWith("*")) {
|
|
90
|
+
// Rest param — captures zero or more remaining path segments joined by "/".
|
|
91
|
+
// Rest must be the last segment in the pattern.
|
|
92
|
+
params[patternPart.slice(1)] = pathParts.slice(pj).join("/");
|
|
93
|
+
return params;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (pj >= pathParts.length) return null;
|
|
97
|
+
const pathPart = pathParts[pj]!;
|
|
98
|
+
|
|
99
|
+
if (patternPart.startsWith(":")) {
|
|
100
|
+
params[patternPart.slice(1)] = pathPart;
|
|
101
|
+
} else if (patternPart !== pathPart) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
pi++;
|
|
105
|
+
pj++;
|
|
106
|
+
}
|
|
107
|
+
if (pj < pathParts.length) return null;
|
|
108
|
+
return params;
|
|
98
109
|
}
|
package/src/routing/scanner.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface RouteFile {
|
|
|
13
13
|
| "error"
|
|
14
14
|
| "simple";
|
|
15
15
|
paramNames: string[]; // ["slug"] in /blog/:slug
|
|
16
|
+
restParamNames: string[]; // ["rest"] in /blog/*rest
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export interface RouteTree {
|
|
@@ -25,24 +26,32 @@ export interface RouteTree {
|
|
|
25
26
|
export function filePathToRoutePath(
|
|
26
27
|
filePath: string,
|
|
27
28
|
routesDir: string,
|
|
28
|
-
): { pattern: string; params: string[] } {
|
|
29
|
+
): { pattern: string; params: string[]; restParams: string[] } {
|
|
29
30
|
const rel = relative(routesDir, filePath)
|
|
30
|
-
.replace(/\\/g, "/")
|
|
31
|
-
.replace(/\.(tsx?|jsx?)$/, "")
|
|
31
|
+
.replace(/\\/g, "/")
|
|
32
|
+
.replace(/\.(tsx?|jsx?)$/, "")
|
|
33
|
+
.replace(/(?:^|\/)(\([^)]+\))(?=\/|$)/g, "") // strip (groupName) anywhere
|
|
34
|
+
.replace(/\/+/g, "/"); // collapse double slashes
|
|
32
35
|
|
|
33
36
|
const params: string[] = [];
|
|
37
|
+
const restParams: string[] = [];
|
|
34
38
|
const pattern =
|
|
35
39
|
rel
|
|
36
40
|
.replace(/\/?(\+\w+(\.\w+)?|index)$/, "") // index/+page → directory
|
|
37
|
-
.replace(/\[([^\]]+)\]/g, (_,
|
|
41
|
+
.replace(/\[(\.\.\.)?([^\]]+)\]/g, (_, dots, name) => {
|
|
42
|
+
// [...rest] → *rest (captures zero or more remaining path segments)
|
|
43
|
+
if (dots) {
|
|
44
|
+
restParams.push(name);
|
|
45
|
+
return `*${name}`;
|
|
46
|
+
}
|
|
38
47
|
// [param] → :param
|
|
39
|
-
params.push(
|
|
40
|
-
return `:${
|
|
48
|
+
params.push(name);
|
|
49
|
+
return `:${name}`;
|
|
41
50
|
})
|
|
42
51
|
.replace(/^\/?/, "/") || // ensure leading slash
|
|
43
52
|
"/";
|
|
44
53
|
|
|
45
|
-
return { pattern, params };
|
|
54
|
+
return { pattern, params, restParams };
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export async function scanRoutes(
|
|
@@ -58,7 +67,10 @@ export async function scanRoutes(
|
|
|
58
67
|
for await (const file of glob.scan(routesDir)) {
|
|
59
68
|
const filePath = join(routesDir, file);
|
|
60
69
|
const name = basename(file).replace(/\.(tsx?|jsx?)$/, "");
|
|
61
|
-
const { pattern, params } = filePathToRoutePath(
|
|
70
|
+
const { pattern, params, restParams } = filePathToRoutePath(
|
|
71
|
+
filePath,
|
|
72
|
+
routesDir,
|
|
73
|
+
);
|
|
62
74
|
|
|
63
75
|
let type: RouteFile["type"];
|
|
64
76
|
if (name === "+page") type = "page";
|
|
@@ -77,6 +89,7 @@ export async function scanRoutes(
|
|
|
77
89
|
clientPath, // add this
|
|
78
90
|
type,
|
|
79
91
|
paramNames: params,
|
|
92
|
+
restParamNames: restParams,
|
|
80
93
|
};
|
|
81
94
|
|
|
82
95
|
if (type === "simple" || type === "page" || type === "pageServer")
|
|
@@ -86,12 +99,11 @@ export async function scanRoutes(
|
|
|
86
99
|
if (type === "error") errors.push(routeFile);
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
// sort so static routes match before dynamic ones
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
102
|
+
// sort so static routes match before dynamic ones, rest params last
|
|
103
|
+
const scoreRoute = (r: RouteFile) =>
|
|
104
|
+
r.paramNames.length + r.restParamNames.length * 1000;
|
|
105
|
+
routes.sort((a, b) => scoreRoute(a) - scoreRoute(b));
|
|
106
|
+
servers.sort((a, b) => scoreRoute(a) - scoreRoute(b));
|
|
95
107
|
|
|
96
108
|
assertNoDuplicatePaths(
|
|
97
109
|
routes.filter((r) => r.type === "page" || r.type === "simple"),
|
|
@@ -9,39 +9,39 @@ const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
|
9
9
|
// Inline the same hash helper used in bun-plugin.ts to avoid importing
|
|
10
10
|
// unexported internals from @sigil-dev/compiler.
|
|
11
11
|
function computeHash(filePath: string): string {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
let h = 0x811c9dc5;
|
|
13
|
+
for (let i = 0; i < filePath.length; i++) {
|
|
14
|
+
h ^= filePath.charCodeAt(i);
|
|
15
|
+
h = (h * 0x01000193) >>> 0;
|
|
16
|
+
}
|
|
17
|
+
return h.toString(36);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function rewriteRelativeImports(filePath: string) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const basedir = dirname(filePath);
|
|
22
|
+
const rewrite = (source?: { value: string }) => {
|
|
23
|
+
if (!source?.value.startsWith(".")) return;
|
|
24
|
+
source.value = resolve(basedir, source.value);
|
|
25
|
+
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
27
|
+
return {
|
|
28
|
+
name: "sigil-rewrite-relative-route-imports",
|
|
29
|
+
visitor: {
|
|
30
|
+
ImportDeclaration(path: any) {
|
|
31
|
+
rewrite(path.node.source);
|
|
32
|
+
},
|
|
33
|
+
ExportNamedDeclaration(path: any) {
|
|
34
|
+
rewrite(path.node.source);
|
|
35
|
+
},
|
|
36
|
+
ExportAllDeclaration(path: any) {
|
|
37
|
+
rewrite(path.node.source);
|
|
38
|
+
},
|
|
39
|
+
CallExpression(path: any) {
|
|
40
|
+
if (path.node.callee.type !== "Import") return;
|
|
41
|
+
rewrite(path.node.arguments[0]);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -54,48 +54,48 @@ function rewriteRelativeImports(filePath: string) {
|
|
|
54
54
|
* Bun.build only ever sees plain JavaScript.
|
|
55
55
|
*/
|
|
56
56
|
export async function transformRoutes(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
routes: RouteFile[],
|
|
58
|
+
outDir: string,
|
|
59
|
+
mode: "hydrate" | "dom",
|
|
60
|
+
plugins: GrimoirePlugin[] = [],
|
|
61
61
|
): Promise<Map<string, string>> {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const map = new Map<string, string>();
|
|
63
|
+
// loader: "ts" — Babel already consumed JSX, Bun only strips remaining types
|
|
64
|
+
const transpiler = new Bun.Transpiler({ loader: "ts", target: "browser" });
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
await Promise.all(
|
|
67
|
+
routes.map(async (route, index) => {
|
|
68
|
+
let code = await Bun.file(route.filePath).text();
|
|
69
|
+
const hash = computeHash(route.filePath);
|
|
70
|
+
code = code.replace(STYLE_RE, "");
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
const res = transformSync(code, {
|
|
73
|
+
configFile: false,
|
|
74
|
+
babelrc: false,
|
|
75
|
+
parserOpts: {
|
|
76
|
+
plugins: ["typescript", "jsx"], // no isTSX
|
|
77
|
+
},
|
|
78
|
+
plugins: [
|
|
79
|
+
[sigilPlugin, { hash, mode }],
|
|
80
|
+
rewriteRelativeImports(route.filePath),
|
|
81
|
+
],
|
|
82
|
+
filename: route.filePath,
|
|
83
|
+
});
|
|
84
|
+
let out = transpiler.transformSync(res?.code ?? "");
|
|
85
|
+
for (const plugin of plugins) {
|
|
86
|
+
if (plugin.transform)
|
|
87
|
+
out = (await plugin.transform(out, route.filePath)) ?? out;
|
|
88
|
+
}
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
const rel = relative(process.cwd(), route.filePath)
|
|
91
|
+
.replace(/\.tsx?$/, "")
|
|
92
|
+
.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
93
|
+
const nameBase = rel || basename(route.filePath).replace(/\.[jt]sx$/, "");
|
|
94
|
+
const outPath = join(outDir, `${index}-${nameBase}.${mode}.js`);
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
await Bun.write(outPath, out);
|
|
97
|
+
map.set(route.filePath, outPath);
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
return map;
|
|
101
101
|
}
|