swift-rust 1.1.0 → 1.2.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/bin/build.mjs +21 -2
- package/bin/dev-server.mjs +38 -7
- package/bin/error-overlay.mjs +2 -2
- 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`);
|
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") {
|
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
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swift-rust",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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
|
+
}
|