Lazy-import keeper.ts so --help and --version skip nut.js load
keeper.ts statically imports @nut-tree-fork/nut-js, which dlopens a
sizeable native .node binary. That load dominated cold-start: ~1.2s
for 'move --version' immediately after install, vs ~125ms warm.
For a flag that just prints a string, that's all overhead.
Change: dynamic 'await import("./keeper.ts")' placed after the
--help and --version short-circuits. The dynamic import is wrapped
in try/catch so import-time failures (e.g., missing native binary,
unsupported architecture) route through the same failRuntime() path
as anything thrown by the loop.
Expected cold start for --help and --version drops to ~50-150ms
(Bun + reading a handful of small files, no native module load).
move with no flags pays the same load cost as today.
tsc --noEmit and the test suite remain clean.
This commit is contained in:
+23
-3
@@ -12,13 +12,17 @@
|
||||
*
|
||||
* Order of operations:
|
||||
* 1. Parse CLI args. Bad input -> stderr + usage hint, exit 2.
|
||||
* 2. `--help` / `--version` short-circuit before any I/O or mouse work.
|
||||
* 2. `--help` / `--version` short-circuit before any I/O, config load, or
|
||||
* mouse work. `keeper.ts` is also lazy-imported (see below) so these
|
||||
* flags don't pay the cost of loading the nut.js native binary.
|
||||
* 3. Load + validate the config file (default XDG path, or `--config
|
||||
* <path>` if supplied). Validation failures share the exit-2 path.
|
||||
* 4. Resolve the full `Config` (CLI > file > DEFAULT_CONFIG) — verbose
|
||||
* lives inside `Config` and is layered with the same precedence as
|
||||
* the numeric fields.
|
||||
* 5. Run keeper. Any unhandled rejection exits 1.
|
||||
* 5. Lazy-import `keeper.ts` (dynamic import keeps nut.js out of the
|
||||
* `--help` / `--version` startup path) and run it. Any unhandled
|
||||
* rejection — from the import itself or from the loop — exits 1.
|
||||
*
|
||||
* Runtime: Bun (uses `@nut-tree-fork/nut-js` via `keeper.ts`). The shebang
|
||||
* above lets this file run as a real CLI once linked via `bun link`.
|
||||
@@ -29,7 +33,13 @@ import type { ParsedCliArgs } from "./cli.ts";
|
||||
import { resolveConfig } from "./config.ts";
|
||||
import type { ConfigOverrides } from "./config.ts";
|
||||
import { loadConfigFile } from "./configFile.ts";
|
||||
import { runKeeper } from "./keeper.ts";
|
||||
|
||||
// `keeper.ts` is intentionally NOT statically imported here. It transitively
|
||||
// pulls in `@nut-tree-fork/nut-js`, which in turn dlopens a sizeable native
|
||||
// `.node` binary. On a cold first run that load dominates startup (~1 s on
|
||||
// macOS). For `--help` and `--version` we never actually need nut.js, so we
|
||||
// defer the import to the only branch that actually runs the keeper loop.
|
||||
// See the dynamic `await import("./keeper.ts")` near the bottom of the file.
|
||||
|
||||
/**
|
||||
* Print a user-error message and exit 2. Used for anything that comes from
|
||||
@@ -98,4 +108,14 @@ const cliOverrides: ConfigOverrides = {
|
||||
|
||||
const config = resolveConfig(fileOverrides, cliOverrides);
|
||||
|
||||
// Dynamic import so nut.js and the rest of the keeper machinery aren't
|
||||
// loaded for invocations that exit early (--help, --version, validation
|
||||
// failures). The `try` covers import-time failures too (e.g., a missing
|
||||
// nut.js native binary), routing them through the same runtime-failure
|
||||
// path as anything raised by the loop itself.
|
||||
try {
|
||||
const { runKeeper } = await import("./keeper.ts");
|
||||
runKeeper(config).catch(failRuntime);
|
||||
} catch (err: unknown) {
|
||||
failRuntime(err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user