@textjs/app 0.0.0-stage → 1.0.3
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 +34 -2
- package/bin/text.js +29 -0
- package/bin/text.ts +13 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli/build.d.ts +39 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +157 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/config.d.ts +22 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +73 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/dev.d.ts +12 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +49 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +26 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +192 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.d.ts +31 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +111 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/start.d.ts +14 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +183 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/ui.d.ts +35 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +73 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +93 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +143 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +44 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +26 -0
- package/dist/context.js.map +1 -0
- package/dist/dev-errors.d.ts +68 -0
- package/dist/dev-errors.d.ts.map +1 -0
- package/dist/dev-errors.js +121 -0
- package/dist/dev-errors.js.map +1 -0
- package/dist/devtools.d.ts +64 -0
- package/dist/devtools.d.ts.map +1 -0
- package/dist/devtools.js +186 -0
- package/dist/devtools.js.map +1 -0
- package/dist/frame-mirror.d.ts +19 -0
- package/dist/frame-mirror.d.ts.map +1 -0
- package/dist/frame-mirror.js +141 -0
- package/dist/frame-mirror.js.map +1 -0
- package/dist/frame-protocol.d.ts +175 -0
- package/dist/frame-protocol.d.ts.map +1 -0
- package/dist/frame-protocol.js +369 -0
- package/dist/frame-protocol.js.map +1 -0
- package/dist/history.d.ts +54 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +152 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +32 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +54 -0
- package/dist/link.js.map +1 -0
- package/dist/live-player.d.ts +83 -0
- package/dist/live-player.d.ts.map +1 -0
- package/dist/live-player.js +413 -0
- package/dist/live-player.js.map +1 -0
- package/dist/live-protocol.d.ts +132 -0
- package/dist/live-protocol.d.ts.map +1 -0
- package/dist/live-protocol.js +278 -0
- package/dist/live-protocol.js.map +1 -0
- package/dist/live-stub.d.ts +10 -0
- package/dist/live-stub.d.ts.map +1 -0
- package/dist/live-stub.js +32 -0
- package/dist/live-stub.js.map +1 -0
- package/dist/mcp/cli.d.ts +22 -0
- package/dist/mcp/cli.d.ts.map +1 -0
- package/dist/mcp/cli.js +177 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/mcp/frame.d.ts +58 -0
- package/dist/mcp/frame.d.ts.map +1 -0
- package/dist/mcp/frame.js +169 -0
- package/dist/mcp/frame.js.map +1 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/inspect.d.ts +95 -0
- package/dist/mcp/inspect.d.ts.map +1 -0
- package/dist/mcp/inspect.js +386 -0
- package/dist/mcp/inspect.js.map +1 -0
- package/dist/mcp/keys.d.ts +20 -0
- package/dist/mcp/keys.d.ts.map +1 -0
- package/dist/mcp/keys.js +87 -0
- package/dist/mcp/keys.js.map +1 -0
- package/dist/mcp/logs.d.ts +63 -0
- package/dist/mcp/logs.d.ts.map +1 -0
- package/dist/mcp/logs.js +217 -0
- package/dist/mcp/logs.js.map +1 -0
- package/dist/mcp/server.d.ts +64 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +485 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session.d.ts +230 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +742 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/navigation-errors.d.ts +28 -0
- package/dist/navigation-errors.d.ts.map +1 -0
- package/dist/navigation-errors.js +43 -0
- package/dist/navigation-errors.js.map +1 -0
- package/dist/navigation.d.ts +21 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +33 -0
- package/dist/navigation.js.map +1 -0
- package/dist/router.d.ts +76 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +296 -0
- package/dist/router.js.map +1 -0
- package/dist/routing.d.ts +101 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +286 -0
- package/dist/routing.js.map +1 -0
- package/dist/server/adapters/build-step.d.ts +34 -0
- package/dist/server/adapters/build-step.d.ts.map +1 -0
- package/dist/server/adapters/build-step.js +112 -0
- package/dist/server/adapters/build-step.js.map +1 -0
- package/dist/server/adapters/bun.d.ts +8 -0
- package/dist/server/adapters/bun.d.ts.map +1 -0
- package/dist/server/adapters/bun.js +89 -0
- package/dist/server/adapters/bun.js.map +1 -0
- package/dist/server/adapters/bundle.d.ts +21 -0
- package/dist/server/adapters/bundle.d.ts.map +1 -0
- package/dist/server/adapters/bundle.js +86 -0
- package/dist/server/adapters/bundle.js.map +1 -0
- package/dist/server/adapters/cloudflare.d.ts +29 -0
- package/dist/server/adapters/cloudflare.d.ts.map +1 -0
- package/dist/server/adapters/cloudflare.js +105 -0
- package/dist/server/adapters/cloudflare.js.map +1 -0
- package/dist/server/adapters/index.d.ts +10 -0
- package/dist/server/adapters/index.d.ts.map +1 -0
- package/dist/server/adapters/index.js +24 -0
- package/dist/server/adapters/index.js.map +1 -0
- package/dist/server/adapters/runtime-node.d.ts +16 -0
- package/dist/server/adapters/runtime-node.d.ts.map +1 -0
- package/dist/server/adapters/runtime-node.js +29 -0
- package/dist/server/adapters/runtime-node.js.map +1 -0
- package/dist/server/adapters/runtime.d.ts +42 -0
- package/dist/server/adapters/runtime.d.ts.map +1 -0
- package/dist/server/adapters/runtime.js +115 -0
- package/dist/server/adapters/runtime.js.map +1 -0
- package/dist/server/adapters/shared.d.ts +66 -0
- package/dist/server/adapters/shared.d.ts.map +1 -0
- package/dist/server/adapters/shared.js +124 -0
- package/dist/server/adapters/shared.js.map +1 -0
- package/dist/server/adapters/static.d.ts +9 -0
- package/dist/server/adapters/static.d.ts.map +1 -0
- package/dist/server/adapters/static.js +70 -0
- package/dist/server/adapters/static.js.map +1 -0
- package/dist/server/adapters/vercel.d.ts +43 -0
- package/dist/server/adapters/vercel.d.ts.map +1 -0
- package/dist/server/adapters/vercel.js +119 -0
- package/dist/server/adapters/vercel.js.map +1 -0
- package/dist/server/bun-jsx.d.ts +18 -0
- package/dist/server/bun-jsx.d.ts.map +1 -0
- package/dist/server/bun-jsx.js +56 -0
- package/dist/server/bun-jsx.js.map +1 -0
- package/dist/server/cache-route.d.ts +38 -0
- package/dist/server/cache-route.d.ts.map +1 -0
- package/dist/server/cache-route.js +109 -0
- package/dist/server/cache-route.js.map +1 -0
- package/dist/server/cache.d.ts +131 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/cache.js +409 -0
- package/dist/server/cache.js.map +1 -0
- package/dist/server/frames.d.ts +45 -0
- package/dist/server/frames.d.ts.map +1 -0
- package/dist/server/frames.js +294 -0
- package/dist/server/frames.js.map +1 -0
- package/dist/server/http.d.ts +80 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +453 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/jsx-dev-runtime.d.ts +5 -0
- package/dist/server/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/server/jsx-dev-runtime.js +6 -0
- package/dist/server/jsx-dev-runtime.js.map +1 -0
- package/dist/server/jsx-runtime.d.ts +10 -0
- package/dist/server/jsx-runtime.d.ts.map +1 -0
- package/dist/server/jsx-runtime.js +11 -0
- package/dist/server/jsx-runtime.js.map +1 -0
- package/dist/server/live-app.d.ts +87 -0
- package/dist/server/live-app.d.ts.map +1 -0
- package/dist/server/live-app.js +307 -0
- package/dist/server/live-app.js.map +1 -0
- package/dist/server/live-build.d.ts +70 -0
- package/dist/server/live-build.d.ts.map +1 -0
- package/dist/server/live-build.js +222 -0
- package/dist/server/live-build.js.map +1 -0
- package/dist/server/live-dev.d.ts +62 -0
- package/dist/server/live-dev.d.ts.map +1 -0
- package/dist/server/live-dev.js +313 -0
- package/dist/server/live-dev.js.map +1 -0
- package/dist/server/live-manifest.d.ts +23 -0
- package/dist/server/live-manifest.d.ts.map +1 -0
- package/dist/server/live-manifest.js +7 -0
- package/dist/server/live-manifest.js.map +1 -0
- package/dist/server/live-prod.d.ts +30 -0
- package/dist/server/live-prod.d.ts.map +1 -0
- package/dist/server/live-prod.js +97 -0
- package/dist/server/live-prod.js.map +1 -0
- package/dist/server/live-routes.d.ts +72 -0
- package/dist/server/live-routes.d.ts.map +1 -0
- package/dist/server/live-routes.js +274 -0
- package/dist/server/live-routes.js.map +1 -0
- package/dist/server/live-web.d.ts +16 -0
- package/dist/server/live-web.d.ts.map +1 -0
- package/dist/server/live-web.js +159 -0
- package/dist/server/live-web.js.map +1 -0
- package/dist/server/live.d.ts +138 -0
- package/dist/server/live.d.ts.map +1 -0
- package/dist/server/live.js +542 -0
- package/dist/server/live.js.map +1 -0
- package/dist/server/middleware-node.d.ts +48 -0
- package/dist/server/middleware-node.d.ts.map +1 -0
- package/dist/server/middleware-node.js +167 -0
- package/dist/server/middleware-node.js.map +1 -0
- package/dist/server/middleware-runtime.d.ts +97 -0
- package/dist/server/middleware-runtime.d.ts.map +1 -0
- package/dist/server/middleware-runtime.js +447 -0
- package/dist/server/middleware-runtime.js.map +1 -0
- package/dist/server/middleware.d.ts +145 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +365 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/ppr.d.ts +22 -0
- package/dist/server/ppr.d.ts.map +1 -0
- package/dist/server/ppr.js +58 -0
- package/dist/server/ppr.js.map +1 -0
- package/dist/server/scan.d.ts +48 -0
- package/dist/server/scan.d.ts.map +1 -0
- package/dist/server/scan.js +161 -0
- package/dist/server/scan.js.map +1 -0
- package/dist/server/server-components.d.ts +65 -0
- package/dist/server/server-components.d.ts.map +1 -0
- package/dist/server/server-components.js +215 -0
- package/dist/server/server-components.js.map +1 -0
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +5 -0
- package/dist/terminal.js.map +1 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +5 -0
- package/dist/testing.js.map +1 -0
- package/dist/vite-plugin.d.ts +51 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +403 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +130 -4
- package/src/cli/build.ts +182 -0
- package/src/cli/config.ts +72 -0
- package/src/cli/dev.ts +54 -0
- package/src/cli/index.ts +185 -0
- package/src/cli/run.ts +124 -0
- package/src/cli/start.ts +194 -0
- package/src/cli/ui.ts +93 -0
- package/src/client.tsx +122 -0
- package/src/config.ts +155 -0
- package/src/context.ts +69 -0
- package/src/dev-errors.ts +167 -0
- package/src/devtools.tsx +419 -0
- package/src/frame-mirror.ts +145 -0
- package/src/frame-protocol.ts +435 -0
- package/src/history.ts +201 -0
- package/src/index.ts +9 -0
- package/src/link.tsx +108 -0
- package/src/live-player.ts +449 -0
- package/src/live-protocol.ts +317 -0
- package/src/live-stub.tsx +32 -0
- package/src/mcp/cli.ts +188 -0
- package/src/mcp/frame.ts +224 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/inspect.ts +445 -0
- package/src/mcp/keys.ts +86 -0
- package/src/mcp/logs.ts +225 -0
- package/src/mcp/server.ts +595 -0
- package/src/mcp/session.ts +821 -0
- package/src/navigation-errors.ts +60 -0
- package/src/navigation.ts +44 -0
- package/src/router.tsx +497 -0
- package/src/routing.ts +344 -0
- package/src/server/adapters/build-step.ts +135 -0
- package/src/server/adapters/bun.ts +92 -0
- package/src/server/adapters/bundle.ts +97 -0
- package/src/server/adapters/cloudflare.ts +111 -0
- package/src/server/adapters/index.ts +29 -0
- package/src/server/adapters/runtime-node.ts +34 -0
- package/src/server/adapters/runtime.ts +145 -0
- package/src/server/adapters/shared.ts +159 -0
- package/src/server/adapters/static.ts +68 -0
- package/src/server/adapters/vercel.ts +149 -0
- package/src/server/bun-jsx.ts +65 -0
- package/src/server/cache-route.ts +139 -0
- package/src/server/cache.ts +498 -0
- package/src/server/frames.ts +326 -0
- package/src/server/http.ts +467 -0
- package/src/server/jsx-dev-runtime.ts +9 -0
- package/src/server/jsx-runtime.ts +13 -0
- package/src/server/live-app.tsx +374 -0
- package/src/server/live-build.ts +258 -0
- package/src/server/live-dev.ts +357 -0
- package/src/server/live-manifest.ts +25 -0
- package/src/server/live-prod.ts +120 -0
- package/src/server/live-routes.ts +298 -0
- package/src/server/live-web.ts +170 -0
- package/src/server/live.ts +551 -0
- package/src/server/middleware-node.ts +168 -0
- package/src/server/middleware-runtime.ts +478 -0
- package/src/server/middleware.ts +451 -0
- package/src/server/ppr.ts +62 -0
- package/src/server/scan.ts +190 -0
- package/src/server/server-components.ts +224 -0
- package/src/terminal.ts +5 -0
- package/src/testing.ts +5 -0
- package/src/vite-plugin.ts +413 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@textjs/app/vite` — the Vite plugin behind `text dev` and `text build`:
|
|
3
|
+
* file-system App Router manifest, virtual `index.html` + client entry,
|
|
4
|
+
* `app/**\/route.ts` API handlers, rewrites / proxy and React Fast Refresh.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
8
|
+
import { isIP } from "node:net";
|
|
9
|
+
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import react from "@vitejs/plugin-react";
|
|
12
|
+
import type { Plugin, ProxyOptions, ViteDevServer } from "vite";
|
|
13
|
+
import { BRAND_MARK, ENGINE_NAME, txMessage, VERSION } from "@textjs/core";
|
|
14
|
+
import { normalizeBasePath, toClientConfig, type Rewrite, type TextConfig } from "./config";
|
|
15
|
+
import { applyRewrites, compileRewrite, normalizeRequestTarget, proxyRequest, requestOrigin, runRouteHandler, sendWebResponse, toWebRequest, type CompiledRewrite, type RouteHandlerModule } from "./server/http";
|
|
16
|
+
import { attachFrameStream } from "./server/frames";
|
|
17
|
+
import { createLiveDevHandler, textjsLivePlugin } from "./server/live-dev";
|
|
18
|
+
import { withServerStubs } from "./server/live-routes";
|
|
19
|
+
import { runRouteHandlerWithCache } from "./server/cache-route";
|
|
20
|
+
import { createDevMiddleware, isServerOnlySource } from "./server/middleware-node";
|
|
21
|
+
import { generateRoutesModule, isRouteFile, matchApiRoute, scanApp, toApiManifest, toPosix, toStaticManifest, type AppScan } from "./server/scan";
|
|
22
|
+
import { flattenRoutes, type RouteManifest, type RouteSegmentNode } from "./routing";
|
|
23
|
+
|
|
24
|
+
export const VIRTUAL_ROUTES = "virtual:textjs/routes";
|
|
25
|
+
export const VIRTUAL_CONFIG = "virtual:textjs/config";
|
|
26
|
+
export const VIRTUAL_ENTRY = "virtual:textjs/entry";
|
|
27
|
+
export const VIRTUAL_API = "virtual:textjs/api";
|
|
28
|
+
/** The script `src` used by the generated `index.html`. */
|
|
29
|
+
export const ENTRY_URL = "/@textjs/entry";
|
|
30
|
+
/**
|
|
31
|
+
* The URL every page's HTML is transformed as (Vite's SPA fallback does the same).
|
|
32
|
+
* The request URL can't be used: Vite maps `/?q` to the project directory and then
|
|
33
|
+
* watches the inline `<style>`'s "file", `/`, so chokidar crawls the whole disk.
|
|
34
|
+
*/
|
|
35
|
+
export const HTML_TRANSFORM_URL = "/index.html";
|
|
36
|
+
|
|
37
|
+
const RESOLVED = (id: string): string => `\0${id}`;
|
|
38
|
+
|
|
39
|
+
export interface TextLogger {
|
|
40
|
+
info(message: string): void;
|
|
41
|
+
error(message: string): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TextVitePluginOptions extends TextConfig {
|
|
45
|
+
/** Project root (defaults to Vite's `root`). */
|
|
46
|
+
root?: string;
|
|
47
|
+
/** Request / HMR log lines (the CLI passes its logger). */
|
|
48
|
+
logger?: TextLogger;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mirrors Vite's `server.allowedHosts` check. Text.js API routes and user
|
|
53
|
+
* middleware are registered in the pre-hook phase (ahead of Vite's own host
|
|
54
|
+
* check), so they get the same DNS-rebinding protection here.
|
|
55
|
+
*/
|
|
56
|
+
export function isAllowedDevHost(server: Pick<ViteDevServer, "config">, hostHeader: string | undefined): boolean {
|
|
57
|
+
const cfg = server.config.server;
|
|
58
|
+
if (cfg.allowedHosts === true || cfg.https) return true;
|
|
59
|
+
if (!hostHeader) return false;
|
|
60
|
+
const host = hostHeader.trim();
|
|
61
|
+
if (host.startsWith("[")) {
|
|
62
|
+
// `[v6]` or `[v6]:port` only: nothing may follow the bracket but a port.
|
|
63
|
+
const m = /^\[([^\]]+)\](?::\d{1,5})?$/.exec(host);
|
|
64
|
+
return m !== null && isIP(m[1] as string) === 6;
|
|
65
|
+
}
|
|
66
|
+
const hostname = host.replace(/:\d+$/, "");
|
|
67
|
+
if (isIP(hostname) === 4 || hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
68
|
+
const extra = (server.config as { additionalAllowedHosts?: readonly string[] }).additionalAllowedHosts ?? [];
|
|
69
|
+
if (extra.includes(hostname)) return true;
|
|
70
|
+
for (const allowed of cfg.allowedHosts ?? []) {
|
|
71
|
+
if (allowed === hostname) return true;
|
|
72
|
+
if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const here = dirname(realpathSync(fileURLToPath(import.meta.url)));
|
|
78
|
+
const sourceExt = extname(fileURLToPath(import.meta.url)) === ".ts" ? ".tsx" : extname(fileURLToPath(import.meta.url));
|
|
79
|
+
|
|
80
|
+
/** `@textjs/app/client` next to this module (`src/` or `dist/`), else as the package's own `exports` map resolves it. */
|
|
81
|
+
function resolveClientModule(): string {
|
|
82
|
+
const sibling = join(here, `client${sourceExt}`);
|
|
83
|
+
if (existsSync(sibling)) return sibling;
|
|
84
|
+
for (let dir = here; dirname(dir) !== dir; dir = dirname(dir)) {
|
|
85
|
+
const pkgFile = join(dir, "package.json");
|
|
86
|
+
if (!existsSync(pkgFile)) continue;
|
|
87
|
+
const pkg = JSON.parse(readFileSync(pkgFile, "utf8")) as { name?: string; exports?: Record<string, string | { default?: string }> };
|
|
88
|
+
if (pkg.name !== "@textjs/app") continue;
|
|
89
|
+
const entry = pkg.exports?.["./client"];
|
|
90
|
+
const rel = typeof entry === "string" ? entry : entry?.default;
|
|
91
|
+
if (rel) return join(dir, rel);
|
|
92
|
+
}
|
|
93
|
+
return sibling;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Absolute path of `@textjs/app/client` (deduped with the user's `@textjs/app` imports via realpath). */
|
|
97
|
+
export const CLIENT_MODULE = toPosix(resolveClientModule());
|
|
98
|
+
|
|
99
|
+
export const FAVICON = `data:image/svg+xml,${encodeURIComponent(
|
|
100
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#0a0a0a"/><path d="M7 10h18L16 25z" fill="#ededed"/></svg>`,
|
|
101
|
+
)}`;
|
|
102
|
+
|
|
103
|
+
function escapeHtml(s: string): string {
|
|
104
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c] as string);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The generated `index.html` used when the project has none. */
|
|
108
|
+
export function createIndexHtml(title: string, entryUrl = ENTRY_URL): string {
|
|
109
|
+
return `<!doctype html>
|
|
110
|
+
<html lang="en">
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="utf-8" />
|
|
113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
114
|
+
<meta name="generator" content="Text.js ${VERSION} (${ENGINE_NAME})" />
|
|
115
|
+
<meta name="color-scheme" content="dark" />
|
|
116
|
+
<title>${escapeHtml(title)}</title>
|
|
117
|
+
<link rel="icon" href="${FAVICON}" />
|
|
118
|
+
<style>
|
|
119
|
+
html, body { margin: 0; min-height: 100%; background: #0a0a0a; color: #ededed; }
|
|
120
|
+
body { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
121
|
+
font-family: Geist, Inter, system-ui, -apple-system, "Segoe UI", sans-serif; -webkit-font-smoothing: antialiased; }
|
|
122
|
+
</style>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<div id="__textjs"></div>
|
|
126
|
+
<noscript>${BRAND_MARK} Text.js apps render into console.log, which requires JavaScript.</noscript>
|
|
127
|
+
<script type="module" src="${entryUrl}"></script>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function createEntryModule(clientModule = CLIENT_MODULE): string {
|
|
134
|
+
return `import { bootTextApp } from ${JSON.stringify(clientModule)};
|
|
135
|
+
import routes from ${JSON.stringify(VIRTUAL_ROUTES)};
|
|
136
|
+
import config from ${JSON.stringify(VIRTUAL_CONFIG)};
|
|
137
|
+
|
|
138
|
+
bootTextApp({ routes, config, dev: import.meta.env.DEV, hot: import.meta.hot });
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Generate the server-side API handler bundle entry. */
|
|
143
|
+
export function generateApiModule(scan: AppScan): string {
|
|
144
|
+
const imports: string[] = [];
|
|
145
|
+
const handlers: string[] = [];
|
|
146
|
+
const manifest = toApiManifest(scan);
|
|
147
|
+
let n = 0;
|
|
148
|
+
const walk = (node: RouteSegmentNode): RouteSegmentNode => {
|
|
149
|
+
const out: RouteSegmentNode = { segment: node.segment };
|
|
150
|
+
if (node.files?.page) {
|
|
151
|
+
const id = `r${n++}`;
|
|
152
|
+
imports.push(`import * as ${id} from ${JSON.stringify(toPosix(node.files.page))};`);
|
|
153
|
+
out.page = {};
|
|
154
|
+
out.files = { page: id };
|
|
155
|
+
handlers.push(`${JSON.stringify(id)}: ${id}`);
|
|
156
|
+
}
|
|
157
|
+
if (node.children) out.children = node.children.map(walk);
|
|
158
|
+
return out;
|
|
159
|
+
};
|
|
160
|
+
const tree = walk(manifest);
|
|
161
|
+
return `${imports.join("\n")}
|
|
162
|
+
export const manifest = ${JSON.stringify(tree)};
|
|
163
|
+
export const handlers = { ${handlers.join(", ")} };
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function findUp(start: string, rel: string): string | null {
|
|
168
|
+
let dir = start;
|
|
169
|
+
for (;;) {
|
|
170
|
+
const p = join(dir, rel);
|
|
171
|
+
if (existsSync(p)) return p;
|
|
172
|
+
const parent = dirname(dir);
|
|
173
|
+
if (parent === dir) return null;
|
|
174
|
+
dir = parent;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const OPTIMIZE = ["react", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom", "react-dom/client", "react-reconciler", "react-reconciler/constants.js", "scheduler", "yoga-layout/load"];
|
|
179
|
+
|
|
180
|
+
function toViteProxy(proxy: TextConfig["proxy"]): Record<string, string | ProxyOptions> | undefined {
|
|
181
|
+
if (!proxy) return undefined;
|
|
182
|
+
const out: Record<string, string | ProxyOptions> = {};
|
|
183
|
+
for (const [k, v] of Object.entries(proxy)) out[k] = typeof v === "string" ? v : { ...v };
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function resolveRewrites(rewrites: TextConfig["rewrites"]): Promise<CompiledRewrite[]> {
|
|
188
|
+
if (!rewrites) return [];
|
|
189
|
+
const list: Rewrite[] = typeof rewrites === "function" ? await rewrites() : rewrites;
|
|
190
|
+
return list.map(compileRewrite);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Whether Vite's `server.proxy` owns `url` (same key semantics: a path prefix, or a RegExp source starting with `^`). */
|
|
194
|
+
export function matchesProxy(proxy: TextConfig["proxy"], url: string): boolean {
|
|
195
|
+
if (!proxy) return false;
|
|
196
|
+
return Object.keys(proxy).some((key) => (key.startsWith("^") ? new RegExp(key).test(url) : url.startsWith(key)));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isHtmlRequest(url: string, accept: string | undefined): boolean {
|
|
200
|
+
const pathname = url.split("?")[0] ?? "/";
|
|
201
|
+
if (pathname.startsWith("/@") || pathname.startsWith("/node_modules/") || pathname.startsWith("/__")) return false;
|
|
202
|
+
const last = pathname.split("/").pop() ?? "";
|
|
203
|
+
if (last.endsWith(".html")) return true;
|
|
204
|
+
if (last.includes(".")) return false;
|
|
205
|
+
return accept === undefined || accept.includes("text/html") || accept.includes("*/*");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Text.js Vite plugin (includes `@vitejs/plugin-react` for Fast Refresh). */
|
|
209
|
+
export function textjsVitePlugin(options: TextVitePluginOptions = {}): Plugin[] {
|
|
210
|
+
const appDirName = options.appDir ?? "app";
|
|
211
|
+
const basePath = normalizeBasePath(options.basePath);
|
|
212
|
+
let root = resolve(options.root ?? process.cwd());
|
|
213
|
+
let scan: AppScan = scanApp(root, appDirName);
|
|
214
|
+
let apiManifest: RouteManifest = toApiManifest(scan);
|
|
215
|
+
let routesSignature = "";
|
|
216
|
+
const log = options.logger;
|
|
217
|
+
|
|
218
|
+
const rescan = (): boolean => {
|
|
219
|
+
scan = scanApp(root, appDirName);
|
|
220
|
+
apiManifest = toApiManifest(scan);
|
|
221
|
+
const sig = JSON.stringify(generateRoutesModule(scan));
|
|
222
|
+
const changed = sig !== routesSignature;
|
|
223
|
+
routesSignature = sig;
|
|
224
|
+
return changed;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const userHtml = (): string | null => {
|
|
228
|
+
const p = join(root, "index.html");
|
|
229
|
+
return existsSync(p) ? p : null;
|
|
230
|
+
};
|
|
231
|
+
const htmlPath = (): string => join(root, "index.html");
|
|
232
|
+
const title = (): string => options.title ?? `${BRAND_MARK} Text.js`;
|
|
233
|
+
|
|
234
|
+
const plugin: Plugin = {
|
|
235
|
+
name: "@textjs/app",
|
|
236
|
+
enforce: "pre",
|
|
237
|
+
|
|
238
|
+
config(user, env) {
|
|
239
|
+
root = resolve(user.root ?? options.root ?? process.cwd());
|
|
240
|
+
rescan();
|
|
241
|
+
const optimize = OPTIMIZE.filter((id) => {
|
|
242
|
+
const pkg = id.startsWith("@") ? id.split("/").slice(0, 2).join("/") : (id.split("/")[0] as string);
|
|
243
|
+
return findUp(root, join("node_modules", pkg, "package.json")) !== null;
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
root,
|
|
247
|
+
appType: "custom",
|
|
248
|
+
base: basePath ? `${basePath}/` : undefined,
|
|
249
|
+
resolve: {
|
|
250
|
+
dedupe: ["react", "react-dom"],
|
|
251
|
+
},
|
|
252
|
+
optimizeDeps: {
|
|
253
|
+
entries: [`${appDirName}/**/*.{tsx,ts,jsx,js}`],
|
|
254
|
+
include: optimize,
|
|
255
|
+
},
|
|
256
|
+
server: options.proxy ? { proxy: toViteProxy(options.proxy) } : undefined,
|
|
257
|
+
preview: options.proxy ? { proxy: toViteProxy(options.proxy) } : undefined,
|
|
258
|
+
build:
|
|
259
|
+
env.command === "build" && !user.build?.ssr
|
|
260
|
+
? {
|
|
261
|
+
outDir: options.outDir ?? "dist",
|
|
262
|
+
target: "es2022",
|
|
263
|
+
chunkSizeWarningLimit: 4096,
|
|
264
|
+
rollupOptions: { input: userHtml() ?? htmlPath() },
|
|
265
|
+
}
|
|
266
|
+
: { target: "es2022" },
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
configResolved(config) {
|
|
271
|
+
root = config.root;
|
|
272
|
+
rescan();
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
buildStart() {
|
|
276
|
+
rescan();
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
resolveId(rawId) {
|
|
280
|
+
// `build.ssr` entries arrive resolved against the root.
|
|
281
|
+
const v = rawId.indexOf("virtual:textjs/");
|
|
282
|
+
const id = v > 0 && !rawId.startsWith("\0") ? rawId.slice(v) : rawId;
|
|
283
|
+
if (id === VIRTUAL_ROUTES || id === VIRTUAL_CONFIG || id === VIRTUAL_API) return RESOLVED(id);
|
|
284
|
+
if (id === VIRTUAL_ENTRY || id === ENTRY_URL || id === `${basePath}${ENTRY_URL}`) return RESOLVED(VIRTUAL_ENTRY);
|
|
285
|
+
if (!userHtml() && toPosix(id) === toPosix(htmlPath())) return id;
|
|
286
|
+
return null;
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
load(id) {
|
|
290
|
+
if (id === RESOLVED(VIRTUAL_ROUTES)) return generateRoutesModule(withServerStubs(scan), { hmr: true });
|
|
291
|
+
if (id === RESOLVED(VIRTUAL_CONFIG)) return `export default ${JSON.stringify(toClientConfig(options))};`;
|
|
292
|
+
if (id === RESOLVED(VIRTUAL_ENTRY)) return createEntryModule();
|
|
293
|
+
if (id === RESOLVED(VIRTUAL_API)) return generateApiModule(scan);
|
|
294
|
+
if (!userHtml() && toPosix(id) === toPosix(htmlPath())) return createIndexHtml(title());
|
|
295
|
+
return null;
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
handleHotUpdate(ctx) {
|
|
299
|
+
if (log && !ctx.file.includes("/node_modules/")) log.info(` ✓ Fast Refresh ${toPosix(relative(root, ctx.file))}`);
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
async configureServer(server: ViteDevServer) {
|
|
303
|
+
const appDir = join(root, appDirName);
|
|
304
|
+
server.watcher.add(appDir);
|
|
305
|
+
const onStructure = (file: string): void => {
|
|
306
|
+
if (!isRouteFile(file, appDir) && !toPosix(file).startsWith(toPosix(appDir))) return;
|
|
307
|
+
if (!rescan()) return;
|
|
308
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED(VIRTUAL_ROUTES));
|
|
309
|
+
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
310
|
+
log?.info(` ○ Route manifest updated (${flattenRoutes(toStaticManifest(scan)).length} routes)`);
|
|
311
|
+
server.ws.send({ type: "full-reload" });
|
|
312
|
+
};
|
|
313
|
+
server.watcher.on("add", onStructure);
|
|
314
|
+
server.watcher.on("unlink", onStructure);
|
|
315
|
+
server.watcher.on("addDir", onStructure);
|
|
316
|
+
server.watcher.on("unlinkDir", onStructure);
|
|
317
|
+
|
|
318
|
+
const rewrites = await resolveRewrites(options.rewrites);
|
|
319
|
+
// Registered before user middleware and API routes so neither bypasses the host check.
|
|
320
|
+
const serverOnly = options.cacheHandler ? [resolve(root, options.cacheHandler)] : [];
|
|
321
|
+
server.middlewares.use((req, res, next) => {
|
|
322
|
+
if (!isAllowedDevHost(server, req.headers.host)) {
|
|
323
|
+
res.statusCode = 403;
|
|
324
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
325
|
+
res.end(`${BRAND_MARK} ${txMessage("TX028", JSON.stringify((req.headers.host ?? "").replace(/:\d+$/, "")), "vite.server.allowedHosts")}`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// `//evil.com/x` must not become `request.url` → http://evil.com/x.
|
|
329
|
+
req.url = normalizeRequestTarget(req.url);
|
|
330
|
+
let pathname = req.url.split("?")[0] ?? "/";
|
|
331
|
+
if (basePath && (pathname === basePath || pathname.startsWith(`${basePath}/`))) pathname = pathname.slice(basePath.length) || "/";
|
|
332
|
+
if (pathname.startsWith("/@fs/")) pathname = `/${relative(root, pathname.slice(4)).split(sep).join("/")}`;
|
|
333
|
+
if (isServerOnlySource(pathname, root, appDirName, serverOnly)) {
|
|
334
|
+
res.statusCode = 404;
|
|
335
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
336
|
+
res.end("404 | This page could not be found.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
next();
|
|
340
|
+
});
|
|
341
|
+
// `middleware.ts` runs before user middleware, route handlers and pages.
|
|
342
|
+
server.middlewares.use(createDevMiddleware(server, { root, basePath, log }));
|
|
343
|
+
// Editor previews (VS Code): `/__textjs/frames`, `/__textjs/input`, `/__textjs/status`.
|
|
344
|
+
const frames = attachFrameStream(server, root, { basePath });
|
|
345
|
+
server.httpServer?.once("close", () => frames.close());
|
|
346
|
+
// Server-rendered routes (`export const runtime = "server"`): live sessions + frame player HTML.
|
|
347
|
+
const live = createLiveDevHandler(server, { root, appDir: appDirName, basePath, title: title(), viewport: options.viewport, live: options.live, logger: log });
|
|
348
|
+
server.httpServer?.once("close", () => live.close());
|
|
349
|
+
const userHook = await options.configureServer?.(server as never);
|
|
350
|
+
|
|
351
|
+
server.middlewares.use(async (req, res, next) => {
|
|
352
|
+
// Vite's proxy middleware runs after this one: leave `proxy` paths to it (a `fetch()` sends `Accept: */*`, which reads as a page request).
|
|
353
|
+
if (matchesProxy(options.proxy, req.url ?? "/")) return next();
|
|
354
|
+
const started = performance.now();
|
|
355
|
+
const origin = requestOrigin(req);
|
|
356
|
+
let url = req.url ?? "/";
|
|
357
|
+
if (basePath) {
|
|
358
|
+
if (url === basePath || url.startsWith(`${basePath}/`) || url.startsWith(`${basePath}?`)) url = url.slice(basePath.length) || "/";
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const rewritten = rewrites.length > 0 ? applyRewrites(rewrites, url) : null;
|
|
362
|
+
if (rewritten) {
|
|
363
|
+
if (/^[a-z]+:\/\//i.test(rewritten)) {
|
|
364
|
+
await proxyRequest(req, res, rewritten, origin);
|
|
365
|
+
log?.info(` ${req.method} ${url} → ${rewritten} ${res.statusCode} in ${Math.round(performance.now() - started)}ms`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
url = rewritten;
|
|
369
|
+
req.url = basePath + rewritten;
|
|
370
|
+
}
|
|
371
|
+
const pathname = url.split("?")[0] ?? "/";
|
|
372
|
+
if (await live.handle(req, res, url)) return;
|
|
373
|
+
const api = matchApiRoute(apiManifest, pathname);
|
|
374
|
+
if (api) {
|
|
375
|
+
const mod = (await server.ssrLoadModule(api.file)) as RouteHandlerModule;
|
|
376
|
+
const request = toWebRequest(Object.assign(req, { url }), origin, res);
|
|
377
|
+
// Route segment config applies in dev, but responses are never cached (like Next.js).
|
|
378
|
+
const response = await runRouteHandlerWithCache(mod, request, { params: api.params, pattern: api.pattern, pathname, cache: false });
|
|
379
|
+
await sendWebResponse(res, response, request.method);
|
|
380
|
+
log?.info(` ${req.method} ${pathname} ${response.status} in ${Math.round(performance.now() - started)}ms`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if ((req.method === "GET" || req.method === "HEAD") && isHtmlRequest(url, req.headers.accept)) {
|
|
384
|
+
const custom = userHtml();
|
|
385
|
+
const raw = custom ? readFileSync(custom, "utf8") : createIndexHtml(title(), `${basePath}${ENTRY_URL}`);
|
|
386
|
+
const html = await server.transformIndexHtml(HTML_TRANSFORM_URL, raw, req.originalUrl);
|
|
387
|
+
res.statusCode = 200;
|
|
388
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
389
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
390
|
+
res.end(req.method === "HEAD" ? undefined : html);
|
|
391
|
+
log?.info(` ${req.method} ${pathname} 200 in ${Math.round(performance.now() - started)}ms`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
396
|
+
log?.error(` ⨯ ${req.method} ${url}\n${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
397
|
+
res.statusCode = 500;
|
|
398
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
399
|
+
res.end(`${BRAND_MARK} Text.js: ${error instanceof Error ? error.message : String(error)}`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
next();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return typeof userHook === "function" ? userHook : undefined;
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
return [...(react() as Plugin[]), plugin, ...textjsLivePlugin({ appDir: appDirName, root })];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export { scanApp, toStaticManifest, listApiRoutes, listPages, generateRoutesModule, type AppScan } from "./server/scan";
|
|
413
|
+
export default textjsVitePlugin;
|