swift-rust 1.1.0 → 1.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/bin/build.mjs +30 -2
- package/bin/dev-server.mjs +269 -11
- package/bin/error-overlay.mjs +2 -2
- package/bin/runtime/navigator.js +238 -0
- package/dist/link.d.ts +1 -1
- package/dist/link.d.ts.map +1 -1
- package/dist/link.jsx +11 -2
- package/dist/link.jsx.map +1 -1
- package/package.json +4 -2
- package/tsconfig.base.json +21 -0
package/bin/build.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { join, resolve, dirname, sep } from "node:path";
|
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
|
|
17
17
|
const cwd = process.cwd();
|
|
18
|
-
const APP_DIR_CANDIDATES = [resolve(cwd, "
|
|
18
|
+
const APP_DIR_CANDIDATES = [resolve(cwd, "src", "app"), resolve(cwd, "app")];
|
|
19
19
|
const APP_DIR = APP_DIR_CANDIDATES.find((p) => existsSync(p)) ?? resolve(cwd, "app");
|
|
20
20
|
const PUBLIC_DIR = resolve(cwd, "public");
|
|
21
21
|
const OUT_DIR = resolve(cwd, ".vercel", "output");
|
|
@@ -200,6 +200,15 @@ function stripHmrScript(html) {
|
|
|
200
200
|
return html.replace(/\s*<script src="\/_swift-rust\/hmr-client\.js"[^>]*>\s*<\/script>/g, "");
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
// The dev-time image endpoint (/_swift-rust/image) only exists while the dev
|
|
204
|
+
// server runs. On Vercel the platform serves an optimizing endpoint at
|
|
205
|
+
// /_vercel/image with the exact same query contract (url, w, q), so we swap the
|
|
206
|
+
// path prefix at build time. The `images` config written into config.json
|
|
207
|
+
// enables (and bounds) that optimizer. Handles `src`, `srcset`, and `&`.
|
|
208
|
+
function rewriteImageUrls(html) {
|
|
209
|
+
return html.split("/_swift-rust/image?").join("/_vercel/image?");
|
|
210
|
+
}
|
|
211
|
+
|
|
203
212
|
function writeStaticFile(outDir, pathname, html) {
|
|
204
213
|
const rel = pathname === "/" ? "index.html" : `${pathname.replace(/^\//, "")}/index.html`;
|
|
205
214
|
const outPath = join(outDir, rel);
|
|
@@ -263,6 +272,16 @@ function writeConfigJson(outDir, _hasPublic) {
|
|
|
263
272
|
overrides: {
|
|
264
273
|
"404.html": { path: "404", contentType: "text/html; charset=utf-8" },
|
|
265
274
|
},
|
|
275
|
+
// Enables Vercel's image optimizer (/_vercel/image). `sizes` MUST match the
|
|
276
|
+
// widths <Image> requests (packages/image DEVICE_SIZES) or requests 400.
|
|
277
|
+
images: {
|
|
278
|
+
sizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
279
|
+
formats: ["image/avif", "image/webp"],
|
|
280
|
+
minimumCacheTTL: 86400,
|
|
281
|
+
// Allow optimizing local SVG assets (e.g. blog covers); sandboxed by CSP.
|
|
282
|
+
dangerouslyAllowSVG: true,
|
|
283
|
+
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
|
284
|
+
},
|
|
266
285
|
};
|
|
267
286
|
writeFileSync(join(outDir, "config.json"), `${JSON.stringify(config, null, 2)}\n`);
|
|
268
287
|
}
|
|
@@ -325,7 +344,7 @@ async function main() {
|
|
|
325
344
|
try {
|
|
326
345
|
const { status, body } = await fetchRoute(route);
|
|
327
346
|
if (status === 200) {
|
|
328
|
-
const cleaned = await localizeIslands(stripHmrScript(body));
|
|
347
|
+
const cleaned = rewriteImageUrls(await localizeIslands(stripHmrScript(body)));
|
|
329
348
|
writeStaticFile(STATIC_DIR, route, cleaned);
|
|
330
349
|
okCount++;
|
|
331
350
|
process.stdout.write(` ${paint("green", "✓")} ${route}\n`);
|
|
@@ -393,6 +412,15 @@ async function main() {
|
|
|
393
412
|
}
|
|
394
413
|
}
|
|
395
414
|
|
|
415
|
+
// Client navigator runtime (SPA navigation). The rendered HTML references
|
|
416
|
+
// /_swift-rust/navigator.js; emit it as a static asset so deployed sites
|
|
417
|
+
// get client-side navigation too.
|
|
418
|
+
const navSrc = join(RUNTIME_DIR, "navigator.js");
|
|
419
|
+
if (existsSync(navSrc)) {
|
|
420
|
+
writeRawFile(STATIC_DIR, "_swift-rust/navigator.js", readFileSync(navSrc));
|
|
421
|
+
process.stdout.write(` ${paint("green", "✓")} _swift-rust/navigator.js\n`);
|
|
422
|
+
}
|
|
423
|
+
|
|
396
424
|
writeConfigJson(OUT_DIR, hasPublic);
|
|
397
425
|
|
|
398
426
|
const total = Date.now() - start;
|
package/bin/dev-server.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, statSync, readFileSync, readdirSync, writeFileSync, unlinkSync, watch as fsWatch } from "node:fs";
|
|
3
|
-
import { join, resolve, extname, relative, dirname, sep } from "node:path";
|
|
3
|
+
import { join, resolve, extname, relative, dirname, basename, sep } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { performance } from "node:perf_hooks";
|
|
6
6
|
import { errorOverlayHTML as renderErrorOverlay } from "./error-overlay.mjs";
|
|
@@ -17,7 +17,7 @@ function getArg(name, fallback) {
|
|
|
17
17
|
return fallback;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const port = parseInt(getArg("port", process.env.PORT || "
|
|
20
|
+
const port = parseInt(getArg("port", process.env.PORT || "3210"), 10);
|
|
21
21
|
const hostname = getArg("hostname", "0.0.0.0");
|
|
22
22
|
|
|
23
23
|
const c = {
|
|
@@ -36,7 +36,7 @@ const useColor = process.stdout.isTTY !== false && !process.env.NO_COLOR;
|
|
|
36
36
|
const paint = (color, s) => (useColor ? `${c[color]}${s}${c.reset}` : s);
|
|
37
37
|
|
|
38
38
|
const VERSION = "0.1.0";
|
|
39
|
-
const APP_DIR_CANDIDATES = [resolve(cwd, "
|
|
39
|
+
const APP_DIR_CANDIDATES = [resolve(cwd, "src", "app"), resolve(cwd, "app")];
|
|
40
40
|
const APP_DIR = APP_DIR_CANDIDATES.find((p) => existsSync(p)) ?? resolve(cwd, "app");
|
|
41
41
|
const PUBLIC_DIR = resolve(cwd, "public");
|
|
42
42
|
const SWIFT_RUST_CONFIG = resolve(cwd, "swift-rust.config.json");
|
|
@@ -617,6 +617,7 @@ function mergeMetadata(...metas) {
|
|
|
617
617
|
if (m.description) out.description = m.description;
|
|
618
618
|
if (m.keywords) out.keywords = m.keywords;
|
|
619
619
|
if (m.openGraph) out.openGraph = { ...(out.openGraph || {}), ...m.openGraph };
|
|
620
|
+
if (m.twitter) out.twitter = { ...(out.twitter || {}), ...m.twitter };
|
|
620
621
|
}
|
|
621
622
|
return out;
|
|
622
623
|
}
|
|
@@ -641,6 +642,24 @@ function metadataToHead(meta) {
|
|
|
641
642
|
for (const img of meta.openGraph.images) {
|
|
642
643
|
const url = typeof img === "string" ? img : img.url;
|
|
643
644
|
if (url) parts.push(`<meta property="og:image" content="${escapeHtml(url)}" />`);
|
|
645
|
+
if (typeof img === "object" && img) {
|
|
646
|
+
if (img.width) parts.push(`<meta property="og:image:width" content="${escapeHtml(img.width)}" />`);
|
|
647
|
+
if (img.height) parts.push(`<meta property="og:image:height" content="${escapeHtml(img.height)}" />`);
|
|
648
|
+
if (img.alt) parts.push(`<meta property="og:image:alt" content="${escapeHtml(img.alt)}" />`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (meta.twitter) {
|
|
654
|
+
if (meta.twitter.card) parts.push(`<meta name="twitter:card" content="${escapeHtml(meta.twitter.card)}" />`);
|
|
655
|
+
if (meta.twitter.site) parts.push(`<meta name="twitter:site" content="${escapeHtml(meta.twitter.site)}" />`);
|
|
656
|
+
if (meta.twitter.creator) parts.push(`<meta name="twitter:creator" content="${escapeHtml(meta.twitter.creator)}" />`);
|
|
657
|
+
if (meta.twitter.title) parts.push(`<meta name="twitter:title" content="${escapeHtml(meta.twitter.title)}" />`);
|
|
658
|
+
if (meta.twitter.description) parts.push(`<meta name="twitter:description" content="${escapeHtml(meta.twitter.description)}" />`);
|
|
659
|
+
if (meta.twitter.images) {
|
|
660
|
+
for (const img of meta.twitter.images) {
|
|
661
|
+
const url = typeof img === "string" ? img : img.url;
|
|
662
|
+
if (url) parts.push(`<meta name="twitter:image" content="${escapeHtml(url)}" />`);
|
|
644
663
|
}
|
|
645
664
|
}
|
|
646
665
|
}
|
|
@@ -796,10 +815,12 @@ function warnRoutingFile(file, msg) {
|
|
|
796
815
|
function validateRoutingConventions() {
|
|
797
816
|
const names = [...SPECIAL_FILES, "page"];
|
|
798
817
|
const appRel = relative(cwd, APP_DIR) || "app";
|
|
799
|
-
const
|
|
800
|
-
const
|
|
818
|
+
const srcRoot = dirname(APP_DIR);
|
|
819
|
+
const usesSrc = basename(APP_DIR) === "app" && basename(srcRoot) === "src";
|
|
820
|
+
const scan = (dir, label, allow = []) => {
|
|
801
821
|
if (!existsSync(dir) || resolve(dir) === resolve(APP_DIR)) return;
|
|
802
822
|
for (const name of names) {
|
|
823
|
+
if (allow.includes(name)) continue;
|
|
803
824
|
const f = findFile(dir, name);
|
|
804
825
|
if (f) {
|
|
805
826
|
warnRoutingFile(
|
|
@@ -811,7 +832,8 @@ function validateRoutingConventions() {
|
|
|
811
832
|
}
|
|
812
833
|
};
|
|
813
834
|
scan(cwd, "the project root");
|
|
814
|
-
|
|
835
|
+
// proxy.ts is allowed to live at the src/ root (sibling of src/app).
|
|
836
|
+
if (usesSrc) scan(srcRoot, `"src/" (outside "app/")`, ["proxy"]);
|
|
815
837
|
}
|
|
816
838
|
|
|
817
839
|
// ── RFC 0001 route pipeline: config → schema → guard → loader/action ────────
|
|
@@ -902,7 +924,16 @@ async function runRoutePipeline(route, ctx) {
|
|
|
902
924
|
|
|
903
925
|
// proxy.ts — phase 1, outer → inner. Cheap, data-free interception
|
|
904
926
|
// (Next.js calls this "proxy"; "middleware" is accepted with a warning).
|
|
905
|
-
|
|
927
|
+
// When using a src/ directory, a root proxy lives at src/proxy.ts (sibling
|
|
928
|
+
// of src/app), so it runs before any in-app proxy.
|
|
929
|
+
const proxyFiles = [];
|
|
930
|
+
const srcRoot = dirname(APP_DIR);
|
|
931
|
+
if (basename(APP_DIR) === "app" && basename(srcRoot) === "src") {
|
|
932
|
+
const rootProxy = findFile(srcRoot, "proxy");
|
|
933
|
+
if (rootProxy) proxyFiles.push({ file: rootProxy, dir: srcRoot });
|
|
934
|
+
}
|
|
935
|
+
proxyFiles.push(...collectRouteFiles(chain, "proxy"));
|
|
936
|
+
for (const { file } of proxyFiles) {
|
|
906
937
|
const mod = await loadModuleFresh(file);
|
|
907
938
|
const fn = mod.default ?? mod.proxy;
|
|
908
939
|
if (typeof fn === "function") {
|
|
@@ -1095,6 +1126,98 @@ function cacheControlFromPlan(plan) {
|
|
|
1095
1126
|
return null;
|
|
1096
1127
|
}
|
|
1097
1128
|
|
|
1129
|
+
// ── Parallel routes (@slot dirs → fragment / fallback / default) ────────────
|
|
1130
|
+
function findDynamicChild(dir) {
|
|
1131
|
+
try {
|
|
1132
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
1133
|
+
if (e.isDirectory() && /^\[.+\]$/.test(e.name)) return join(dir, e.name);
|
|
1134
|
+
}
|
|
1135
|
+
} catch {}
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Resolve one @slot dir against the URL segments below its layout. Returns a
|
|
1140
|
+
// React element (the slot's matched page/fragment, its default.tsx, wrapped in
|
|
1141
|
+
// any slot layouts + a fallback.tsx Suspense boundary) or null.
|
|
1142
|
+
async function resolveSlotElement(slotDir, segs) {
|
|
1143
|
+
const React = await import("react");
|
|
1144
|
+
let cur = slotDir;
|
|
1145
|
+
const slotLayouts = [];
|
|
1146
|
+
let matched = true;
|
|
1147
|
+
for (let i = 0; i < segs.length; i++) {
|
|
1148
|
+
const layoutFile = findFile(cur, "layout");
|
|
1149
|
+
if (layoutFile) slotLayouts.push(layoutFile);
|
|
1150
|
+
let next = join(cur, segs[i]);
|
|
1151
|
+
if (!existsSync(next) || !statSync(next).isDirectory()) {
|
|
1152
|
+
const dyn = findDynamicChild(cur);
|
|
1153
|
+
if (dyn) next = dyn;
|
|
1154
|
+
else {
|
|
1155
|
+
matched = false;
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
cur = next;
|
|
1160
|
+
}
|
|
1161
|
+
let leafFile = null;
|
|
1162
|
+
if (matched) {
|
|
1163
|
+
const layoutFile = findFile(cur, "layout");
|
|
1164
|
+
if (layoutFile && !slotLayouts.includes(layoutFile)) slotLayouts.push(layoutFile);
|
|
1165
|
+
leafFile = findFile(cur, "page") || findFile(cur, "fragment");
|
|
1166
|
+
}
|
|
1167
|
+
if (!leafFile) {
|
|
1168
|
+
// Unmatched slot → default.tsx (Next.js semantics), else nothing.
|
|
1169
|
+
const def = findFile(slotDir, "default");
|
|
1170
|
+
if (!def) return null;
|
|
1171
|
+
leafFile = def;
|
|
1172
|
+
slotLayouts.length = 0;
|
|
1173
|
+
const rootLayout = findFile(slotDir, "layout");
|
|
1174
|
+
if (rootLayout) slotLayouts.push(rootLayout);
|
|
1175
|
+
}
|
|
1176
|
+
let mod;
|
|
1177
|
+
try {
|
|
1178
|
+
mod = await loadModuleFresh(leafFile);
|
|
1179
|
+
} catch {
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
const Comp = mod.default ?? mod.Page ?? mod.Fragment ?? mod.page ?? mod.fragment;
|
|
1183
|
+
if (!Comp) return null;
|
|
1184
|
+
let el = React.createElement(Comp, {});
|
|
1185
|
+
for (let i = slotLayouts.length - 1; i >= 0; i--) {
|
|
1186
|
+
try {
|
|
1187
|
+
const lm = await loadModuleFresh(slotLayouts[i]);
|
|
1188
|
+
const L = lm.default ?? lm.Layout ?? lm.layout;
|
|
1189
|
+
if (L) el = React.createElement(L, null, el);
|
|
1190
|
+
} catch {}
|
|
1191
|
+
}
|
|
1192
|
+
const fallbackFile = findFile(slotDir, "fallback");
|
|
1193
|
+
if (fallbackFile) {
|
|
1194
|
+
try {
|
|
1195
|
+
const fb = await loadModuleFresh(fallbackFile);
|
|
1196
|
+
const Fallback = fb.default ?? fb.Fallback;
|
|
1197
|
+
if (Fallback) el = React.createElement(React.Suspense, { fallback: React.createElement(Fallback, {}) }, el);
|
|
1198
|
+
} catch {}
|
|
1199
|
+
}
|
|
1200
|
+
return el;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Collect all @slot dirs of a layout dir into named props for the layout.
|
|
1204
|
+
async function resolveParallelSlots(dir, segs) {
|
|
1205
|
+
const slots = {};
|
|
1206
|
+
let entries;
|
|
1207
|
+
try {
|
|
1208
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1209
|
+
} catch {
|
|
1210
|
+
return slots;
|
|
1211
|
+
}
|
|
1212
|
+
for (const e of entries) {
|
|
1213
|
+
if (e.isDirectory() && e.name.startsWith("@")) {
|
|
1214
|
+
const el = await resolveSlotElement(join(dir, e.name), segs);
|
|
1215
|
+
if (el) slots[e.name.slice(1)] = el;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return slots;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1098
1221
|
async function renderRoute(urlPath, req) {
|
|
1099
1222
|
const segments = urlToRouteSegments(urlPath);
|
|
1100
1223
|
const route = resolvePageRoute(segments);
|
|
@@ -1168,10 +1291,28 @@ async function renderRoute(urlPath, req) {
|
|
|
1168
1291
|
tree,
|
|
1169
1292
|
);
|
|
1170
1293
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1294
|
+
// Wrap the page with each segment's template then layout, innermost dir
|
|
1295
|
+
// first, so the nesting is layout > template > children (Next.js order).
|
|
1296
|
+
// template.tsx re-mounts on navigation; on the server it just wraps.
|
|
1297
|
+
const dirChain = route.dirChain && route.dirChain.length ? route.dirChain : [APP_DIR];
|
|
1298
|
+
for (let i = dirChain.length - 1; i >= 0; i--) {
|
|
1299
|
+
const dir = dirChain[i];
|
|
1300
|
+
const templateFile = findFile(dir, "template");
|
|
1301
|
+
if (templateFile) {
|
|
1302
|
+
const tmod = await loadModuleFresh(templateFile);
|
|
1303
|
+
const Template = tmod.default ?? tmod.Template ?? tmod.template;
|
|
1304
|
+
if (Template) tree = React.createElement(Template, null, tree);
|
|
1305
|
+
}
|
|
1306
|
+
const layoutFile = findFile(dir, "layout");
|
|
1307
|
+
if (layoutFile) {
|
|
1308
|
+
const layoutMod = await loadModuleFresh(layoutFile);
|
|
1309
|
+
const Layout = layoutMod.default ?? layoutMod.Layout ?? layoutMod.layout;
|
|
1310
|
+
if (Layout) {
|
|
1311
|
+
// Parallel routes: @slot subdirs of this layout become named props.
|
|
1312
|
+
const slotProps = await resolveParallelSlots(dir, segments.slice(i));
|
|
1313
|
+
tree = React.createElement(Layout, slotProps, tree);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1175
1316
|
}
|
|
1176
1317
|
const html = await renderToStringCompat(tree);
|
|
1177
1318
|
if (runtime?.__setRouteContext) runtime.__setRouteContext(null);
|
|
@@ -1226,10 +1367,93 @@ async function renderRoute(urlPath, req) {
|
|
|
1226
1367
|
}
|
|
1227
1368
|
} catch {}
|
|
1228
1369
|
}
|
|
1370
|
+
// global-error.tsx — root-level boundary. It renders its own <html>/<body>,
|
|
1371
|
+
// so it replaces the whole document. Only kicks in for uncaught errors when
|
|
1372
|
+
// no closer error/error-recovery boundary handled them.
|
|
1373
|
+
const globalErrorFile = findFile(APP_DIR, "global-error");
|
|
1374
|
+
if (globalErrorFile) {
|
|
1375
|
+
try {
|
|
1376
|
+
const React = await import("react");
|
|
1377
|
+
const mod = await loadModule(globalErrorFile, { bust: true });
|
|
1378
|
+
const GlobalError = mod.default ?? mod.GlobalError;
|
|
1379
|
+
if (GlobalError) {
|
|
1380
|
+
const inner = await renderToStringCompat(
|
|
1381
|
+
React.createElement(GlobalError, { error: err, reset: () => {} }),
|
|
1382
|
+
);
|
|
1383
|
+
// status:200 here only gates past the dev error overlay; the
|
|
1384
|
+
// rawResponse carries the real 500 to the client.
|
|
1385
|
+
return {
|
|
1386
|
+
status: 200,
|
|
1387
|
+
html: null,
|
|
1388
|
+
error: null,
|
|
1389
|
+
segments,
|
|
1390
|
+
rawResponse: new Response(`<!DOCTYPE html>${inner}`, {
|
|
1391
|
+
status: 500,
|
|
1392
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
1393
|
+
}),
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
} catch {}
|
|
1397
|
+
}
|
|
1229
1398
|
return { status: 500, html: null, error: err, segments, pageFile: route.file };
|
|
1230
1399
|
}
|
|
1231
1400
|
}
|
|
1232
1401
|
|
|
1402
|
+
async function resolveTransitionConfig(segments) {
|
|
1403
|
+
const file = findRouteFileUp(segments || [], "transition");
|
|
1404
|
+
if (!file) return null;
|
|
1405
|
+
try {
|
|
1406
|
+
const mod = await loadModuleFresh(file);
|
|
1407
|
+
const cfg = mod.default && typeof mod.default === "object" ? mod.default : {};
|
|
1408
|
+
const type = mod.type ?? cfg.type ?? "fade";
|
|
1409
|
+
const out = { type };
|
|
1410
|
+
const duration = mod.duration ?? cfg.duration;
|
|
1411
|
+
if (duration) out.duration = Number(duration);
|
|
1412
|
+
return out;
|
|
1413
|
+
} catch {
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
async function resolvePrefetchConfig(segments) {
|
|
1419
|
+
const file = findRouteFileUp(segments || [], "prefetch");
|
|
1420
|
+
if (!file) return null;
|
|
1421
|
+
try {
|
|
1422
|
+
const mod = await loadModuleFresh(file);
|
|
1423
|
+
const cfg = mod.default && typeof mod.default === "object" ? mod.default : {};
|
|
1424
|
+
const strategy = mod.strategy ?? cfg.strategy ?? "hover";
|
|
1425
|
+
const out = { strategy };
|
|
1426
|
+
const margin = mod.margin ?? cfg.margin;
|
|
1427
|
+
if (margin) out.margin = String(margin);
|
|
1428
|
+
return out;
|
|
1429
|
+
} catch {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const pendingOverlayCache = new Map();
|
|
1435
|
+
async function renderPendingOverlay(segments) {
|
|
1436
|
+
const file = findRouteFileUp(segments || [], "pending");
|
|
1437
|
+
if (!file) return "";
|
|
1438
|
+
try {
|
|
1439
|
+
let inner;
|
|
1440
|
+
const cached = pendingOverlayCache.get(file);
|
|
1441
|
+
if (cached && cached.gen === buildGeneration) {
|
|
1442
|
+
inner = cached.html;
|
|
1443
|
+
} else {
|
|
1444
|
+
const React = await import("react");
|
|
1445
|
+
const mod = await loadModuleFresh(file);
|
|
1446
|
+
const Pending = mod.default ?? mod.Pending ?? mod.pending;
|
|
1447
|
+
if (!Pending) return "";
|
|
1448
|
+
inner = await renderToStringCompat(React.createElement(Pending, {}));
|
|
1449
|
+
pendingOverlayCache.set(file, { gen: buildGeneration, html: inner });
|
|
1450
|
+
}
|
|
1451
|
+
return `<div id="__sr-pending" data-sr-pending hidden style="position:fixed;top:0;left:0;right:0;z-index:2147483646;pointer-events:none">${inner}</div>`;
|
|
1452
|
+
} catch {
|
|
1453
|
+
return "";
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1233
1457
|
async function renderNotFound(segments) {
|
|
1234
1458
|
const notFoundFile = findNotFound(segments);
|
|
1235
1459
|
if (!notFoundFile) {
|
|
@@ -1318,6 +1542,7 @@ async function wrapInDocumentAsync({ head, body }) {
|
|
|
1318
1542
|
<meta charset="utf-8" />
|
|
1319
1543
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1320
1544
|
${fullHead}
|
|
1545
|
+
<script src="/_swift-rust/navigator.js" defer></script>
|
|
1321
1546
|
<script src="/_swift-rust/hmr-client.js" defer></script>
|
|
1322
1547
|
</head>
|
|
1323
1548
|
<body>${body}</body>
|
|
@@ -1333,6 +1558,15 @@ async function tryHmrClient() {
|
|
|
1333
1558
|
}
|
|
1334
1559
|
}
|
|
1335
1560
|
|
|
1561
|
+
function readNavigatorClient() {
|
|
1562
|
+
try {
|
|
1563
|
+
const file = join(dirname(new URL(import.meta.url).pathname), "runtime", "navigator.js");
|
|
1564
|
+
return readFileSync(file, "utf8");
|
|
1565
|
+
} catch {
|
|
1566
|
+
return null;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1336
1570
|
function shouldIgnoreFile(filename) {
|
|
1337
1571
|
if (!filename) return true;
|
|
1338
1572
|
const base = filename.split(sep).pop();
|
|
@@ -1716,6 +1950,14 @@ async function handleFetch(req) {
|
|
|
1716
1950
|
return new Response("Not found", { status: 404 });
|
|
1717
1951
|
}
|
|
1718
1952
|
|
|
1953
|
+
if (pathname === "/_swift-rust/navigator.js") {
|
|
1954
|
+
const client = readNavigatorClient();
|
|
1955
|
+
if (client) {
|
|
1956
|
+
return new Response(client, { headers: { "Content-Type": "application/javascript; charset=utf-8" } });
|
|
1957
|
+
}
|
|
1958
|
+
return new Response("// navigator unavailable", { status: 404, headers: { "Content-Type": "application/javascript" } });
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1719
1961
|
if (pathname === "/_swift-rust/image") {
|
|
1720
1962
|
const target = url.searchParams.get("url");
|
|
1721
1963
|
const w = parseInt(url.searchParams.get("w") || "0", 10);
|
|
@@ -1958,6 +2200,22 @@ async function handleFetch(req) {
|
|
|
1958
2200
|
const src = `/_swift-rust/island.js?p=${encodeURIComponent(renderResult.pageFile)}`;
|
|
1959
2201
|
doc = doc.replace("</body>", `<script type="module" src="${src}"></script>\n</body>`);
|
|
1960
2202
|
}
|
|
2203
|
+
// pending.tsx → hidden overlay the client navigator reveals while a
|
|
2204
|
+
// navigation is in flight (see runtime/navigator.js).
|
|
2205
|
+
const pendingOverlay = await renderPendingOverlay(renderResult.segments);
|
|
2206
|
+
if (pendingOverlay) doc = doc.replace("</body>", `${pendingOverlay}\n</body>`);
|
|
2207
|
+
// prefetch.ts → client prefetch strategy for the navigator.
|
|
2208
|
+
const prefetchCfg = await resolvePrefetchConfig(renderResult.segments);
|
|
2209
|
+
if (prefetchCfg) {
|
|
2210
|
+
const json = JSON.stringify(prefetchCfg).replace(/</g, "\\u003c");
|
|
2211
|
+
doc = doc.replace("</body>", `<script>window.__SR_PREFETCH__=${json}</script>\n</body>`);
|
|
2212
|
+
}
|
|
2213
|
+
// transition.tsx → View Transitions config for the navigator's swap.
|
|
2214
|
+
const transitionCfg = await resolveTransitionConfig(renderResult.segments);
|
|
2215
|
+
if (transitionCfg) {
|
|
2216
|
+
const json = JSON.stringify(transitionCfg).replace(/</g, "\\u003c");
|
|
2217
|
+
doc = doc.replace("</body>", `<script>window.__SR_TRANSITION__=${json}</script>\n</body>`);
|
|
2218
|
+
}
|
|
1961
2219
|
const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
|
|
1962
2220
|
for (const c of renderResult.setCookies || []) headers.append("Set-Cookie", c);
|
|
1963
2221
|
// config.ts headers
|
package/bin/error-overlay.mjs
CHANGED
|
@@ -113,8 +113,8 @@ function classifyError(message) {
|
|
|
113
113
|
"The dev server could not bind to the requested port, " +
|
|
114
114
|
"or it ran out of system resources.",
|
|
115
115
|
suggestions: [
|
|
116
|
-
"Close any other process using this port (usually
|
|
117
|
-
"Run with --port
|
|
116
|
+
"Close any other process using this port (usually 3210)",
|
|
117
|
+
"Run with --port <number> to use a different port",
|
|
118
118
|
"Check disk space with `df -h`",
|
|
119
119
|
],
|
|
120
120
|
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Swift-Rust client navigator — turns full-page loads into SPA navigation for
|
|
2
|
+
// <a>/<Link>. Intercepts same-origin clicks, fetches the destination HTML,
|
|
3
|
+
// swaps <body>, re-runs body scripts (island bootstrap + serialized state),
|
|
4
|
+
// syncs <title>/meta, and manages history. Ships in dev and in the build
|
|
5
|
+
// output (NOT dev-only like the HMR client).
|
|
6
|
+
(() => {
|
|
7
|
+
if (window.__SR_NAV__) return;
|
|
8
|
+
const nav = (window.__SR_NAV__ = { cache: new Map(), inflight: new Map() });
|
|
9
|
+
const ORIGIN = location.origin;
|
|
10
|
+
const MAX_CACHE = 32;
|
|
11
|
+
|
|
12
|
+
function internalAnchor(a) {
|
|
13
|
+
if (!a || a.target === "_blank" || a.hasAttribute("download")) return null;
|
|
14
|
+
if (a.dataset.srNoNav !== undefined) return null;
|
|
15
|
+
const raw = a.getAttribute("href");
|
|
16
|
+
if (!raw || raw.startsWith("#") || raw.startsWith("mailto:") || raw.startsWith("tel:")) return null;
|
|
17
|
+
let url;
|
|
18
|
+
try {
|
|
19
|
+
url = new URL(a.href, location.href);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (url.origin !== ORIGIN) return null;
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function remember(key, html) {
|
|
28
|
+
if (nav.cache.size >= MAX_CACHE) nav.cache.delete(nav.cache.keys().next().value);
|
|
29
|
+
nav.cache.set(key, html);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fetch (and cache) a route's HTML. Deduped per URL so prefetch + click share.
|
|
33
|
+
function fetchDoc(url) {
|
|
34
|
+
const key = url;
|
|
35
|
+
if (nav.cache.has(key)) return Promise.resolve(nav.cache.get(key));
|
|
36
|
+
if (nav.inflight.has(key)) return nav.inflight.get(key);
|
|
37
|
+
const p = fetch(url, { headers: { "x-swift-rust-nav": "1" }, credentials: "same-origin" })
|
|
38
|
+
.then((res) => {
|
|
39
|
+
if (!res.ok && res.status !== 404) throw new Error("nav fetch " + res.status);
|
|
40
|
+
return res.text();
|
|
41
|
+
})
|
|
42
|
+
.then((html) => {
|
|
43
|
+
remember(key, html);
|
|
44
|
+
nav.inflight.delete(key);
|
|
45
|
+
return html;
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
nav.inflight.delete(key);
|
|
49
|
+
throw err;
|
|
50
|
+
});
|
|
51
|
+
nav.inflight.set(key, p);
|
|
52
|
+
return p;
|
|
53
|
+
}
|
|
54
|
+
nav.prefetch = (url) => fetchDoc(new URL(url, location.href).href).catch(() => {});
|
|
55
|
+
|
|
56
|
+
// Scripts inserted via DOM cloning don't execute; clone them into fresh nodes.
|
|
57
|
+
function runScripts(root) {
|
|
58
|
+
for (const old of root.querySelectorAll("script")) {
|
|
59
|
+
const s = document.createElement("script");
|
|
60
|
+
for (const att of old.attributes) s.setAttribute(att.name, att.value);
|
|
61
|
+
s.textContent = old.textContent;
|
|
62
|
+
old.replaceWith(s);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// transition.tsx → wrap the DOM swap in the View Transitions API. Config is
|
|
67
|
+
// injected as window.__SR_TRANSITION__ = { type, duration }. Falls back to a
|
|
68
|
+
// plain swap when unsupported, type "none", or reduced-motion is requested.
|
|
69
|
+
function injectTransitionStyle() {
|
|
70
|
+
if (document.getElementById("__sr-transition-style")) return;
|
|
71
|
+
const s = document.createElement("style");
|
|
72
|
+
s.id = "__sr-transition-style";
|
|
73
|
+
s.textContent =
|
|
74
|
+
"::view-transition-old(root),::view-transition-new(root){animation-duration:var(--sr-transition-duration,250ms)}" +
|
|
75
|
+
'html[data-sr-transition="slide"]::view-transition-old(root){animation-name:sr-vt-slide-out}' +
|
|
76
|
+
'html[data-sr-transition="slide"]::view-transition-new(root){animation-name:sr-vt-slide-in}' +
|
|
77
|
+
"@keyframes sr-vt-slide-out{to{opacity:0;transform:translateX(-24px)}}" +
|
|
78
|
+
"@keyframes sr-vt-slide-in{from{opacity:0;transform:translateX(24px)}}";
|
|
79
|
+
(document.head || document.documentElement).appendChild(s);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function withTransition(apply) {
|
|
83
|
+
const t = window.__SR_TRANSITION__;
|
|
84
|
+
const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
85
|
+
if (!t || !t.type || t.type === "none" || reduce || typeof document.startViewTransition !== "function") {
|
|
86
|
+
apply();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const root = document.documentElement;
|
|
90
|
+
root.dataset.srTransition = t.type;
|
|
91
|
+
if (t.duration) root.style.setProperty("--sr-transition-duration", t.duration + "ms");
|
|
92
|
+
try {
|
|
93
|
+
const vt = document.startViewTransition(() => apply());
|
|
94
|
+
await vt.finished;
|
|
95
|
+
} catch {
|
|
96
|
+
// DOM was already updated inside the callback; nothing to recover.
|
|
97
|
+
} finally {
|
|
98
|
+
delete root.dataset.srTransition;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function syncHead(doc) {
|
|
103
|
+
const title = doc.querySelector("title");
|
|
104
|
+
if (title) document.title = title.textContent || document.title;
|
|
105
|
+
const selectors = ['meta[name="description"]', 'meta[property^="og:"]', 'meta[name^="twitter:"]', 'link[rel="canonical"]'];
|
|
106
|
+
for (const sel of selectors) {
|
|
107
|
+
document.head.querySelectorAll(sel).forEach((m) => m.remove());
|
|
108
|
+
doc.head.querySelectorAll(sel).forEach((m) => document.head.appendChild(m.cloneNode(true)));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// pending.tsx overlay: revealed only if a navigation outlasts the threshold,
|
|
113
|
+
// so fast (cached) navigations don't flash it.
|
|
114
|
+
let pendingTimer = null;
|
|
115
|
+
function showPending() {
|
|
116
|
+
const el = document.getElementById("__sr-pending");
|
|
117
|
+
if (el) el.hidden = false;
|
|
118
|
+
}
|
|
119
|
+
function clearPending() {
|
|
120
|
+
if (pendingTimer) {
|
|
121
|
+
clearTimeout(pendingTimer);
|
|
122
|
+
pendingTimer = null;
|
|
123
|
+
}
|
|
124
|
+
const el = document.getElementById("__sr-pending");
|
|
125
|
+
if (el) el.hidden = true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function navigate(href, { push = true, scroll = true, replace = false } = {}) {
|
|
129
|
+
let html;
|
|
130
|
+
nav.active = href;
|
|
131
|
+
if (pendingTimer) clearTimeout(pendingTimer);
|
|
132
|
+
const delay = typeof window.__SR_NAV_PENDING_DELAY === "number" ? window.__SR_NAV_PENDING_DELAY : 120;
|
|
133
|
+
pendingTimer = setTimeout(showPending, delay);
|
|
134
|
+
window.dispatchEvent(new CustomEvent("sr:navigate-start", { detail: { url: href } }));
|
|
135
|
+
try {
|
|
136
|
+
html = await fetchDoc(href);
|
|
137
|
+
} catch {
|
|
138
|
+
location.href = href; // hard fallback
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (nav.active !== href) return; // superseded by a newer navigation
|
|
142
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
143
|
+
if (!doc.body) {
|
|
144
|
+
location.href = href;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const apply = () => {
|
|
148
|
+
document.body.replaceWith(doc.body);
|
|
149
|
+
runScripts(document.body);
|
|
150
|
+
syncHead(doc);
|
|
151
|
+
};
|
|
152
|
+
await withTransition(apply);
|
|
153
|
+
if (push) {
|
|
154
|
+
if (replace) history.replaceState({ srNav: true }, "", href);
|
|
155
|
+
else history.pushState({ srNav: true }, "", href);
|
|
156
|
+
}
|
|
157
|
+
if (scroll) window.scrollTo(0, 0);
|
|
158
|
+
clearPending();
|
|
159
|
+
window.dispatchEvent(new CustomEvent("sr:navigate-end", { detail: { url: href } }));
|
|
160
|
+
}
|
|
161
|
+
nav.navigate = navigate;
|
|
162
|
+
|
|
163
|
+
document.addEventListener(
|
|
164
|
+
"click",
|
|
165
|
+
(e) => {
|
|
166
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
167
|
+
const a = e.target.closest && e.target.closest("a");
|
|
168
|
+
const url = internalAnchor(a);
|
|
169
|
+
if (!url) return;
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
if (url.href === location.href) return;
|
|
172
|
+
navigate(url.href, {
|
|
173
|
+
push: true,
|
|
174
|
+
scroll: a.dataset.srScroll !== "false",
|
|
175
|
+
replace: a.dataset.srReplace === "true",
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
false,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
window.addEventListener("popstate", () => {
|
|
182
|
+
navigate(location.href, { push: false, scroll: false });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ── Prefetch (prefetch.ts) ────────────────────────────────────────────────
|
|
186
|
+
// Strategy comes from the per-route prefetch.ts, injected as
|
|
187
|
+
// window.__SR_PREFETCH__ = { strategy, margin? }. Default: "hover".
|
|
188
|
+
// Per-link opt-out via data-sr-prefetch="false".
|
|
189
|
+
function strategy() {
|
|
190
|
+
const c = window.__SR_PREFETCH__;
|
|
191
|
+
return (c && c.strategy) || "hover";
|
|
192
|
+
}
|
|
193
|
+
function prefetchableURL(a) {
|
|
194
|
+
if (!a || a.dataset.srPrefetch === "false") return null;
|
|
195
|
+
return internalAnchor(a);
|
|
196
|
+
}
|
|
197
|
+
function intent(e) {
|
|
198
|
+
if (strategy() !== "hover") return;
|
|
199
|
+
const a = e.target.closest && e.target.closest("a");
|
|
200
|
+
const url = prefetchableURL(a);
|
|
201
|
+
if (url && url.href !== location.href) nav.prefetch(url.href);
|
|
202
|
+
}
|
|
203
|
+
document.addEventListener("mouseover", intent, { passive: true });
|
|
204
|
+
document.addEventListener("focusin", intent);
|
|
205
|
+
document.addEventListener("touchstart", intent, { passive: true });
|
|
206
|
+
|
|
207
|
+
let io = null;
|
|
208
|
+
function scanViewport() {
|
|
209
|
+
if (io) {
|
|
210
|
+
io.disconnect();
|
|
211
|
+
io = null;
|
|
212
|
+
}
|
|
213
|
+
if (strategy() !== "viewport" || !("IntersectionObserver" in window)) return;
|
|
214
|
+
const margin = (window.__SR_PREFETCH__ && window.__SR_PREFETCH__.margin) || "200px";
|
|
215
|
+
io = new IntersectionObserver(
|
|
216
|
+
(entries) => {
|
|
217
|
+
for (const en of entries) {
|
|
218
|
+
if (!en.isIntersecting) continue;
|
|
219
|
+
const url = prefetchableURL(en.target);
|
|
220
|
+
if (url && url.href !== location.href) nav.prefetch(url.href);
|
|
221
|
+
io.unobserve(en.target);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{ rootMargin: margin },
|
|
225
|
+
);
|
|
226
|
+
document.querySelectorAll("a[href]").forEach((a) => {
|
|
227
|
+
if (prefetchableURL(a)) io.observe(a);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
window.addEventListener("sr:navigate-end", scanViewport);
|
|
231
|
+
if (document.readyState === "loading") {
|
|
232
|
+
document.addEventListener("DOMContentLoaded", scanViewport, { once: true });
|
|
233
|
+
} else {
|
|
234
|
+
scanViewport();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
injectTransitionStyle();
|
|
238
|
+
})();
|
package/dist/link.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
|
6
6
|
replace?: boolean;
|
|
7
7
|
scroll?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export declare function Link({ href, prefetch
|
|
9
|
+
export declare function Link({ href, prefetch, replace, scroll, children, ...rest }: LinkProps): import("react").JSX.Element;
|
|
10
10
|
//# sourceMappingURL=link.d.ts.map
|
package/dist/link.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7D,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7D,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,+BAYrF"}
|
package/dist/link.jsx
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
export function Link({ href, prefetch
|
|
2
|
-
|
|
1
|
+
export function Link({ href, prefetch, replace, scroll, children, ...rest }) {
|
|
2
|
+
// The client navigator (runtime/navigator.js) reads these data-* hints to
|
|
3
|
+
// drive SPA navigation. Plain <a> semantics are preserved when JS is off.
|
|
4
|
+
const dataAttrs = {};
|
|
5
|
+
if (replace)
|
|
6
|
+
dataAttrs["data-sr-replace"] = "true";
|
|
7
|
+
if (scroll === false)
|
|
8
|
+
dataAttrs["data-sr-scroll"] = "false";
|
|
9
|
+
if (prefetch === false)
|
|
10
|
+
dataAttrs["data-sr-prefetch"] = "false";
|
|
11
|
+
return (<a href={href} {...dataAttrs} {...rest}>
|
|
3
12
|
{children}
|
|
4
13
|
</a>);
|
|
5
14
|
}
|
package/dist/link.jsx.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.jsx","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAUA,MAAM,UAAU,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"link.jsx","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAUA,MAAM,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAa;IACpF,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,SAAS,GAA2B,EAAE,CAAC;IAC7C,IAAI,OAAO;QAAE,SAAS,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC;IACnD,IAAI,MAAM,KAAK,KAAK;QAAE,SAAS,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC;IAC5D,IAAI,QAAQ,KAAK,KAAK;QAAE,SAAS,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC;IAChE,OAAO,CACL,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,CACrC;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,CAAC,CAAC,CACL,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swift-rust",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "The full-stack React framework powered with Rust + Bun. TSX-first, Rust rendering, 10x faster than Next.js, single binary deploy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://swift-rust.dev",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"exports": {
|
|
24
24
|
"./package.json": "./package.json",
|
|
25
|
+
"./tsconfig.base.json": "./tsconfig.base.json",
|
|
25
26
|
".": {
|
|
26
27
|
"types": "./dist/index.d.ts",
|
|
27
28
|
"import": "./dist/index.js"
|
|
@@ -74,7 +75,8 @@
|
|
|
74
75
|
"dist",
|
|
75
76
|
"bin",
|
|
76
77
|
"scripts",
|
|
77
|
-
"native"
|
|
78
|
+
"native",
|
|
79
|
+
"tsconfig.base.json"
|
|
78
80
|
],
|
|
79
81
|
"scripts": {
|
|
80
82
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "swift-rust base",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"moduleDetection": "force",
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUncheckedIndexedAccess": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"noEmit": true
|
|
20
|
+
}
|
|
21
|
+
}
|