vite-plugin-mirrorstate 0.2.3 → 0.3.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.
- package/dist/index.js +45 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,11 +14,17 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
14
14
|
};
|
|
15
15
|
let wss;
|
|
16
16
|
let watcher;
|
|
17
|
-
let
|
|
17
|
+
let fileSequences = new Map(); // Track sequence number per file
|
|
18
|
+
let lastWrittenState = new Map(); // Track last written state hash per file to detect external changes
|
|
18
19
|
let lastMessageHash = new Map(); // Track last message hash per client to prevent duplicates
|
|
19
20
|
let watcherReady = false; // Track if watcher has finished initial scan
|
|
21
|
+
let viteRoot; // Captured vite root directory
|
|
20
22
|
return {
|
|
21
23
|
name: "vite-plugin-mirrorstate",
|
|
24
|
+
configResolved(config) {
|
|
25
|
+
// Capture vite root for use in other hooks
|
|
26
|
+
viteRoot = config.root || process.cwd();
|
|
27
|
+
},
|
|
22
28
|
configureServer(server) {
|
|
23
29
|
const wsPath = opts.path;
|
|
24
30
|
wss = new WebSocketServer({ noServer: true });
|
|
@@ -107,21 +113,31 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
107
113
|
}
|
|
108
114
|
try {
|
|
109
115
|
const relativePath = path.relative(baseDir, filePath);
|
|
110
|
-
// Skip if this was a recent write from WebSocket to prevent echo
|
|
111
|
-
if (recentWrites.has(relativePath)) {
|
|
112
|
-
recentWrites.delete(relativePath);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
116
|
const content = fs.readFileSync(filePath, "utf8");
|
|
116
117
|
const data = JSON.parse(content);
|
|
117
118
|
const name = relativePath.replace(/\.mirror\.json$/, "");
|
|
118
|
-
//
|
|
119
|
+
// Create hash of the state to detect if this is our own write
|
|
120
|
+
const stateHash = JSON.stringify(data);
|
|
121
|
+
const lastHash = lastWrittenState.get(name);
|
|
122
|
+
// Skip if this matches what we just wrote (echo from our own write)
|
|
123
|
+
if (lastHash === stateHash) {
|
|
124
|
+
logger(`Skipping file watcher echo for ${name}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// This is an external change - increment sequence number
|
|
128
|
+
const currentSeq = fileSequences.get(name) ?? 0;
|
|
129
|
+
const seq = currentSeq + 1;
|
|
130
|
+
fileSequences.set(name, seq);
|
|
131
|
+
// Update our record of the last written state
|
|
132
|
+
lastWrittenState.set(name, stateHash);
|
|
133
|
+
// Broadcast file change with sequence number
|
|
119
134
|
wss.clients.forEach((client) => {
|
|
120
135
|
if (client.readyState === client.OPEN) {
|
|
121
136
|
client.send(JSON.stringify({
|
|
122
137
|
type: "fileChange",
|
|
123
138
|
name,
|
|
124
139
|
state: data,
|
|
140
|
+
seq,
|
|
125
141
|
source: "external",
|
|
126
142
|
}));
|
|
127
143
|
}
|
|
@@ -131,7 +147,7 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
131
147
|
if (mod) {
|
|
132
148
|
server.moduleGraph.invalidateModule(mod);
|
|
133
149
|
}
|
|
134
|
-
logger(`Mirror file changed externally: ${name}`);
|
|
150
|
+
logger(`Mirror file changed externally: ${name} (seq: ${seq})`);
|
|
135
151
|
}
|
|
136
152
|
catch (error) {
|
|
137
153
|
console.error(`Error reading mirror file ${filePath}:`, error);
|
|
@@ -142,33 +158,18 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
142
158
|
const clientId = Math.random().toString(36).substring(7);
|
|
143
159
|
ws.clientId = clientId;
|
|
144
160
|
logger(`Client connected to MirrorState (${clientId})`);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
:
|
|
148
|
-
|
|
149
|
-
ignore: "node_modules/**",
|
|
150
|
-
}));
|
|
151
|
-
mirrorFiles.forEach((filePath) => {
|
|
152
|
-
try {
|
|
153
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
154
|
-
const data = JSON.parse(content);
|
|
155
|
-
const relativePath = path.relative(server.config.root || process.cwd(), filePath);
|
|
156
|
-
const name = relativePath.replace(/\.mirror\.json$/, "");
|
|
157
|
-
ws.send(JSON.stringify({
|
|
158
|
-
type: "initialState",
|
|
159
|
-
name,
|
|
160
|
-
state: data,
|
|
161
|
-
}));
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
console.error(`Error reading initial state from ${filePath}:`, error);
|
|
165
|
-
}
|
|
161
|
+
// Send clientId to the client
|
|
162
|
+
const connectedMessage = JSON.stringify({
|
|
163
|
+
type: "connected",
|
|
164
|
+
clientId,
|
|
166
165
|
});
|
|
166
|
+
logger(`Sending connected message: ${connectedMessage}`);
|
|
167
|
+
ws.send(connectedMessage);
|
|
167
168
|
ws.on("message", (message) => {
|
|
168
169
|
try {
|
|
169
170
|
const messageStr = message.toString();
|
|
170
171
|
const data = JSON.parse(messageStr);
|
|
171
|
-
const { name, state } = data;
|
|
172
|
+
const { clientId: msgClientId, name, state } = data;
|
|
172
173
|
// Create a hash of the message to detect duplicates
|
|
173
174
|
const messageHash = `${name}:${JSON.stringify(state)}`;
|
|
174
175
|
const lastHash = lastMessageHash.get(clientId);
|
|
@@ -185,28 +186,32 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
185
186
|
const jsonContent = opts.prettyPrint
|
|
186
187
|
? JSON.stringify(state, null, 2)
|
|
187
188
|
: JSON.stringify(state);
|
|
189
|
+
// Increment sequence number for this file BEFORE writing
|
|
190
|
+
const currentSeq = fileSequences.get(name) ?? 0;
|
|
191
|
+
const seq = currentSeq + 1;
|
|
192
|
+
fileSequences.set(name, seq);
|
|
193
|
+
// Record state hash to detect our own write in file watcher
|
|
194
|
+
lastWrittenState.set(name, JSON.stringify(state));
|
|
188
195
|
// Write state to file
|
|
189
196
|
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);
|
|
193
197
|
// Invalidate the virtual module for HMR
|
|
194
198
|
const mod = server.moduleGraph.getModuleById("\0virtual:mirrorstate/initial-states");
|
|
195
199
|
if (mod) {
|
|
196
200
|
server.moduleGraph.invalidateModule(mod);
|
|
197
201
|
}
|
|
198
|
-
// Broadcast to
|
|
202
|
+
// Broadcast to all OTHER clients (skip sender - they already applied optimistically)
|
|
199
203
|
wss.clients.forEach((client) => {
|
|
200
204
|
if (client !== ws && client.readyState === client.OPEN) {
|
|
201
205
|
client.send(JSON.stringify({
|
|
202
206
|
type: "fileChange",
|
|
207
|
+
clientId: msgClientId,
|
|
208
|
+
seq,
|
|
203
209
|
name,
|
|
204
210
|
state: state,
|
|
205
|
-
source: clientId,
|
|
206
211
|
}));
|
|
207
212
|
}
|
|
208
213
|
});
|
|
209
|
-
logger(`Updated ${name}
|
|
214
|
+
logger(`Updated ${name} (seq: ${seq}) from ${clientId}:`, state);
|
|
210
215
|
}
|
|
211
216
|
catch (error) {
|
|
212
217
|
console.error("Error handling client message:", error);
|
|
@@ -233,7 +238,8 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
233
238
|
}
|
|
234
239
|
if (id === "\0virtual:mirrorstate/initial-states") {
|
|
235
240
|
// During build, read all mirror files and inline them
|
|
236
|
-
|
|
241
|
+
// Use vite root instead of process.cwd() to handle monorepos correctly
|
|
242
|
+
const baseDir = viteRoot || process.cwd();
|
|
237
243
|
const pattern = Array.isArray(opts.filePattern)
|
|
238
244
|
? opts.filePattern.map((p) => path.join(baseDir, p))
|
|
239
245
|
: [path.join(baseDir, opts.filePattern)];
|
|
@@ -245,7 +251,8 @@ export function mirrorStatePlugin(options = {}) {
|
|
|
245
251
|
try {
|
|
246
252
|
const content = fs.readFileSync(filePath, "utf8");
|
|
247
253
|
const data = JSON.parse(content);
|
|
248
|
-
|
|
254
|
+
// Use baseDir (vite root) for relative path calculation
|
|
255
|
+
const relativePath = path.relative(baseDir, filePath);
|
|
249
256
|
const name = relativePath.replace(/\.mirror\.json$/, "");
|
|
250
257
|
states[name] = data;
|
|
251
258
|
logger(`Inlined initial state for ${name}`);
|
package/package.json
CHANGED