@nifrajs/web 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +18 -0
- package/css-modules.d.ts +34 -0
- package/dist/build.d.ts +110 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +238 -0
- package/dist/build.js.map +1 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +204 -0
- package/dist/client.js.map +1 -0
- package/dist/deferred.d.ts +90 -0
- package/dist/deferred.d.ts.map +1 -0
- package/dist/deferred.js +310 -0
- package/dist/deferred.js.map +1 -0
- package/dist/dev-error.d.ts +17 -0
- package/dist/dev-error.d.ts.map +1 -0
- package/dist/dev-error.js +59 -0
- package/dist/dev-error.js.map +1 -0
- package/dist/dev.d.ts +25 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +83 -0
- package/dist/dev.js.map +1 -0
- package/dist/draft.d.ts +58 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +55 -0
- package/dist/draft.js.map +1 -0
- package/dist/fonts-google.d.ts +109 -0
- package/dist/fonts-google.d.ts.map +1 -0
- package/dist/fonts-google.js +291 -0
- package/dist/fonts-google.js.map +1 -0
- package/dist/fonts.d.ts +67 -0
- package/dist/fonts.d.ts.map +1 -0
- package/dist/fonts.js +103 -0
- package/dist/fonts.js.map +1 -0
- package/dist/fs.d.ts +10 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +24 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +784 -0
- package/dist/index.js.map +1 -0
- package/dist/islands.d.ts +37 -0
- package/dist/islands.d.ts.map +1 -0
- package/dist/islands.js +107 -0
- package/dist/islands.js.map +1 -0
- package/dist/isr.d.ts +154 -0
- package/dist/isr.d.ts.map +1 -0
- package/dist/isr.js +331 -0
- package/dist/isr.js.map +1 -0
- package/dist/manifest.d.ts +178 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +213 -0
- package/dist/manifest.js.map +1 -0
- package/dist/plugins/css-modules.d.ts +40 -0
- package/dist/plugins/css-modules.d.ts.map +1 -0
- package/dist/plugins/css-modules.js +0 -0
- package/dist/plugins/css-modules.js.map +1 -0
- package/dist/plugins/kit.d.ts +44 -0
- package/dist/plugins/kit.d.ts.map +1 -0
- package/dist/plugins/kit.js +105 -0
- package/dist/plugins/kit.js.map +1 -0
- package/dist/plugins/scss.d.ts +27 -0
- package/dist/plugins/scss.d.ts.map +1 -0
- package/dist/plugins/scss.js +78 -0
- package/dist/plugins/scss.js.map +1 -0
- package/dist/prerender.d.ts +77 -0
- package/dist/prerender.d.ts.map +1 -0
- package/dist/prerender.js +139 -0
- package/dist/prerender.js.map +1 -0
- package/dist/query.d.ts +62 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +184 -0
- package/dist/query.js.map +1 -0
- package/dist/router.d.ts +169 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +531 -0
- package/dist/router.js.map +1 -0
- package/dist/vite.d.ts +49 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +144 -0
- package/dist/vite.js.map +1 -0
- package/package.json +127 -0
- package/src/build.ts +375 -0
- package/src/client.ts +218 -0
- package/src/deferred.ts +339 -0
- package/src/dev-error.ts +62 -0
- package/src/dev.ts +108 -0
- package/src/draft.ts +82 -0
- package/src/fonts-google.ts +397 -0
- package/src/fonts.ts +147 -0
- package/src/fs.ts +26 -0
- package/src/index.ts +1202 -0
- package/src/islands.ts +121 -0
- package/src/isr.ts +461 -0
- package/src/manifest.ts +366 -0
- package/src/plugins/css-modules.ts +0 -0
- package/src/plugins/kit.ts +123 -0
- package/src/plugins/scss.ts +108 -0
- package/src/prerender.ts +209 -0
- package/src/query.ts +250 -0
- package/src/router.ts +676 -0
- package/src/vite.ts +212 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only error overlay. When a loader/action/render throws during `@nifrajs/web/vite` dev, this
|
|
3
|
+
* renders a readable full-page overlay — the message, the source-mapped stack (Vite's
|
|
4
|
+
* `ssrFixStacktrace` runs first), and the request line — instead of the bare `err.stack` text dump.
|
|
5
|
+
* Dev-only by construction: it's called solely from the Vite middleware's catch, never in
|
|
6
|
+
* production (production maps errors to the `_error` route boundary).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Render the dev error overlay HTML. `req` (method + url) is shown for context. The page is
|
|
10
|
+
* self-contained (inline CSS), dark, and labels itself a dev overlay so it's never mistaken for a
|
|
11
|
+
* production error page.
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderDevErrorOverlay(err: unknown, req: {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
}): string;
|
|
17
|
+
//# sourceMappingURL=dev-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-error.d.ts","sourceRoot":"","sources":["../src/dev-error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkBH;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAgChG"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only error overlay. When a loader/action/render throws during `@nifrajs/web/vite` dev, this
|
|
3
|
+
* renders a readable full-page overlay — the message, the source-mapped stack (Vite's
|
|
4
|
+
* `ssrFixStacktrace` runs first), and the request line — instead of the bare `err.stack` text dump.
|
|
5
|
+
* Dev-only by construction: it's called solely from the Vite middleware's catch, never in
|
|
6
|
+
* production (production maps errors to the `_error` route boundary).
|
|
7
|
+
*/
|
|
8
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
9
|
+
/** Split a stack into the message (first line) + the frame lines, for separate styling. */
|
|
10
|
+
function parseStack(err) {
|
|
11
|
+
const stack = err.stack ?? `${err.name}: ${err.message}`;
|
|
12
|
+
const lines = stack.split("\n");
|
|
13
|
+
// The message can span multiple leading lines (some errors do); frames start at the first " at ".
|
|
14
|
+
const firstFrame = lines.findIndex((l) => /^\s*at\s/.test(l));
|
|
15
|
+
if (firstFrame === -1)
|
|
16
|
+
return { message: stack, frames: [] };
|
|
17
|
+
return {
|
|
18
|
+
message: lines.slice(0, firstFrame).join("\n").trim(),
|
|
19
|
+
frames: lines.slice(firstFrame).map((l) => l.trim()),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Render the dev error overlay HTML. `req` (method + url) is shown for context. The page is
|
|
24
|
+
* self-contained (inline CSS), dark, and labels itself a dev overlay so it's never mistaken for a
|
|
25
|
+
* production error page.
|
|
26
|
+
*/
|
|
27
|
+
export function renderDevErrorOverlay(err, req) {
|
|
28
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
29
|
+
const { message, frames } = parseStack(error);
|
|
30
|
+
const name = esc(error.name || "Error");
|
|
31
|
+
const framesHtml = frames.length > 0
|
|
32
|
+
? `<ol class="frames">${frames.map((f) => `<li>${esc(f)}</li>`).join("")}</ol>`
|
|
33
|
+
: `<p class="no-frames">No stack frames.</p>`;
|
|
34
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${name} — nifra dev</title>
|
|
35
|
+
<style>
|
|
36
|
+
:root { color-scheme: dark }
|
|
37
|
+
body { margin: 0; background: #0b0d10; color: #e6e6e6; font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
38
|
+
.bar { background: #2a0e0e; border-bottom: 1px solid #5a1d1d; padding: 10px 20px; font-weight: 700; color: #ff8a8a; display: flex; gap: 12px; align-items: baseline; }
|
|
39
|
+
.bar .tag { font-size: 11px; font-weight: 600; color: #ffb4b4; background: #401616; padding: 2px 8px; border-radius: 99px; letter-spacing: .04em; text-transform: uppercase; }
|
|
40
|
+
.bar .req { margin-left: auto; color: #c98; font-weight: 500; font-size: 12px; }
|
|
41
|
+
main { padding: 24px 28px; max-width: 1000px; }
|
|
42
|
+
h1 { font-size: 13px; color: #9aa; font-weight: 600; margin: 0 0 6px; text-transform: uppercase; letter-spacing: .05em; }
|
|
43
|
+
.message { font-size: 18px; color: #ff9b9b; white-space: pre-wrap; margin: 0 0 28px; font-weight: 600; }
|
|
44
|
+
.frames { list-style: none; margin: 0; padding: 0; border-left: 2px solid #333; }
|
|
45
|
+
.frames li { padding: 3px 0 3px 16px; color: #9fb0c0; white-space: pre-wrap; word-break: break-all; }
|
|
46
|
+
.frames li:first-child { color: #d6e2ee; }
|
|
47
|
+
.no-frames { color: #778; }
|
|
48
|
+
footer { margin-top: 28px; color: #667; font-size: 12px; }
|
|
49
|
+
</style></head><body>
|
|
50
|
+
<div class="bar"><span class="tag">nifra dev</span><span>${name}</span><span class="req">${esc(req.method)} ${esc(req.url)}</span></div>
|
|
51
|
+
<main>
|
|
52
|
+
<h1>Unhandled error during SSR</h1>
|
|
53
|
+
<p class="message">${esc(message)}</p>
|
|
54
|
+
<h1>Stack</h1>
|
|
55
|
+
${framesHtml}
|
|
56
|
+
<footer>This overlay is shown only by the dev server. In production this error maps to your <code>_error</code> route boundary.</footer>
|
|
57
|
+
</main></body></html>`;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=dev-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-error.js","sourceRoot":"","sources":["../src/dev-error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAChC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AAE9F,2FAA2F;AAC3F,SAAS,UAAU,CAAC,GAAU;IAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAA;IACxD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC/B,qGAAqG;IACrG,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7D,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;IAC5D,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;QACrD,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;KACrD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY,EAAE,GAAoC;IACtF,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IACjE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,CAAA;IACvC,MAAM,UAAU,GACd,MAAM,CAAC,MAAM,GAAG,CAAC;QACf,CAAC,CAAC,sBAAsB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO;QAC/E,CAAC,CAAC,2CAA2C,CAAA;IACjD,OAAO,qEAAqE,IAAI;;;;;;;;;;;;;;;;2DAgBvB,IAAI,4BAA4B,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;;;uBAGnG,GAAG,CAAC,OAAO,CAAC;;IAE/B,UAAU;;sBAEQ,CAAA;AACtB,CAAC"}
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type BuildClientOptions } from "./build.js";
|
|
2
|
+
/** Minimal app surface the dev server needs — `createWebApp(...)` satisfies it. */
|
|
3
|
+
interface FetchApp {
|
|
4
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
5
|
+
}
|
|
6
|
+
export interface DevServerOptions extends Omit<BuildClientOptions, "minify"> {
|
|
7
|
+
/**
|
|
8
|
+
* Build the nifra app for the freshly-built client entry. `importQuery` changes on every
|
|
9
|
+
* rebuild — pass it to `discoverRoutes(routesDir, { importQuery })` so SSR re-imports edited
|
|
10
|
+
* route modules instead of Bun's cached copies.
|
|
11
|
+
*/
|
|
12
|
+
readonly createApp: (clientEntry: string, importQuery: string) => FetchApp | Promise<FetchApp>;
|
|
13
|
+
/** Directories to watch (default: `[routesDir]`). */
|
|
14
|
+
readonly watch?: readonly string[];
|
|
15
|
+
/** Port to listen on (default 3000). */
|
|
16
|
+
readonly port?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface DevServer {
|
|
19
|
+
readonly port: number;
|
|
20
|
+
stop(): void;
|
|
21
|
+
}
|
|
22
|
+
/** Start the dev server: build → serve → watch → rebuild + reload on change. */
|
|
23
|
+
export declare function createDevServer(options: DevServerOptions): Promise<DevServer>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,kBAAkB,EAAe,MAAM,YAAY,CAAA;AAEjE,mFAAmF;AACnF,UAAU,QAAQ;IAChB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9F,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,IAAI,IAAI,CAAA;CACb;AAOD,gFAAgF;AAChF,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAoEnF"}
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web/dev` — a zero-config dev server (Bun-only). Builds the client unminified on boot,
|
|
3
|
+
* serves the nifra app + hashed assets, and injects a live-reload client. Watches the routes dir:
|
|
4
|
+
* on change it rebuilds the client and re-creates the app with a fresh `importQuery` (so SSR
|
|
5
|
+
* picks up edited route modules past Bun's import cache), then tells the browser to reload.
|
|
6
|
+
* Bun-only + build-time; never imported by the edge runtime.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, unwatchFile, watchFile } from "node:fs";
|
|
9
|
+
import { buildClient } from "./build.js";
|
|
10
|
+
const RELOAD_PATH = "/__nifra_dev";
|
|
11
|
+
const RELOAD_TOPIC = "reload";
|
|
12
|
+
// Live-reload client: reloads on a server "reload" push; reconnects if the socket drops.
|
|
13
|
+
const reloadClient = `<script>(()=>{const x=()=>{const w=new WebSocket((location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"${RELOAD_PATH}");w.onmessage=()=>location.reload();w.onclose=()=>setTimeout(x,500)};x()})()</script>`;
|
|
14
|
+
/** Start the dev server: build → serve → watch → rebuild + reload on change. */
|
|
15
|
+
export async function createDevServer(options) {
|
|
16
|
+
const { createApp, port = 3000, outDir, routesDir } = options;
|
|
17
|
+
const publicPath = options.publicPath ?? "/assets/";
|
|
18
|
+
let version = 0;
|
|
19
|
+
const rebuild = async () => {
|
|
20
|
+
const manifest = await buildClient({ ...options, minify: false });
|
|
21
|
+
return createApp(manifest.entry, `v=${version}`);
|
|
22
|
+
};
|
|
23
|
+
let app = await rebuild();
|
|
24
|
+
const server = Bun.serve({
|
|
25
|
+
port,
|
|
26
|
+
async fetch(req, srv) {
|
|
27
|
+
const url = new URL(req.url);
|
|
28
|
+
if (url.pathname === RELOAD_PATH) {
|
|
29
|
+
return srv.upgrade(req) ? undefined : new Response("expected websocket", { status: 426 });
|
|
30
|
+
}
|
|
31
|
+
if (url.pathname.startsWith(publicPath)) {
|
|
32
|
+
const file = Bun.file(`${outDir}/${url.pathname.slice(publicPath.length)}`);
|
|
33
|
+
return (await file.exists())
|
|
34
|
+
? new Response(file, { headers: { "content-type": "text/javascript; charset=utf-8" } })
|
|
35
|
+
: new Response("Not Found", { status: 404 });
|
|
36
|
+
}
|
|
37
|
+
const res = await app.fetch(req);
|
|
38
|
+
if (!(res.headers.get("content-type") ?? "").includes("text/html"))
|
|
39
|
+
return res;
|
|
40
|
+
const headers = new Headers(res.headers);
|
|
41
|
+
headers.delete("content-length"); // the body grows with the injected reload client
|
|
42
|
+
const html = (await res.text()).replace("</body>", `${reloadClient}</body>`);
|
|
43
|
+
return new Response(html, { status: res.status, headers });
|
|
44
|
+
},
|
|
45
|
+
websocket: {
|
|
46
|
+
open: (ws) => ws.subscribe(RELOAD_TOPIC),
|
|
47
|
+
message: () => { },
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
// On a route change: rebuild the client, re-create the app with a fresh import cache-buster,
|
|
51
|
+
// then push a reload. Debounced so a burst of saves rebuilds once.
|
|
52
|
+
let timer;
|
|
53
|
+
const onChange = () => {
|
|
54
|
+
if (timer)
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
timer = setTimeout(() => {
|
|
57
|
+
version += 1;
|
|
58
|
+
rebuild()
|
|
59
|
+
.then((next) => {
|
|
60
|
+
app = next;
|
|
61
|
+
server.publish(RELOAD_TOPIC, "reload");
|
|
62
|
+
console.log(`[nifra/web/dev] rebuilt + reloaded (v${version})`);
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => console.error("[nifra/web/dev] rebuild failed:", err));
|
|
65
|
+
}, 60);
|
|
66
|
+
};
|
|
67
|
+
// Poll the source files for changes. Bun's fs.watch is unreliable here, so watchFile (mtime
|
|
68
|
+
// polling) is used. New files need a restart to be tracked — fine for dev.
|
|
69
|
+
const watched = (options.watch ?? [routesDir]).flatMap((dir) => readdirSync(dir, { recursive: true })
|
|
70
|
+
.filter((file) => /\.(tsx|jsx|ts|js|mdx|svelte|vue)$/.test(file))
|
|
71
|
+
.map((file) => `${dir}/${file}`));
|
|
72
|
+
for (const file of watched)
|
|
73
|
+
watchFile(file, { interval: 150 }, onChange);
|
|
74
|
+
return {
|
|
75
|
+
port: server.port ?? port,
|
|
76
|
+
stop: () => {
|
|
77
|
+
for (const file of watched)
|
|
78
|
+
unwatchFile(file);
|
|
79
|
+
server.stop();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=dev.js.map
|
package/dist/dev.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"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"}
|
package/dist/draft.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draft / preview mode — let an editor see unpublished content by flipping a signed cookie that
|
|
3
|
+
* loaders read (`ctx.draft`) and ISR bypasses (fresh render, never cached). The cookie is **HMAC-signed**
|
|
4
|
+
* (so a visitor can't forge it) and **HttpOnly** (JS can't read it). You gate `enableDraft` yourself —
|
|
5
|
+
* behind an editor login or a `?token=` check — exactly like Next's `draftMode().enable()`.
|
|
6
|
+
*
|
|
7
|
+
* // a route you've protected (e.g. checked ?token= against a secret):
|
|
8
|
+
* app.get("/api/draft", async (c) => {
|
|
9
|
+
* await enableDraft(c, env.DRAFT_SECRET)
|
|
10
|
+
* return redirect("/")
|
|
11
|
+
* })
|
|
12
|
+
*
|
|
13
|
+
* // wire the same secret so the framework can verify the cookie:
|
|
14
|
+
* createWebApp({ ..., draftSecret: env.DRAFT_SECRET }) // → loaders get ctx.draft
|
|
15
|
+
* withISR(app, { ..., draftSecret: env.DRAFT_SECRET }) // → editors bypass the cache
|
|
16
|
+
*/
|
|
17
|
+
import { type CookieOptions } from "@nifrajs/core";
|
|
18
|
+
/** The cookie name nifra uses for draft/preview mode. */
|
|
19
|
+
export declare const DRAFT_COOKIE = "__nifra_draft";
|
|
20
|
+
/** The response-cookie surface `enableDraft`/`disableDraft` need — nifra's `c.set`. Structural, so any
|
|
21
|
+
* nifra handler context satisfies it without importing the full `Context`. */
|
|
22
|
+
export interface DraftCookieControls {
|
|
23
|
+
cookie(name: string, value: string, options?: CookieOptions): void;
|
|
24
|
+
deleteCookie(name: string, options?: {
|
|
25
|
+
readonly path?: string;
|
|
26
|
+
readonly domain?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
}
|
|
29
|
+
export interface EnableDraftOptions {
|
|
30
|
+
/** Cookie lifetime in **seconds** (default `3600` = 1h). Keep it short — draft is an editor session. */
|
|
31
|
+
readonly maxAgeSeconds?: number;
|
|
32
|
+
/** Cookie `Path` (default `"/"`). */
|
|
33
|
+
readonly path?: string;
|
|
34
|
+
/** Override the `Secure` attribute (defaults to `true` — secure-by-default). Pass `false` only for
|
|
35
|
+
* local `http://` dev, where a `Secure` cookie isn't stored. */
|
|
36
|
+
readonly secure?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Turn draft mode **on** for this client by setting a signed, HttpOnly `__nifra_draft` cookie. Call it
|
|
40
|
+
* from a route you've already authorized. `secret` signs the cookie — pass the SAME secret to
|
|
41
|
+
* `createWebApp({ draftSecret })` and `withISR({ draftSecret })` so the framework can verify it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function enableDraft(c: {
|
|
44
|
+
readonly set: DraftCookieControls;
|
|
45
|
+
}, secret: string, options?: EnableDraftOptions): Promise<void>;
|
|
46
|
+
/** Turn draft mode **off**: clear the `__nifra_draft` cookie. Match the `path` used in `enableDraft`. */
|
|
47
|
+
export declare function disableDraft(c: {
|
|
48
|
+
readonly set: DraftCookieControls;
|
|
49
|
+
}, options?: {
|
|
50
|
+
readonly path?: string;
|
|
51
|
+
}): void;
|
|
52
|
+
/**
|
|
53
|
+
* Whether `request` carries a **valid** signed draft cookie (constant-time verify via `unsignValue`).
|
|
54
|
+
* `createWebApp` uses it to set `ctx.draft`; `withISR` uses it to bypass the cache for editors. A
|
|
55
|
+
* missing, forged, or tampered cookie returns `false`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isDraftEnabled(request: Request, secret: string): Promise<boolean>;
|
|
58
|
+
//# sourceMappingURL=draft.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,KAAK,aAAa,EAAwC,MAAM,eAAe,CAAA;AAExF,yDAAyD;AACzD,eAAO,MAAM,YAAY,kBAAkB,CAAA;AAM3C;8EAC8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;IAClE,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACjG;AAED,MAAM,WAAW,kBAAkB;IACjC,wGAAwG;IACxG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,qCAAqC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;oEACgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,CAAC,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,yGAAyG;AACzG,wBAAgB,YAAY,CAC1B,CAAC,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,EACxC,OAAO,GAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIvF"}
|
package/dist/draft.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draft / preview mode — let an editor see unpublished content by flipping a signed cookie that
|
|
3
|
+
* loaders read (`ctx.draft`) and ISR bypasses (fresh render, never cached). The cookie is **HMAC-signed**
|
|
4
|
+
* (so a visitor can't forge it) and **HttpOnly** (JS can't read it). You gate `enableDraft` yourself —
|
|
5
|
+
* behind an editor login or a `?token=` check — exactly like Next's `draftMode().enable()`.
|
|
6
|
+
*
|
|
7
|
+
* // a route you've protected (e.g. checked ?token= against a secret):
|
|
8
|
+
* app.get("/api/draft", async (c) => {
|
|
9
|
+
* await enableDraft(c, env.DRAFT_SECRET)
|
|
10
|
+
* return redirect("/")
|
|
11
|
+
* })
|
|
12
|
+
*
|
|
13
|
+
* // wire the same secret so the framework can verify the cookie:
|
|
14
|
+
* createWebApp({ ..., draftSecret: env.DRAFT_SECRET }) // → loaders get ctx.draft
|
|
15
|
+
* withISR(app, { ..., draftSecret: env.DRAFT_SECRET }) // → editors bypass the cache
|
|
16
|
+
*/
|
|
17
|
+
import { parseCookies, signValue, unsignValue } from "@nifrajs/core";
|
|
18
|
+
/** The cookie name nifra uses for draft/preview mode. */
|
|
19
|
+
export const DRAFT_COOKIE = "__nifra_draft";
|
|
20
|
+
// The signed cookie's payload is a fixed marker — a *valid signature* (not the value) is the signal
|
|
21
|
+
// that the server issued this via enableDraft(). Nothing secret is stored in it.
|
|
22
|
+
const DRAFT_MARKER = "1";
|
|
23
|
+
/**
|
|
24
|
+
* Turn draft mode **on** for this client by setting a signed, HttpOnly `__nifra_draft` cookie. Call it
|
|
25
|
+
* from a route you've already authorized. `secret` signs the cookie — pass the SAME secret to
|
|
26
|
+
* `createWebApp({ draftSecret })` and `withISR({ draftSecret })` so the framework can verify it.
|
|
27
|
+
*/
|
|
28
|
+
export async function enableDraft(c, secret, options = {}) {
|
|
29
|
+
const signed = await signValue(DRAFT_MARKER, secret);
|
|
30
|
+
// Explicit security attributes (don't lean on the context default): signed + HttpOnly + SameSite=Lax.
|
|
31
|
+
const cookieOptions = {
|
|
32
|
+
httpOnly: true,
|
|
33
|
+
sameSite: "lax",
|
|
34
|
+
path: options.path ?? "/",
|
|
35
|
+
maxAge: options.maxAgeSeconds ?? 3600,
|
|
36
|
+
...(options.secure === undefined ? {} : { secure: options.secure }),
|
|
37
|
+
};
|
|
38
|
+
c.set.cookie(DRAFT_COOKIE, signed, cookieOptions);
|
|
39
|
+
}
|
|
40
|
+
/** Turn draft mode **off**: clear the `__nifra_draft` cookie. Match the `path` used in `enableDraft`. */
|
|
41
|
+
export function disableDraft(c, options = {}) {
|
|
42
|
+
c.set.deleteCookie(DRAFT_COOKIE, { path: options.path ?? "/" });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether `request` carries a **valid** signed draft cookie (constant-time verify via `unsignValue`).
|
|
46
|
+
* `createWebApp` uses it to set `ctx.draft`; `withISR` uses it to bypass the cache for editors. A
|
|
47
|
+
* missing, forged, or tampered cookie returns `false`.
|
|
48
|
+
*/
|
|
49
|
+
export async function isDraftEnabled(request, secret) {
|
|
50
|
+
const cookie = parseCookies(request.headers.get("cookie"))[DRAFT_COOKIE];
|
|
51
|
+
if (cookie === undefined)
|
|
52
|
+
return false;
|
|
53
|
+
return (await unsignValue(cookie, secret)) !== null;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAsB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAExF,yDAAyD;AACzD,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAA;AAE3C,oGAAoG;AACpG,iFAAiF;AACjF,MAAM,YAAY,GAAG,GAAG,CAAA;AAmBxB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,CAAwC,EACxC,MAAc,EACd,UAA8B,EAAE;IAEhC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;IACpD,sGAAsG;IACtG,MAAM,aAAa,GAAkB;QACnC,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,GAAG;QACzB,MAAM,EAAE,OAAO,CAAC,aAAa,IAAI,IAAI;QACrC,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;KACpE,CAAA;IACD,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,CAAA;AACnD,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,YAAY,CAC1B,CAAwC,EACxC,UAAsC,EAAE;IAExC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC,CAAA;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAgB,EAAE,MAAc;IACnE,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAA;IACxE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACtC,OAAO,CAAC,MAAM,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI,CAAA;AACrD,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time Google Fonts automation — the `next/font/google` equivalent. At **build time** (never on
|
|
3
|
+
* the request path) it: builds the Google Fonts CSS2 URL, downloads the stylesheet, parses the
|
|
4
|
+
* `@font-face` rules, downloads each `.woff2`, content-hashes it, writes it next to your assets, and
|
|
5
|
+
* hands back a **self-hosted** `@font-face` stylesheet + the matching `<link rel="preload">`s. The
|
|
6
|
+
* result is identical to dropping the files in yourself and calling {@link fontFace} — no runtime CDN
|
|
7
|
+
* hotlink, no layout shift, hashed filenames for immutable caching.
|
|
8
|
+
*
|
|
9
|
+
* // fonts.build.ts — run once at build time (e.g. a prebuild step)
|
|
10
|
+
* import { loadGoogleFont } from "@nifrajs/web/fonts"
|
|
11
|
+
* const inter = await loadGoogleFont(
|
|
12
|
+
* { family: "Inter", weights: [400, 700], subsets: ["latin"] },
|
|
13
|
+
* { outDir: "public/fonts" }, // → public/fonts/inter-latin-normal-400-<hash>.woff2
|
|
14
|
+
* )
|
|
15
|
+
* await Bun.write("app/fonts.css", inter.css) // import this stylesheet from your app
|
|
16
|
+
* // inter.preloads → spread into a root layout's `meta.link`
|
|
17
|
+
*
|
|
18
|
+
* Security: this fetches remote content and writes it to disk, so every input is validated and the
|
|
19
|
+
* font-file host is **allowlisted to `fonts.gstatic.com` over https** — a tampered/MITM'd stylesheet
|
|
20
|
+
* cannot make the build fetch an arbitrary URL (SSRF) or write an attacker-chosen blob. Downloads are
|
|
21
|
+
* size-capped. Filenames are derived only from validated tokens + a content hash (no path traversal).
|
|
22
|
+
*/
|
|
23
|
+
import { type FontDisplay } from "./fonts.js";
|
|
24
|
+
/** Options describing the Google font to fetch + self-host. */
|
|
25
|
+
export interface GoogleFontOptions {
|
|
26
|
+
/** Family name exactly as Google lists it, e.g. `"Inter"`, `"Open Sans"`, `"Roboto Mono"`. */
|
|
27
|
+
readonly family: string;
|
|
28
|
+
/** Weights to request — numbers (`400`), numeric strings, a variable range (`"100 900"`), or the
|
|
29
|
+
* keywords `"normal"`/`"bold"`. Defaults to `[400]`. */
|
|
30
|
+
readonly weights?: readonly (number | string)[];
|
|
31
|
+
/** Styles to request. Defaults to `["normal"]`. */
|
|
32
|
+
readonly styles?: readonly ("normal" | "italic")[];
|
|
33
|
+
/** Keep only these named subsets (`"latin"`, `"latin-ext"`, `"cyrillic"`, …). Google returns every
|
|
34
|
+
* subset it has as a separate `@font-face`; this filters to the ones you serve. Defaults to keeping
|
|
35
|
+
* all returned subsets. Ignored when {@link text} is set (glyph subsetting supersedes it). */
|
|
36
|
+
readonly subsets?: readonly string[];
|
|
37
|
+
/** `font-display` strategy for the generated faces. Defaults to `"swap"`. */
|
|
38
|
+
readonly display?: FontDisplay;
|
|
39
|
+
/** Glyph subsetting: request only the glyphs needed to render exactly this text (Google's `&text=`).
|
|
40
|
+
* Ideal for a logo/heading font — produces one tiny file. */
|
|
41
|
+
readonly text?: string;
|
|
42
|
+
/** CLS metric overrides forwarded to every generated `@font-face` (the layout-shift fix). */
|
|
43
|
+
readonly sizeAdjust?: string;
|
|
44
|
+
readonly ascentOverride?: string;
|
|
45
|
+
readonly descentOverride?: string;
|
|
46
|
+
readonly lineGapOverride?: string;
|
|
47
|
+
}
|
|
48
|
+
/** A single `@font-face` block parsed out of Google's stylesheet. */
|
|
49
|
+
export interface ParsedFontFace {
|
|
50
|
+
readonly family: string;
|
|
51
|
+
readonly style: string;
|
|
52
|
+
readonly weight: string;
|
|
53
|
+
readonly subset: string;
|
|
54
|
+
readonly unicodeRange?: string;
|
|
55
|
+
readonly src: readonly {
|
|
56
|
+
readonly url: string;
|
|
57
|
+
readonly format?: string;
|
|
58
|
+
}[];
|
|
59
|
+
}
|
|
60
|
+
/** One downloaded + written font file. */
|
|
61
|
+
export interface FontAsset {
|
|
62
|
+
/** The hashed filename written under `outDir` (no directory part). */
|
|
63
|
+
readonly fileName: string;
|
|
64
|
+
/** The public URL the generated `@font-face`/preload reference (`${publicPath}/${fileName}`). */
|
|
65
|
+
readonly href: string;
|
|
66
|
+
/** The original `fonts.gstatic.com` URL the bytes came from. */
|
|
67
|
+
readonly sourceUrl: string;
|
|
68
|
+
readonly bytes: Uint8Array;
|
|
69
|
+
readonly subset: string;
|
|
70
|
+
readonly weight: string;
|
|
71
|
+
readonly style: string;
|
|
72
|
+
}
|
|
73
|
+
export interface LoadGoogleFontResult {
|
|
74
|
+
readonly family: string;
|
|
75
|
+
/** A self-hosted `@font-face` stylesheet (one rule per written file). Import it from your app. */
|
|
76
|
+
readonly css: string;
|
|
77
|
+
/** Every file written to `outDir`. */
|
|
78
|
+
readonly assets: readonly FontAsset[];
|
|
79
|
+
/** `fontPreload()` link-attribute sets — spread the ones you want into a layout's `meta.link`.
|
|
80
|
+
* Preloading *every* weight/subset is wasteful; usually preload just the primary subset + weight. */
|
|
81
|
+
readonly preloads: readonly Record<string, string>[];
|
|
82
|
+
}
|
|
83
|
+
export interface LoadGoogleFontIO {
|
|
84
|
+
/** Directory to write the hashed `.woff2` files into (created if missing). */
|
|
85
|
+
readonly outDir: string;
|
|
86
|
+
/** URL prefix the files are served under. Defaults to `"/fonts"`. */
|
|
87
|
+
readonly publicPath?: string;
|
|
88
|
+
/** Injectable `fetch` (defaults to the global). Tests pass a canned implementation. */
|
|
89
|
+
readonly fetch?: typeof fetch;
|
|
90
|
+
/** Injectable writer (defaults to `node:fs`). Tests pass an in-memory sink. */
|
|
91
|
+
readonly writeFile?: (path: string, bytes: Uint8Array) => Promise<void>;
|
|
92
|
+
/** Per-file download cap in bytes. Defaults to 5 MB (real woff2 are well under 1 MB). */
|
|
93
|
+
readonly maxBytesPerFile?: number;
|
|
94
|
+
}
|
|
95
|
+
/** `true` iff `raw` is an `https://fonts.gstatic.com/…` URL — the only host we'll download from. */
|
|
96
|
+
export declare function isAllowedFontUrl(raw: string): boolean;
|
|
97
|
+
/** Build the Google Fonts CSS2 request URL. Pure + fully validated, so it's safe to feed a dynamic
|
|
98
|
+
* family/weights/text. Exported for advanced callers who fetch + parse the stylesheet themselves. */
|
|
99
|
+
export declare function googleFontsCssUrl(options: GoogleFontOptions): string;
|
|
100
|
+
/** Parse Google's stylesheet into structured faces, capturing the `/* subset */` label that precedes
|
|
101
|
+
* each `@font-face`. Pure — exported so callers can run their own download/write pipeline. */
|
|
102
|
+
export declare function parseGoogleFontCss(css: string): ParsedFontFace[];
|
|
103
|
+
/**
|
|
104
|
+
* Download a Google font, self-host it, and return a CLS-safe `@font-face` stylesheet + preloads.
|
|
105
|
+
* See the module header for the full flow and security model. I/O (`fetch`, `writeFile`) is injectable
|
|
106
|
+
* so this is unit-testable without the network.
|
|
107
|
+
*/
|
|
108
|
+
export declare function loadGoogleFont(options: GoogleFontOptions, io: LoadGoogleFontIO): Promise<LoadGoogleFontResult>;
|
|
109
|
+
//# sourceMappingURL=fonts-google.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fonts-google.d.ts","sourceRoot":"","sources":["../src/fonts-google.ts"],"names":[],"mappings":"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"}
|