@nisli/core 0.50.0 → 0.52.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,183 +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
- * `transformSource` (wrap `component()` call sites) now lives in the shared
49
- * `../hmr/transform.js` and is re-exported here so this module's surface — and
50
- * the tests that import it from './plugin.js' — are unchanged (ADR 0110).
51
- */
52
- import { transformSource } from '../hmr/transform.js';
53
- export { transformSource };
54
- /**
55
- * Source of the virtual client shim that esbuild's `inject` pulls into every JS
56
- * module. Because it is `inject`ed (not a `banner`), esbuild RESOLVES the
57
- * `runtimeSpecifier` import and BUNDLES `connect` inline — no bare specifier
58
- * survives in the output (the 0.49.0 bug). `clientUrl` is baked in so the
59
- * client targets the right SSE origin. Pure function — exported for testing.
60
- */
61
- export function clientShimSource(runtimeSpecifier, clientUrl) {
62
- return (`import { connect as __nisliConnect } from '${runtimeSpecifier}';\n` +
63
- `__nisliConnect({ path: ${JSON.stringify(clientUrl)} });\n`);
64
- }
65
- // ── The plugin ──────────────────────────────────────────────────────
66
- /**
67
- * Create the dev-only Nisli HMR esbuild plugin. Add it to `plugins` ONLY when
68
- * building with `--watch`; never on the prod build path.
69
- */
70
- export function nisliHmrPlugin(options) {
71
- const coreSpecifier = options.coreSpecifier ?? '@nisli/core';
72
- const runtimeSpecifier = options.runtimeSpecifier ?? '@nisli/core/esbuild-hmr/runtime';
73
- const clientUrl = options.clientUrl ?? '/esbuild';
74
- const filter = options.filter ?? DEFAULT_FILTER;
75
- const { broadcaster } = options;
76
- let prevHashes = null;
77
- return {
78
- name: 'nisli-hmr',
79
- setup(build) {
80
- // Inject the browser dev client via esbuild's `inject` (NOT `banner`).
81
- // A banner is raw passthrough text — its bare `import` would leak into the
82
- // output and the browser could not resolve it. `inject` points at a
83
- // virtual shim that esbuild resolves + bundles, inlining the runtime so no
84
- // bare specifier survives. esbuild applies `inject` only to JS modules, so
85
- // the viewer's file/copy-loaded `logo.svg` entry is never touched.
86
- build.initialOptions.inject = [
87
- ...(build.initialOptions.inject ?? []),
88
- CLIENT_SHIM_PATH,
89
- ];
90
- build.onResolve({ filter: CLIENT_SHIM_FILTER }, (args) => {
91
- if (args.path !== CLIENT_SHIM_PATH)
92
- return undefined;
93
- return { path: CLIENT_SHIM_PATH, namespace: CLIENT_SHIM_NAMESPACE };
94
- });
95
- build.onLoad({ filter: /.*/, namespace: CLIENT_SHIM_NAMESPACE }, () => ({
96
- contents: clientShimSource(runtimeSpecifier, clientUrl),
97
- loader: 'js',
98
- // A virtual module has no on-disk location, so esbuild won't know where
99
- // to resolve the runtime import from. Anchor it to the build's working
100
- // directory (where the consumer's `@nisli/core` resolves).
101
- resolveDir: build.initialOptions.absWorkingDir ?? process.cwd(),
102
- }));
103
- // 1. Transform: wrap component() call sites (Ruling 2).
104
- build.onLoad({ filter }, (args) => {
105
- if (args.path.includes('node_modules'))
106
- return undefined;
107
- const code = readFileSafe(args.path);
108
- if (code === null)
109
- return undefined;
110
- const contents = transformSource(code, coreSpecifier, runtimeSpecifier);
111
- if (contents === code)
112
- return undefined; // nothing to wrap; let esbuild load it
113
- return { contents, loader: loaderFor(args.path) };
114
- });
115
- // 2. Diff outputs and broadcast a change payload (Ruling 4 transport).
116
- build.onEnd((result) => {
117
- const outputs = result.metafile?.outputs;
118
- if (!outputs) {
119
- // No metafile -> cannot diff -> signal a reload-worthy change.
120
- broadcaster.broadcast({ added: [], removed: [], updated: [] });
121
- return;
122
- }
123
- const nextHashes = new Map();
124
- for (const [path, meta] of Object.entries(outputs)) {
125
- nextHashes.set(path, meta.hash ?? String(meta.bytes));
126
- }
127
- const payload = diffOutputs(prevHashes, nextHashes);
128
- prevHashes = nextHashes;
129
- // First build establishes the baseline; don't broadcast on it.
130
- if (payload)
131
- broadcaster.broadcast(payload);
132
- });
133
- },
134
- };
135
- }
136
- /**
137
- * Diff two output-hash maps into an esbuild-shaped change payload.
138
- * Returns `null` for the very first build (no previous baseline).
139
- * Exported for testing.
140
- */
141
- export function diffOutputs(prev, next) {
142
- if (prev === null)
143
- return null;
144
- const added = [];
145
- const removed = [];
146
- const updated = [];
147
- for (const [path, hash] of next) {
148
- const before = prev.get(path);
149
- if (before === undefined)
150
- added.push(path);
151
- else if (before !== hash)
152
- updated.push(path);
153
- }
154
- for (const path of prev.keys()) {
155
- if (!next.has(path))
156
- removed.push(path);
157
- }
158
- return { added, removed, updated };
159
- }
160
- // ── helpers ─────────────────────────────────────────────────────────
161
- function loaderFor(path) {
162
- if (path.endsWith('.tsx'))
163
- return 'tsx';
164
- if (path.endsWith('.jsx'))
165
- return 'jsx';
166
- if (/\.[mc]?ts$/.test(path))
167
- return 'ts';
168
- return 'js';
169
- }
170
- /**
171
- * Read a file synchronously. Build-time only (Node); returns null on failure so
172
- * esbuild falls back to its own loader. `node:fs` is fine here — this module is
173
- * never reachable from the `.` runtime graph (ADR 0021, Ruling 1).
174
- */
175
- function readFileSafe(path) {
176
- try {
177
- return readFileSync(path, 'utf8');
178
- }
179
- catch {
180
- return null;
181
- }
182
- }
183
- //# 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,86 +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
- export { __register, pendingRemountCount, drainRemounts, remount, __resetRegistry, type HmrSetup, } from '../hmr/registry.js';
21
- /** esbuild's `change` event payload shape (added/removed/updated paths). */
22
- export interface EsbuildChangePayload {
23
- added: string[];
24
- removed: string[];
25
- updated: string[];
26
- }
27
- /** What to do with a change, decided from the output diff (Ruling 4). */
28
- export type HmrAction = 'css' | 'js' | 'reload';
29
- /** Minimal structural EventSource (avoids depending on DOM lib EventSource). */
30
- interface EventSourceLike {
31
- addEventListener(type: string, listener: (ev: {
32
- data?: string;
33
- }) => void): void;
34
- close?(): void;
35
- }
36
- /** Options for {@link connect}. Seams kept injectable for tests. */
37
- export interface ConnectOptions {
38
- /** SSE endpoint. Defaults to esbuild's `/esbuild`. */
39
- path?: string;
40
- /**
41
- * Factory for the EventSource. Defaults to the global `EventSource`.
42
- * Injectable so the client logic is testable without a live SSE server.
43
- */
44
- eventSourceFactory?: (url: string) => EventSourceLike;
45
- /**
46
- * Re-import the rebuilt JS so the wrapper re-runs `__register` for changed
47
- * tags. Defaults to a cache-busted dynamic import of the page's module
48
- * scripts. Returns a promise resolving when re-evaluation is done.
49
- */
50
- reimport?: () => Promise<void>;
51
- /** Force a full reload. Defaults to `location.reload()`. */
52
- reload?: () => void;
53
- }
54
- /**
55
- * Decide the action for an esbuild output diff:
56
- * - every changed output is CSS -> 'css' (hot-swap, preserves everything)
57
- * - any JS output changed -> 'js' (re-import; re-mount changed tags
58
- * or escalate to reload if none)
59
- * - nothing actionable -> 'reload'
60
- */
61
- export declare function classifyChange(payload: EsbuildChangePayload): HmrAction;
62
- /**
63
- * Hot-swap stylesheets in place by re-pointing matching `<link>` hrefs with a
64
- * cache-busting query — no reload, no state loss (esbuild's documented CSS
65
- * technique). Returns the number of links swapped.
66
- */
67
- export declare function hotSwapCss(updated: string[]): number;
68
- /**
69
- * Connect to the SSE change channel and apply updates with escalation.
70
- * Safe to call once from the dev client; injected by the plugin in watch builds.
71
- */
72
- export declare function connect(options?: ConnectOptions): EventSourceLike;
73
- /** Close the active SSE connection (test/teardown). */
74
- export declare function disconnect(): void;
75
- /**
76
- * Apply a classified change. Exposed for tests so the escalation ladder is
77
- * verifiable without a live EventSource.
78
- *
79
- * For a JS change we re-import (re-runs the wrapper's `__register` calls); if no
80
- * tag's setup actually changed, the edit was a non-component module (util/
81
- * service) and we escalate to a full reload — the correct, safe fallback.
82
- */
83
- export declare function applyChange(payload: EsbuildChangePayload, deps: {
84
- reimport: () => Promise<void>;
85
- reload: () => void;
86
- }): Promise<HmrAction>;
@@ -1,163 +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
- // ── Types ───────────────────────────────────────────────────────────
21
- // Transport-agnostic registry/remount core lives in ../hmr/registry.js and is
22
- // shared with the Vite adapter (ADR 0110). Re-exported here so this module's
23
- // public surface — and the tests that import from './runtime.js' — are
24
- // unchanged by the extraction.
25
- export { __register, pendingRemountCount, drainRemounts, remount, __resetRegistry, } from '../hmr/registry.js';
26
- import { beginManualDrain, drainRemounts, endManualDrain, pendingRemountCount } from '../hmr/registry.js';
27
- // ── Change classification + escalation (Ruling 4) ───────────────────
28
- const CSS_RE = /\.css($|\?)/;
29
- /**
30
- * Decide the action for an esbuild output diff:
31
- * - every changed output is CSS -> 'css' (hot-swap, preserves everything)
32
- * - any JS output changed -> 'js' (re-import; re-mount changed tags
33
- * or escalate to reload if none)
34
- * - nothing actionable -> 'reload'
35
- */
36
- export function classifyChange(payload) {
37
- const touched = [...payload.added, ...payload.removed, ...payload.updated];
38
- if (touched.length === 0)
39
- return 'reload';
40
- if (touched.every((p) => CSS_RE.test(p)))
41
- return 'css';
42
- if (touched.some((p) => /\.[mc]?js($|\?)/.test(p)))
43
- return 'js';
44
- return 'reload';
45
- }
46
- /**
47
- * Hot-swap stylesheets in place by re-pointing matching `<link>` hrefs with a
48
- * cache-busting query — no reload, no state loss (esbuild's documented CSS
49
- * technique). Returns the number of links swapped.
50
- */
51
- export function hotSwapCss(updated) {
52
- let swapped = 0;
53
- const links = document.querySelectorAll('link[rel="stylesheet"]');
54
- links.forEach((node) => {
55
- const link = node;
56
- const href = link.getAttribute('href');
57
- if (!href)
58
- return;
59
- const base = href.split('?')[0] ?? href;
60
- const matches = updated.some((u) => {
61
- const uBase = (u.split('?')[0] ?? u).replace(/^\.?\//, '');
62
- return uBase.endsWith(base.replace(/^\.?\//, ''));
63
- });
64
- if (matches) {
65
- link.setAttribute('href', `${base}?t=${Date.now()}`);
66
- swapped++;
67
- }
68
- });
69
- return swapped;
70
- }
71
- // ── EventSource client (transport) ──────────────────────────────────
72
- let connected = null;
73
- /**
74
- * Connect to the SSE change channel and apply updates with escalation.
75
- * Safe to call once from the dev client; injected by the plugin in watch builds.
76
- */
77
- export function connect(options = {}) {
78
- // Idempotency guard (critical for the bundled re-import model). The plugin
79
- // injects `connect()` at the entry's top level, and a JS update re-evaluates
80
- // the entry (so `__register` re-runs for changed tags). That re-eval also
81
- // re-runs this `connect()` call. Because `connected` lives in the persistent
82
- // HMR runtime chunk (re-imported entries reuse it by name, un-busted), the
83
- // guard survives across re-imports and keeps EXACTLY ONE EventSource + change
84
- // listener alive — otherwise listeners stack and each edit fans out into N
85
- // compounding re-imports.
86
- if (connected)
87
- return connected;
88
- const path = options.path ?? '/esbuild';
89
- const reload = options.reload ?? (() => location.reload());
90
- const reimport = options.reimport ?? defaultReimport;
91
- const factory = options.eventSourceFactory ??
92
- ((url) => new globalThis.EventSource(url));
93
- const source = factory(path);
94
- connected = source;
95
- source.addEventListener('change', (ev) => {
96
- let payload;
97
- try {
98
- payload = ev.data ? JSON.parse(ev.data) : { added: [], removed: [], updated: [] };
99
- }
100
- catch {
101
- reload();
102
- return;
103
- }
104
- void applyChange(payload, { reimport, reload });
105
- });
106
- return source;
107
- }
108
- /** Close the active SSE connection (test/teardown). */
109
- export function disconnect() {
110
- connected?.close?.();
111
- connected = null;
112
- }
113
- /**
114
- * Apply a classified change. Exposed for tests so the escalation ladder is
115
- * verifiable without a live EventSource.
116
- *
117
- * For a JS change we re-import (re-runs the wrapper's `__register` calls); if no
118
- * tag's setup actually changed, the edit was a non-component module (util/
119
- * service) and we escalate to a full reload — the correct, safe fallback.
120
- */
121
- export async function applyChange(payload, deps) {
122
- const action = classifyChange(payload);
123
- if (action === 'css') {
124
- hotSwapCss(payload.updated);
125
- return 'css';
126
- }
127
- if (action === 'js') {
128
- // Own the drain: suppress the auto microtask so ALL changed tags register
129
- // during re-import before any re-mount runs.
130
- beginManualDrain();
131
- try {
132
- await deps.reimport();
133
- }
134
- finally {
135
- endManualDrain();
136
- }
137
- if (pendingRemountCount() > 0) {
138
- drainRemounts();
139
- return 'js';
140
- }
141
- // JS changed but no component setup changed -> non-component edit -> reload.
142
- deps.reload();
143
- return 'reload';
144
- }
145
- deps.reload();
146
- return 'reload';
147
- }
148
- /**
149
- * Default re-import: re-fetch every module `<script type="module">` with a
150
- * cache-busting query so the bundle re-evaluates and the wrapper re-runs
151
- * `__register` for changed tags.
152
- */
153
- async function defaultReimport() {
154
- const scripts = document.querySelectorAll('script[type="module"][src]');
155
- const imports = [];
156
- scripts.forEach((node) => {
157
- const src = node.getAttribute('src');
158
- if (src)
159
- imports.push(import(/* @vite-ignore */ `${src.split('?')[0]}?t=${Date.now()}`));
160
- });
161
- await Promise.all(imports);
162
- }
163
- //# 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;
@@ -1,87 +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
- /// <reference types="node" />
18
- import { createServer } from 'node:http';
19
- /**
20
- * Create a standalone SSE hub. Returns immediately; call `listen()` to bind.
21
- * The plugin calls `broadcast()` from its `onEnd` after each watch rebuild.
22
- */
23
- export function createHmrServer(options = {}) {
24
- const path = options.path ?? '/esbuild';
25
- const clients = new Set();
26
- const server = createServer((req, res) => {
27
- if (req.url !== path) {
28
- res.statusCode = 404;
29
- res.end();
30
- return;
31
- }
32
- // SSE handshake.
33
- res.writeHead(200, {
34
- 'Content-Type': 'text/event-stream',
35
- 'Cache-Control': 'no-cache',
36
- Connection: 'keep-alive',
37
- 'Access-Control-Allow-Origin': '*',
38
- });
39
- res.write('retry: 500\n\n');
40
- clients.add(res);
41
- req.on('close', () => {
42
- clients.delete(res);
43
- });
44
- });
45
- const broadcast = (payload) => {
46
- const frame = `event: change\ndata: ${JSON.stringify(payload)}\n\n`;
47
- for (const res of clients) {
48
- try {
49
- res.write(frame);
50
- }
51
- catch {
52
- clients.delete(res);
53
- }
54
- }
55
- };
56
- return {
57
- server,
58
- broadcast,
59
- get clientCount() {
60
- return clients.size;
61
- },
62
- listen(port = 0, host = '127.0.0.1') {
63
- return new Promise((resolve) => {
64
- server.listen(port, host, () => {
65
- const addr = server.address();
66
- const boundPort = typeof addr === 'object' && addr ? addr.port : port;
67
- resolve({ port: boundPort, host });
68
- });
69
- });
70
- },
71
- close() {
72
- return new Promise((resolve) => {
73
- for (const res of clients) {
74
- try {
75
- res.end();
76
- }
77
- catch {
78
- /* ignore */
79
- }
80
- }
81
- clients.clear();
82
- server.close(() => resolve());
83
- });
84
- },
85
- };
86
- }
87
- //# sourceMappingURL=server.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/esbuild-hmr/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,8BAA8B;AAC9B,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAC;AAgCjG;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IAClE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAoB,EAAE,GAAmB,EAAE,EAAE;QACxE,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;YACrB,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,iBAAiB;QACjB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACjB,cAAc,EAAE,mBAAmB;YACnC,eAAe,EAAE,UAAU;YAC3B,UAAU,EAAE,YAAY;YACxB,6BAA6B,EAAE,GAAG;SACnC,CAAC,CAAC;QACH,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,CAAC,OAAsB,EAAQ,EAAE;QACjD,MAAM,KAAK,GAAG,wBAAwB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC;QACpE,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,MAAM;QACN,SAAS;QACT,IAAI,WAAW;YACb,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;QACD,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,WAAW;YACjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;oBAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC9B,MAAM,SAAS,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;oBACtE,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrC,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QACD,KAAK;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC7B,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;oBAC1B,IAAI,CAAC;wBACH,GAAG,CAAC,GAAG,EAAE,CAAC;oBACZ,CAAC;oBAAC,MAAM,CAAC;wBACP,YAAY;oBACd,CAAC;gBACH,CAAC;gBACD,OAAO,CAAC,KAAK,EAAE,CAAC;gBAChB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}