@nisli/core 0.50.0 → 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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The tag→setup registry, the stable indirection thunk, and the in-place
5
5
  * re-mount through the custom-element lifecycle. This is the part that is
6
- * IDENTICAL regardless of how change notifications arrive — esbuild SSE
6
+ * IDENTICAL regardless of how change notifications arrive — change
7
7
  * (`../esbuild-hmr/*`) or Vite `import.meta.hot` (`../vite-hmr/*`) both drive
8
8
  * the same registry. Keeping it here means one implementation, one set of
9
9
  * correctness guarantees (ADR 0008.1 disposal), and no duplication.
@@ -17,7 +17,7 @@ export type HmrSetup = (props: unknown, host: HTMLElement) => unknown;
17
17
  * Begin a caller-owned drain window: suppress auto-scheduling AND cancel any
18
18
  * pending microtask drain, so the caller can re-evaluate a whole bundle and
19
19
  * register ALL changed tags before a single explicit `drainRemounts()`. Used by
20
- * the esbuild whole-bundle path; the Vite path drains per-module instead.
20
+ * the Vite path drains per-module.
21
21
  */
22
22
  export declare function beginManualDrain(): void;
23
23
  /** End a caller-owned drain window (re-enable auto-scheduling). */
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The tag→setup registry, the stable indirection thunk, and the in-place
5
5
  * re-mount through the custom-element lifecycle. This is the part that is
6
- * IDENTICAL regardless of how change notifications arrive — esbuild SSE
6
+ * IDENTICAL regardless of how change notifications arrive — change
7
7
  * (`../esbuild-hmr/*`) or Vite `import.meta.hot` (`../vite-hmr/*`) both drive
8
8
  * the same registry. Keeping it here means one implementation, one set of
9
9
  * correctness guarantees (ADR 0008.1 disposal), and no duplication.
@@ -19,7 +19,7 @@ let remountScheduled = false;
19
19
  /**
20
20
  * When true, `__register` accumulates pending tags but does NOT auto-schedule a
21
21
  * microtask drain — the caller owns the drain so it can wait for a full module
22
- * re-eval to register ALL changed tags first (the esbuild whole-bundle path).
22
+ * re-eval to register ALL changed tags first.
23
23
  * The Vite path leaves this false and drains explicitly from its accept hook.
24
24
  */
25
25
  let suppressAutoDrain = false;
@@ -27,7 +27,7 @@ let suppressAutoDrain = false;
27
27
  * Begin a caller-owned drain window: suppress auto-scheduling AND cancel any
28
28
  * pending microtask drain, so the caller can re-evaluate a whole bundle and
29
29
  * register ALL changed tags before a single explicit `drainRemounts()`. Used by
30
- * the esbuild whole-bundle path; the Vite path drains per-module instead.
30
+ * the Vite path drains per-module.
31
31
  */
32
32
  export function beginManualDrain() {
33
33
  suppressAutoDrain = true;
@@ -4,7 +4,7 @@
4
4
  * Wraps the `component` a module imports from `@nisli/core` with the registry
5
5
  * indirection so a rebuilt module's `component()` calls re-register their setup
6
6
  * and trigger an in-place re-mount. Transport-agnostic and pure — used by both
7
- * the esbuild `onLoad` transform and the Vite `transform` hook.
7
+ * the Vite `transform` hook.
8
8
  */
9
9
  /**
10
10
  * Rewrite a module's source so the `component` it imports from `coreSpecifier`
@@ -4,7 +4,7 @@
4
4
  * Wraps the `component` a module imports from `@nisli/core` with the registry
5
5
  * indirection so a rebuilt module's `component()` calls re-register their setup
6
6
  * and trigger an in-place re-mount. Transport-agnostic and pure — used by both
7
- * the esbuild `onLoad` transform and the Vite `transform` hook.
7
+ * the Vite `transform` hook.
8
8
  */
9
9
  function escapeRegExp(s) {
10
10
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * vite-hmr/plugin.ts — Dev-only Vite plugin for Nisli HMR (ADR 0110).
3
3
  *
4
- * Mirrors the esbuild plugin's component-wrapping (shared `transformSource`),
4
+ * Vite plugin that wraps component() calls (shared `transformSource`),
5
5
  * but drives re-mount through Vite's native `import.meta.hot` instead of an SSE
6
6
  * hub + whole-bundle re-import. For each source module that imports `component`
7
7
  * from `@nisli/core`, it:
@@ -8,7 +8,7 @@
8
8
  * - `__drain()` is called from the module's `import.meta.hot.accept` hook to
9
9
  * apply the queued re-mounts.
10
10
  *
11
- * Unlike the esbuild path, Vite re-evaluates ONLY the changed module against a
11
+ * Vite re-evaluates ONLY the changed module against a
12
12
  * STABLE framework module — so there is exactly one `@nisli/core` instance and
13
13
  * no lifecycle/reactive split-brain. This module just re-exports the
14
14
  * transport-agnostic registry core.
@@ -8,7 +8,7 @@
8
8
  * - `__drain()` is called from the module's `import.meta.hot.accept` hook to
9
9
  * apply the queued re-mounts.
10
10
  *
11
- * Unlike the esbuild path, Vite re-evaluates ONLY the changed module against a
11
+ * Vite re-evaluates ONLY the changed module against a
12
12
  * STABLE framework module — so there is exactly one `@nisli/core` instance and
13
13
  * no lifecycle/reactive split-brain. This module just re-exports the
14
14
  * transport-agnostic registry core.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nisli/core",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "A reactive web component framework. Signals, templates, dependency injection — no build step, no virtual DOM, no dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,18 +22,6 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "default": "./dist/index.js"
24
24
  },
25
- "./esbuild-hmr": {
26
- "types": "./dist/esbuild-hmr/plugin.d.ts",
27
- "default": "./dist/esbuild-hmr/plugin.js"
28
- },
29
- "./esbuild-hmr/runtime": {
30
- "types": "./dist/esbuild-hmr/runtime.d.ts",
31
- "default": "./dist/esbuild-hmr/runtime.js"
32
- },
33
- "./esbuild-hmr/server": {
34
- "types": "./dist/esbuild-hmr/server.d.ts",
35
- "default": "./dist/esbuild-hmr/server.js"
36
- },
37
25
  "./vite-hmr": {
38
26
  "types": "./dist/vite-hmr/plugin.d.ts",
39
27
  "default": "./dist/vite-hmr/plugin.js"
@@ -43,17 +31,8 @@
43
31
  "default": "./dist/vite-hmr/runtime.js"
44
32
  }
45
33
  },
46
- "peerDependencies": {
47
- "esbuild": "*"
48
- },
49
- "peerDependenciesMeta": {
50
- "esbuild": {
51
- "optional": true
52
- }
53
- },
54
34
  "devDependencies": {
55
- "@types/node": "^22.0.0",
56
- "esbuild": "^0.28.1"
35
+ "@types/node": "^22.0.0"
57
36
  },
58
37
  "files": [
59
38
  "dist",
@@ -1,154 +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
- import type { ChangeBroadcaster } from './server.js';
36
- /** A subset of esbuild's `OnLoadArgs` we rely on. */
37
- interface OnLoadArgs {
38
- path: string;
39
- }
40
- /** A subset of esbuild's `OnLoadResult`. */
41
- interface OnLoadResult {
42
- contents: string;
43
- loader?: 'ts' | 'tsx' | 'js' | 'jsx';
44
- /** Directory used to resolve imports from a virtual module (the client shim). */
45
- resolveDir?: string;
46
- }
47
- /** A subset of esbuild's `OnResolveArgs`. */
48
- interface OnResolveArgs {
49
- path: string;
50
- }
51
- /** A subset of esbuild's `OnResolveResult`. */
52
- interface OnResolveResult {
53
- path: string;
54
- namespace?: string;
55
- }
56
- /** A subset of esbuild's metafile. */
57
- interface Metafile {
58
- outputs: Record<string, {
59
- bytes: number;
60
- hash?: string;
61
- }>;
62
- }
63
- /** A subset of esbuild's `BuildResult`. */
64
- interface BuildResult {
65
- metafile?: Metafile;
66
- }
67
- /** The hooks surface of esbuild's `PluginBuild` we use. */
68
- interface PluginBuild {
69
- initialOptions: {
70
- banner?: {
71
- js?: string;
72
- } & Record<string, string | undefined>;
73
- /** Files esbuild injects into every JS module (never file/copy entries). */
74
- inject?: string[];
75
- /** esbuild's working directory; anchors virtual-module resolution. */
76
- absWorkingDir?: string;
77
- [key: string]: unknown;
78
- };
79
- onResolve(options: {
80
- filter: RegExp;
81
- namespace?: string;
82
- }, callback: (args: OnResolveArgs) => OnResolveResult | undefined): void;
83
- onLoad(options: {
84
- filter: RegExp;
85
- namespace?: string;
86
- }, callback: (args: OnLoadArgs) => OnLoadResult | undefined): void;
87
- onEnd(callback: (result: BuildResult) => void): void;
88
- }
89
- /** esbuild's `Plugin` shape (structural). */
90
- export interface EsbuildPlugin {
91
- name: string;
92
- setup(build: PluginBuild): void;
93
- }
94
- export interface NisliHmrPluginOptions {
95
- /**
96
- * Broadcaster used to push `change` events to connected clients. Supply the
97
- * one returned by `createHmrServer()` (server.ts), or any object with a
98
- * `broadcast(payload)` method (e.g. an esbuild `serve` adapter).
99
- */
100
- broadcaster: ChangeBroadcaster;
101
- /**
102
- * The module specifier author code imports `component` from.
103
- * Defaults to `@nisli/core`.
104
- */
105
- coreSpecifier?: string;
106
- /**
107
- * The specifier of the browser dev runtime, inlined into the bundle via the
108
- * injected client shim. Defaults to `@nisli/core/esbuild-hmr/runtime`.
109
- */
110
- runtimeSpecifier?: string;
111
- /**
112
- * The SSE endpoint the browser dev client connects to. Baked into the injected
113
- * shim as `connect({ path: clientUrl })`. Defaults to `/esbuild` (esbuild's
114
- * client convention, same-origin).
115
- *
116
- * Set this to an ABSOLUTE URL when the SSE hub (server.ts) runs on a different
117
- * origin than the page server — e.g. `http://localhost:3031/esbuild` when the
118
- * hub listens on :3031 and the page is served by Hono on :3040. The hub already
119
- * sends `Access-Control-Allow-Origin: '*'`, so cross-origin SSE works.
120
- */
121
- clientUrl?: string;
122
- /** Files to transform. Defaults to TS/JS sources (excludes node_modules). */
123
- filter?: RegExp;
124
- }
125
- /**
126
- * `transformSource` (wrap `component()` call sites) now lives in the shared
127
- * `../hmr/transform.js` and is re-exported here so this module's surface — and
128
- * the tests that import it from './plugin.js' — are unchanged (ADR 0110).
129
- */
130
- import { transformSource } from '../hmr/transform.js';
131
- export { transformSource };
132
- /**
133
- * Source of the virtual client shim that esbuild's `inject` pulls into every JS
134
- * module. Because it is `inject`ed (not a `banner`), esbuild RESOLVES the
135
- * `runtimeSpecifier` import and BUNDLES `connect` inline — no bare specifier
136
- * survives in the output (the 0.49.0 bug). `clientUrl` is baked in so the
137
- * client targets the right SSE origin. Pure function — exported for testing.
138
- */
139
- export declare function clientShimSource(runtimeSpecifier: string, clientUrl: string): string;
140
- /**
141
- * Create the dev-only Nisli HMR esbuild plugin. Add it to `plugins` ONLY when
142
- * building with `--watch`; never on the prod build path.
143
- */
144
- export declare function nisliHmrPlugin(options: NisliHmrPluginOptions): EsbuildPlugin;
145
- /**
146
- * Diff two output-hash maps into an esbuild-shaped change payload.
147
- * Returns `null` for the very first build (no previous baseline).
148
- * Exported for testing.
149
- */
150
- export declare function diffOutputs(prev: Map<string, string> | null, next: Map<string, string>): {
151
- added: string[];
152
- removed: string[];
153
- updated: string[];
154
- } | null;
@@ -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"}