@anaemia/core 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugins/lightningcss.js +4 -4
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +1 -1
- package/dist/runtime/entry-client.jsx +2 -4
- package/dist/runtime/entry-server.d.ts.map +1 -1
- package/dist/runtime/entry-server.jsx +41 -31
- package/dist/runtime/route-request.js +1 -1
- package/dist/runtime/rpc-client.js +3 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/anaemia.d.ts +75 -0
- package/src/config.ts +9 -1
- package/src/index.ts +9 -1
- package/src/plugins/index.ts +1 -1
- package/src/plugins/lightningcss.ts +11 -11
- package/src/runtime/context.browser.ts +1 -1
- package/src/runtime/context.ts +6 -3
- package/src/runtime/entry-client.tsx +4 -8
- package/src/runtime/entry-server.tsx +54 -30
- package/src/runtime/resources.ts +1 -1
- package/src/runtime/route-data.ts +1 -1
- package/src/runtime/route-request.ts +2 -2
- package/src/runtime/rpc-client.ts +6 -6
- package/src/runtime/webpack.d.ts +1 -1
- package/src/types.ts +18 -3
- package/test/integration/hmr.test.mjs +18 -5
- package/test/rpc-client.test.mjs +1 -1
- package/test/run-on-server.test.mjs +2 -6
- package/tsconfig.json +1 -1
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright [
|
|
189
|
+
Copyright [2026] [colourlabs]
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
package/README.md
CHANGED
package/dist/config.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ export interface AnaemiaConfig {
|
|
|
38
38
|
styles?: {
|
|
39
39
|
sass?: boolean;
|
|
40
40
|
modules?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* customize the generated CSS module class names.
|
|
43
|
+
* defaults to "[name]__[local]__[hash:base64:5]" in both development and production
|
|
44
|
+
* for readable, themeable class names. Set to "[hash:base64:8]" if you prefer
|
|
45
|
+
* fully hashed production output and don't need external theme support.
|
|
46
|
+
*/
|
|
47
|
+
modulesLocalIdentName?: string;
|
|
41
48
|
};
|
|
42
49
|
experimental?: {
|
|
43
50
|
outputModule?: boolean;
|
|
@@ -54,7 +61,7 @@ export interface AnaemiaConfig {
|
|
|
54
61
|
* client: { __APP_VERSION__: JSON.stringify("1.0.0") },
|
|
55
62
|
* server: { __DB_POOL_SIZE__: "10" }
|
|
56
63
|
* }
|
|
57
|
-
|
|
64
|
+
*/
|
|
58
65
|
define?: {
|
|
59
66
|
client?: Record<string, string>;
|
|
60
67
|
server?: Record<string, string>;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAC;IAE9D;;OAEG;IACH,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAC;IAE9D;;OAEG;IACH,YAAY,CAAC,EAAE;QACb,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;QACtB,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;KACvB,CAAC;IAEF;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;IAEtC;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAC;IAE9D;;OAEG;IACH,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAC;IAE9D;;OAEG;IACH,YAAY,CAAC,EAAE;QACb,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;QACtB,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;KACvB,CAAC;IAEF;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;IAEtC;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAElB;;;;;WAKG;QACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;IACF,YAAY,CAAC,EAAE;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAE1B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,CAAC;CACH;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAEjE"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { runOnServer } from "./runtime/context.js";
|
|
|
3
3
|
export { RouteDataController, useRouteData } from "./runtime/route-data.js";
|
|
4
4
|
export { $$executeClientRpc } from "./runtime/rpc-client.js";
|
|
5
5
|
export { createServerResource } from "./runtime/resources.js";
|
|
6
|
-
export type { LoaderArgs, LoaderFunction, InferServerData, GuardContext, GuardResult, GuardFn, ServerFunction } from "./types.js";
|
|
6
|
+
export type { LoaderArgs, LoaderFunction, InferServerData, GuardContext, GuardResult, GuardFn, ServerFunction, } from "./types.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,WAAW,EACX,OAAO,EACP,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -14,7 +14,7 @@ export function anaemiaLightningCssPlugin(options = {}) {
|
|
|
14
14
|
clientRspackConfig(config) {
|
|
15
15
|
const isProd = process.env.NODE_ENV === "production";
|
|
16
16
|
if (config.module?.rules) {
|
|
17
|
-
config.module.rules
|
|
17
|
+
for (const rule of config.module.rules) {
|
|
18
18
|
if (rule && typeof rule === "object" && rule.test && rule.test.toString().includes("ss")) {
|
|
19
19
|
const currentUse = Array.isArray(rule.use) ? rule.use : [];
|
|
20
20
|
rule.use = [
|
|
@@ -28,7 +28,7 @@ export function anaemiaLightningCssPlugin(options = {}) {
|
|
|
28
28
|
},
|
|
29
29
|
];
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|
|
32
32
|
}
|
|
33
33
|
if (isProd) {
|
|
34
34
|
config.optimization = {
|
|
@@ -46,7 +46,7 @@ export function anaemiaLightningCssPlugin(options = {}) {
|
|
|
46
46
|
},
|
|
47
47
|
serverRspackConfig(config) {
|
|
48
48
|
if (config.module?.rules) {
|
|
49
|
-
config.module.rules
|
|
49
|
+
for (const rule of config.module.rules) {
|
|
50
50
|
if (rule && typeof rule === "object" && rule.test && rule.test.toString().includes("ss")) {
|
|
51
51
|
const currentUse = Array.isArray(rule.use) ? rule.use : [];
|
|
52
52
|
rule.use = [
|
|
@@ -60,7 +60,7 @@ export function anaemiaLightningCssPlugin(options = {}) {
|
|
|
60
60
|
},
|
|
61
61
|
];
|
|
62
62
|
}
|
|
63
|
-
}
|
|
63
|
+
}
|
|
64
64
|
}
|
|
65
65
|
return config;
|
|
66
66
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AAE7C,eAAO,MAAM,uBAAuB,oBAA2B,CAAC;AAChE,eAAO,MAAM,UAAU,yCAAgD,CAAC;AAGxE,wBAAgB,WAAW,CAAC,CAAC,SAAS,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AAE7C,eAAO,MAAM,uBAAuB,oBAA2B,CAAC;AAChE,eAAO,MAAM,UAAU,yCAAgD,CAAC;AAGxE,wBAAgB,WAAW,CAAC,CAAC,SAAS,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAyB1F"}
|
package/dist/runtime/context.js
CHANGED
|
@@ -3,7 +3,7 @@ export const serverFunctionsRegistry = new Map();
|
|
|
3
3
|
export const ssrStorage = new AsyncLocalStorage();
|
|
4
4
|
globalThis.__ANAEMIA_SERVER_STORAGE__ = ssrStorage;
|
|
5
5
|
export function runOnServer(backendFn, id) {
|
|
6
|
-
const hashId = id
|
|
6
|
+
const hashId = id ?? crypto.randomUUID();
|
|
7
7
|
serverFunctionsRegistry.set(hashId, backendFn);
|
|
8
8
|
const rpcProxy = async function (...args) {
|
|
9
9
|
const result = await backendFn(...args);
|
|
@@ -9,11 +9,9 @@ if (!mountTarget) {
|
|
|
9
9
|
const root = mountTarget;
|
|
10
10
|
async function start() {
|
|
11
11
|
await preloadActiveClientRoute(window.location.pathname);
|
|
12
|
-
const mount = root.hasChildNodes()
|
|
13
|
-
? hydrate
|
|
14
|
-
: render;
|
|
12
|
+
const mount = root.hasChildNodes() ? hydrate : render;
|
|
15
13
|
mount(() => (<Router>
|
|
16
14
|
<App />
|
|
17
15
|
</Router>), root);
|
|
18
16
|
}
|
|
19
|
-
start();
|
|
17
|
+
await start();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-server.d.ts","sourceRoot":"","sources":["../../src/runtime/entry-server.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA2C5B,QAAA,MAAM,GAAG,4EAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"entry-server.d.ts","sourceRoot":"","sources":["../../src/runtime/entry-server.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA2C5B,QAAA,MAAM,GAAG,4EAAa,CAAC;AAqXvB,eAAe,GAAG,CAAC"}
|
|
@@ -6,7 +6,7 @@ import { renderToStringAsync, generateHydrationScript } from "solid-js/web";
|
|
|
6
6
|
import { Router } from "@solidjs/router";
|
|
7
7
|
import { ssrStorage, serverFunctionsRegistry } from "./context.js";
|
|
8
8
|
import fs from "node:fs";
|
|
9
|
-
import path from "path";
|
|
9
|
+
import path from "node:path";
|
|
10
10
|
// @ts-expect-error - resolved by Rspack
|
|
11
11
|
import App from "anaemia-user-app";
|
|
12
12
|
// @ts-expect-error - resolved by Rspack
|
|
@@ -185,7 +185,9 @@ function matchRoute(manifest, reqPath) {
|
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
for (const route of sortedRoutes) {
|
|
188
|
-
const regexStr = route.urlPattern
|
|
188
|
+
const regexStr = route.urlPattern
|
|
189
|
+
.replace(/:([a-zA-Z0-9_-]+)/g, "(?<$1>[^/]+)")
|
|
190
|
+
.replace(/\*([a-zA-Z0-9_-]*)/g, "(?<catchall>.*)");
|
|
189
191
|
const match = new RegExp(`^${regexStr}$`).exec(reqPath);
|
|
190
192
|
if (match) {
|
|
191
193
|
return {
|
|
@@ -206,8 +208,8 @@ function matchRoute(manifest, reqPath) {
|
|
|
206
208
|
app.get("*", async (c) => {
|
|
207
209
|
if (isDev)
|
|
208
210
|
await loadManifestAndTemplate();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
const template = memoizedHtmlTemplate;
|
|
212
|
+
const manifest = memoizedManifest;
|
|
211
213
|
if (!template || !manifest) {
|
|
212
214
|
return c.text("anaemia engine error: build assets are missing", 500);
|
|
213
215
|
}
|
|
@@ -219,7 +221,11 @@ app.get("*", async (c) => {
|
|
|
219
221
|
let htmlPayload;
|
|
220
222
|
if (targetPattern) {
|
|
221
223
|
try {
|
|
222
|
-
const guardResult = await runGuards(targetPattern, {
|
|
224
|
+
const guardResult = await runGuards(targetPattern, {
|
|
225
|
+
params,
|
|
226
|
+
request: c.req.raw,
|
|
227
|
+
url: reqPath,
|
|
228
|
+
});
|
|
223
229
|
if (guardResult) {
|
|
224
230
|
if ("redirect" in guardResult)
|
|
225
231
|
return c.redirect(guardResult.redirect, (guardResult.status ?? 302));
|
|
@@ -275,43 +281,47 @@ app.get("*", async (c) => {
|
|
|
275
281
|
}
|
|
276
282
|
let assetScripts = "";
|
|
277
283
|
let assetStyles = "";
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
assetScripts += `<script type="module" src="${normalizeAssetUrl(jsFile)}"></script>\n`;
|
|
286
|
-
});
|
|
284
|
+
const processChunkAssets = (chunk) => {
|
|
285
|
+
if (!chunk)
|
|
286
|
+
return;
|
|
287
|
+
if (chunk.js) {
|
|
288
|
+
const jsSpecs = Array.isArray(chunk.js) ? chunk.js : [chunk.js];
|
|
289
|
+
for (const jsFile of jsSpecs) {
|
|
290
|
+
assetScripts += `<script type="module" src="${normalizeAssetUrl(jsFile)}"></script>\n`;
|
|
287
291
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
292
|
+
}
|
|
293
|
+
if (chunk.css) {
|
|
294
|
+
const cssSpecs = Array.isArray(chunk.css) ? chunk.css : [chunk.css];
|
|
295
|
+
for (const cssFile of cssSpecs) {
|
|
296
|
+
assetStyles += `<link rel="stylesheet" href="${normalizeAssetUrl(cssFile)}">\n`;
|
|
293
297
|
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
processChunkAssets(manifest.chunks[activeChunk]);
|
|
302
|
-
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
processChunkAssets(manifest.chunks["client"]);
|
|
301
|
+
processChunkAssets(manifest.chunks["commons"]);
|
|
302
|
+
processChunkAssets(manifest.chunks["vendors"]);
|
|
303
|
+
if (activeChunk && activeChunk !== "client")
|
|
304
|
+
processChunkAssets(manifest.chunks[activeChunk]);
|
|
303
305
|
const hydrationScript = generateHydrationScript();
|
|
304
306
|
const rawStorePayload = Object.fromEntries(store);
|
|
305
307
|
const finalHydrationStatePayload = {
|
|
306
308
|
__LOADER_DATA__: rawStorePayload.__LOADER_DATA__ || {},
|
|
307
309
|
__SERVER_FUNCTION_DATA__: rawStorePayload.__SERVER_FUNCTION_DATA__ || {},
|
|
308
310
|
};
|
|
309
|
-
const serializedData = JSON.stringify(finalHydrationStatePayload)
|
|
311
|
+
const serializedData = JSON.stringify(finalHydrationStatePayload)
|
|
312
|
+
.replace(/&/g, "\\u0026")
|
|
313
|
+
.replace(/</g, "\\u003c")
|
|
314
|
+
.replace(/>/g, "\\u003e")
|
|
315
|
+
.replace(/\//g, "\\u002f");
|
|
310
316
|
const dataScript = `<script id="__ANAEMIA_DATA__" type="application/json">${serializedData}</script>\n`;
|
|
311
|
-
const devNoCacheTag = isDev
|
|
317
|
+
const devNoCacheTag = isDev
|
|
318
|
+
? `<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">\n<meta http-equiv="Pragma" content="no-cache">\n<meta http-equiv="Expires" content="0">\n`
|
|
319
|
+
: "";
|
|
312
320
|
const combinedHeadInjections = `${devNoCacheTag}${assetStyles}${dataScript}${hydrationScript}`;
|
|
313
321
|
const sanitizedPayload = htmlPayload.trim();
|
|
314
|
-
let completeHtmlOutput = ENTRY_TAG_REGEX.test(template)
|
|
322
|
+
let completeHtmlOutput = ENTRY_TAG_REGEX.test(template)
|
|
323
|
+
? template.replace(ENTRY_TAG_REGEX, (_, open, _tag, _inner, close) => `${open}${sanitizedPayload}${close}`)
|
|
324
|
+
: template.replace("</body>", () => `<div anaemia-entry>${sanitizedPayload}</div></body>`);
|
|
315
325
|
completeHtmlOutput = completeHtmlOutput.replace("<head>", `<head>${combinedHeadInjections}`);
|
|
316
326
|
completeHtmlOutput = completeHtmlOutput.replace("</body>", `${assetScripts}</body>`);
|
|
317
327
|
if (isDev) {
|
|
@@ -3,7 +3,7 @@ import { ssrStorage } from "./context.js";
|
|
|
3
3
|
export function createRouteRequest(pathname) {
|
|
4
4
|
if (isServer) {
|
|
5
5
|
const honoContext = ssrStorage.getStore()?.get("honoContext");
|
|
6
|
-
const request = honoContext?.req
|
|
6
|
+
const request = honoContext?.req.raw;
|
|
7
7
|
if (request)
|
|
8
8
|
return request;
|
|
9
9
|
return new Request(new URL(pathname, "http://localhost").toString());
|
|
@@ -37,7 +37,7 @@ export function $$executeClientRpc(hashId) {
|
|
|
37
37
|
return match.data;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
return
|
|
40
|
+
return;
|
|
41
41
|
}
|
|
42
42
|
ensureCacheInitialized();
|
|
43
43
|
const serverFunctionData = _clientCache?.__SERVER_FUNCTION_DATA__?.[hashId];
|
|
@@ -54,7 +54,7 @@ export function $$executeClientRpc(hashId) {
|
|
|
54
54
|
});
|
|
55
55
|
if (!response.ok)
|
|
56
56
|
throw new Error(`[anaemia] RPC execution failed: ${response.status}`);
|
|
57
|
-
return await response.json();
|
|
57
|
+
return (await response.json());
|
|
58
58
|
};
|
|
59
59
|
asyncRpcCall.id = hashId;
|
|
60
60
|
asyncRpcCall.readHydrationCache = function (...args) {
|
|
@@ -68,7 +68,7 @@ export function $$executeClientRpc(hashId) {
|
|
|
68
68
|
return match.data;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
return
|
|
71
|
+
return;
|
|
72
72
|
}
|
|
73
73
|
ensureCacheInitialized();
|
|
74
74
|
const serverFunctionData = _clientCache?.__SERVER_FUNCTION_DATA__?.[hashId];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IACxF,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,YAAY,GAAG,OAAO,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IACxF,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,YAAY,GAAG,OAAO,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CACnH,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,KACrB,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhG,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,WAAW,GACnB,IAAI,GACJ,SAAS,GACT;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;CAAE,GACpD;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,KACG,IAAI,GACJ,SAAS,GACT;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAAC,IAAI,GAAG,SAAS,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEtH,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG;IAClG,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anaemia/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"types": "./dist/plugins/index.d.ts",
|
|
29
29
|
"import": "./dist/plugins/index.js"
|
|
30
30
|
},
|
|
31
|
+
"./client": "./anaemia.d.ts",
|
|
31
32
|
"./package.json": "./package.json"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
@@ -36,6 +37,12 @@
|
|
|
36
37
|
"@solidjs/router": "^0.16.1",
|
|
37
38
|
"hono": "^4.12.23"
|
|
38
39
|
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/babel__core": "^7.20.5",
|
|
42
|
+
"@types/node": "^25.9.1",
|
|
43
|
+
"jiti": "^2.7.0",
|
|
44
|
+
"playwright": "^1.60.0"
|
|
45
|
+
},
|
|
39
46
|
"peerDependencies": {
|
|
40
47
|
"@rspack/core": "*",
|
|
41
48
|
"lightningcss": "^1.32.0",
|
|
@@ -49,12 +56,6 @@
|
|
|
49
56
|
"optional": true
|
|
50
57
|
}
|
|
51
58
|
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@types/babel__core": "^7.20.5",
|
|
54
|
-
"@types/node": "^25.9.1",
|
|
55
|
-
"jiti": "^2.7.0",
|
|
56
|
-
"playwright": "^1.60.0"
|
|
57
|
-
},
|
|
58
59
|
"scripts": {
|
|
59
60
|
"build": "tsc",
|
|
60
61
|
"test": "pnpm run build && node --test test/*.test.mjs",
|
package/src/anaemia.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/// <reference types="@rspack/core/module" />
|
|
2
|
+
|
|
3
|
+
// asset imports
|
|
4
|
+
declare module "*.png" {
|
|
5
|
+
const src: string;
|
|
6
|
+
export default src;
|
|
7
|
+
}
|
|
8
|
+
declare module "*.jpg" {
|
|
9
|
+
const src: string;
|
|
10
|
+
export default src;
|
|
11
|
+
}
|
|
12
|
+
declare module "*.jpeg" {
|
|
13
|
+
const src: string;
|
|
14
|
+
export default src;
|
|
15
|
+
}
|
|
16
|
+
declare module "*.gif" {
|
|
17
|
+
const src: string;
|
|
18
|
+
export default src;
|
|
19
|
+
}
|
|
20
|
+
declare module "*.webp" {
|
|
21
|
+
const src: string;
|
|
22
|
+
export default src;
|
|
23
|
+
}
|
|
24
|
+
declare module "*.avif" {
|
|
25
|
+
const src: string;
|
|
26
|
+
export default src;
|
|
27
|
+
}
|
|
28
|
+
declare module "*.ico" {
|
|
29
|
+
const src: string;
|
|
30
|
+
export default src;
|
|
31
|
+
}
|
|
32
|
+
declare module "*.svg" {
|
|
33
|
+
const src: string;
|
|
34
|
+
export default src;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// query suffixes
|
|
38
|
+
declare module "*?raw" {
|
|
39
|
+
const content: string;
|
|
40
|
+
export default content;
|
|
41
|
+
}
|
|
42
|
+
declare module "*?url" {
|
|
43
|
+
const src: string;
|
|
44
|
+
export default src;
|
|
45
|
+
}
|
|
46
|
+
declare module "*?inline" {
|
|
47
|
+
const src: string;
|
|
48
|
+
export default src;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// CSS modules
|
|
52
|
+
declare module "*.module.css" {
|
|
53
|
+
const classes: Record<string, string>;
|
|
54
|
+
export default classes;
|
|
55
|
+
}
|
|
56
|
+
declare module "*.module.scss" {
|
|
57
|
+
const classes: Record<string, string>;
|
|
58
|
+
export default classes;
|
|
59
|
+
}
|
|
60
|
+
declare module "*.module.sass" {
|
|
61
|
+
const classes: Record<string, string>;
|
|
62
|
+
export default classes;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// import.meta.env
|
|
66
|
+
interface ImportMetaEnv {
|
|
67
|
+
readonly MODE: string;
|
|
68
|
+
readonly DEV: boolean;
|
|
69
|
+
readonly PROD: boolean;
|
|
70
|
+
readonly [key: string]: string | boolean | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface ImportMeta {
|
|
74
|
+
readonly env: ImportMetaEnv;
|
|
75
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -45,6 +45,14 @@ export interface AnaemiaConfig {
|
|
|
45
45
|
styles?: {
|
|
46
46
|
sass?: boolean;
|
|
47
47
|
modules?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* customize the generated CSS module class names.
|
|
51
|
+
* defaults to "[name]__[local]__[hash:base64:5]" in both development and production
|
|
52
|
+
* for readable, themeable class names. Set to "[hash:base64:8]" if you prefer
|
|
53
|
+
* fully hashed production output and don't need external theme support.
|
|
54
|
+
*/
|
|
55
|
+
modulesLocalIdentName?: string;
|
|
48
56
|
};
|
|
49
57
|
experimental?: {
|
|
50
58
|
outputModule?: boolean;
|
|
@@ -63,7 +71,7 @@ export interface AnaemiaConfig {
|
|
|
63
71
|
* client: { __APP_VERSION__: JSON.stringify("1.0.0") },
|
|
64
72
|
* server: { __DB_POOL_SIZE__: "10" }
|
|
65
73
|
* }
|
|
66
|
-
|
|
74
|
+
*/
|
|
67
75
|
define?: {
|
|
68
76
|
client?: Record<string, string>;
|
|
69
77
|
server?: Record<string, string>;
|
package/src/index.ts
CHANGED
|
@@ -5,4 +5,12 @@ export { RouteDataController, useRouteData } from "./runtime/route-data.js";
|
|
|
5
5
|
export { $$executeClientRpc } from "./runtime/rpc-client.js";
|
|
6
6
|
export { createServerResource } from "./runtime/resources.js";
|
|
7
7
|
|
|
8
|
-
export type {
|
|
8
|
+
export type {
|
|
9
|
+
LoaderArgs,
|
|
10
|
+
LoaderFunction,
|
|
11
|
+
InferServerData,
|
|
12
|
+
GuardContext,
|
|
13
|
+
GuardResult,
|
|
14
|
+
GuardFn,
|
|
15
|
+
ServerFunction,
|
|
16
|
+
} from "./types.js";
|
package/src/plugins/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { anaemiaLightningCssPlugin } from "./lightningcss.js";
|
|
1
|
+
export { anaemiaLightningCssPlugin } from "./lightningcss.js";
|
|
@@ -11,12 +11,12 @@ export function anaemiaLightningCssPlugin(options: { browserslist?: string[] } =
|
|
|
11
11
|
|
|
12
12
|
const localRequire = createRequire(import.meta.url);
|
|
13
13
|
let rspackModule: RspackModule;
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
try {
|
|
16
16
|
rspackModule = localRequire("@rspack/core");
|
|
17
17
|
} catch {
|
|
18
18
|
throw new Error(
|
|
19
|
-
"[anaemia] The LightningCSS plugin requires '@rspack/core' to be available in the execution workspace."
|
|
19
|
+
"[anaemia] The LightningCSS plugin requires '@rspack/core' to be available in the execution workspace.",
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -27,22 +27,22 @@ export function anaemiaLightningCssPlugin(options: { browserslist?: string[] } =
|
|
|
27
27
|
const isProd = process.env.NODE_ENV === "production";
|
|
28
28
|
|
|
29
29
|
if (config.module?.rules) {
|
|
30
|
-
config.module.rules
|
|
30
|
+
for (const rule of config.module.rules) {
|
|
31
31
|
if (rule && typeof rule === "object" && rule.test && rule.test.toString().includes("ss")) {
|
|
32
32
|
const currentUse = Array.isArray(rule.use) ? rule.use : [];
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
rule.use = [
|
|
35
35
|
...currentUse,
|
|
36
36
|
{
|
|
37
37
|
loader: "builtin:lightningcss-loader",
|
|
38
|
-
options: {
|
|
38
|
+
options: {
|
|
39
39
|
targets,
|
|
40
40
|
modules: rule.type === "css/auto",
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
];
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (isProd) {
|
|
@@ -63,24 +63,24 @@ export function anaemiaLightningCssPlugin(options: { browserslist?: string[] } =
|
|
|
63
63
|
|
|
64
64
|
serverRspackConfig(config) {
|
|
65
65
|
if (config.module?.rules) {
|
|
66
|
-
config.module.rules
|
|
66
|
+
for (const rule of config.module.rules) {
|
|
67
67
|
if (rule && typeof rule === "object" && rule.test && rule.test.toString().includes("ss")) {
|
|
68
68
|
const currentUse = Array.isArray(rule.use) ? rule.use : [];
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
rule.use = [
|
|
71
71
|
...currentUse,
|
|
72
72
|
{
|
|
73
73
|
loader: "builtin:lightningcss-loader",
|
|
74
|
-
options: {
|
|
74
|
+
options: {
|
|
75
75
|
targets,
|
|
76
76
|
modules: rule.type === "css/auto",
|
|
77
77
|
},
|
|
78
78
|
},
|
|
79
79
|
];
|
|
80
80
|
}
|
|
81
|
-
}
|
|
81
|
+
}
|
|
82
82
|
}
|
|
83
83
|
return config;
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
|
-
}
|
|
86
|
+
}
|
|
@@ -3,4 +3,4 @@ export const ssrStorage = null as any;
|
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
4
4
|
export const serverFunctionsRegistry = new Map<string, Function>();
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
-
export const runOnServer = null as any;
|
|
6
|
+
export const runOnServer = null as any;
|
package/src/runtime/context.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const ssrStorage = new AsyncLocalStorage<Map<string, unknown>>();
|
|
|
7
7
|
(globalThis as unknown as Record<string, unknown>).__ANAEMIA_SERVER_STORAGE__ = ssrStorage;
|
|
8
8
|
|
|
9
9
|
export function runOnServer<T extends AnyFn>(backendFn: T, id?: string): T & { id: string } {
|
|
10
|
-
const hashId = id
|
|
10
|
+
const hashId = id ?? crypto.randomUUID();
|
|
11
11
|
serverFunctionsRegistry.set(hashId, backendFn);
|
|
12
12
|
|
|
13
13
|
const rpcProxy = async function (...args: unknown[]) {
|
|
@@ -17,7 +17,10 @@ export function runOnServer<T extends AnyFn>(backendFn: T, id?: string): T & { i
|
|
|
17
17
|
if (!store.has("__SERVER_FUNCTION_DATA__")) {
|
|
18
18
|
store.set("__SERVER_FUNCTION_DATA__", {});
|
|
19
19
|
}
|
|
20
|
-
const functionCache = store.get("__SERVER_FUNCTION_DATA__") as Record<
|
|
20
|
+
const functionCache = store.get("__SERVER_FUNCTION_DATA__") as Record<
|
|
21
|
+
string,
|
|
22
|
+
Record<string, unknown> | undefined
|
|
23
|
+
>;
|
|
21
24
|
if (!functionCache[hashId]) {
|
|
22
25
|
functionCache[hashId] = {};
|
|
23
26
|
}
|
|
@@ -28,4 +31,4 @@ export function runOnServer<T extends AnyFn>(backendFn: T, id?: string): T & { i
|
|
|
28
31
|
};
|
|
29
32
|
rpcProxy.id = hashId;
|
|
30
33
|
return rpcProxy as unknown as T & { id: string };
|
|
31
|
-
}
|
|
34
|
+
}
|
|
@@ -4,9 +4,7 @@ import { Router } from "@solidjs/router";
|
|
|
4
4
|
// @ts-expect-error - resolved by Rspack
|
|
5
5
|
import App, { preloadActiveClientRoute } from "anaemia-user-app";
|
|
6
6
|
|
|
7
|
-
const mountTarget = document.querySelector(
|
|
8
|
-
"[anaemia-entry]"
|
|
9
|
-
) as HTMLElement | null;
|
|
7
|
+
const mountTarget = document.querySelector("[anaemia-entry]") as HTMLElement | null;
|
|
10
8
|
|
|
11
9
|
if (!mountTarget) {
|
|
12
10
|
throw new Error("[anaemia] missing mount target");
|
|
@@ -17,9 +15,7 @@ const root = mountTarget;
|
|
|
17
15
|
async function start() {
|
|
18
16
|
await preloadActiveClientRoute(window.location.pathname);
|
|
19
17
|
|
|
20
|
-
const mount = root.hasChildNodes()
|
|
21
|
-
? hydrate
|
|
22
|
-
: render;
|
|
18
|
+
const mount = root.hasChildNodes() ? hydrate : render;
|
|
23
19
|
|
|
24
20
|
mount(
|
|
25
21
|
() => (
|
|
@@ -27,8 +23,8 @@ async function start() {
|
|
|
27
23
|
<App />
|
|
28
24
|
</Router>
|
|
29
25
|
),
|
|
30
|
-
root
|
|
26
|
+
root,
|
|
31
27
|
);
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
start();
|
|
30
|
+
await start();
|
|
@@ -7,7 +7,7 @@ import { renderToStringAsync, generateHydrationScript } from "solid-js/web";
|
|
|
7
7
|
import { Router } from "@solidjs/router";
|
|
8
8
|
import { ssrStorage, serverFunctionsRegistry } from "./context.js";
|
|
9
9
|
import fs from "node:fs";
|
|
10
|
-
import path from "path";
|
|
10
|
+
import path from "node:path";
|
|
11
11
|
import type { StatusCode, RedirectStatusCode } from "hono/utils/http-status";
|
|
12
12
|
|
|
13
13
|
// @ts-expect-error - resolved by Rspack
|
|
@@ -82,7 +82,7 @@ if (isDev) {
|
|
|
82
82
|
"/assets/*",
|
|
83
83
|
serveStatic({
|
|
84
84
|
root: path.resolve(process.cwd(), "./dist/client"),
|
|
85
|
-
})
|
|
85
|
+
}),
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -191,7 +191,16 @@ type RouteMatch = {
|
|
|
191
191
|
params: Record<string, string>;
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
-
type GuardFn = (ctx: {
|
|
194
|
+
type GuardFn = (ctx: {
|
|
195
|
+
params: Record<string, string>;
|
|
196
|
+
request: Request;
|
|
197
|
+
url: string;
|
|
198
|
+
}) =>
|
|
199
|
+
| void
|
|
200
|
+
| undefined
|
|
201
|
+
| { redirect: string; status?: 301 | 302 | 307 | 308 }
|
|
202
|
+
| { status: number; body?: string }
|
|
203
|
+
| Promise<void | undefined | { redirect: string; status?: number } | { status: number; body?: string }>;
|
|
195
204
|
|
|
196
205
|
async function runGuards(pattern: string, ctx: { params: Record<string, string>; request: Request; url: string }) {
|
|
197
206
|
const chain: (() => Promise<GuardFn[]>)[] = serverGuardRegistry.get(pattern) ?? [];
|
|
@@ -221,7 +230,9 @@ function matchRoute(manifest: RouteManifest, reqPath: string): RouteMatch {
|
|
|
221
230
|
}
|
|
222
231
|
|
|
223
232
|
for (const route of sortedRoutes) {
|
|
224
|
-
const regexStr = route.urlPattern
|
|
233
|
+
const regexStr = route.urlPattern
|
|
234
|
+
.replace(/:([a-zA-Z0-9_-]+)/g, "(?<$1>[^/]+)")
|
|
235
|
+
.replace(/\*([a-zA-Z0-9_-]*)/g, "(?<catchall>.*)");
|
|
225
236
|
|
|
226
237
|
const match = new RegExp(`^${regexStr}$`).exec(reqPath);
|
|
227
238
|
if (match) {
|
|
@@ -245,8 +256,8 @@ function matchRoute(manifest: RouteManifest, reqPath: string): RouteMatch {
|
|
|
245
256
|
app.get("*", async (c) => {
|
|
246
257
|
if (isDev) await loadManifestAndTemplate();
|
|
247
258
|
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
const template = memoizedHtmlTemplate;
|
|
260
|
+
const manifest = memoizedManifest;
|
|
250
261
|
|
|
251
262
|
if (!template || !manifest) {
|
|
252
263
|
return c.text("anaemia engine error: build assets are missing", 500);
|
|
@@ -262,9 +273,14 @@ app.get("*", async (c) => {
|
|
|
262
273
|
|
|
263
274
|
if (targetPattern) {
|
|
264
275
|
try {
|
|
265
|
-
const guardResult = await runGuards(targetPattern, {
|
|
276
|
+
const guardResult = await runGuards(targetPattern, {
|
|
277
|
+
params,
|
|
278
|
+
request: c.req.raw,
|
|
279
|
+
url: reqPath,
|
|
280
|
+
});
|
|
266
281
|
if (guardResult) {
|
|
267
|
-
if ("redirect" in guardResult)
|
|
282
|
+
if ("redirect" in guardResult)
|
|
283
|
+
return c.redirect(guardResult.redirect, (guardResult.status ?? 302) as RedirectStatusCode);
|
|
268
284
|
if ("status" in guardResult) statusCode = guardResult.status as StatusCode;
|
|
269
285
|
}
|
|
270
286
|
} catch (err) {
|
|
@@ -324,28 +340,28 @@ app.get("*", async (c) => {
|
|
|
324
340
|
let assetScripts = "";
|
|
325
341
|
let assetStyles = "";
|
|
326
342
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
});
|
|
343
|
+
const processChunkAssets = (chunk: ChunkAssets | undefined) => {
|
|
344
|
+
if (!chunk) return;
|
|
345
|
+
if (chunk.js) {
|
|
346
|
+
const jsSpecs = Array.isArray(chunk.js) ? chunk.js : [chunk.js];
|
|
347
|
+
|
|
348
|
+
for (const jsFile of jsSpecs) {
|
|
349
|
+
assetScripts += `<script type="module" src="${normalizeAssetUrl(jsFile)}"></script>\n`;
|
|
335
350
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
351
|
+
}
|
|
352
|
+
if (chunk.css) {
|
|
353
|
+
const cssSpecs = Array.isArray(chunk.css) ? chunk.css : [chunk.css];
|
|
354
|
+
|
|
355
|
+
for (const cssFile of cssSpecs) {
|
|
356
|
+
assetStyles += `<link rel="stylesheet" href="${normalizeAssetUrl(cssFile)}">\n`;
|
|
341
357
|
}
|
|
342
|
-
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
343
360
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
361
|
+
processChunkAssets(manifest.chunks["client"]);
|
|
362
|
+
processChunkAssets(manifest.chunks["commons"]);
|
|
363
|
+
processChunkAssets(manifest.chunks["vendors"]);
|
|
364
|
+
if (activeChunk && activeChunk !== "client") processChunkAssets(manifest.chunks[activeChunk]);
|
|
349
365
|
|
|
350
366
|
const hydrationScript = generateHydrationScript();
|
|
351
367
|
const rawStorePayload = Object.fromEntries(store);
|
|
@@ -355,16 +371,24 @@ app.get("*", async (c) => {
|
|
|
355
371
|
__SERVER_FUNCTION_DATA__: rawStorePayload.__SERVER_FUNCTION_DATA__ || {},
|
|
356
372
|
};
|
|
357
373
|
|
|
358
|
-
const serializedData = JSON.stringify(finalHydrationStatePayload)
|
|
374
|
+
const serializedData = JSON.stringify(finalHydrationStatePayload)
|
|
375
|
+
.replace(/&/g, "\\u0026")
|
|
376
|
+
.replace(/</g, "\\u003c")
|
|
377
|
+
.replace(/>/g, "\\u003e")
|
|
378
|
+
.replace(/\//g, "\\u002f");
|
|
359
379
|
|
|
360
380
|
const dataScript = `<script id="__ANAEMIA_DATA__" type="application/json">${serializedData}</script>\n`;
|
|
361
381
|
|
|
362
|
-
const devNoCacheTag = isDev
|
|
382
|
+
const devNoCacheTag = isDev
|
|
383
|
+
? `<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">\n<meta http-equiv="Pragma" content="no-cache">\n<meta http-equiv="Expires" content="0">\n`
|
|
384
|
+
: "";
|
|
363
385
|
|
|
364
386
|
const combinedHeadInjections = `${devNoCacheTag}${assetStyles}${dataScript}${hydrationScript}`;
|
|
365
387
|
const sanitizedPayload = htmlPayload.trim();
|
|
366
388
|
|
|
367
|
-
let completeHtmlOutput = ENTRY_TAG_REGEX.test(template)
|
|
389
|
+
let completeHtmlOutput = ENTRY_TAG_REGEX.test(template)
|
|
390
|
+
? template.replace(ENTRY_TAG_REGEX, (_, open, _tag, _inner, close) => `${open}${sanitizedPayload}${close}`)
|
|
391
|
+
: template.replace("</body>", () => `<div anaemia-entry>${sanitizedPayload}</div></body>`);
|
|
368
392
|
|
|
369
393
|
completeHtmlOutput = completeHtmlOutput.replace("<head>", `<head>${combinedHeadInjections}`);
|
|
370
394
|
completeHtmlOutput = completeHtmlOutput.replace("</body>", `${assetScripts}</body>`);
|
package/src/runtime/resources.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function createServerResource<Source, Return>(
|
|
|
18
18
|
readHydrationCache?: (s: Source) => Return | undefined;
|
|
19
19
|
id?: string;
|
|
20
20
|
},
|
|
21
|
-
options?: ResourceOptions<Return, Source
|
|
21
|
+
options?: ResourceOptions<Return, Source>,
|
|
22
22
|
): ResourceReturn<Return, unknown> {
|
|
23
23
|
if (isServer) {
|
|
24
24
|
let ssrInitialValue: Return | undefined = undefined;
|
|
@@ -5,9 +5,9 @@ import { ssrStorage } from "./context.js";
|
|
|
5
5
|
export function createRouteRequest(pathname: string): Request {
|
|
6
6
|
if (isServer) {
|
|
7
7
|
const honoContext = ssrStorage.getStore()?.get("honoContext") as Context | undefined;
|
|
8
|
-
const request = honoContext?.req
|
|
8
|
+
const request = honoContext?.req.raw;
|
|
9
9
|
if (request) return request;
|
|
10
10
|
return new Request(new URL(pathname, "http://localhost").toString());
|
|
11
11
|
}
|
|
12
12
|
return new Request(new URL(pathname, window.location.origin).toString());
|
|
13
|
-
}
|
|
13
|
+
}
|
|
@@ -34,8 +34,8 @@ function ensureCacheInitialized() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function findLooseCacheMatch(
|
|
37
|
-
serverFunctionData: Record<string, unknown
|
|
38
|
-
targetArg: string
|
|
37
|
+
serverFunctionData: Record<string, unknown> | null | undefined,
|
|
38
|
+
targetArg: string,
|
|
39
39
|
): CacheMatch | undefined {
|
|
40
40
|
if (!serverFunctionData) return undefined;
|
|
41
41
|
const strictKey = JSON.stringify([targetArg]);
|
|
@@ -62,7 +62,7 @@ export function $$executeClientRpc(hashId: string) {
|
|
|
62
62
|
if (match) return match.data;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
return
|
|
65
|
+
return;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
ensureCacheInitialized();
|
|
@@ -80,7 +80,7 @@ export function $$executeClientRpc(hashId: string) {
|
|
|
80
80
|
body: JSON.stringify(args),
|
|
81
81
|
});
|
|
82
82
|
if (!response.ok) throw new Error(`[anaemia] RPC execution failed: ${response.status}`);
|
|
83
|
-
return await response.json() as unknown;
|
|
83
|
+
return (await response.json()) as unknown;
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
asyncRpcCall.id = hashId;
|
|
@@ -94,7 +94,7 @@ export function $$executeClientRpc(hashId: string) {
|
|
|
94
94
|
if (match) return match.data;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
return
|
|
97
|
+
return;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
ensureCacheInitialized();
|
|
@@ -104,4 +104,4 @@ export function $$executeClientRpc(hashId: string) {
|
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
return asyncRpcCall;
|
|
107
|
-
}
|
|
107
|
+
}
|
package/src/runtime/webpack.d.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -13,7 +13,9 @@ export interface LoaderArgs<Params extends Record<string, string> = Record<strin
|
|
|
13
13
|
/**
|
|
14
14
|
* represents an application page loader function.
|
|
15
15
|
*/
|
|
16
|
-
export type LoaderFunction<ResponseData = unknown, Params extends Record<string, string> = Record<string, string>> = (
|
|
16
|
+
export type LoaderFunction<ResponseData = unknown, Params extends Record<string, string> = Record<string, string>> = (
|
|
17
|
+
args: LoaderArgs<Params>,
|
|
18
|
+
) => Promise<ResponseData> | ResponseData;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* extracts and unwraps the true data structure returned by a server function or loader.
|
|
@@ -27,9 +29,22 @@ export type GuardContext = {
|
|
|
27
29
|
url: string;
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
export type GuardResult =
|
|
32
|
+
export type GuardResult =
|
|
33
|
+
| void
|
|
34
|
+
| undefined
|
|
35
|
+
| { redirect: string; status?: 301 | 302 | 307 | 308 }
|
|
36
|
+
| { status: 401 | 403 | 404 | 500; body?: string };
|
|
31
37
|
|
|
32
|
-
export type GuardFn = (ctx: {
|
|
38
|
+
export type GuardFn = (ctx: {
|
|
39
|
+
params: Record<string, string>;
|
|
40
|
+
request: Request;
|
|
41
|
+
url: string;
|
|
42
|
+
}) =>
|
|
43
|
+
| void
|
|
44
|
+
| undefined
|
|
45
|
+
| { redirect: string; status?: RedirectStatusCode }
|
|
46
|
+
| { status: number; body?: string }
|
|
47
|
+
| Promise<void | undefined | { redirect: string; status?: RedirectStatusCode } | { status: number; body?: string }>;
|
|
33
48
|
|
|
34
49
|
export type ServerFunction<Args extends unknown[], Return> = ((...args: Args) => Promise<Return>) & {
|
|
35
50
|
id?: string;
|
|
@@ -28,7 +28,9 @@ async function resolveConfigPort() {
|
|
|
28
28
|
return userConfig.port;
|
|
29
29
|
}
|
|
30
30
|
} catch (err) {
|
|
31
|
-
console.warn(
|
|
31
|
+
console.warn(
|
|
32
|
+
`[HMR test warning]: failed parsing anaemia.config.ts, falling back to port 3000. Error: ${err.message}`,
|
|
33
|
+
);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -119,11 +121,18 @@ test("integration - dev server HMR & hydration lifecycle", async (t) => {
|
|
|
119
121
|
|
|
120
122
|
const titleText = await page.textContent("h1");
|
|
121
123
|
assert.equal(titleText.trim(), "anaemia");
|
|
122
|
-
assert.equal(
|
|
124
|
+
assert.equal(
|
|
125
|
+
consoleErrors.length,
|
|
126
|
+
0,
|
|
127
|
+
`Errors during initial render: ${consoleErrors.map((e) => e.message).join(", ")}`,
|
|
128
|
+
);
|
|
123
129
|
});
|
|
124
130
|
|
|
125
131
|
await t.test("should push hot module updates to browser when files change", async () => {
|
|
126
|
-
const updatedContent = originalComponentContent.replace(
|
|
132
|
+
const updatedContent = originalComponentContent.replace(
|
|
133
|
+
`<h1 class={styles.title}>anaemia</h1>`,
|
|
134
|
+
`<h1 class={styles.title}>anaemia updated!</h1>`,
|
|
135
|
+
);
|
|
127
136
|
|
|
128
137
|
assert.notEqual(originalComponentContent, updatedContent, "Regex failed to modify component text.");
|
|
129
138
|
fs.writeFileSync(componentPath, updatedContent, "utf-8");
|
|
@@ -134,7 +143,7 @@ test("integration - dev server HMR & hydration lifecycle", async (t) => {
|
|
|
134
143
|
const h1 = document.querySelector("h1");
|
|
135
144
|
return h1 && h1.textContent.trim() === "anaemia updated!";
|
|
136
145
|
},
|
|
137
|
-
{ timeout: 10000 }
|
|
146
|
+
{ timeout: 10000 },
|
|
138
147
|
);
|
|
139
148
|
});
|
|
140
149
|
|
|
@@ -145,6 +154,10 @@ test("integration - dev server HMR & hydration lifecycle", async (t) => {
|
|
|
145
154
|
|
|
146
155
|
const titleText = await page.textContent("h1");
|
|
147
156
|
assert.equal(titleText.trim(), "anaemia updated!");
|
|
148
|
-
assert.equal(
|
|
157
|
+
assert.equal(
|
|
158
|
+
consoleErrors.length,
|
|
159
|
+
0,
|
|
160
|
+
`Errors detected after page refresh: ${consoleErrors.map((e) => e.message).join(", ")}`,
|
|
161
|
+
);
|
|
149
162
|
});
|
|
150
163
|
});
|
package/test/rpc-client.test.mjs
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
runOnServer,
|
|
5
|
-
serverFunctionsRegistry,
|
|
6
|
-
ssrStorage,
|
|
7
|
-
} from "../dist/runtime/context.js";
|
|
3
|
+
import { runOnServer, serverFunctionsRegistry, ssrStorage } from "../dist/runtime/context.js";
|
|
8
4
|
import { createRouteRequest } from "../dist/runtime/route-request.js";
|
|
9
5
|
|
|
10
6
|
test("runOnServer returns a callable function and registers the original implementation", async () => {
|
|
@@ -26,7 +22,7 @@ test("runOnServer records SSR results by id and argument list for hydration", as
|
|
|
26
22
|
|
|
27
23
|
assert.deepEqual(store.get("__SERVER_FUNCTION_DATA__"), {
|
|
28
24
|
greet: {
|
|
29
|
-
|
|
25
|
+
'["Ada"]': "hello Ada",
|
|
30
26
|
},
|
|
31
27
|
});
|
|
32
28
|
});
|
package/tsconfig.json
CHANGED