@decohere/flux 0.0.1-dev.109 → 0.0.1-dev.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -214,7 +214,7 @@ Only source files (`.ts`) that a served entry imports trigger a reboot.
214
214
 
215
215
  **Memory grows over a long session.** Each reboot imports changed modules under a new `?version=` URL, and Node's ES module cache never releases the old versions. That's fine for a working day; if a session runs for days with thousands of reboots, restart it.
216
216
 
217
- Flux uses **Forge’s compiler**, with one SWC config per project built once per run. SWC leaves imports as written; Flux's resolve hook maps each one to its `.ts` source: `@scope/pkg` and `@scope/pkg/sub` through the Mesh aliases (and tsconfig `paths`), `./file.js` and `./file` to `file.ts`, and `./dir` to `dir/index.ts`. Doing it in one place, instead of in SWC (`jsc.paths`, `resolveFully`) and again in the hook, made loading about 20% faster once Node and SWC are warm (`pnpm bench:flux`, #513). That is a debug rule so you can edit a library and hit HMR without a pre-build or `node_modules` workspace symlink. Release still resolves through real `node_modules` (published `exports` / `main`).
217
+ Flux uses **Forge’s compiler**, with one SWC config per project built once per run. SWC leaves imports as written; Flux's resolve hook maps each one to its `.ts` source: `@scope/pkg` and `@scope/pkg/sub` through the Mesh aliases (and tsconfig `paths`), `./file.js` and `./file` to `file.ts`, and `./dir` to `dir/index.ts`. Doing it in one place, instead of in SWC (`jsc.paths`, `resolveFully`) and again in the hook, made loading about 20% faster once Node and SWC are warm (`pnpm bench flux-load`, #513). That is a debug rule so you can edit a library and hit HMR without a pre-build or `node_modules` workspace symlink. Release still resolves through real `node_modules` (published `exports` / `main`).
218
218
 
219
219
  Why aliases, and a tree of where a symlink misses `src/index.ts`: [Forge — debug path aliases](../forge/README.md#debug-path-aliases-not-node_modules-links).
220
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/flux",
3
- "version": "0.0.1-dev.109",
3
+ "version": "0.0.1-dev.110",
4
4
  "description": "Hot module reload server for Node.js ESM applications",
5
5
  "keywords": [
6
6
  "typescript",
@@ -25,11 +25,11 @@
25
25
  "test": "echo \"Error: no test specified\" && exit 1"
26
26
  },
27
27
  "dependencies": {
28
- "@decohere/crucible": "0.0.1-dev.109",
29
- "@decohere/forge": "0.0.1-dev.109",
30
- "@decohere/mesh": "0.0.1-dev.109",
31
- "@decohere/nexus": "0.0.1-dev.109",
32
- "@decohere/scribe": "0.0.1-dev.109",
28
+ "@decohere/crucible": "0.0.1-dev.110",
29
+ "@decohere/forge": "0.0.1-dev.110",
30
+ "@decohere/mesh": "0.0.1-dev.110",
31
+ "@decohere/nexus": "0.0.1-dev.110",
32
+ "@decohere/scribe": "0.0.1-dev.110",
33
33
  "commander": "^13.1.0",
34
34
  "rxjs": "^7.8.2"
35
35
  },
@@ -1,145 +0,0 @@
1
- /**
2
- * Cold-load benchmark for flux: how long the load hook takes to compile and import a workspace
3
- * whose files import each other, the way an app's do (#513).
4
- *
5
- * pnpm bench:flux # 8 libraries × 40 files, 10 processes
6
- * BENCH_LIBS=4 BENCH_FILES=100 BENCH_RUNS=5 pnpm bench:flux
7
- *
8
- * Generates the workspace in a temp directory: each library re-exports a `models/` directory
9
- * through its index (a directory import), each model imports the previous one (relative, with
10
- * `.js`) and the previous library by package name, and has a decorated class. Each process
11
- * imports the last library twice under different URL versions with a fresh loader cache, so
12
- * both loads transpile every file: the first is a real cold start, the second runs with Node
13
- * and SWC warmed up. Prints the median of each and the time per file.
14
- */ "use strict";
15
- Object.defineProperty(exports, "__esModule", {
16
- value: true
17
- });
18
- const _nodechild_process = require("node:child_process");
19
- const _nodefs = require("node:fs");
20
- const _nodeos = require("node:os");
21
- const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
22
- const _nodeurl = require("node:url");
23
- const _forge = require("@decohere/forge");
24
- const _sourcemoduleloader = require("../src/source-module-loader.js");
25
- function _interop_require_default(obj) {
26
- return obj && obj.__esModule ? obj : {
27
- default: obj
28
- };
29
- }
30
- const libs = Number(process.env.BENCH_LIBS ?? 8);
31
- const filesPerLib = Number(process.env.BENCH_FILES ?? 40);
32
- const runs = Number(process.env.BENCH_RUNS ?? 10);
33
- function write(file, content) {
34
- (0, _nodefs.mkdirSync)(_nodepath.default.dirname(file), {
35
- recursive: true
36
- });
37
- (0, _nodefs.writeFileSync)(file, content);
38
- }
39
- function generate(root) {
40
- write(_nodepath.default.join(root, "package.json"), JSON.stringify({
41
- name: "flux-bench",
42
- type: "module"
43
- }));
44
- write(_nodepath.default.join(root, "tsconfig.json"), JSON.stringify({
45
- compilerOptions: {
46
- experimentalDecorators: true
47
- }
48
- }));
49
- for(let lib = 0; lib < libs; lib++){
50
- const dir = _nodepath.default.join(root, "projects", `lib-${lib}`);
51
- write(_nodepath.default.join(dir, "package.json"), JSON.stringify({
52
- name: `@bench/lib-${lib}`,
53
- type: "module",
54
- main: "src/index.ts"
55
- }));
56
- const previousLib = lib > 0 ? `@bench/lib-${lib - 1}` : null;
57
- write(_nodepath.default.join(dir, "src/index.ts"), `export * from "./models";\n`);
58
- const exports1 = [];
59
- for(let file = 0; file < filesPerLib; file++){
60
- const name = `Model${lib}x${file}`;
61
- const previous = file > 0 ? `Model${lib}x${file - 1}` : null;
62
- write(_nodepath.default.join(dir, "src/models", `model-${file}.ts`), [
63
- previous ? `import { ${previous} } from "./model-${file - 1}.js";` : "",
64
- previousLib && file === 0 ? `import { Model${lib - 1}x0 } from "${previousLib}";` : "",
65
- `const tag = (_target: unknown) => {};`,
66
- `@tag`,
67
- `export class ${name} {`,
68
- `\tpublic readonly id: number = ${file};`,
69
- `\tpublic constructor(public readonly label: string = "${name}") {}`,
70
- `\tpublic describe(): string { return \`\${this.label}:\${this.id}\`; }`,
71
- previous ? `\tpublic previous = ${previous};` : "",
72
- previousLib && file === 0 ? `\tpublic upstream = Model${lib - 1}x0;` : "",
73
- `}`
74
- ].filter(Boolean).join("\n"));
75
- exports1.push(`export * from "./model-${file}.js";`);
76
- }
77
- write(_nodepath.default.join(dir, "src/models/index.ts"), `${exports1.join("\n")}\n`);
78
- }
79
- return _nodepath.default.join(root, "projects", `lib-${libs - 1}`, "src/index.ts");
80
- }
81
- /** One sample, in this process: the first load (cold) and a second one (warm Node and SWC). */ async function sample() {
82
- const root = (0, _nodefs.mkdtempSync)(_nodepath.default.join((0, _nodeos.tmpdir)(), "flux-bench-"));
83
- try {
84
- const entry = generate(root);
85
- const workspace = await (0, _forge.createWorkspace)({
86
- rootDir: root
87
- });
88
- const loader = new _sourcemoduleloader.SourceModuleLoader();
89
- const load = async (version)=>{
90
- const started = performance.now();
91
- await loader.configure({
92
- buildConfig: "debug",
93
- workspace,
94
- versionFileUrl: (fileUrl)=>`${fileUrl}?v=${version}`
95
- });
96
- loader.registerHooks();
97
- await loader.importEntry(entry);
98
- return performance.now() - started;
99
- };
100
- const cold = await load(1);
101
- const warm = await load(2);
102
- return {
103
- files: [
104
- ...loader.sourceFilePaths()
105
- ].length,
106
- cold,
107
- warm
108
- };
109
- } finally{
110
- (0, _nodefs.rmSync)(root, {
111
- recursive: true,
112
- force: true
113
- });
114
- }
115
- }
116
- const median = (values)=>[
117
- ...values
118
- ].sort((a, b)=>a - b)[Math.floor(values.length / 2)] ?? 0;
119
- if (process.argv.includes("--sample")) {
120
- console.log(JSON.stringify(await sample()));
121
- } else {
122
- // Each sample runs in a fresh process: Node never unloads modules, so loads in one process
123
- // slow down as the heap grows and the numbers drift.
124
- const results = Array.from({
125
- length: runs
126
- }, ()=>{
127
- const child = (0, _nodechild_process.spawnSync)(process.execPath, [
128
- ...process.execArgv,
129
- (0, _nodeurl.fileURLToPath)(require("url").pathToFileURL(__filename).toString()),
130
- "--sample"
131
- ], {
132
- encoding: "utf8",
133
- env: process.env
134
- });
135
- if (child.status !== 0) throw new Error(`sample failed:\n${child.stderr}`);
136
- const last = child.stdout.trim().split("\n").at(-1) ?? "";
137
- return JSON.parse(last);
138
- });
139
- const files = results[0]?.files ?? 0;
140
- const report = (label, values)=>{
141
- const ms = median(values);
142
- return `${label} ${ms.toFixed(1)} ms (${(ms / files * 1000).toFixed(0)} µs/file)`;
143
- };
144
- console.log(`flux load, ${files} files, median of ${runs} processes: ` + `${report("first", results.map((r)=>r.cold))}, ` + `${report("second", results.map((r)=>r.warm))}`);
145
- }
@@ -1,136 +0,0 @@
1
- /**
2
- * Cold-load benchmark for flux: how long the load hook takes to compile and import a workspace
3
- * whose files import each other, the way an app's do (#513).
4
- *
5
- * pnpm bench:flux # 8 libraries × 40 files, 10 processes
6
- * BENCH_LIBS=4 BENCH_FILES=100 BENCH_RUNS=5 pnpm bench:flux
7
- *
8
- * Generates the workspace in a temp directory: each library re-exports a `models/` directory
9
- * through its index (a directory import), each model imports the previous one (relative, with
10
- * `.js`) and the previous library by package name, and has a decorated class. Each process
11
- * imports the last library twice under different URL versions with a fresh loader cache, so
12
- * both loads transpile every file: the first is a real cold start, the second runs with Node
13
- * and SWC warmed up. Prints the median of each and the time per file.
14
- */ import { spawnSync } from "node:child_process";
15
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
16
- import { tmpdir } from "node:os";
17
- import path from "node:path";
18
- import { fileURLToPath } from "node:url";
19
- import { createWorkspace } from "@decohere/forge";
20
- import { SourceModuleLoader } from "../src/source-module-loader.js";
21
- const libs = Number(process.env.BENCH_LIBS ?? 8);
22
- const filesPerLib = Number(process.env.BENCH_FILES ?? 40);
23
- const runs = Number(process.env.BENCH_RUNS ?? 10);
24
- function write(file, content) {
25
- mkdirSync(path.dirname(file), {
26
- recursive: true
27
- });
28
- writeFileSync(file, content);
29
- }
30
- function generate(root) {
31
- write(path.join(root, "package.json"), JSON.stringify({
32
- name: "flux-bench",
33
- type: "module"
34
- }));
35
- write(path.join(root, "tsconfig.json"), JSON.stringify({
36
- compilerOptions: {
37
- experimentalDecorators: true
38
- }
39
- }));
40
- for(let lib = 0; lib < libs; lib++){
41
- const dir = path.join(root, "projects", `lib-${lib}`);
42
- write(path.join(dir, "package.json"), JSON.stringify({
43
- name: `@bench/lib-${lib}`,
44
- type: "module",
45
- main: "src/index.ts"
46
- }));
47
- const previousLib = lib > 0 ? `@bench/lib-${lib - 1}` : null;
48
- write(path.join(dir, "src/index.ts"), `export * from "./models";\n`);
49
- const exports = [];
50
- for(let file = 0; file < filesPerLib; file++){
51
- const name = `Model${lib}x${file}`;
52
- const previous = file > 0 ? `Model${lib}x${file - 1}` : null;
53
- write(path.join(dir, "src/models", `model-${file}.ts`), [
54
- previous ? `import { ${previous} } from "./model-${file - 1}.js";` : "",
55
- previousLib && file === 0 ? `import { Model${lib - 1}x0 } from "${previousLib}";` : "",
56
- `const tag = (_target: unknown) => {};`,
57
- `@tag`,
58
- `export class ${name} {`,
59
- `\tpublic readonly id: number = ${file};`,
60
- `\tpublic constructor(public readonly label: string = "${name}") {}`,
61
- `\tpublic describe(): string { return \`\${this.label}:\${this.id}\`; }`,
62
- previous ? `\tpublic previous = ${previous};` : "",
63
- previousLib && file === 0 ? `\tpublic upstream = Model${lib - 1}x0;` : "",
64
- `}`
65
- ].filter(Boolean).join("\n"));
66
- exports.push(`export * from "./model-${file}.js";`);
67
- }
68
- write(path.join(dir, "src/models/index.ts"), `${exports.join("\n")}\n`);
69
- }
70
- return path.join(root, "projects", `lib-${libs - 1}`, "src/index.ts");
71
- }
72
- /** One sample, in this process: the first load (cold) and a second one (warm Node and SWC). */ async function sample() {
73
- const root = mkdtempSync(path.join(tmpdir(), "flux-bench-"));
74
- try {
75
- const entry = generate(root);
76
- const workspace = await createWorkspace({
77
- rootDir: root
78
- });
79
- const loader = new SourceModuleLoader();
80
- const load = async (version)=>{
81
- const started = performance.now();
82
- await loader.configure({
83
- buildConfig: "debug",
84
- workspace,
85
- versionFileUrl: (fileUrl)=>`${fileUrl}?v=${version}`
86
- });
87
- loader.registerHooks();
88
- await loader.importEntry(entry);
89
- return performance.now() - started;
90
- };
91
- const cold = await load(1);
92
- const warm = await load(2);
93
- return {
94
- files: [
95
- ...loader.sourceFilePaths()
96
- ].length,
97
- cold,
98
- warm
99
- };
100
- } finally{
101
- rmSync(root, {
102
- recursive: true,
103
- force: true
104
- });
105
- }
106
- }
107
- const median = (values)=>[
108
- ...values
109
- ].sort((a, b)=>a - b)[Math.floor(values.length / 2)] ?? 0;
110
- if (process.argv.includes("--sample")) {
111
- console.log(JSON.stringify(await sample()));
112
- } else {
113
- // Each sample runs in a fresh process: Node never unloads modules, so loads in one process
114
- // slow down as the heap grows and the numbers drift.
115
- const results = Array.from({
116
- length: runs
117
- }, ()=>{
118
- const child = spawnSync(process.execPath, [
119
- ...process.execArgv,
120
- fileURLToPath(import.meta.url),
121
- "--sample"
122
- ], {
123
- encoding: "utf8",
124
- env: process.env
125
- });
126
- if (child.status !== 0) throw new Error(`sample failed:\n${child.stderr}`);
127
- const last = child.stdout.trim().split("\n").at(-1) ?? "";
128
- return JSON.parse(last);
129
- });
130
- const files = results[0]?.files ?? 0;
131
- const report = (label, values)=>{
132
- const ms = median(values);
133
- return `${label} ${ms.toFixed(1)} ms (${(ms / files * 1000).toFixed(0)} µs/file)`;
134
- };
135
- console.log(`flux load, ${files} files, median of ${runs} processes: ` + `${report("first", results.map((r)=>r.cold))}, ` + `${report("second", results.map((r)=>r.warm))}`);
136
- }
@@ -1 +0,0 @@
1
- export {};