@bgub/fig-refresh 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Ben Gubler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @bgub/fig-refresh
|
|
2
|
+
|
|
3
|
+
Fast Refresh runtime for [Fig](https://github.com/bgub/fig). It tracks
|
|
4
|
+
component families across module re-evaluations and installs the refresh
|
|
5
|
+
handler that lets the reconciler swap implementations in place while
|
|
6
|
+
preserving state.
|
|
7
|
+
|
|
8
|
+
This package is wiring, not an API you call from application code: build
|
|
9
|
+
tooling (such as `@bgub/fig-vite`) injects `register`/`setSignature` calls
|
|
10
|
+
into transformed modules and drives `performRefresh` on hot updates.
|
|
11
|
+
|
|
12
|
+
See the repository's `concepts/` directory for the underlying contracts.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @bgub/fig-refresh
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Fig packages are ESM-only and require Node `^20.19.0 || >=22.12.0` for Node runtime entry
|
|
21
|
+
points.
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RefreshUpdate } from "@bgub/fig-reconciler/refresh";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
declare function register(type: unknown, id: string): void;
|
|
4
|
+
declare function setSignature(type: unknown, key: string, forceReset?: boolean): void;
|
|
5
|
+
declare function injectScheduleRefresh(scheduleRefresh: (update: RefreshUpdate) => void): void;
|
|
6
|
+
declare function performRefresh(): RefreshUpdate | null;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { injectScheduleRefresh, performRefresh, register, setSignature };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { setRefreshHandler } from "@bgub/fig-reconciler/refresh";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
const familiesByType = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
const familiesById = /* @__PURE__ */ new Map();
|
|
5
|
+
const signatures = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const scheduleRefreshFns = /* @__PURE__ */ new Set();
|
|
7
|
+
const unscheduledRefreshes = [];
|
|
8
|
+
let pendingUpdates = [];
|
|
9
|
+
let installed = false;
|
|
10
|
+
function asKey(type) {
|
|
11
|
+
return typeof type === "function" || typeof type === "object" && type !== null ? type : null;
|
|
12
|
+
}
|
|
13
|
+
function ensureInstalled() {
|
|
14
|
+
if (installed) return;
|
|
15
|
+
installed = true;
|
|
16
|
+
setRefreshHandler(resolveFamilyByType);
|
|
17
|
+
}
|
|
18
|
+
function resolveFamilyByType(type) {
|
|
19
|
+
const key = asKey(type);
|
|
20
|
+
return key === null ? void 0 : familiesByType.get(key);
|
|
21
|
+
}
|
|
22
|
+
function register(type, id) {
|
|
23
|
+
ensureInstalled();
|
|
24
|
+
const key = asKey(type);
|
|
25
|
+
if (key === null || familiesByType.has(key)) return;
|
|
26
|
+
let family = familiesById.get(id);
|
|
27
|
+
if (family === void 0) {
|
|
28
|
+
family = { current: type };
|
|
29
|
+
familiesById.set(id, family);
|
|
30
|
+
} else pendingUpdates.push([family, key]);
|
|
31
|
+
familiesByType.set(key, family);
|
|
32
|
+
}
|
|
33
|
+
function setSignature(type, key, forceReset = false) {
|
|
34
|
+
const target = asKey(type);
|
|
35
|
+
if (target !== null) signatures.set(target, {
|
|
36
|
+
forceReset,
|
|
37
|
+
key
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function injectScheduleRefresh(scheduleRefresh) {
|
|
41
|
+
ensureInstalled();
|
|
42
|
+
scheduleRefreshFns.add(scheduleRefresh);
|
|
43
|
+
for (const update of unscheduledRefreshes) scheduleRefresh(update);
|
|
44
|
+
unscheduledRefreshes.length = 0;
|
|
45
|
+
}
|
|
46
|
+
function performRefresh() {
|
|
47
|
+
if (pendingUpdates.length === 0) return null;
|
|
48
|
+
const updates = pendingUpdates;
|
|
49
|
+
pendingUpdates = [];
|
|
50
|
+
const updatedFamilies = /* @__PURE__ */ new Set();
|
|
51
|
+
const staleFamilies = /* @__PURE__ */ new Set();
|
|
52
|
+
for (const [family, nextType] of updates) {
|
|
53
|
+
const prevType = family.current;
|
|
54
|
+
family.current = nextType;
|
|
55
|
+
if (isSignatureStale(prevType, nextType)) staleFamilies.add(family);
|
|
56
|
+
else updatedFamilies.add(family);
|
|
57
|
+
}
|
|
58
|
+
for (const family of staleFamilies) updatedFamilies.delete(family);
|
|
59
|
+
const update = {
|
|
60
|
+
staleFamilies,
|
|
61
|
+
updatedFamilies
|
|
62
|
+
};
|
|
63
|
+
if (scheduleRefreshFns.size === 0) {
|
|
64
|
+
unscheduledRefreshes.push(update);
|
|
65
|
+
return update;
|
|
66
|
+
}
|
|
67
|
+
for (const scheduleRefresh of scheduleRefreshFns) scheduleRefresh(update);
|
|
68
|
+
return update;
|
|
69
|
+
}
|
|
70
|
+
function isSignatureStale(prevType, nextType) {
|
|
71
|
+
const prevKey = asKey(prevType);
|
|
72
|
+
const nextKey = asKey(nextType);
|
|
73
|
+
const prev = prevKey === null ? void 0 : signatures.get(prevKey);
|
|
74
|
+
const next = nextKey === null ? void 0 : signatures.get(nextKey);
|
|
75
|
+
if (next?.forceReset === true) return true;
|
|
76
|
+
if (prev === void 0 && next === void 0) return false;
|
|
77
|
+
if (prev === void 0 || next === void 0) return true;
|
|
78
|
+
return prev.key !== next.key;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { injectScheduleRefresh, performRefresh, register, setSignature };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n type RefreshFamily,\n type RefreshUpdate,\n setRefreshHandler,\n} from \"@bgub/fig-reconciler/refresh\";\n\n// The Fig Fast Refresh runtime. A bundler transform (dev only) registers each\n// component version under a stable id and records its hook signature, then calls\n// performRefresh() from an import.meta.hot.accept handler. The runtime groups\n// versions into families, decides which can re-render in place vs must remount,\n// and drives the reconciler.\n\ninterface Signature {\n forceReset: boolean;\n key: string;\n}\n\nconst familiesByType = new WeakMap<object, RefreshFamily>();\nconst familiesById = new Map<string, RefreshFamily>();\nconst signatures = new WeakMap<object, Signature>();\nconst scheduleRefreshFns = new Set<(update: RefreshUpdate) => void>();\nconst unscheduledRefreshes: RefreshUpdate[] = [];\nlet pendingUpdates: Array<[RefreshFamily, object]> = [];\nlet installed = false;\n\nfunction asKey(type: unknown): object | null {\n return typeof type === \"function\" ||\n (typeof type === \"object\" && type !== null)\n ? (type as object)\n : null;\n}\n\n// Install the reconciler handler lazily so importing the runtime has no effect\n// until something actually registers (keeps it tree-shakeable / prod-safe).\nfunction ensureInstalled(): void {\n if (installed) return;\n installed = true;\n setRefreshHandler(resolveFamilyByType);\n}\n\nfunction resolveFamilyByType(type: unknown): RefreshFamily | undefined {\n const key = asKey(type);\n return key === null ? undefined : familiesByType.get(key);\n}\n\n// Register a component version under a stable id (e.g. \"src/App.tsx#App\"). The\n// first version creates a family; later versions of the same id queue an update.\nexport function register(type: unknown, id: string): void {\n ensureInstalled();\n const key = asKey(type);\n if (key === null || familiesByType.has(key)) return;\n\n let family = familiesById.get(id);\n if (family === undefined) {\n family = { current: type };\n familiesById.set(id, family);\n } else {\n pendingUpdates.push([family, key]);\n }\n familiesByType.set(key, family);\n}\n\n// Record a component's hook signature. Differing signatures between versions\n// (or forceReset) mean a remount; identical signatures re-render in place.\nexport function setSignature(\n type: unknown,\n key: string,\n forceReset = false,\n): void {\n const target = asKey(type);\n if (target !== null) signatures.set(target, { forceReset, key });\n}\n\n// Connect a renderer's scheduleRefresh (e.g. from @bgub/fig-dom). The app's dev\n// bootstrap calls this once.\nexport function injectScheduleRefresh(\n scheduleRefresh: (update: RefreshUpdate) => void,\n): void {\n ensureInstalled();\n scheduleRefreshFns.add(scheduleRefresh);\n for (const update of unscheduledRefreshes) scheduleRefresh(update);\n unscheduledRefreshes.length = 0;\n}\n\n// Apply queued registrations: advance each family to its newest version, bucket\n// it as updated (re-render in place) or stale (remount), and drive the renderers.\nexport function performRefresh(): RefreshUpdate | null {\n if (pendingUpdates.length === 0) return null;\n\n const updates = pendingUpdates;\n pendingUpdates = [];\n\n const updatedFamilies = new Set<RefreshFamily>();\n const staleFamilies = new Set<RefreshFamily>();\n\n for (const [family, nextType] of updates) {\n const prevType = family.current;\n family.current = nextType;\n if (isSignatureStale(prevType, nextType)) staleFamilies.add(family);\n else updatedFamilies.add(family);\n }\n\n // If a family was edited more than once and any edit was stale, prefer remount.\n for (const family of staleFamilies) updatedFamilies.delete(family);\n\n const update: RefreshUpdate = { staleFamilies, updatedFamilies };\n if (scheduleRefreshFns.size === 0) {\n unscheduledRefreshes.push(update);\n return update;\n }\n for (const scheduleRefresh of scheduleRefreshFns) scheduleRefresh(update);\n return update;\n}\n\nfunction isSignatureStale(prevType: unknown, nextType: unknown): boolean {\n const prevKey = asKey(prevType);\n const nextKey = asKey(nextType);\n const prev = prevKey === null ? undefined : signatures.get(prevKey);\n const next = nextKey === null ? undefined : signatures.get(nextKey);\n\n if (next?.forceReset === true) return true;\n // Neither version recorded a signature: treat as a safe in-place update.\n if (prev === undefined && next === undefined) return false;\n // One has a signature and the other doesn't, or the keys differ: remount.\n if (prev === undefined || next === undefined) return true;\n return prev.key !== next.key;\n}\n"],"mappings":";;AAiBA,MAAM,iCAAiB,IAAI,QAA+B;AAC1D,MAAM,+BAAe,IAAI,IAA2B;AACpD,MAAM,6BAAa,IAAI,QAA2B;AAClD,MAAM,qCAAqB,IAAI,IAAqC;AACpE,MAAM,uBAAwC,CAAC;AAC/C,IAAI,iBAAiD,CAAC;AACtD,IAAI,YAAY;AAEhB,SAAS,MAAM,MAA8B;CAC3C,OAAO,OAAO,SAAS,cACpB,OAAO,SAAS,YAAY,SAAS,OACnC,OACD;AACN;AAIA,SAAS,kBAAwB;CAC/B,IAAI,WAAW;CACf,YAAY;CACZ,kBAAkB,mBAAmB;AACvC;AAEA,SAAS,oBAAoB,MAA0C;CACrE,MAAM,MAAM,MAAM,IAAI;CACtB,OAAO,QAAQ,OAAO,KAAA,IAAY,eAAe,IAAI,GAAG;AAC1D;AAIA,SAAgB,SAAS,MAAe,IAAkB;CACxD,gBAAgB;CAChB,MAAM,MAAM,MAAM,IAAI;CACtB,IAAI,QAAQ,QAAQ,eAAe,IAAI,GAAG,GAAG;CAE7C,IAAI,SAAS,aAAa,IAAI,EAAE;CAChC,IAAI,WAAW,KAAA,GAAW;EACxB,SAAS,EAAE,SAAS,KAAK;EACzB,aAAa,IAAI,IAAI,MAAM;CAC7B,OACE,eAAe,KAAK,CAAC,QAAQ,GAAG,CAAC;CAEnC,eAAe,IAAI,KAAK,MAAM;AAChC;AAIA,SAAgB,aACd,MACA,KACA,aAAa,OACP;CACN,MAAM,SAAS,MAAM,IAAI;CACzB,IAAI,WAAW,MAAM,WAAW,IAAI,QAAQ;EAAE;EAAY;CAAI,CAAC;AACjE;AAIA,SAAgB,sBACd,iBACM;CACN,gBAAgB;CAChB,mBAAmB,IAAI,eAAe;CACtC,KAAK,MAAM,UAAU,sBAAsB,gBAAgB,MAAM;CACjE,qBAAqB,SAAS;AAChC;AAIA,SAAgB,iBAAuC;CACrD,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,MAAM,UAAU;CAChB,iBAAiB,CAAC;CAElB,MAAM,kCAAkB,IAAI,IAAmB;CAC/C,MAAM,gCAAgB,IAAI,IAAmB;CAE7C,KAAK,MAAM,CAAC,QAAQ,aAAa,SAAS;EACxC,MAAM,WAAW,OAAO;EACxB,OAAO,UAAU;EACjB,IAAI,iBAAiB,UAAU,QAAQ,GAAG,cAAc,IAAI,MAAM;OAC7D,gBAAgB,IAAI,MAAM;CACjC;CAGA,KAAK,MAAM,UAAU,eAAe,gBAAgB,OAAO,MAAM;CAEjE,MAAM,SAAwB;EAAE;EAAe;CAAgB;CAC/D,IAAI,mBAAmB,SAAS,GAAG;EACjC,qBAAqB,KAAK,MAAM;EAChC,OAAO;CACT;CACA,KAAK,MAAM,mBAAmB,oBAAoB,gBAAgB,MAAM;CACxE,OAAO;AACT;AAEA,SAAS,iBAAiB,UAAmB,UAA4B;CACvE,MAAM,UAAU,MAAM,QAAQ;CAC9B,MAAM,UAAU,MAAM,QAAQ;CAC9B,MAAM,OAAO,YAAY,OAAO,KAAA,IAAY,WAAW,IAAI,OAAO;CAClE,MAAM,OAAO,YAAY,OAAO,KAAA,IAAY,WAAW,IAAI,OAAO;CAElE,IAAI,MAAM,eAAe,MAAM,OAAO;CAEtC,IAAI,SAAS,KAAA,KAAa,SAAS,KAAA,GAAW,OAAO;CAErD,IAAI,SAAS,KAAA,KAAa,SAAS,KAAA,GAAW,OAAO;CACrD,OAAO,KAAK,QAAQ,KAAK;AAC3B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bgub/fig-refresh",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Fig Fast Refresh runtime (HMR)",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"homepage": "https://github.com/bgub/fig",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"author": "Ben Gubler <nebrelbug@gmail.com>",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/bgub/fig",
|
|
25
|
+
"directory": "packages/fig-refresh"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/bgub/fig/issues"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@bgub/fig-reconciler": "0.0.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@bgub/fig-reconciler": "0.0.1"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown --config ../../tsdown.config.ts && node ../../scripts/strip-declaration-map-comments.mjs dist",
|
|
45
|
+
"test": "vitest run --config ../../vite.config.ts src"
|
|
46
|
+
}
|
|
47
|
+
}
|