@janit/fu 0.0.2
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 +300 -0
- package/dist/app.d.ts +25 -0
- package/dist/app.js +80 -0
- package/dist/app.js.map +1 -0
- package/dist/build.d.ts +4 -0
- package/dist/build.js +108 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +59 -0
- package/dist/client.js.map +1 -0
- package/dist/dev.d.ts +2 -0
- package/dist/dev.js +167 -0
- package/dist/dev.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/hmr-runtime.js +112 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.js +12 -0
- package/dist/mod.js.map +1 -0
- package/dist/plugins.d.ts +68 -0
- package/dist/plugins.js +246 -0
- package/dist/plugins.js.map +1 -0
- package/dist/render.d.ts +38 -0
- package/dist/render.js +189 -0
- package/dist/render.js.map +1 -0
- package/dist/router.d.ts +21 -0
- package/dist/router.js +48 -0
- package/dist/router.js.map +1 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +98 -0
- package/src/cli.ts +25 -0
package/dist/dev.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Dev driver: rolldown DevEngine (HMR patches) + crossws (transport) +
|
|
2
|
+
// nitro dev server (SSR). No Vite anywhere.
|
|
3
|
+
import { DevEngine } from "rolldown/experimental";
|
|
4
|
+
import { serve } from "crossws/server";
|
|
5
|
+
import { build as nitroBuild, createDevServer, createNitro, prepare } from "nitro/builder";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { bootModule, css, jsx, optional, ssrModule, virtual, walk } from "./plugins.js";
|
|
9
|
+
/**
|
|
10
|
+
* Directory holding the framework's own runtime modules, whose paths are handed
|
|
11
|
+
* to rolldown. `import.meta.dirname` is undefined when this module is loaded
|
|
12
|
+
* from a remote URL, and a bundler cannot fetch `https:` modules anyway — so
|
|
13
|
+
* fail with the reason rather than a TypeError three frames later.
|
|
14
|
+
*/
|
|
15
|
+
const HERE = import.meta.dirname ?? remoteFrameworkError();
|
|
16
|
+
/**
|
|
17
|
+
* Extension of the framework's own runtime modules: `.ts` when running from
|
|
18
|
+
* source (this repo, or JSR), `.js` when running from the compiled npm build.
|
|
19
|
+
*/
|
|
20
|
+
const EXT = import.meta.url.endsWith(".js") ? ".js" : ".ts";
|
|
21
|
+
function remoteFrameworkError() {
|
|
22
|
+
throw new Error("fu: the framework is loaded from a remote URL (" + import.meta.url + "), " +
|
|
23
|
+
"so its runtime modules cannot be handed to the bundler. Install it " +
|
|
24
|
+
"instead — `npm:@janit/fu` in a Deno import map, or `npm i @janit/fu` — " +
|
|
25
|
+
"so it resolves to a real directory.");
|
|
26
|
+
}
|
|
27
|
+
export async function dev(opts) {
|
|
28
|
+
const root = path.resolve(opts.root);
|
|
29
|
+
const port = opts.port ?? 1337;
|
|
30
|
+
// Bind all interfaces so the dev server is reachable from other machines
|
|
31
|
+
// and from inside containers, not just loopback.
|
|
32
|
+
const hostname = opts.hostname ?? "0.0.0.0";
|
|
33
|
+
const hmrPort = port + 1;
|
|
34
|
+
const clientDir = path.join(root, "dist/client");
|
|
35
|
+
const genDir = path.join(root, ".fu");
|
|
36
|
+
const routeFiles = walk(root, path.join(root, "routes"));
|
|
37
|
+
const islandFiles = walk(root, path.join(root, "islands"));
|
|
38
|
+
fs.rmSync(clientDir, { recursive: true, force: true });
|
|
39
|
+
fs.mkdirSync(clientDir, { recursive: true });
|
|
40
|
+
const sheets = new Map();
|
|
41
|
+
const peers = new Map();
|
|
42
|
+
let cssVersion = 0;
|
|
43
|
+
const writeSheets = () => {
|
|
44
|
+
if (sheets.size)
|
|
45
|
+
fs.writeFileSync(path.join(clientDir, "style.css"), [...sheets.values()].join("\n"));
|
|
46
|
+
};
|
|
47
|
+
// `implement` takes the runtime SOURCE, not a path — a path gets inlined
|
|
48
|
+
// literally and parsed as a regex. `$ADDR` is only substituted in rolldown's
|
|
49
|
+
// own default runtime, so do it here.
|
|
50
|
+
const hmrRuntime = fs.readFileSync(path.join(HERE, "hmr-runtime.js"), "utf8")
|
|
51
|
+
.replaceAll("$ADDR", `localhost:${hmrPort}`);
|
|
52
|
+
const engine = await DevEngine.create({
|
|
53
|
+
input: { boot: "fu:boot" },
|
|
54
|
+
plugins: [
|
|
55
|
+
virtual({ "fu:boot": bootModule(path.join(HERE, `client${EXT}`), islandFiles, root) }),
|
|
56
|
+
css(sheets),
|
|
57
|
+
jsx({ hmr: true }),
|
|
58
|
+
],
|
|
59
|
+
platform: "browser",
|
|
60
|
+
moduleTypes: { ".css": "js" },
|
|
61
|
+
experimental: { devMode: { host: "localhost", port: hmrPort, implement: hmrRuntime } },
|
|
62
|
+
}, { dir: clientDir, format: "esm", entryFileNames: "[name].js", chunkFileNames: "[name].js" }, {
|
|
63
|
+
watch: { enabled: true },
|
|
64
|
+
onOutput(o) {
|
|
65
|
+
if (o instanceof Error)
|
|
66
|
+
return console.error("[fu] client build failed:", o.message);
|
|
67
|
+
writeSheets();
|
|
68
|
+
},
|
|
69
|
+
onHmrUpdates(r) {
|
|
70
|
+
if (r instanceof Error)
|
|
71
|
+
return console.error("[fu] hmr error:", r.message);
|
|
72
|
+
writeSheets();
|
|
73
|
+
for (const { clientId, update } of r.updates) {
|
|
74
|
+
const peer = peers.get(clientId);
|
|
75
|
+
if (!peer || update.type === "Noop")
|
|
76
|
+
continue;
|
|
77
|
+
if (update.type === "FullReload") {
|
|
78
|
+
peer.send(JSON.stringify({ type: "hmr:reload" }));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Always deliver the patch for real. Reporting a payload as delivered
|
|
82
|
+
// when it was not corrupts per-client shipped-state and updates stop
|
|
83
|
+
// firing silently.
|
|
84
|
+
fs.writeFileSync(path.join(clientDir, update.filename), update.code);
|
|
85
|
+
peer.send(JSON.stringify({
|
|
86
|
+
type: "hmr:update",
|
|
87
|
+
path: "/" + update.filename,
|
|
88
|
+
url: "/" + update.filename,
|
|
89
|
+
changedIds: update.changedIds,
|
|
90
|
+
}));
|
|
91
|
+
engine.notifyPayloadDelivered(update.filename);
|
|
92
|
+
if ((update.changedIds ?? []).some((id) => id.endsWith(".css"))) {
|
|
93
|
+
peer.send(JSON.stringify({ type: "fu:css", href: `/style.css?v=${++cssVersion}` }));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
await engine.run();
|
|
99
|
+
await engine.ensureLatestBuildOutput();
|
|
100
|
+
// Transport. Inline hooks behave uniformly across node/deno/bun; returning a
|
|
101
|
+
// plain `{crossws}` object from `fetch` fails on Deno.
|
|
102
|
+
const clientIdOf = (peer) => {
|
|
103
|
+
const raw = peer?.request?.url;
|
|
104
|
+
if (!raw)
|
|
105
|
+
return null;
|
|
106
|
+
try {
|
|
107
|
+
return new URL(raw, "http://localhost").searchParams.get("clientId");
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
serve({
|
|
114
|
+
port: hmrPort,
|
|
115
|
+
fetch: () => new Response("fu hmr"),
|
|
116
|
+
websocket: {
|
|
117
|
+
async open(peer) {
|
|
118
|
+
const id = clientIdOf(peer);
|
|
119
|
+
if (!id)
|
|
120
|
+
return;
|
|
121
|
+
peers.set(id, peer);
|
|
122
|
+
await engine.registerClient(id);
|
|
123
|
+
peer.send(JSON.stringify({ type: "connected" }));
|
|
124
|
+
},
|
|
125
|
+
async close(peer) {
|
|
126
|
+
const id = clientIdOf(peer);
|
|
127
|
+
if (!id)
|
|
128
|
+
return;
|
|
129
|
+
peers.delete(id);
|
|
130
|
+
await engine.removeClient(id);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// SSR.
|
|
135
|
+
fs.rmSync(genDir, { recursive: true, force: true });
|
|
136
|
+
fs.mkdirSync(genDir, { recursive: true });
|
|
137
|
+
const ssrEntry = path.join(genDir, "ssr.ts");
|
|
138
|
+
const assets = `{ js: [{ href: "/boot.js" }], css: [{ href: "/style.css" }] }`;
|
|
139
|
+
fs.writeFileSync(ssrEntry, ssrModule({
|
|
140
|
+
renderPath: path.join(HERE, `render${EXT}`),
|
|
141
|
+
routeFiles,
|
|
142
|
+
root,
|
|
143
|
+
assets,
|
|
144
|
+
appPath: optional(root, "app.ts", "app.tsx"),
|
|
145
|
+
shellPath: optional(root, "routes/_app.tsx"),
|
|
146
|
+
errorPath: optional(root, "routes/_error.tsx"),
|
|
147
|
+
}));
|
|
148
|
+
const nitro = await createNitro({
|
|
149
|
+
dev: true,
|
|
150
|
+
rootDir: root,
|
|
151
|
+
serverDir: genDir,
|
|
152
|
+
scanDirs: [],
|
|
153
|
+
publicAssets: [{ dir: clientDir, baseURL: "/" }],
|
|
154
|
+
handlers: [{ route: "/**", handler: ssrEntry, format: "web", lazy: false }],
|
|
155
|
+
rollupConfig: {
|
|
156
|
+
plugins: [css(new Map()), jsx({ stampIslands: true })],
|
|
157
|
+
moduleTypes: { ".css": "js" },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
const server = createDevServer(nitro);
|
|
161
|
+
server.listen({ port, hostname });
|
|
162
|
+
// Order matters: listen -> prepare -> build. `build` starts the dev runner.
|
|
163
|
+
await prepare(nitro);
|
|
164
|
+
await nitroBuild(nitro);
|
|
165
|
+
console.log(`[fu] dev http://localhost:${port} (bound ${hostname}:${port})`);
|
|
166
|
+
}
|
|
167
|
+
//# 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/errors.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RouteError } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Throw from a handler or middleware to produce a specific status.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* if (!session) throw new HttpError(403, "Not your todo");
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export declare class HttpError extends Error {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
constructor(status: number, message?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function statusText(status: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Normalise anything thrown into a status and a message.
|
|
16
|
+
*
|
|
17
|
+
* A `status` property is honoured wherever it appears, so errors from other
|
|
18
|
+
* libraries (h3, fetch wrappers) map sensibly instead of collapsing to 500.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toRouteError(err: unknown): RouteError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw from a handler or middleware to produce a specific status.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* if (!session) throw new HttpError(403, "Not your todo");
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
export class HttpError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
constructor(status, message) {
|
|
11
|
+
super(message ?? statusText(status));
|
|
12
|
+
this.name = "HttpError";
|
|
13
|
+
this.status = status;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const TEXT = {
|
|
17
|
+
400: "Bad Request",
|
|
18
|
+
401: "Unauthorized",
|
|
19
|
+
403: "Forbidden",
|
|
20
|
+
404: "Not Found",
|
|
21
|
+
405: "Method Not Allowed",
|
|
22
|
+
409: "Conflict",
|
|
23
|
+
410: "Gone",
|
|
24
|
+
422: "Unprocessable Content",
|
|
25
|
+
429: "Too Many Requests",
|
|
26
|
+
500: "Internal Server Error",
|
|
27
|
+
502: "Bad Gateway",
|
|
28
|
+
503: "Service Unavailable",
|
|
29
|
+
};
|
|
30
|
+
export function statusText(status) {
|
|
31
|
+
return TEXT[status] ?? (status >= 500 ? "Internal Server Error" : "Error");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Normalise anything thrown into a status and a message.
|
|
35
|
+
*
|
|
36
|
+
* A `status` property is honoured wherever it appears, so errors from other
|
|
37
|
+
* libraries (h3, fetch wrappers) map sensibly instead of collapsing to 500.
|
|
38
|
+
*/
|
|
39
|
+
export function toRouteError(err) {
|
|
40
|
+
if (err instanceof HttpError) {
|
|
41
|
+
return { status: err.status, message: err.message, cause: err };
|
|
42
|
+
}
|
|
43
|
+
const status = err?.status;
|
|
44
|
+
if (typeof status === "number" && status >= 400 && status <= 599) {
|
|
45
|
+
const message = err.message;
|
|
46
|
+
return {
|
|
47
|
+
status,
|
|
48
|
+
message: typeof message === "string" && message ? message : statusText(status),
|
|
49
|
+
cause: err,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { status: 500, message: statusText(500), cause: err };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,OAAgB;QAC1C,KAAK,CAAC,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,IAAI,GAA2B;IACnC,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,oBAAoB;IACzB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,qBAAqB;CAC3B,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;QAC7B,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAClE,CAAC;IACD,MAAM,MAAM,GAAI,GAAmC,EAAE,MAAM,CAAC;IAC5D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QACjE,MAAM,OAAO,GAAI,GAA6B,CAAC,OAAO,CAAC;QACvD,OAAO;YACL,MAAM;YACN,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;YAC9E,KAAK,EAAE,GAAG;SACX,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Fresh Urquell HMR client runtime. Rolldown's default runtime registers new module
|
|
3
|
+
// factories but never *applies* them; this one does the apply walk:
|
|
4
|
+
// swap the module, re-run its factory, then fire its accept callbacks.
|
|
5
|
+
|
|
6
|
+
/** @type {any} */
|
|
7
|
+
var BaseDevRuntime = DevRuntime;
|
|
8
|
+
|
|
9
|
+
class ModuleHotContext {
|
|
10
|
+
/** @type {{ deps: string[], fn: (mod: any) => void }[]} */
|
|
11
|
+
acceptCallbacks = [];
|
|
12
|
+
constructor(moduleId, devRuntime) {
|
|
13
|
+
this.moduleId = moduleId;
|
|
14
|
+
this.devRuntime = devRuntime;
|
|
15
|
+
}
|
|
16
|
+
accept(...args) {
|
|
17
|
+
if (args.length === 1) {
|
|
18
|
+
this.acceptCallbacks.push({ deps: [this.moduleId], fn: args[0] });
|
|
19
|
+
} else if (args.length !== 0) {
|
|
20
|
+
throw new Error('Invalid arguments for `import.meta.hot.accept`');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
invalidate() {
|
|
24
|
+
socket.send(JSON.stringify({ type: 'hmr:invalidate', moduleId: this.moduleId }));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class FuDevRuntime extends BaseDevRuntime {
|
|
29
|
+
/** @type {Map<string, ModuleHotContext>} */
|
|
30
|
+
moduleHotContexts = new Map();
|
|
31
|
+
createModuleHotContext(moduleId) {
|
|
32
|
+
const ctx = new ModuleHotContext(moduleId, this);
|
|
33
|
+
this.moduleHotContexts.set(moduleId, ctx);
|
|
34
|
+
return ctx;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const clientId = crypto.randomUUID();
|
|
39
|
+
const addr = new URL('ws://$ADDR');
|
|
40
|
+
addr.searchParams.set('clientId', clientId);
|
|
41
|
+
const socket = new WebSocket(addr);
|
|
42
|
+
|
|
43
|
+
/** @type {any} */
|
|
44
|
+
const runtime = new FuDevRuntime(clientId);
|
|
45
|
+
globalThis.__rolldown_runtime__ ??= runtime;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply one patch: import it (registering new factories), then for every
|
|
49
|
+
* changed module that accepted itself, drop its cache, re-run the factory and
|
|
50
|
+
* hand the fresh exports to its accept callbacks. Anything that did not accept
|
|
51
|
+
* falls back to a full reload.
|
|
52
|
+
*/
|
|
53
|
+
async function applyPatch(url, allChangedIds) {
|
|
54
|
+
const rt = globalThis.__rolldown_runtime__;
|
|
55
|
+
// Stylesheets are swapped via the <link>, so they never need to accept and
|
|
56
|
+
// must not drag the page into a full reload.
|
|
57
|
+
const changedIds = (allChangedIds || []).filter((id) => !id.endsWith('.css'));
|
|
58
|
+
const selfAccepting = changedIds.filter((id) => {
|
|
59
|
+
const ctx = rt.moduleHotContexts.get(id);
|
|
60
|
+
return ctx && ctx.acceptCallbacks.length > 0;
|
|
61
|
+
});
|
|
62
|
+
try {
|
|
63
|
+
await import(url + (url.includes('?') ? '&' : '?') + 't=' + Date.now());
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error('[hmr] failed to load patch', url, err);
|
|
66
|
+
location.reload();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!changedIds || changedIds.length === 0) return;
|
|
70
|
+
if (selfAccepting.length !== changedIds.length) {
|
|
71
|
+
console.debug('[hmr] some modules did not accept; reloading');
|
|
72
|
+
location.reload();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (const id of selfAccepting) {
|
|
76
|
+
rt.removeModuleCache(id);
|
|
77
|
+
rt.initModule(id);
|
|
78
|
+
const exports = rt.loadExports(id);
|
|
79
|
+
const ctx = rt.moduleHotContexts.get(id);
|
|
80
|
+
for (const { fn } of ctx ? ctx.acceptCallbacks : []) fn(exports);
|
|
81
|
+
}
|
|
82
|
+
console.debug('[hmr] applied', selfAccepting.join(', '));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Swap the stylesheet in place. A new <link> is inserted and the old one is
|
|
87
|
+
* only removed once the replacement has loaded, so the page never flashes
|
|
88
|
+
* unstyled.
|
|
89
|
+
*/
|
|
90
|
+
function swapStylesheet(href) {
|
|
91
|
+
const links = [...document.querySelectorAll('link[rel="stylesheet"]')];
|
|
92
|
+
const old = links[links.length - 1];
|
|
93
|
+
const next = document.createElement('link');
|
|
94
|
+
next.rel = 'stylesheet';
|
|
95
|
+
next.href = href;
|
|
96
|
+
next.onload = () => { if (old && old !== next) old.remove(); };
|
|
97
|
+
(old ? old.parentNode : document.head).insertBefore(next, old ? old.nextSibling : null);
|
|
98
|
+
console.debug('[hmr] css swapped ->', href);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
socket.onmessage = function (event) {
|
|
102
|
+
const data = JSON.parse(event.data);
|
|
103
|
+
if (data.type === 'connected') {
|
|
104
|
+
console.debug('[hmr] connected');
|
|
105
|
+
} else if (data.type === 'hmr:update') {
|
|
106
|
+
applyPatch(data.url, data.changedIds);
|
|
107
|
+
} else if (data.type === 'fu:css') {
|
|
108
|
+
swapStylesheet(data.href);
|
|
109
|
+
} else if (data.type === 'hmr:reload') {
|
|
110
|
+
location.reload();
|
|
111
|
+
}
|
|
112
|
+
};
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fresh Urquell — a minimal islands framework on Preact, rolldown and Nitro.
|
|
3
|
+
*
|
|
4
|
+
* This entrypoint is the bundler-agnostic core: it needs only a route manifest
|
|
5
|
+
* and an asset list, both plain data. Drivers live in `./build.ts` and
|
|
6
|
+
* `./dev.ts`.
|
|
7
|
+
*/
|
|
8
|
+
export { App, compose } from "./app.ts";
|
|
9
|
+
export { HttpError, statusText, toRouteError } from "./errors.ts";
|
|
10
|
+
export { createHandler, document, renderError } from "./render.ts";
|
|
11
|
+
export type { HandlerParts, ShellProps } from "./render.ts";
|
|
12
|
+
export { buildRoutes, filePathToPattern, match } from "./router.ts";
|
|
13
|
+
export type { Matched, Route } from "./router.ts";
|
|
14
|
+
export type { Asset, Assets, Ctx, FuOptions, Head, Middleware, PageContext, RouteError, RouteHandler, RouteManifest, RouteModule, } from "./types.ts";
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fresh Urquell — a minimal islands framework on Preact, rolldown and Nitro.
|
|
3
|
+
*
|
|
4
|
+
* This entrypoint is the bundler-agnostic core: it needs only a route manifest
|
|
5
|
+
* and an asset list, both plain data. Drivers live in `./build.ts` and
|
|
6
|
+
* `./dev.ts`.
|
|
7
|
+
*/
|
|
8
|
+
export { App, compose } from "./app.js";
|
|
9
|
+
export { HttpError, statusText, toRouteError } from "./errors.js";
|
|
10
|
+
export { createHandler, document, renderError } from "./render.js";
|
|
11
|
+
export { buildRoutes, filePathToPattern, match } from "./router.js";
|
|
12
|
+
//# sourceMappingURL=mod.js.map
|
package/dist/mod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Plugin } from "rolldown";
|
|
2
|
+
export interface JsxOptions {
|
|
3
|
+
/** Stamp island exports with their module id (SSR build only). */
|
|
4
|
+
stampIslands?: boolean;
|
|
5
|
+
/** Inject `import.meta.hot.accept` into islands (dev client only). */
|
|
6
|
+
hmr?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* TypeScript + Preact JSX via oxc — the same native transform Vite delegates
|
|
10
|
+
* to. Also carries the two island-specific source rewrites, since both need the
|
|
11
|
+
* transformed output.
|
|
12
|
+
*/
|
|
13
|
+
export declare function jsx(opts?: JsxOptions): Plugin;
|
|
14
|
+
/**
|
|
15
|
+
* Mark every exported component so the SSR renderer can spot a hydration
|
|
16
|
+
* boundary from the component function alone.
|
|
17
|
+
*
|
|
18
|
+
* Uses oxc's parser rather than a regex: `export const X = () => {}` is the
|
|
19
|
+
* most natural way to write a Preact component, and a regex over `export
|
|
20
|
+
* function` silently misses it — the island renders and then never hydrates,
|
|
21
|
+
* with no error anywhere.
|
|
22
|
+
*
|
|
23
|
+
* An anonymous `export default () => {}` has no binding to stamp, so it is
|
|
24
|
+
* rewritten into a named const. That is the one case that shifts positions and
|
|
25
|
+
* therefore invalidates the sourcemap, which the caller drops.
|
|
26
|
+
*/
|
|
27
|
+
export declare function stampIslands(js: string, key: string, id: string): {
|
|
28
|
+
code: string;
|
|
29
|
+
rewrote: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* CSS. Rolldown removed CSS bundling and ships no CSS builtin, so the framework
|
|
33
|
+
* owns the pipeline via lightningcss:
|
|
34
|
+
* `*.module.css` -> CSS Modules (scoped names + JS exports object)
|
|
35
|
+
* `*.css` -> global stylesheet
|
|
36
|
+
* Native nesting, `@layer` and `color-mix` pass straight through.
|
|
37
|
+
*
|
|
38
|
+
* Pair this with `moduleTypes: { ".css": "js" }`, or rolldown classifies the
|
|
39
|
+
* module by extension and refuses to bundle it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function css(collected: Map<string, string>): Plugin;
|
|
42
|
+
/** Serve generated modules by exact id. */
|
|
43
|
+
export declare function virtual(mods: Record<string, string>): Plugin;
|
|
44
|
+
export declare function hash(s: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Recursively list source files under `dir`, as root-relative "/a/b.tsx".
|
|
47
|
+
* Underscore-prefixed files are skipped: they are framework files
|
|
48
|
+
* (`routes/_app.tsx`), not routes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function walk(root: string, dir: string, out?: string[]): string[];
|
|
51
|
+
/** Absolute path to an optional project file, or null when absent. */
|
|
52
|
+
export declare function optional(root: string, ...names: string[]): string | null;
|
|
53
|
+
/** Generated module bodies both drivers need. */
|
|
54
|
+
export declare function bootModule(clientPath: string, islandFiles: string[], root: string): string;
|
|
55
|
+
export interface SsrModuleOptions {
|
|
56
|
+
renderPath: string;
|
|
57
|
+
routeFiles: string[];
|
|
58
|
+
root: string;
|
|
59
|
+
/** Serialized Assets literal. */
|
|
60
|
+
assets: string;
|
|
61
|
+
/** Absolute path to the project's `app.ts`, if it has one. */
|
|
62
|
+
appPath?: string | null;
|
|
63
|
+
/** Absolute path to the project's `routes/_app.tsx`, if it has one. */
|
|
64
|
+
shellPath?: string | null;
|
|
65
|
+
/** Absolute path to the project's `routes/_error.tsx`, if it has one. */
|
|
66
|
+
errorPath?: string | null;
|
|
67
|
+
}
|
|
68
|
+
export declare function ssrModule(o: SsrModuleOptions): string;
|