bosia 0.2.2 → 0.3.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 +39 -39
- package/package.json +56 -53
- package/src/ambient.d.ts +31 -0
- package/src/cli/add.ts +120 -114
- package/src/cli/build.ts +10 -10
- package/src/cli/create.ts +142 -137
- package/src/cli/dev.ts +8 -8
- package/src/cli/feat.ts +291 -132
- package/src/cli/index.ts +51 -42
- package/src/cli/registry.ts +136 -115
- package/src/cli/start.ts +17 -17
- package/src/cli/test.ts +25 -0
- package/src/core/build.ts +72 -56
- package/src/core/client/App.svelte +177 -153
- package/src/core/client/appState.svelte.ts +57 -0
- package/src/core/client/enhance.ts +112 -0
- package/src/core/client/hydrate.ts +97 -65
- package/src/core/client/prefetch.ts +101 -94
- package/src/core/client/router.svelte.ts +64 -51
- package/src/core/cookies.ts +70 -66
- package/src/core/cors.ts +44 -35
- package/src/core/csrf.ts +38 -38
- package/src/core/dedup.ts +17 -17
- package/src/core/dev.ts +165 -168
- package/src/core/env.ts +155 -128
- package/src/core/envCodegen.ts +73 -73
- package/src/core/errors.ts +48 -49
- package/src/core/hooks.ts +50 -50
- package/src/core/html.ts +192 -139
- package/src/core/matcher.ts +130 -121
- package/src/core/paths.ts +8 -10
- package/src/core/plugin.ts +113 -107
- package/src/core/prerender.ts +191 -118
- package/src/core/renderer.ts +359 -265
- package/src/core/routeFile.ts +140 -127
- package/src/core/routeTypes.ts +144 -83
- package/src/core/scanner.ts +125 -95
- package/src/core/server.ts +543 -370
- package/src/core/types.ts +25 -20
- package/src/lib/client.ts +12 -0
- package/src/lib/index.ts +8 -8
- package/src/lib/utils.ts +44 -30
- package/templates/default/.prettierignore +5 -0
- package/templates/default/.prettierrc.json +9 -0
- package/templates/default/README.md +5 -5
- package/templates/default/package.json +22 -18
- package/templates/default/src/app.css +80 -80
- package/templates/default/src/app.d.ts +3 -3
- package/templates/default/src/routes/+error.svelte +7 -10
- package/templates/default/src/routes/+layout.svelte +2 -2
- package/templates/default/src/routes/+page.svelte +31 -29
- package/templates/default/src/routes/about/+page.svelte +3 -3
- package/templates/default/tsconfig.json +20 -20
- package/templates/demo/.prettierignore +5 -0
- package/templates/demo/.prettierrc.json +9 -0
- package/templates/demo/README.md +9 -9
- package/templates/demo/package.json +22 -17
- package/templates/demo/src/app.css +80 -80
- package/templates/demo/src/app.d.ts +3 -3
- package/templates/demo/src/hooks.server.ts +9 -9
- package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
- package/templates/demo/src/routes/(public)/+page.svelte +96 -67
- package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
- package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
- package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
- package/templates/demo/src/routes/+error.svelte +10 -7
- package/templates/demo/src/routes/+layout.server.ts +4 -4
- package/templates/demo/src/routes/+layout.svelte +2 -2
- package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
- package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
- package/templates/demo/src/routes/api/hello/+server.ts +25 -25
- package/templates/demo/tsconfig.json +20 -20
- package/templates/todo/.prettierignore +5 -0
- package/templates/todo/.prettierrc.json +9 -0
- package/templates/todo/README.md +9 -9
- package/templates/todo/package.json +22 -17
- package/templates/todo/src/app.css +80 -80
- package/templates/todo/src/app.d.ts +7 -7
- package/templates/todo/src/hooks.server.ts +9 -9
- package/templates/todo/src/routes/+error.svelte +10 -7
- package/templates/todo/src/routes/+layout.server.ts +4 -4
- package/templates/todo/src/routes/+layout.svelte +2 -2
- package/templates/todo/src/routes/+page.svelte +44 -44
- package/templates/todo/template.json +1 -1
- package/templates/todo/tsconfig.json +20 -20
package/src/core/matcher.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RouteMatch } from "./types.ts";
|
|
1
|
+
import type { RouteMatch, TrailingSlash } from "./types.ts";
|
|
2
2
|
|
|
3
3
|
// ─── Route Matcher ───────────────────────────────────────
|
|
4
4
|
// Single shared matcher used by both client and server at runtime.
|
|
@@ -12,57 +12,55 @@ import type { RouteMatch } from "./types.ts";
|
|
|
12
12
|
// ─── Compiled Route Types ────────────────────────────────
|
|
13
13
|
|
|
14
14
|
interface CompiledRoute {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
regex: RegExp;
|
|
16
|
+
paramNames: string[];
|
|
17
|
+
isExact: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// ─── Pattern Compiler ────────────────────────────────────
|
|
21
21
|
|
|
22
22
|
/** Escape regex special chars in a literal string segment. */
|
|
23
23
|
function escapeRegex(s: string): string {
|
|
24
|
-
|
|
24
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Pre-compile a route pattern into a RegExp for fast matching.
|
|
29
29
|
*/
|
|
30
30
|
function compilePattern(pattern: string): CompiledRoute {
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return { regex: new RegExp(regexStr), paramNames, isExact: false };
|
|
31
|
+
// No dynamic segments — exact match via ===
|
|
32
|
+
if (!pattern.includes("[")) {
|
|
33
|
+
return { regex: null!, paramNames: [], isExact: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const paramNames: string[] = [];
|
|
37
|
+
|
|
38
|
+
// Catch-all: /prefix/[...name]
|
|
39
|
+
const catchallMatch = pattern.match(/^(.*?)\/\[\.\.\.(\w+)\]$/);
|
|
40
|
+
if (catchallMatch) {
|
|
41
|
+
const prefix = catchallMatch[1] || "";
|
|
42
|
+
paramNames.push(catchallMatch[2]!);
|
|
43
|
+
const escaped = prefix ? escapeRegex(prefix) : "";
|
|
44
|
+
// Root catch-all /[...rest] must have at least one char after /
|
|
45
|
+
const regex = prefix ? new RegExp(`^${escaped}\\/(.+)$`) : new RegExp(`^\\/(.+)$`);
|
|
46
|
+
return { regex, paramNames, isExact: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Dynamic segments: /blog/[slug]/comments → ^\/blog\/([^/]+)\/comments$
|
|
50
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
51
|
+
let regexStr = "^";
|
|
52
|
+
for (const seg of segments) {
|
|
53
|
+
regexStr += "\\/";
|
|
54
|
+
if (seg.startsWith("[") && seg.endsWith("]")) {
|
|
55
|
+
paramNames.push(seg.slice(1, -1));
|
|
56
|
+
regexStr += "([^/]+)";
|
|
57
|
+
} else {
|
|
58
|
+
regexStr += escapeRegex(seg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
regexStr += "$";
|
|
62
|
+
|
|
63
|
+
return { regex: new RegExp(regexStr), paramNames, isExact: false };
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
/**
|
|
@@ -71,12 +69,12 @@ function compilePattern(pattern: string): CompiledRoute {
|
|
|
71
69
|
* Call once at startup — all modules sharing the same route array see the result.
|
|
72
70
|
*/
|
|
73
71
|
export function compileRoutes<T extends { pattern: string }>(
|
|
74
|
-
|
|
72
|
+
routes: T[],
|
|
75
73
|
): (T & { _compiled: CompiledRoute })[] {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
for (const route of routes) {
|
|
75
|
+
(route as any)._compiled = compilePattern(route.pattern);
|
|
76
|
+
}
|
|
77
|
+
return routes as (T & { _compiled: CompiledRoute })[];
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
// ─── Legacy Pattern Matcher (fallback for uncompiled routes) ─
|
|
@@ -85,51 +83,48 @@ export function compileRoutes<T extends { pattern: string }>(
|
|
|
85
83
|
* Match a URL pathname against a single route pattern.
|
|
86
84
|
* Returns extracted params if matched, null otherwise.
|
|
87
85
|
*/
|
|
88
|
-
function matchPattern(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return params;
|
|
86
|
+
function matchPattern(pattern: string, pathname: string): Record<string, string> | null {
|
|
87
|
+
// Strip trailing slash (but keep "/" as-is)
|
|
88
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
89
|
+
pathname = pathname.slice(0, -1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Exact match
|
|
93
|
+
if (pattern === pathname) return {};
|
|
94
|
+
|
|
95
|
+
// Catch-all pattern: /[...name] or /prefix/[...name]
|
|
96
|
+
const catchallMatch = pattern.match(/^(.*?)\/\[\.\.\.(\w+)\]$/);
|
|
97
|
+
if (catchallMatch) {
|
|
98
|
+
const prefix = catchallMatch[1] || "";
|
|
99
|
+
const paramName = catchallMatch[2]!;
|
|
100
|
+
if (prefix === "" || pathname.startsWith(prefix + "/") || pathname === prefix) {
|
|
101
|
+
const rest = prefix ? pathname.slice(prefix.length + 1) : pathname.slice(1);
|
|
102
|
+
// Don't let a root catch-all match "/" with an empty slug.
|
|
103
|
+
// If you want the catch-all to also serve "/", add an explicit +page.svelte at the root.
|
|
104
|
+
if (!prefix && rest === "") return null;
|
|
105
|
+
return { [paramName]: rest };
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Dynamic segments: must have same segment count
|
|
111
|
+
if (!pattern.includes("[")) return null;
|
|
112
|
+
|
|
113
|
+
const patParts = pattern.split("/").filter(Boolean);
|
|
114
|
+
const pathParts = pathname.split("/").filter(Boolean);
|
|
115
|
+
if (patParts.length !== pathParts.length) return null;
|
|
116
|
+
|
|
117
|
+
const params: Record<string, string> = {};
|
|
118
|
+
for (let i = 0; i < patParts.length; i++) {
|
|
119
|
+
const pp = patParts[i]!;
|
|
120
|
+
const val = pathParts[i]!;
|
|
121
|
+
if (pp.startsWith("[") && pp.endsWith("]")) {
|
|
122
|
+
params[pp.slice(1, -1)] = val;
|
|
123
|
+
} else if (pp !== val) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return params;
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
// ─── Route Matching ──────────────────────────────────────
|
|
@@ -139,22 +134,22 @@ function matchPattern(
|
|
|
139
134
|
* Returns extracted params if matched, null otherwise.
|
|
140
135
|
*/
|
|
141
136
|
function matchCompiled(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
137
|
+
compiled: CompiledRoute,
|
|
138
|
+
pattern: string,
|
|
139
|
+
pathname: string,
|
|
145
140
|
): Record<string, string> | null {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
141
|
+
if (compiled.isExact) {
|
|
142
|
+
return pattern === pathname ? {} : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const m = compiled.regex.exec(pathname);
|
|
146
|
+
if (!m) return null;
|
|
147
|
+
|
|
148
|
+
const params: Record<string, string> = {};
|
|
149
|
+
for (let i = 0; i < compiled.paramNames.length; i++) {
|
|
150
|
+
params[compiled.paramNames[i]!] = m[i + 1]!;
|
|
151
|
+
}
|
|
152
|
+
return params;
|
|
158
153
|
}
|
|
159
154
|
|
|
160
155
|
/**
|
|
@@ -163,21 +158,35 @@ function matchCompiled(
|
|
|
163
158
|
* Single pass — first match wins.
|
|
164
159
|
*/
|
|
165
160
|
export function findMatch<T extends { pattern: string }>(
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
routes: T[],
|
|
162
|
+
pathname: string,
|
|
168
163
|
): RouteMatch<T> | null {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
// Strip trailing slash (but keep "/" as-is)
|
|
165
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
166
|
+
pathname = pathname.slice(0, -1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const route of routes) {
|
|
170
|
+
const compiled = (route as any)._compiled as CompiledRoute | undefined;
|
|
171
|
+
const params = compiled
|
|
172
|
+
? matchCompiled(compiled, route.pattern, pathname)
|
|
173
|
+
: matchPattern(route.pattern, pathname);
|
|
174
|
+
if (params !== null) return { route, params };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Trailing-Slash Canonicalization ──────────────────────
|
|
181
|
+
// Returns the canonical pathname for a given mode, or null if pathname is
|
|
182
|
+
// already canonical. Root "/" is never modified. Caller decides whether to
|
|
183
|
+
// 308-redirect (server) or replaceState (client).
|
|
184
|
+
|
|
185
|
+
export function canonicalPathname(pathname: string, mode: TrailingSlash): string | null {
|
|
186
|
+
if (mode === "ignore") return null;
|
|
187
|
+
if (pathname === "/") return null;
|
|
188
|
+
const endsWithSlash = pathname.endsWith("/");
|
|
189
|
+
if (mode === "never" && endsWithSlash) return pathname.slice(0, -1);
|
|
190
|
+
if (mode === "always" && !endsWithSlash) return pathname + "/";
|
|
191
|
+
return null;
|
|
183
192
|
}
|
package/src/core/paths.ts
CHANGED
|
@@ -16,17 +16,15 @@ const isInstalledAsDep = parentDir.endsWith("node_modules");
|
|
|
16
16
|
const HOISTED_NM = isInstalledAsDep ? parentDir : null;
|
|
17
17
|
|
|
18
18
|
/** NODE_PATH value covering both nested and hoisted dependency locations */
|
|
19
|
-
export const BOSIA_NODE_PATH = HOISTED_NM
|
|
20
|
-
? [NESTED_NM, HOISTED_NM].join(":")
|
|
21
|
-
: NESTED_NM;
|
|
19
|
+
export const BOSIA_NODE_PATH = HOISTED_NM ? [NESTED_NM, HOISTED_NM].join(":") : NESTED_NM;
|
|
22
20
|
|
|
23
21
|
/** Find a binary from bosia's dependencies (handles hoisting) */
|
|
24
22
|
export function resolveBosiaBin(name: string): string {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
const nested = join(NESTED_NM, ".bin", name);
|
|
24
|
+
if (existsSync(nested)) return nested;
|
|
25
|
+
if (HOISTED_NM) {
|
|
26
|
+
const hoisted = join(HOISTED_NM, ".bin", name);
|
|
27
|
+
if (existsSync(hoisted)) return hoisted;
|
|
28
|
+
}
|
|
29
|
+
return nested; // fallback — will produce a clear ENOENT
|
|
32
30
|
}
|
package/src/core/plugin.ts
CHANGED
|
@@ -8,126 +8,132 @@ import { join, dirname } from "path";
|
|
|
8
8
|
|
|
9
9
|
let cachedTsconfigPaths: Record<string, string[]> | null = null;
|
|
10
10
|
async function getTsconfigPaths() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
if (cachedTsconfigPaths !== null) return cachedTsconfigPaths;
|
|
12
|
+
try {
|
|
13
|
+
const tsconfig = await Bun.file(join(process.cwd(), "tsconfig.json")).json();
|
|
14
|
+
cachedTsconfigPaths = tsconfig?.compilerOptions?.paths || {};
|
|
15
|
+
} catch {
|
|
16
|
+
cachedTsconfigPaths = {};
|
|
17
|
+
}
|
|
18
|
+
return cachedTsconfigPaths!;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function makeBosiaPlugin(target: "browser" | "bun" = "bun") {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
return {
|
|
23
|
+
name: "bosia-resolver",
|
|
24
|
+
setup(build: import("bun").PluginBuilder) {
|
|
25
|
+
// bosia:routes → .bosia/routes.client.ts (browser) or .bosia/routes.ts (server)
|
|
26
|
+
// Client-only file excludes serverRoutes/apiRoutes to prevent the browser
|
|
27
|
+
// bundler from following server-side dynamic imports into Node builtins.
|
|
28
|
+
build.onResolve({ filter: /^bosia:routes$/ }, () => ({
|
|
29
|
+
path: join(
|
|
30
|
+
process.cwd(),
|
|
31
|
+
".bosia",
|
|
32
|
+
target === "browser" ? "routes.client.ts" : "routes.ts",
|
|
33
|
+
),
|
|
34
|
+
}));
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
// $env → .bosia/env.client.ts (browser) or .bosia/env.server.ts (bun)
|
|
37
|
+
build.onResolve({ filter: /^\$env$/ }, () => ({
|
|
38
|
+
path: join(
|
|
39
|
+
process.cwd(),
|
|
40
|
+
".bosia",
|
|
41
|
+
target === "browser" ? "env.client.ts" : "env.server.ts",
|
|
42
|
+
),
|
|
43
|
+
}));
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
// Handle all $ aliases using tsconfig.json paths (e.g. $lib, $registry)
|
|
46
|
+
build.onResolve({ filter: /^\$/ }, async (args) => {
|
|
47
|
+
if (args.path === "$env") return undefined; // Handled above
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const paths = await getTsconfigPaths();
|
|
50
|
+
let longestMatch = "";
|
|
51
|
+
let targetPattern = "";
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
54
|
+
const prefix = pattern.replace(/\*$/, "");
|
|
55
|
+
if (args.path.startsWith(prefix) && prefix.length > longestMatch.length) {
|
|
56
|
+
longestMatch = prefix;
|
|
57
|
+
targetPattern = (targets as string[])[0];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (longestMatch && targetPattern) {
|
|
62
|
+
const suffix = args.path.slice(longestMatch.length);
|
|
63
|
+
const targetDir = targetPattern.replace(/\*$/, "");
|
|
64
|
+
const resolved = join(process.cwd(), targetDir, suffix);
|
|
65
|
+
return { path: await resolveWithExts(resolved) };
|
|
66
|
+
}
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// Fallback for $lib/* if not in tsconfig
|
|
69
|
+
if (args.path.startsWith("$lib/")) {
|
|
70
|
+
const rel = args.path.slice(5);
|
|
71
|
+
const base = join(process.cwd(), "src", "lib", rel);
|
|
72
|
+
return { path: await resolveWithExts(base) };
|
|
73
|
+
}
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
return undefined;
|
|
76
|
+
});
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
78
|
+
// Force svelte imports to resolve from the app's node_modules.
|
|
79
|
+
// Without this, when bosia is symlinked (bun link / workspace),
|
|
80
|
+
// hydrate.ts resolves "svelte" from the framework's location while
|
|
81
|
+
// compiled components resolve "svelte/internal/client" from the app's.
|
|
82
|
+
// Two different Svelte copies = duplicate runtime state = broken hydration.
|
|
83
|
+
//
|
|
84
|
+
// require.resolve uses the "default" export condition, which for
|
|
85
|
+
// bare "svelte" returns index-server.js. For browser builds we need
|
|
86
|
+
// index-client.js, so we read the "browser" condition from package.json.
|
|
87
|
+
const appDir = process.cwd();
|
|
88
|
+
let svelteBrowserEntry: string | null = null;
|
|
89
|
+
if (target === "browser") {
|
|
90
|
+
try {
|
|
91
|
+
const svelteDir = dirname(
|
|
92
|
+
require.resolve("svelte/package.json", { paths: [appDir] }),
|
|
93
|
+
);
|
|
94
|
+
const pkg = require(join(svelteDir, "package.json"));
|
|
95
|
+
const dotExport = pkg.exports?.["."];
|
|
96
|
+
const browserPath = typeof dotExport === "object" ? dotExport.browser : null;
|
|
97
|
+
if (browserPath) {
|
|
98
|
+
svelteBrowserEntry = join(svelteDir, browserPath);
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
build.onResolve({ filter: /^svelte(\/.*)?$/ }, (args) => {
|
|
103
|
+
try {
|
|
104
|
+
// Bare "svelte" in browser build: use the "browser" export condition
|
|
105
|
+
if (args.path === "svelte" && svelteBrowserEntry) {
|
|
106
|
+
return { path: svelteBrowserEntry };
|
|
107
|
+
}
|
|
108
|
+
return { path: require.resolve(args.path, { paths: [appDir] }) };
|
|
109
|
+
} catch {
|
|
110
|
+
return undefined; // fall through to default resolution
|
|
111
|
+
}
|
|
112
|
+
});
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
// "tailwindcss" inside app.css is a Tailwind CLI directive —
|
|
115
|
+
// it's already compiled to public/bosia-tw.css by the CLI step.
|
|
116
|
+
// Return an empty CSS module so Bun's CSS bundler doesn't choke on it.
|
|
117
|
+
build.onResolve({ filter: /^tailwindcss$/ }, () => ({
|
|
118
|
+
path: "tailwindcss",
|
|
119
|
+
namespace: "bosia-empty-css",
|
|
120
|
+
}));
|
|
121
|
+
build.onLoad({ filter: /.*/, namespace: "bosia-empty-css" }, () => ({
|
|
122
|
+
contents: "",
|
|
123
|
+
loader: "css",
|
|
124
|
+
}));
|
|
125
|
+
},
|
|
126
|
+
};
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
async function resolveWithExts(base: string): Promise<string> {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
if (await Bun.file(base).exists()) return base;
|
|
131
|
+
for (const ext of [".ts", ".svelte", ".js"]) {
|
|
132
|
+
if (await Bun.file(base + ext).exists()) return base + ext;
|
|
133
|
+
}
|
|
134
|
+
for (const idx of ["index.ts", "index.svelte", "index.js"]) {
|
|
135
|
+
const p = join(base, idx);
|
|
136
|
+
if (await Bun.file(p).exists()) return p;
|
|
137
|
+
}
|
|
138
|
+
return base;
|
|
133
139
|
}
|