@nisli/core 0.49.2 → 0.51.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.
@@ -1,219 +0,0 @@
1
- /**
2
- * esbuild-hmr/plugin.ts — Dev-only esbuild plugin for Nisli component HMR.
3
- *
4
- * DEV-ONLY, build-time (Node). Reachable only via `@nisli/core/esbuild-hmr`,
5
- * never from the runtime `.` entry (ADR 0021, Ruling 1). It is added to the
6
- * esbuild plugin list ONLY on the `--watch` path; `pnpm build` (no watch) never
7
- * sees it, so prod is byte-identical by construction (Ruling 5).
8
- *
9
- * `esbuild` is an OPTIONAL peerDependency. To keep `@nisli/core` dependency-free
10
- * we do NOT `import` esbuild here — we type the plugin against a minimal local
11
- * structural surface. The host build script supplies the real esbuild.
12
- *
13
- * What it does:
14
- * 1. onLoad (Ruling 2): for author modules that import `component` from
15
- * `@nisli/core`, rewrite the import so `component(tag, setup, opts)` routes
16
- * `setup` through the runtime tag-keyed registry's STABLE indirection thunk.
17
- * The real `FrameworkComponent` thus captures a thunk that always reads the
18
- * current setup — no class swap, no tag re-definition.
19
- * 2. onEnd (Ruling 4 transport): diff the metafile outputs against the previous
20
- * build and broadcast a `change` payload ({added, removed, updated}) over
21
- * SSE so the browser client can escalate (css/js/reload).
22
- * 3. Inject the browser dev client (`@nisli/core/esbuild-hmr/runtime` +
23
- * `connect({ path })`) via esbuild's `inject` option pointed at a VIRTUAL
24
- * shim module, so the runtime is RESOLVED + BUNDLED into the output (no
25
- * author HTML/JS change needed). We deliberately do NOT use `banner`: a
26
- * banner is raw passthrough text that esbuild neither resolves nor bundles,
27
- * so a bare `import … from "@nisli/core/esbuild-hmr/runtime"` would leak
28
- * verbatim into the output and the browser would throw
29
- * `Failed to resolve module specifier` (no import map). `inject` runs the
30
- * shim through onResolve/onLoad so the runtime is inlined at build time, and
31
- * esbuild applies `inject` ONLY to JS modules — never to file/copy-loaded
32
- * entries (e.g. the viewer's `logo.svg`). Verified empirically against
33
- * esbuild 0.28.1.
34
- */
35
- /// <reference types="node" />
36
- import { readFileSync } from 'node:fs';
37
- // ── Source transform (Ruling 2) ─────────────────────────────────────
38
- const DEFAULT_FILTER = /\.[mc]?[jt]sx?$/;
39
- /**
40
- * Virtual path + namespace for the injected client shim. The path is a bare
41
- * sentinel (not a real file); onResolve claims it into a private namespace and
42
- * onLoad supplies its generated source, so esbuild bundles the runtime inline.
43
- */
44
- const CLIENT_SHIM_PATH = 'nisli-hmr-client-shim';
45
- const CLIENT_SHIM_NAMESPACE = 'nisli-hmr-shim';
46
- const CLIENT_SHIM_FILTER = /^nisli-hmr-client-shim$/;
47
- /**
48
- * Rewrite a module's source so the `component` it imports from `@nisli/core` is
49
- * wrapped by the registry indirection. Pure function — exported for testing.
50
- *
51
- * Strategy: alias the real import, then shadow `component` with a wrapper that
52
- * registers the setup and passes a STABLE thunk to the real `component`. The
53
- * wrapper is a no-op-shaped passthrough at runtime cost of one Map write +
54
- * one thunk allocation per `component()` call.
55
- *
56
- * Returns the original source unchanged if it does not import `component` from
57
- * the core specifier (nothing to wrap).
58
- */
59
- export function transformSource(code, coreSpecifier, runtimeSpecifier) {
60
- const spec = escapeRegExp(coreSpecifier);
61
- // Match a named import of `component` (alone or among others) from core.
62
- const importRe = new RegExp(`import\\s*\\{([^}]*)\\}\\s*from\\s*['"]${spec}['"]`, 'g');
63
- let importsComponent = false;
64
- const rewritten = code.replace(importRe, (_full, names) => {
65
- const parts = names.split(',').map((s) => s.trim()).filter(Boolean);
66
- const next = parts.map((p) => {
67
- // Handle `component` and `component as X` — we only wrap the bare name.
68
- if (p === 'component') {
69
- importsComponent = true;
70
- return 'component as __nisliRealComponent';
71
- }
72
- return p;
73
- });
74
- return `import { ${next.join(', ')} } from '${coreSpecifier}'`;
75
- });
76
- if (!importsComponent)
77
- return code;
78
- // Prepend the registry-import + wrapper. `__register` returns the STABLE
79
- // thunk the real component captures; the wrapper preserves the factory's
80
- // return value and signature.
81
- const preamble = `import { __register as __nisliRegister } from '${runtimeSpecifier}';\n` +
82
- `function component(tag, setup, opts) {\n` +
83
- ` return __nisliRealComponent(tag, __nisliRegister(tag, setup), opts);\n` +
84
- `}\n`;
85
- return preamble + rewritten;
86
- }
87
- /**
88
- * Source of the virtual client shim that esbuild's `inject` pulls into every JS
89
- * module. Because it is `inject`ed (not a `banner`), esbuild RESOLVES the
90
- * `runtimeSpecifier` import and BUNDLES `connect` inline — no bare specifier
91
- * survives in the output (the 0.49.0 bug). `clientUrl` is baked in so the
92
- * client targets the right SSE origin. Pure function — exported for testing.
93
- */
94
- export function clientShimSource(runtimeSpecifier, clientUrl) {
95
- return (`import { connect as __nisliConnect } from '${runtimeSpecifier}';\n` +
96
- `__nisliConnect({ path: ${JSON.stringify(clientUrl)} });\n`);
97
- }
98
- // ── The plugin ──────────────────────────────────────────────────────
99
- /**
100
- * Create the dev-only Nisli HMR esbuild plugin. Add it to `plugins` ONLY when
101
- * building with `--watch`; never on the prod build path.
102
- */
103
- export function nisliHmrPlugin(options) {
104
- const coreSpecifier = options.coreSpecifier ?? '@nisli/core';
105
- const runtimeSpecifier = options.runtimeSpecifier ?? '@nisli/core/esbuild-hmr/runtime';
106
- const clientUrl = options.clientUrl ?? '/esbuild';
107
- const filter = options.filter ?? DEFAULT_FILTER;
108
- const { broadcaster } = options;
109
- let prevHashes = null;
110
- return {
111
- name: 'nisli-hmr',
112
- setup(build) {
113
- // Inject the browser dev client via esbuild's `inject` (NOT `banner`).
114
- // A banner is raw passthrough text — its bare `import` would leak into the
115
- // output and the browser could not resolve it. `inject` points at a
116
- // virtual shim that esbuild resolves + bundles, inlining the runtime so no
117
- // bare specifier survives. esbuild applies `inject` only to JS modules, so
118
- // the viewer's file/copy-loaded `logo.svg` entry is never touched.
119
- build.initialOptions.inject = [
120
- ...(build.initialOptions.inject ?? []),
121
- CLIENT_SHIM_PATH,
122
- ];
123
- build.onResolve({ filter: CLIENT_SHIM_FILTER }, (args) => {
124
- if (args.path !== CLIENT_SHIM_PATH)
125
- return undefined;
126
- return { path: CLIENT_SHIM_PATH, namespace: CLIENT_SHIM_NAMESPACE };
127
- });
128
- build.onLoad({ filter: /.*/, namespace: CLIENT_SHIM_NAMESPACE }, () => ({
129
- contents: clientShimSource(runtimeSpecifier, clientUrl),
130
- loader: 'js',
131
- // A virtual module has no on-disk location, so esbuild won't know where
132
- // to resolve the runtime import from. Anchor it to the build's working
133
- // directory (where the consumer's `@nisli/core` resolves).
134
- resolveDir: build.initialOptions.absWorkingDir ?? process.cwd(),
135
- }));
136
- // 1. Transform: wrap component() call sites (Ruling 2).
137
- build.onLoad({ filter }, (args) => {
138
- if (args.path.includes('node_modules'))
139
- return undefined;
140
- const code = readFileSafe(args.path);
141
- if (code === null)
142
- return undefined;
143
- const contents = transformSource(code, coreSpecifier, runtimeSpecifier);
144
- if (contents === code)
145
- return undefined; // nothing to wrap; let esbuild load it
146
- return { contents, loader: loaderFor(args.path) };
147
- });
148
- // 2. Diff outputs and broadcast a change payload (Ruling 4 transport).
149
- build.onEnd((result) => {
150
- const outputs = result.metafile?.outputs;
151
- if (!outputs) {
152
- // No metafile -> cannot diff -> signal a reload-worthy change.
153
- broadcaster.broadcast({ added: [], removed: [], updated: [] });
154
- return;
155
- }
156
- const nextHashes = new Map();
157
- for (const [path, meta] of Object.entries(outputs)) {
158
- nextHashes.set(path, meta.hash ?? String(meta.bytes));
159
- }
160
- const payload = diffOutputs(prevHashes, nextHashes);
161
- prevHashes = nextHashes;
162
- // First build establishes the baseline; don't broadcast on it.
163
- if (payload)
164
- broadcaster.broadcast(payload);
165
- });
166
- },
167
- };
168
- }
169
- /**
170
- * Diff two output-hash maps into an esbuild-shaped change payload.
171
- * Returns `null` for the very first build (no previous baseline).
172
- * Exported for testing.
173
- */
174
- export function diffOutputs(prev, next) {
175
- if (prev === null)
176
- return null;
177
- const added = [];
178
- const removed = [];
179
- const updated = [];
180
- for (const [path, hash] of next) {
181
- const before = prev.get(path);
182
- if (before === undefined)
183
- added.push(path);
184
- else if (before !== hash)
185
- updated.push(path);
186
- }
187
- for (const path of prev.keys()) {
188
- if (!next.has(path))
189
- removed.push(path);
190
- }
191
- return { added, removed, updated };
192
- }
193
- // ── helpers ─────────────────────────────────────────────────────────
194
- function escapeRegExp(s) {
195
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
196
- }
197
- function loaderFor(path) {
198
- if (path.endsWith('.tsx'))
199
- return 'tsx';
200
- if (path.endsWith('.jsx'))
201
- return 'jsx';
202
- if (/\.[mc]?ts$/.test(path))
203
- return 'ts';
204
- return 'js';
205
- }
206
- /**
207
- * Read a file synchronously. Build-time only (Node); returns null on failure so
208
- * esbuild falls back to its own loader. `node:fs` is fine here — this module is
209
- * never reachable from the `.` runtime graph (ADR 0021, Ruling 1).
210
- */
211
- function readFileSafe(path) {
212
- try {
213
- return readFileSync(path, 'utf8');
214
- }
215
- catch {
216
- return null;
217
- }
218
- }
219
- //# sourceMappingURL=plugin.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/esbuild-hmr/plugin.ts"],"names":[],"mappings":"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"}
@@ -1,122 +0,0 @@
1
- /**
2
- * esbuild-hmr/runtime.ts — Browser dev client for Nisli HMR.
3
- *
4
- * DEV-ONLY. Reachable only via the `@nisli/core/esbuild-hmr/runtime` subpath,
5
- * which the esbuild plugin injects on the `--watch` path. It is NEVER imported
6
- * by the `@nisli/core` runtime `.` entry, so it contributes zero production
7
- * bytes (ADR 0021, Ruling 1 & 5).
8
- *
9
- * Responsibilities (ADR 0021):
10
- * - A tag -> setup registry with a STABLE indirection thunk (Ruling 2).
11
- * - remount() of live elements through the existing custom-element lifecycle:
12
- * disconnectedCallback() -> replaceChildren() -> connectedCallback() (Ruling 3).
13
- * - An EventSource client that classifies an esbuild `change` payload and
14
- * escalates: CSS hot-swap -> component re-mount -> full reload (Ruling 4).
15
- *
16
- * The class is NEVER swapped and the tag is NEVER re-defined; we swap only the
17
- * `setup` behind the registry, working *with* the `customElements.get` guard in
18
- * component.ts rather than against it.
19
- */
20
- /** The author's setup function, kept opaque here (props proxy, host). */
21
- export type HmrSetup = (props: unknown, host: HTMLElement) => unknown;
22
- /** esbuild's `change` event payload shape (added/removed/updated paths). */
23
- export interface EsbuildChangePayload {
24
- added: string[];
25
- removed: string[];
26
- updated: string[];
27
- }
28
- /** What to do with a change, decided from the output diff (Ruling 4). */
29
- export type HmrAction = 'css' | 'js' | 'reload';
30
- /** Minimal structural EventSource (avoids depending on DOM lib EventSource). */
31
- interface EventSourceLike {
32
- addEventListener(type: string, listener: (ev: {
33
- data?: string;
34
- }) => void): void;
35
- close?(): void;
36
- }
37
- /** Options for {@link connect}. Seams kept injectable for tests. */
38
- export interface ConnectOptions {
39
- /** SSE endpoint. Defaults to esbuild's `/esbuild`. */
40
- path?: string;
41
- /**
42
- * Factory for the EventSource. Defaults to the global `EventSource`.
43
- * Injectable so the client logic is testable without a live SSE server.
44
- */
45
- eventSourceFactory?: (url: string) => EventSourceLike;
46
- /**
47
- * Re-import the rebuilt JS so the wrapper re-runs `__register` for changed
48
- * tags. Defaults to a cache-busted dynamic import of the page's module
49
- * scripts. Returns a promise resolving when re-evaluation is done.
50
- */
51
- reimport?: () => Promise<void>;
52
- /** Force a full reload. Defaults to `location.reload()`. */
53
- reload?: () => void;
54
- }
55
- /**
56
- * Register (or update) the setup for a tag and return a STABLE indirection
57
- * thunk that always reads the *current* setup from the registry.
58
- *
59
- * On the first registration for a tag (initial page load) no re-mount is
60
- * scheduled. On a subsequent registration with a *different* setup reference
61
- * (a watch rebuild re-evaluating the module) the tag is queued for re-mount.
62
- *
63
- * The returned thunk is what `component()` hands to the real
64
- * `FrameworkComponent`; because it reads `registry.get(tag)` lazily, the live
65
- * element picks up new setup without any class swap or tag re-definition.
66
- */
67
- export declare function __register(tag: string, setup: HmrSetup): HmrSetup;
68
- /** Number of tags awaiting a re-mount (for orchestration/tests). */
69
- export declare function pendingRemountCount(): number;
70
- /** Drain and re-mount every pending tag. Returns the tags that were remounted. */
71
- export declare function drainRemounts(): string[];
72
- /**
73
- * Re-mount every live element of `tag` in place, on the SAME instance:
74
- *
75
- * disconnectedCallback() — resets `_mounted`, disposes effects + template
76
- * (the ADR 0008.1 disposal contract)
77
- * replaceChildren() — clears old DOM, firing nested custom elements'
78
- * disconnectedCallback so their disposers run too
79
- * connectedCallback() — re-runs the NEW setup (via the registry thunk)
80
- * inside the same `untrack(...)` mount path
81
- *
82
- * Props survive because `_propsProxy` is constructor-owned (component.ts), and
83
- * the element node identity is preserved — siblings, scroll, route, and global
84
- * stores are untouched.
85
- */
86
- export declare function remount(tag: string): void;
87
- /** Test-only: clear the registry and pending state between cases. */
88
- export declare function __resetRegistry(): void;
89
- /**
90
- * Decide the action for an esbuild output diff:
91
- * - every changed output is CSS -> 'css' (hot-swap, preserves everything)
92
- * - any JS output changed -> 'js' (re-import; re-mount changed tags
93
- * or escalate to reload if none)
94
- * - nothing actionable -> 'reload'
95
- */
96
- export declare function classifyChange(payload: EsbuildChangePayload): HmrAction;
97
- /**
98
- * Hot-swap stylesheets in place by re-pointing matching `<link>` hrefs with a
99
- * cache-busting query — no reload, no state loss (esbuild's documented CSS
100
- * technique). Returns the number of links swapped.
101
- */
102
- export declare function hotSwapCss(updated: string[]): number;
103
- /**
104
- * Connect to the SSE change channel and apply updates with escalation.
105
- * Safe to call once from the dev client; injected by the plugin in watch builds.
106
- */
107
- export declare function connect(options?: ConnectOptions): EventSourceLike;
108
- /** Close the active SSE connection (test/teardown). */
109
- export declare function disconnect(): void;
110
- /**
111
- * Apply a classified change. Exposed for tests so the escalation ladder is
112
- * verifiable without a live EventSource.
113
- *
114
- * For a JS change we re-import (re-runs the wrapper's `__register` calls); if no
115
- * tag's setup actually changed, the edit was a non-component module (util/
116
- * service) and we escalate to a full reload — the correct, safe fallback.
117
- */
118
- export declare function applyChange(payload: EsbuildChangePayload, deps: {
119
- reimport: () => Promise<void>;
120
- reload: () => void;
121
- }): Promise<HmrAction>;
122
- export {};
@@ -1,252 +0,0 @@
1
- /**
2
- * esbuild-hmr/runtime.ts — Browser dev client for Nisli HMR.
3
- *
4
- * DEV-ONLY. Reachable only via the `@nisli/core/esbuild-hmr/runtime` subpath,
5
- * which the esbuild plugin injects on the `--watch` path. It is NEVER imported
6
- * by the `@nisli/core` runtime `.` entry, so it contributes zero production
7
- * bytes (ADR 0021, Ruling 1 & 5).
8
- *
9
- * Responsibilities (ADR 0021):
10
- * - A tag -> setup registry with a STABLE indirection thunk (Ruling 2).
11
- * - remount() of live elements through the existing custom-element lifecycle:
12
- * disconnectedCallback() -> replaceChildren() -> connectedCallback() (Ruling 3).
13
- * - An EventSource client that classifies an esbuild `change` payload and
14
- * escalates: CSS hot-swap -> component re-mount -> full reload (Ruling 4).
15
- *
16
- * The class is NEVER swapped and the tag is NEVER re-defined; we swap only the
17
- * `setup` behind the registry, working *with* the `customElements.get` guard in
18
- * component.ts rather than against it.
19
- */
20
- // ── Tag -> setup registry (Ruling 2) ────────────────────────────────
21
- const registry = new Map();
22
- /** Tags whose setup changed since the last drain — pending a re-mount. */
23
- const pendingRemounts = new Set();
24
- let remountScheduled = false;
25
- /**
26
- * When true, `__register` accumulates pending tags but does NOT auto-schedule a
27
- * microtask drain — `applyChange` owns the drain so it can wait for a full
28
- * module re-eval to register ALL changed tags first.
29
- */
30
- let suppressAutoDrain = false;
31
- /**
32
- * Register (or update) the setup for a tag and return a STABLE indirection
33
- * thunk that always reads the *current* setup from the registry.
34
- *
35
- * On the first registration for a tag (initial page load) no re-mount is
36
- * scheduled. On a subsequent registration with a *different* setup reference
37
- * (a watch rebuild re-evaluating the module) the tag is queued for re-mount.
38
- *
39
- * The returned thunk is what `component()` hands to the real
40
- * `FrameworkComponent`; because it reads `registry.get(tag)` lazily, the live
41
- * element picks up new setup without any class swap or tag re-definition.
42
- */
43
- export function __register(tag, setup) {
44
- const prev = registry.get(tag);
45
- registry.set(tag, setup);
46
- if (prev !== undefined && prev !== setup) {
47
- pendingRemounts.add(tag);
48
- if (!suppressAutoDrain)
49
- scheduleRemount();
50
- }
51
- return (props, host) => {
52
- const current = registry.get(tag);
53
- if (!current)
54
- throw new Error(`[nisli-hmr] no setup registered for <${tag}>`);
55
- return current(props, host);
56
- };
57
- }
58
- /** Number of tags awaiting a re-mount (for orchestration/tests). */
59
- export function pendingRemountCount() {
60
- return pendingRemounts.size;
61
- }
62
- /** Drain and re-mount every pending tag. Returns the tags that were remounted. */
63
- export function drainRemounts() {
64
- const tags = [...pendingRemounts];
65
- pendingRemounts.clear();
66
- remountScheduled = false;
67
- for (const tag of tags)
68
- remount(tag);
69
- return tags;
70
- }
71
- function scheduleRemount() {
72
- if (remountScheduled)
73
- return;
74
- remountScheduled = true;
75
- // Defer so a full module re-eval can register *all* changed tags first.
76
- queueMicrotask(() => {
77
- if (remountScheduled)
78
- drainRemounts();
79
- });
80
- }
81
- /**
82
- * Re-mount every live element of `tag` in place, on the SAME instance:
83
- *
84
- * disconnectedCallback() — resets `_mounted`, disposes effects + template
85
- * (the ADR 0008.1 disposal contract)
86
- * replaceChildren() — clears old DOM, firing nested custom elements'
87
- * disconnectedCallback so their disposers run too
88
- * connectedCallback() — re-runs the NEW setup (via the registry thunk)
89
- * inside the same `untrack(...)` mount path
90
- *
91
- * Props survive because `_propsProxy` is constructor-owned (component.ts), and
92
- * the element node identity is preserved — siblings, scroll, route, and global
93
- * stores are untouched.
94
- */
95
- export function remount(tag) {
96
- const elements = document.querySelectorAll(tag);
97
- elements.forEach((node) => {
98
- const el = node;
99
- // Dispose first: must run BEFORE clearing DOM or effects/subscriptions
100
- // leak across reloads (ADR 0008.1 hazard).
101
- el.disconnectedCallback?.();
102
- // Clear old DOM — removal fires nested children's disconnectedCallback.
103
- el.replaceChildren();
104
- // Re-run setup + re-mount on the same instance.
105
- el.connectedCallback?.();
106
- });
107
- }
108
- /** Test-only: clear the registry and pending state between cases. */
109
- export function __resetRegistry() {
110
- registry.clear();
111
- pendingRemounts.clear();
112
- remountScheduled = false;
113
- suppressAutoDrain = false;
114
- }
115
- // ── Change classification + escalation (Ruling 4) ───────────────────
116
- const CSS_RE = /\.css($|\?)/;
117
- /**
118
- * Decide the action for an esbuild output diff:
119
- * - every changed output is CSS -> 'css' (hot-swap, preserves everything)
120
- * - any JS output changed -> 'js' (re-import; re-mount changed tags
121
- * or escalate to reload if none)
122
- * - nothing actionable -> 'reload'
123
- */
124
- export function classifyChange(payload) {
125
- const touched = [...payload.added, ...payload.removed, ...payload.updated];
126
- if (touched.length === 0)
127
- return 'reload';
128
- if (touched.every((p) => CSS_RE.test(p)))
129
- return 'css';
130
- if (touched.some((p) => /\.[mc]?js($|\?)/.test(p)))
131
- return 'js';
132
- return 'reload';
133
- }
134
- /**
135
- * Hot-swap stylesheets in place by re-pointing matching `<link>` hrefs with a
136
- * cache-busting query — no reload, no state loss (esbuild's documented CSS
137
- * technique). Returns the number of links swapped.
138
- */
139
- export function hotSwapCss(updated) {
140
- let swapped = 0;
141
- const links = document.querySelectorAll('link[rel="stylesheet"]');
142
- links.forEach((node) => {
143
- const link = node;
144
- const href = link.getAttribute('href');
145
- if (!href)
146
- return;
147
- const base = href.split('?')[0] ?? href;
148
- const matches = updated.some((u) => {
149
- const uBase = (u.split('?')[0] ?? u).replace(/^\.?\//, '');
150
- return uBase.endsWith(base.replace(/^\.?\//, ''));
151
- });
152
- if (matches) {
153
- link.setAttribute('href', `${base}?t=${Date.now()}`);
154
- swapped++;
155
- }
156
- });
157
- return swapped;
158
- }
159
- // ── EventSource client (transport) ──────────────────────────────────
160
- let connected = null;
161
- /**
162
- * Connect to the SSE change channel and apply updates with escalation.
163
- * Safe to call once from the dev client; injected by the plugin in watch builds.
164
- */
165
- export function connect(options = {}) {
166
- // Idempotency guard (critical for the bundled re-import model). The plugin
167
- // injects `connect()` at the entry's top level, and a JS update re-evaluates
168
- // the entry (so `__register` re-runs for changed tags). That re-eval also
169
- // re-runs this `connect()` call. Because `connected` lives in the persistent
170
- // HMR runtime chunk (re-imported entries reuse it by name, un-busted), the
171
- // guard survives across re-imports and keeps EXACTLY ONE EventSource + change
172
- // listener alive — otherwise listeners stack and each edit fans out into N
173
- // compounding re-imports.
174
- if (connected)
175
- return connected;
176
- const path = options.path ?? '/esbuild';
177
- const reload = options.reload ?? (() => location.reload());
178
- const reimport = options.reimport ?? defaultReimport;
179
- const factory = options.eventSourceFactory ??
180
- ((url) => new globalThis.EventSource(url));
181
- const source = factory(path);
182
- connected = source;
183
- source.addEventListener('change', (ev) => {
184
- let payload;
185
- try {
186
- payload = ev.data ? JSON.parse(ev.data) : { added: [], removed: [], updated: [] };
187
- }
188
- catch {
189
- reload();
190
- return;
191
- }
192
- void applyChange(payload, { reimport, reload });
193
- });
194
- return source;
195
- }
196
- /** Close the active SSE connection (test/teardown). */
197
- export function disconnect() {
198
- connected?.close?.();
199
- connected = null;
200
- }
201
- /**
202
- * Apply a classified change. Exposed for tests so the escalation ladder is
203
- * verifiable without a live EventSource.
204
- *
205
- * For a JS change we re-import (re-runs the wrapper's `__register` calls); if no
206
- * tag's setup actually changed, the edit was a non-component module (util/
207
- * service) and we escalate to a full reload — the correct, safe fallback.
208
- */
209
- export async function applyChange(payload, deps) {
210
- const action = classifyChange(payload);
211
- if (action === 'css') {
212
- hotSwapCss(payload.updated);
213
- return 'css';
214
- }
215
- if (action === 'js') {
216
- // Own the drain: suppress the auto microtask so ALL changed tags register
217
- // during re-import before any re-mount runs.
218
- suppressAutoDrain = true;
219
- remountScheduled = false;
220
- try {
221
- await deps.reimport();
222
- }
223
- finally {
224
- suppressAutoDrain = false;
225
- }
226
- if (pendingRemountCount() > 0) {
227
- drainRemounts();
228
- return 'js';
229
- }
230
- // JS changed but no component setup changed -> non-component edit -> reload.
231
- deps.reload();
232
- return 'reload';
233
- }
234
- deps.reload();
235
- return 'reload';
236
- }
237
- /**
238
- * Default re-import: re-fetch every module `<script type="module">` with a
239
- * cache-busting query so the bundle re-evaluates and the wrapper re-runs
240
- * `__register` for changed tags.
241
- */
242
- async function defaultReimport() {
243
- const scripts = document.querySelectorAll('script[type="module"][src]');
244
- const imports = [];
245
- scripts.forEach((node) => {
246
- const src = node.getAttribute('src');
247
- if (src)
248
- imports.push(import(/* @vite-ignore */ `${src.split('?')[0]}?t=${Date.now()}`));
249
- });
250
- await Promise.all(imports);
251
- }
252
- //# sourceMappingURL=runtime.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/esbuild-hmr/runtime.ts"],"names":[],"mappings":"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"}
@@ -1,51 +0,0 @@
1
- /**
2
- * esbuild-hmr/server.ts — Tiny SSE hub for the Nisli HMR change channel.
3
- *
4
- * DEV-ONLY, Node, build-time. Reachable only via `@nisli/core/esbuild-hmr`,
5
- * never the runtime `.` entry (ADR 0021, Ruling 1). Uses only `node:http`
6
- * (a Node built-in, not a package dependency), so the zero-dep posture of the
7
- * `.` entry is preserved.
8
- *
9
- * Two ways to wire transport (ADR 0021 Engineering Plan §1):
10
- * (a) Standalone: `createHmrServer()` starts a minimal `node:http` SSE hub at
11
- * `/esbuild` (matching esbuild's own client convention) and returns a
12
- * broadcaster the plugin pushes `change` payloads to.
13
- * (b) BYO: any object implementing {@link ChangeBroadcaster} (e.g. an esbuild
14
- * `serve` adapter that owns the `/esbuild` endpoint) can be handed to the
15
- * plugin instead.
16
- */
17
- import { type Server } from 'node:http';
18
- /** esbuild-shaped change payload broadcast to dev clients. */
19
- export interface ChangePayload {
20
- added: string[];
21
- removed: string[];
22
- updated: string[];
23
- }
24
- /** Anything that can push a `change` event to connected dev clients. */
25
- export interface ChangeBroadcaster {
26
- /** Broadcast a change to every connected client. */
27
- broadcast(payload: ChangePayload): void;
28
- /** Current number of connected clients (for diagnostics/tests). */
29
- readonly clientCount: number;
30
- }
31
- /** Handle for the standalone SSE hub. */
32
- export interface HmrServer extends ChangeBroadcaster {
33
- /** The underlying `node:http` server (for `.address()`, lifecycle, tests). */
34
- readonly server: Server;
35
- /** Start listening. Resolves once bound. */
36
- listen(port?: number, host?: string): Promise<{
37
- port: number;
38
- host: string;
39
- }>;
40
- /** Close all client connections and stop the server. */
41
- close(): Promise<void>;
42
- }
43
- export interface CreateHmrServerOptions {
44
- /** SSE route. Defaults to `/esbuild` (esbuild's client convention). */
45
- path?: string;
46
- }
47
- /**
48
- * Create a standalone SSE hub. Returns immediately; call `listen()` to bind.
49
- * The plugin calls `broadcast()` from its `onEnd` after each watch rebuild.
50
- */
51
- export declare function createHmrServer(options?: CreateHmrServerOptions): HmrServer;