@anaemia/core 0.0.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 +201 -0
- package/README.md +3 -0
- package/dist/config.d.ts +63 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/lightningcss.d.ts +5 -0
- package/dist/plugins/lightningcss.d.ts.map +1 -0
- package/dist/plugins/lightningcss.js +69 -0
- package/dist/runtime/context.browser.d.ts +4 -0
- package/dist/runtime/context.browser.d.ts.map +1 -0
- package/dist/runtime/context.browser.js +4 -0
- package/dist/runtime/context.d.ts +8 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +25 -0
- package/dist/runtime/entry-client.d.ts +2 -0
- package/dist/runtime/entry-client.d.ts.map +1 -0
- package/dist/runtime/entry-client.jsx +19 -0
- package/dist/runtime/entry-server.d.ts +4 -0
- package/dist/runtime/entry-server.d.ts.map +1 -0
- package/dist/runtime/entry-server.jsx +346 -0
- package/dist/runtime/resources.d.ts +6 -0
- package/dist/runtime/resources.d.ts.map +1 -0
- package/dist/runtime/resources.js +36 -0
- package/dist/runtime/route-data.d.ts +15 -0
- package/dist/runtime/route-data.d.ts.map +1 -0
- package/dist/runtime/route-data.js +57 -0
- package/dist/runtime/route-request.d.ts +2 -0
- package/dist/runtime/route-request.d.ts.map +1 -0
- package/dist/runtime/route-request.js +11 -0
- package/dist/runtime/rpc-client.d.ts +6 -0
- package/dist/runtime/rpc-client.d.ts.map +1 -0
- package/dist/runtime/rpc-client.js +78 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +61 -0
- package/src/config.ts +74 -0
- package/src/index.ts +13 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/lightningcss.ts +82 -0
- package/src/runtime/context.browser.ts +5 -0
- package/src/runtime/context.ts +34 -0
- package/src/runtime/entry-client.tsx +34 -0
- package/src/runtime/entry-server.tsx +393 -0
- package/src/runtime/resources.ts +49 -0
- package/src/runtime/route-data.ts +93 -0
- package/src/runtime/route-request.ts +12 -0
- package/src/runtime/rpc-client.ts +87 -0
- package/src/runtime/webpack.d.ts +6 -0
- package/src/types.ts +32 -0
- package/test/integration/hmr.test.mjs +152 -0
- package/test/run-on-server.test.mjs +48 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { chromium } from "playwright";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { createJiti } = require("jiti");
|
|
12
|
+
const jiti = createJiti(import.meta.url);
|
|
13
|
+
|
|
14
|
+
function resolveConfigPort() {
|
|
15
|
+
const configPath = path.resolve(projectRoot, "anaemia.config.ts");
|
|
16
|
+
|
|
17
|
+
if (fs.existsSync(configPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const userConfigModule = jiti(configPath);
|
|
20
|
+
const userConfig = userConfigModule.default || userConfigModule;
|
|
21
|
+
|
|
22
|
+
if (userConfig && typeof userConfig.port === "number") {
|
|
23
|
+
return userConfig.port;
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.warn(`[HMR test warning]: failed parsing anaemia.config.ts, falling back to port 3000. Error: ${err.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return 3000;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const TARGET_PORT = resolveConfigPort();
|
|
34
|
+
const BASE_URL = `http://localhost:${TARGET_PORT}`;
|
|
35
|
+
|
|
36
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
37
|
+
const __dirname = path.dirname(__filename);
|
|
38
|
+
const projectRoot = path.resolve(__dirname, "../../../../templates/base-app");
|
|
39
|
+
const componentPath = path.resolve(projectRoot, "src/features/welcome-hero/components/WelcomeHero.tsx");
|
|
40
|
+
|
|
41
|
+
test("integration - dev server HMR & hydration lifecycle", async (t) => {
|
|
42
|
+
let devProcess = null;
|
|
43
|
+
let browser = null;
|
|
44
|
+
let page = null;
|
|
45
|
+
let consoleErrors = [];
|
|
46
|
+
const originalComponentContent = fs.readFileSync(componentPath, "utf-8");
|
|
47
|
+
|
|
48
|
+
t.after(async () => {
|
|
49
|
+
fs.writeFileSync(componentPath, originalComponentContent, "utf-8");
|
|
50
|
+
if (browser) await browser.close();
|
|
51
|
+
|
|
52
|
+
if (devProcess && devProcess.pid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(-devProcess.pid, "SIGKILL");
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
t.beforeEach(() => {
|
|
60
|
+
consoleErrors = [];
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await t.test("should start dev server without errors", async () => {
|
|
64
|
+
devProcess = spawn("pnpm", ["dev"], {
|
|
65
|
+
cwd: projectRoot,
|
|
66
|
+
stdio: "pipe",
|
|
67
|
+
detached: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await new Promise((resolve, reject) => {
|
|
71
|
+
let output = "";
|
|
72
|
+
|
|
73
|
+
const timeout = setTimeout(() => {
|
|
74
|
+
reject(new Error(`Dev server startup timed out. Current output:\n${output}`));
|
|
75
|
+
}, 15000);
|
|
76
|
+
|
|
77
|
+
const checkPortReady = async () => {
|
|
78
|
+
for (let i = 0; i < 20; i++) {
|
|
79
|
+
try {
|
|
80
|
+
// Dynamic URL Check
|
|
81
|
+
const res = await fetch(BASE_URL);
|
|
82
|
+
if (res.status === 200 || res.status === 404) {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
resolve();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {}
|
|
88
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
89
|
+
}
|
|
90
|
+
reject(new Error(`Server process started but port ${TARGET_PORT} never responded to HTTP requests.`));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
devProcess.stdout.on("data", (data) => {
|
|
94
|
+
output += data.toString();
|
|
95
|
+
|
|
96
|
+
if (output.includes("server live at") || output.includes(BASE_URL)) {
|
|
97
|
+
checkPortReady();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
devProcess.stderr.on("data", (data) => {
|
|
102
|
+
const msg = data.toString();
|
|
103
|
+
if (!msg.includes("ExperimentalWarning") && !msg.includes("sync error")) {
|
|
104
|
+
console.error(`[Server Stderr]: ${msg}`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
devProcess.on("error", reject);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await t.test("should render initial server state & connect HMR", async () => {
|
|
113
|
+
browser = await chromium.launch({ headless: true });
|
|
114
|
+
page = await browser.newPage();
|
|
115
|
+
page.on("pageerror", (err) => consoleErrors.push(err));
|
|
116
|
+
|
|
117
|
+
await page.goto(BASE_URL, { waitUntil: "networkidle" });
|
|
118
|
+
|
|
119
|
+
const titleText = await page.textContent("h1");
|
|
120
|
+
assert.equal(titleText.trim(), "anaemia");
|
|
121
|
+
assert.equal(consoleErrors.length, 0, `Errors during initial render: ${consoleErrors.map((e) => e.message).join(", ")}`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await t.test("should push hot module updates to browser when files change", async () => {
|
|
125
|
+
const updatedContent = originalComponentContent.replace(`<h1 class={styles.title}>anaemia</h1>`, `<h1 class={styles.title}>anaemia updated!</h1>`);
|
|
126
|
+
|
|
127
|
+
assert.notEqual(originalComponentContent, updatedContent, "Regex failed to modify component text.");
|
|
128
|
+
fs.writeFileSync(componentPath, updatedContent, "utf-8");
|
|
129
|
+
|
|
130
|
+
await page.waitForFunction(
|
|
131
|
+
() => {
|
|
132
|
+
const h1 = document.querySelector("h1");
|
|
133
|
+
return h1 && h1.textContent.trim() === "anaemia updated!";
|
|
134
|
+
},
|
|
135
|
+
{ timeout: 10000 }
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await t.test("should refresh page without throwing runtime errors", async () => {
|
|
140
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
141
|
+
|
|
142
|
+
await page.reload({ waitUntil: "networkidle" });
|
|
143
|
+
|
|
144
|
+
const titleText = await page.textContent("h1");
|
|
145
|
+
assert.equal(titleText.trim(), "anaemia updated!");
|
|
146
|
+
assert.equal(
|
|
147
|
+
consoleErrors.length,
|
|
148
|
+
0,
|
|
149
|
+
`Errors detected after page refresh: ${consoleErrors.map(e => e.message).join(", ")}`
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
runOnServer,
|
|
5
|
+
serverFunctionsRegistry,
|
|
6
|
+
ssrStorage,
|
|
7
|
+
} from "../dist/runtime/context.js";
|
|
8
|
+
import { createRouteRequest } from "../dist/runtime/route-request.js";
|
|
9
|
+
|
|
10
|
+
test("runOnServer returns a callable function and registers the original implementation", async () => {
|
|
11
|
+
const add = runOnServer((a, b) => a + b, "add");
|
|
12
|
+
|
|
13
|
+
assert.equal(typeof add, "function");
|
|
14
|
+
assert.equal(add.id, "add");
|
|
15
|
+
assert.equal(add.urlId, "add");
|
|
16
|
+
assert.equal(await add(2, 3), 5);
|
|
17
|
+
assert.equal(await serverFunctionsRegistry.get("add")(4, 6), 10);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("runOnServer records SSR results by id and argument list for hydration", async () => {
|
|
21
|
+
const greet = runOnServer((name) => `hello ${name}`, "greet");
|
|
22
|
+
const store = new Map();
|
|
23
|
+
|
|
24
|
+
await ssrStorage.run(store, async () => {
|
|
25
|
+
assert.equal(await greet("Ada"), "hello Ada");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
assert.deepEqual(store.get("__SERVER_FUNCTION_DATA__"), {
|
|
29
|
+
greet: {
|
|
30
|
+
"[\"Ada\"]": "hello Ada",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("createRouteRequest creates a valid request for relative route paths", () => {
|
|
36
|
+
const request = createRouteRequest("/");
|
|
37
|
+
|
|
38
|
+
assert.equal(request.url, "http://localhost/");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("createRouteRequest reuses the active server request when one exists", async () => {
|
|
42
|
+
const raw = new Request("http://example.test/dashboard");
|
|
43
|
+
const store = new Map([["honoContext", { req: { raw } }]]);
|
|
44
|
+
|
|
45
|
+
await ssrStorage.run(store, async () => {
|
|
46
|
+
assert.equal(createRouteRequest("/dashboard"), raw);
|
|
47
|
+
});
|
|
48
|
+
});
|
package/tsconfig.json
ADDED