vite-plugin-mirrorstate 0.2.1 → 0.2.3
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/dist/index.js +102 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const logger = debug("mirrorstate:vite-plugin");
|
|
|
8
8
|
export function mirrorStatePlugin(options = {}) {
|
|
9
9
|
const opts = {
|
|
10
10
|
path: "/mirrorstate",
|
|
11
|
-
filePattern: "**/*.mirror.json",
|
|
11
|
+
filePattern: ["*.mirror.json", "**/*.mirror.json"],
|
|
12
12
|
prettyPrint: true,
|
|
13
13
|
...options,
|
|
14
14
|
};
|
|
@@ -16,6 +16,7 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
16
16
|
let watcher;
|
|
17
17
|
let recentWrites = new Set(); // Track recent writes to prevent echo
|
|
18
18
|
let lastMessageHash = new Map(); // Track last message hash per client to prevent duplicates
|
|
19
|
+
let watcherReady = false; // Track if watcher has finished initial scan
|
|
19
20
|
return {
|
|
20
21
|
name: "vite-plugin-mirrorstate",
|
|
21
22
|
configureServer(server) {
|
|
@@ -28,22 +29,91 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
|
-
|
|
32
|
+
const baseDir = server.config.root || process.cwd();
|
|
33
|
+
const pattern = Array.isArray(opts.filePattern)
|
|
34
|
+
? opts.filePattern.map((p) => path.join(baseDir, p))
|
|
35
|
+
: [path.join(baseDir, opts.filePattern)];
|
|
36
|
+
// Find all existing files matching the pattern
|
|
37
|
+
const existingFiles = pattern.flatMap((p) => glob.sync(p, {
|
|
38
|
+
ignore: "node_modules/**",
|
|
39
|
+
}));
|
|
40
|
+
logger(`Setting up file watcher for ${existingFiles.length} files: ${JSON.stringify(existingFiles)}`);
|
|
41
|
+
// Watch both existing files AND the directory for new files
|
|
42
|
+
const watchTargets = [...existingFiles, baseDir];
|
|
43
|
+
watcher = chokidar.watch(watchTargets, {
|
|
32
44
|
ignored: /node_modules/,
|
|
33
45
|
persistent: true,
|
|
34
46
|
...opts.watchOptions,
|
|
35
47
|
});
|
|
48
|
+
watcher.on("add", (filePath) => {
|
|
49
|
+
// Only process .mirror.json files added after initial scan
|
|
50
|
+
if (!watcherReady || !filePath.endsWith(".mirror.json")) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const relativePath = path.relative(baseDir, filePath);
|
|
55
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
56
|
+
const data = JSON.parse(content);
|
|
57
|
+
const name = relativePath.replace(/\.mirror\.json$/, "");
|
|
58
|
+
// Send new state to all connected clients
|
|
59
|
+
wss.clients.forEach((client) => {
|
|
60
|
+
if (client.readyState === client.OPEN) {
|
|
61
|
+
client.send(JSON.stringify({
|
|
62
|
+
type: "initialState",
|
|
63
|
+
name,
|
|
64
|
+
state: data,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// Invalidate the virtual module for HMR
|
|
69
|
+
const mod = server.moduleGraph.getModuleById("\0virtual:mirrorstate/initial-states");
|
|
70
|
+
if (mod) {
|
|
71
|
+
server.moduleGraph.invalidateModule(mod);
|
|
72
|
+
}
|
|
73
|
+
logger(`New mirror file added: ${name}`);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(`Error reading new mirror file ${filePath}:`, error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
watcher.on("unlink", (filePath) => {
|
|
80
|
+
// Only process .mirror.json files
|
|
81
|
+
if (!filePath.endsWith(".mirror.json")) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const relativePath = path.relative(baseDir, filePath);
|
|
86
|
+
const name = relativePath.replace(/\.mirror\.json$/, "");
|
|
87
|
+
// Invalidate the virtual module for HMR
|
|
88
|
+
const mod = server.moduleGraph.getModuleById("\0virtual:mirrorstate/initial-states");
|
|
89
|
+
if (mod) {
|
|
90
|
+
server.moduleGraph.invalidateModule(mod);
|
|
91
|
+
}
|
|
92
|
+
logger(`Mirror file deleted: ${name}`);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error(`Error handling mirror file deletion ${filePath}:`, error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
watcher.on("ready", () => {
|
|
99
|
+
watcherReady = true;
|
|
100
|
+
logger("File watcher is ready");
|
|
101
|
+
});
|
|
36
102
|
logger(`MirrorState WebSocket listening on ws://localhost:${server.config.server.port || 5173}${wsPath}`);
|
|
37
103
|
watcher.on("change", (filePath) => {
|
|
104
|
+
// Only watch .mirror.json files
|
|
105
|
+
if (!filePath.endsWith(".mirror.json")) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
38
108
|
try {
|
|
109
|
+
const relativePath = path.relative(baseDir, filePath);
|
|
39
110
|
// Skip if this was a recent write from WebSocket to prevent echo
|
|
40
|
-
if (recentWrites.has(
|
|
41
|
-
recentWrites.delete(
|
|
111
|
+
if (recentWrites.has(relativePath)) {
|
|
112
|
+
recentWrites.delete(relativePath);
|
|
42
113
|
return;
|
|
43
114
|
}
|
|
44
115
|
const content = fs.readFileSync(filePath, "utf8");
|
|
45
116
|
const data = JSON.parse(content);
|
|
46
|
-
const relativePath = path.relative(server.config.root || process.cwd(), filePath);
|
|
47
117
|
const name = relativePath.replace(/\.mirror\.json$/, "");
|
|
48
118
|
// This is an external file change (from editor, etc.)
|
|
49
119
|
wss.clients.forEach((client) => {
|
|
@@ -57,7 +127,7 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
57
127
|
}
|
|
58
128
|
});
|
|
59
129
|
// Invalidate the virtual module for HMR
|
|
60
|
-
const mod = server.moduleGraph.getModuleById("
|
|
130
|
+
const mod = server.moduleGraph.getModuleById("\0virtual:mirrorstate/initial-states");
|
|
61
131
|
if (mod) {
|
|
62
132
|
server.moduleGraph.invalidateModule(mod);
|
|
63
133
|
}
|
|
@@ -73,9 +143,11 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
73
143
|
ws.clientId = clientId;
|
|
74
144
|
logger(`Client connected to MirrorState (${clientId})`);
|
|
75
145
|
const pattern = Array.isArray(opts.filePattern)
|
|
76
|
-
? opts.filePattern
|
|
77
|
-
: [opts.filePattern];
|
|
78
|
-
const mirrorFiles = pattern.flatMap((p) => glob.sync(p, {
|
|
146
|
+
? opts.filePattern.map((p) => path.join(baseDir, p))
|
|
147
|
+
: [path.join(baseDir, opts.filePattern)];
|
|
148
|
+
const mirrorFiles = pattern.flatMap((p) => glob.sync(p, {
|
|
149
|
+
ignore: "node_modules/**",
|
|
150
|
+
}));
|
|
79
151
|
mirrorFiles.forEach((filePath) => {
|
|
80
152
|
try {
|
|
81
153
|
const content = fs.readFileSync(filePath, "utf8");
|
|
@@ -107,27 +179,28 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
107
179
|
}
|
|
108
180
|
// Update last message hash for this client
|
|
109
181
|
lastMessageHash.set(clientId, messageHash);
|
|
110
|
-
const
|
|
182
|
+
const baseDir = server.config.root || process.cwd();
|
|
183
|
+
const relativeFilePath = `${name}.mirror.json`;
|
|
184
|
+
const filePath = path.join(baseDir, relativeFilePath);
|
|
111
185
|
const jsonContent = opts.prettyPrint
|
|
112
186
|
? JSON.stringify(state, null, 2)
|
|
113
187
|
: JSON.stringify(state);
|
|
114
|
-
// Mark this as a recent write to prevent file watcher echo
|
|
115
|
-
recentWrites.add(filePath);
|
|
116
188
|
// Write state to file
|
|
117
189
|
fs.writeFileSync(filePath, jsonContent);
|
|
190
|
+
// Mark this as a recent write to prevent file watcher echo
|
|
191
|
+
// (only after successful write)
|
|
192
|
+
recentWrites.add(relativeFilePath);
|
|
118
193
|
// Invalidate the virtual module for HMR
|
|
119
|
-
const mod = server.moduleGraph.getModuleById("
|
|
194
|
+
const mod = server.moduleGraph.getModuleById("\0virtual:mirrorstate/initial-states");
|
|
120
195
|
if (mod) {
|
|
121
196
|
server.moduleGraph.invalidateModule(mod);
|
|
122
197
|
}
|
|
123
198
|
// Broadcast to other clients (exclude sender to prevent echo)
|
|
124
199
|
wss.clients.forEach((client) => {
|
|
125
200
|
if (client !== ws && client.readyState === client.OPEN) {
|
|
126
|
-
const relativePath = path.relative(server.config.root || process.cwd(), filePath);
|
|
127
|
-
const fileName = relativePath.replace(/\.mirror\.json$/, "");
|
|
128
201
|
client.send(JSON.stringify({
|
|
129
202
|
type: "fileChange",
|
|
130
|
-
name
|
|
203
|
+
name,
|
|
131
204
|
state: state,
|
|
132
205
|
source: clientId,
|
|
133
206
|
}));
|
|
@@ -147,21 +220,26 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
147
220
|
});
|
|
148
221
|
},
|
|
149
222
|
resolveId(id) {
|
|
150
|
-
if (id === "virtual:mirrorstate/config"
|
|
151
|
-
|
|
152
|
-
|
|
223
|
+
if (id === "virtual:mirrorstate/config") {
|
|
224
|
+
return "\0" + id;
|
|
225
|
+
}
|
|
226
|
+
if (id === "virtual:mirrorstate/initial-states") {
|
|
227
|
+
return "\0" + id;
|
|
153
228
|
}
|
|
154
229
|
},
|
|
155
230
|
load(id) {
|
|
156
|
-
if (id === "
|
|
231
|
+
if (id === "\0virtual:mirrorstate/config") {
|
|
157
232
|
return `export const WS_PATH = "${opts.path}";`;
|
|
158
233
|
}
|
|
159
|
-
if (id === "
|
|
234
|
+
if (id === "\0virtual:mirrorstate/initial-states") {
|
|
160
235
|
// During build, read all mirror files and inline them
|
|
236
|
+
const baseDir = process.cwd();
|
|
161
237
|
const pattern = Array.isArray(opts.filePattern)
|
|
162
|
-
? opts.filePattern
|
|
163
|
-
: [opts.filePattern];
|
|
164
|
-
const mirrorFiles = pattern.flatMap((p) => glob.sync(p, {
|
|
238
|
+
? opts.filePattern.map((p) => path.join(baseDir, p))
|
|
239
|
+
: [path.join(baseDir, opts.filePattern)];
|
|
240
|
+
const mirrorFiles = pattern.flatMap((p) => glob.sync(p, {
|
|
241
|
+
ignore: "node_modules/**",
|
|
242
|
+
}));
|
|
165
243
|
const states = {};
|
|
166
244
|
mirrorFiles.forEach((filePath) => {
|
|
167
245
|
try {
|
package/package.json
CHANGED