@peachy/plugin-react 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/README.md +3 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +105 -0
- package/dist/templates/hmr-entrypoint.ts +9 -0
- package/dist/templates/hmr-runtime.ts +164 -0
- package/dist/templates/hmr-wrapper.ts +40 -0
- package/package.json +31 -0
package/README.md
ADDED
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { babel } from "@rollup/plugin-babel";
|
|
2
|
+
import ReactFreshBabelPlugin from "react-refresh/babel";
|
|
3
|
+
import BabelJSX from "@babel/plugin-syntax-jsx";
|
|
4
|
+
import BabelTS from "@babel/plugin-syntax-typescript";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
|
|
8
|
+
//#region src/entrypoint.ts
|
|
9
|
+
function addHmrEntrypoint(input) {
|
|
10
|
+
const PREFIX = "\0virtual:hmr-wrapper:";
|
|
11
|
+
if (typeof input === "string") return PREFIX + input;
|
|
12
|
+
else if (Array.isArray(input)) return input.map((entry) => PREFIX + entry);
|
|
13
|
+
else if (typeof input === "object") {
|
|
14
|
+
const wrapped = {};
|
|
15
|
+
for (const [key, value] of Object.entries(input)) wrapped[key] = PREFIX + value;
|
|
16
|
+
return wrapped;
|
|
17
|
+
}
|
|
18
|
+
return input;
|
|
19
|
+
}
|
|
20
|
+
function createHmrEntrypointPlugin() {
|
|
21
|
+
const PREFIX = "\0virtual:hmr-wrapper:";
|
|
22
|
+
const resolvedPath = resolve(import.meta.dirname, "./templates/hmr-entrypoint.ts");
|
|
23
|
+
return {
|
|
24
|
+
name: "hmr2-entrypoint",
|
|
25
|
+
options(opts) {
|
|
26
|
+
return {
|
|
27
|
+
...opts,
|
|
28
|
+
input: addHmrEntrypoint(opts.input)
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
resolveId(id) {
|
|
32
|
+
if (id.startsWith(PREFIX)) return id;
|
|
33
|
+
return null;
|
|
34
|
+
},
|
|
35
|
+
load(id) {
|
|
36
|
+
if (id.startsWith(PREFIX)) {
|
|
37
|
+
const actualEntry = id.slice(21);
|
|
38
|
+
return `import '${resolvedPath}';\nimport '${resolve(process.cwd(), actualEntry)}';`;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/runtime.ts
|
|
47
|
+
function hmrRuntimePlugin(port) {
|
|
48
|
+
const runtime = resolve(import.meta.dirname, "./templates/hmr-runtime.ts");
|
|
49
|
+
return {
|
|
50
|
+
name: "hmr-runtime-plugin",
|
|
51
|
+
resolveId(id) {
|
|
52
|
+
if (id === "\0virtual:hmr-runtime") return runtime;
|
|
53
|
+
},
|
|
54
|
+
async transform(code, id) {
|
|
55
|
+
if (id === runtime) return { code: code.replace("$port$", port.toString()) };
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/wrapper.ts
|
|
62
|
+
function reactRefreshWrapperPlugin() {
|
|
63
|
+
const template = readFileSync(resolve(import.meta.dirname, "./templates/hmr-wrapper.ts"), "utf-8");
|
|
64
|
+
return {
|
|
65
|
+
name: "react-refresh-wrapper",
|
|
66
|
+
transform(code, id) {
|
|
67
|
+
if (!id.startsWith(process.cwd()) || !id.match(/\.[tj]sx?$/) || id.includes("node_modules") || id.startsWith("gi://*")) return null;
|
|
68
|
+
return {
|
|
69
|
+
code: template.replace("$sourceCode$", code).replaceAll("$hmrId$", JSON.stringify(id)),
|
|
70
|
+
map: null
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/index.ts
|
|
78
|
+
function react(port) {
|
|
79
|
+
return [
|
|
80
|
+
babel({
|
|
81
|
+
plugins: [
|
|
82
|
+
BabelJSX,
|
|
83
|
+
[BabelTS, { isTSX: true }],
|
|
84
|
+
ReactFreshBabelPlugin
|
|
85
|
+
],
|
|
86
|
+
babelHelpers: "bundled",
|
|
87
|
+
ignore: [/node_modules/],
|
|
88
|
+
extensions: [
|
|
89
|
+
".js",
|
|
90
|
+
".jsx",
|
|
91
|
+
".es6",
|
|
92
|
+
".es",
|
|
93
|
+
".mjs",
|
|
94
|
+
".ts",
|
|
95
|
+
".tsx"
|
|
96
|
+
]
|
|
97
|
+
}),
|
|
98
|
+
createHmrEntrypointPlugin(),
|
|
99
|
+
reactRefreshWrapperPlugin(),
|
|
100
|
+
hmrRuntimePlugin(port)
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { react };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import * as RefreshRuntime from "react-refresh";
|
|
4
|
+
|
|
5
|
+
globalThis.$RefreshRuntime$ = RefreshRuntime;
|
|
6
|
+
|
|
7
|
+
globalThis.$RefreshRuntime$.injectIntoGlobalHook(globalThis);
|
|
8
|
+
globalThis.$RefreshReg$ = () => {};
|
|
9
|
+
globalThis.$RefreshSig$ = () => (type) => type;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import Gio from "gi://Gio?version=2.0";
|
|
4
|
+
import GLib from "gi://GLib?version=2.0";
|
|
5
|
+
import Soup from "gi://Soup?version=3.0";
|
|
6
|
+
|
|
7
|
+
Gio._promisify(Soup.Session.prototype, "websocket_connect_async");
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
var __hmr__: {
|
|
11
|
+
contexts: Record<string, ImportMetaHot>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// To prevent the connection being garbage collected
|
|
16
|
+
let connection;
|
|
17
|
+
|
|
18
|
+
if (!globalThis.__hmr__) {
|
|
19
|
+
globalThis.__hmr__ = {
|
|
20
|
+
contexts: {},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const socketURL = "ws://localhost:$port$/";
|
|
24
|
+
const session = new Soup.Session({
|
|
25
|
+
idleTimeout: GLib.MAXINT32,
|
|
26
|
+
maxConns: 1,
|
|
27
|
+
timeout: GLib.MAXINT32,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
session
|
|
31
|
+
.websocket_connect_async(
|
|
32
|
+
Soup.Message.new("GET", socketURL.toString()),
|
|
33
|
+
null,
|
|
34
|
+
null,
|
|
35
|
+
GLib.PRIORITY_DEFAULT,
|
|
36
|
+
null,
|
|
37
|
+
)
|
|
38
|
+
.then((conn) => {
|
|
39
|
+
connection = conn;
|
|
40
|
+
console.log("[HMR] Connected to HMR server at", socketURL);
|
|
41
|
+
|
|
42
|
+
connection.set_keepalive_interval(1);
|
|
43
|
+
|
|
44
|
+
connection.connect("message", async (_, _type, event) => {
|
|
45
|
+
const decoder = new TextDecoder();
|
|
46
|
+
const payload = JSON.parse(
|
|
47
|
+
decoder.decode(event.toArray()),
|
|
48
|
+
) as HMRMessage;
|
|
49
|
+
|
|
50
|
+
switch (payload?.type) {
|
|
51
|
+
case "reload":
|
|
52
|
+
console.info("[HMR] Reloading...");
|
|
53
|
+
// TODO: handle this
|
|
54
|
+
break;
|
|
55
|
+
case "hmr":
|
|
56
|
+
{
|
|
57
|
+
if (!payload.updates?.length) return;
|
|
58
|
+
|
|
59
|
+
let anyAccepted = false;
|
|
60
|
+
for (const update of payload.updates) {
|
|
61
|
+
if (globalThis.__hmr__.contexts[update.id]) {
|
|
62
|
+
try {
|
|
63
|
+
const module = await import(
|
|
64
|
+
"file://" + update.url + "?t=" + Date.now()
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const accepted =
|
|
68
|
+
globalThis.__hmr__.contexts[update.id].emit(module);
|
|
69
|
+
|
|
70
|
+
if (accepted) {
|
|
71
|
+
console.info("[HMR] Update accepted by", update.id);
|
|
72
|
+
anyAccepted = true;
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.debug("[HMR] Error when hot-refreshing", err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!anyAccepted) {
|
|
81
|
+
console.info("[HMR] Update rejected, reloading...");
|
|
82
|
+
connection.send_text(JSON.stringify({ type: "reload" }));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
connection.connect("closed", () => {
|
|
90
|
+
console.log("[HMR] Connection closed");
|
|
91
|
+
});
|
|
92
|
+
})
|
|
93
|
+
.catch(console.error);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createHotContext(id: string): ImportMetaHot {
|
|
97
|
+
let callback: undefined | ((mod: ModuleNamespace) => boolean);
|
|
98
|
+
let disposed = false;
|
|
99
|
+
|
|
100
|
+
const hot = {
|
|
101
|
+
accept: (cb) => {
|
|
102
|
+
if (disposed) {
|
|
103
|
+
throw new Error("import.meta.hot.accept() called after dispose()");
|
|
104
|
+
}
|
|
105
|
+
if (callback) {
|
|
106
|
+
throw new Error("import.meta.hot.accept() called multiple times");
|
|
107
|
+
}
|
|
108
|
+
callback = cb;
|
|
109
|
+
},
|
|
110
|
+
dispose: () => {
|
|
111
|
+
disposed = true;
|
|
112
|
+
callback = undefined;
|
|
113
|
+
},
|
|
114
|
+
emit(self: ModuleNamespace) {
|
|
115
|
+
if (disposed) {
|
|
116
|
+
throw new Error("import.meta.hot.emit() called after dispose()");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (callback) {
|
|
120
|
+
return !!callback(self);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (globalThis.__hmr__.contexts[id]) {
|
|
128
|
+
globalThis.__hmr__.contexts[id].dispose();
|
|
129
|
+
globalThis.__hmr__.contexts[id] = undefined;
|
|
130
|
+
}
|
|
131
|
+
globalThis.__hmr__.contexts[id] = hot;
|
|
132
|
+
|
|
133
|
+
return hot;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// From: https://github.com/facebook/metro/blob/e232f39fcd82defb421899a8c9b2b3accee90e86/packages/metro-runtime/src/polyfills/require.js#L948
|
|
137
|
+
export function isReactRefreshBoundary(moduleExports) {
|
|
138
|
+
if (globalThis.$RefreshRuntime$.isLikelyComponentType(moduleExports)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (moduleExports == null || typeof moduleExports !== "object") {
|
|
143
|
+
// Exit if we can't iterate over exports.
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
let hasExports = false;
|
|
147
|
+
let areAllExportsComponents = true;
|
|
148
|
+
for (const key in moduleExports) {
|
|
149
|
+
hasExports = true;
|
|
150
|
+
if (key === "__esModule") {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const desc = Object.getOwnPropertyDescriptor(moduleExports, key);
|
|
154
|
+
if (desc && desc.get) {
|
|
155
|
+
// Don't invoke getters as they may have side effects.
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const exportValue = moduleExports[key];
|
|
159
|
+
if (!globalThis.$RefreshRuntime$.isLikelyComponentType(exportValue)) {
|
|
160
|
+
areAllExportsComponents = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return hasExports && areAllExportsComponents;
|
|
164
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import * as __hmr__ from "\0virtual:hmr-runtime";
|
|
4
|
+
|
|
5
|
+
if (import.meta) {
|
|
6
|
+
import.meta.hot = __hmr__.createHotContext($hmrId$);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
var NO_FAST_REFRESH =
|
|
10
|
+
!globalThis.$RefreshReg$ ||
|
|
11
|
+
!globalThis.$RefreshSig$ ||
|
|
12
|
+
!globalThis.$RefreshRuntime$;
|
|
13
|
+
if (NO_FAST_REFRESH) {
|
|
14
|
+
console.warn(
|
|
15
|
+
"[HMR]: React Refresh is not enabled. Please ensure that you have installed the necessary dependencies and enabled the plugin in your webpack configuration.",
|
|
16
|
+
);
|
|
17
|
+
} else {
|
|
18
|
+
var prevRefreshReg = globalThis.$RefreshReg$;
|
|
19
|
+
var prevRefreshSig = globalThis.$RefreshSig$;
|
|
20
|
+
globalThis.$RefreshReg$ = (type, id) => {
|
|
21
|
+
const fullId = $hmrId$ + "#" + id;
|
|
22
|
+
globalThis.$RefreshRuntime$.register(type, fullId);
|
|
23
|
+
};
|
|
24
|
+
globalThis.$RefreshSig$ =
|
|
25
|
+
globalThis.$RefreshRuntime$.createSignatureFunctionForTransform;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$sourceCode$;
|
|
29
|
+
|
|
30
|
+
if (!NO_FAST_REFRESH) {
|
|
31
|
+
globalThis.$RefreshReg$ = prevRefreshReg;
|
|
32
|
+
globalThis.$RefreshSig$ = prevRefreshSig;
|
|
33
|
+
import.meta.hot.accept((moduleExports) => {
|
|
34
|
+
if (__hmr__.isReactRefreshBoundary(moduleExports)) {
|
|
35
|
+
// TODO: debounce this
|
|
36
|
+
globalThis.$RefreshRuntime$.performReactRefresh();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peachy/plugin-react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "HMR for react",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.mjs",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"author": "Angelo Verlain <hey@vixalien.com>",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"rolldown": "1.0.0-beta.58",
|
|
12
|
+
"tsdown": "0.20.0-beta.3",
|
|
13
|
+
"typescript": "^5.9.3"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@babel/plugin-syntax-jsx": "^7.28.6",
|
|
17
|
+
"@babel/plugin-syntax-typescript": "^7.28.6",
|
|
18
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
19
|
+
"react-refresh": "^0.18.0"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"rolldown": "1.0.0-beta.58"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsdown src/index.ts --dts && mkdir -p dist/templates && cp src/templates/* dist/templates"
|
|
29
|
+
},
|
|
30
|
+
"types": "./dist/index.d.mts"
|
|
31
|
+
}
|