Skip to content

Commit df0c77a

Browse files
docs: update HMR spec with compilation hash documentation
1 parent 850064f commit df0c77a

File tree

17 files changed

+2173
-762
lines changed

17 files changed

+2173
-762
lines changed

apps/vm-hotreload/full-demo/README.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,25 @@ full-demo/
185185

186186
### Running the Demo
187187

188-
#### Option 1: Run Everything (Recommended)
189-
```bash
190-
# From the full-demo directory
191-
npm run demo
192-
```
188+
#### Option 1: All-in-One Startup (Recommended)
193189

194-
#### Option 2: Run Components Separately
190+
1. **Start All Services**:
191+
```bash
192+
./start-all.sh
193+
```
194+
195+
2. **Test the HMR System**:
196+
```bash
197+
bash test_hmr.sh
198+
```
199+
200+
3. **Stop All Services**:
201+
```bash
202+
./stop-all.sh
203+
# Or press Ctrl+C in the start-all.sh terminal
204+
```
205+
206+
#### Option 2: Manual Startup
195207

196208
1. **Start the Backend API Server:**
197209
```bash
@@ -210,7 +222,18 @@ full-demo/
210222
pnpm --filter hmr-client-demo start
211223
```
212224

213-
3. **Open the Admin Interface:**
225+
3. **Start the Client Demo** (optional):
226+
```bash
227+
cd client
228+
pnpm start
229+
```
230+
231+
4. **Test the HMR System**:
232+
```bash
233+
bash test_hmr.sh
234+
```
235+
236+
5. **Open the Admin Interface:**
214237
Open your browser and navigate to: `http://localhost:3000/admin`
215238

216239
## 🎮 Using the Demo

apps/vm-hotreload/full-demo/backend/server.js

Lines changed: 86 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ wss.on('connection', (ws) => {
3030
// In-memory state
3131
let updateHistory = [];
3232
let availableUpdates = [];
33-
let lastUpdateId = 0;
33+
let clientUpdateTracking = new Map(); // Track which updates each client has received
34+
35+
// Generate a unique webpack-style hash
36+
function generateWebpackHash() {
37+
return (
38+
Math.random().toString(36).substring(2, 15) +
39+
Math.random().toString(36).substring(2, 15)
40+
);
41+
}
3442

3543
// Load available updates from updates directory
3644
function loadAvailableUpdates() {
@@ -67,44 +75,59 @@ function extractDescription(content) {
6775
// Get pending updates for client
6876
app.get('/api/updates', (req, res) => {
6977
const clientId = req.query.clientId || 'default';
70-
const lastAppliedId = parseInt(req.query.lastAppliedId) || 0;
71-
72-
const pendingUpdatesRaw = updateHistory.filter(
73-
(update) => update.id > lastAppliedId && update.triggered,
74-
);
78+
const currentHash = req.query.currentHash || '0';
7579

76-
const formattedUpdates = pendingUpdatesRaw.map((update) => {
77-
// Extract module path from content - this is a simplified example
78-
// A more robust solution would parse the JS content or have this info stored
79-
// const modulePathMatch = update.content.match(
80-
// /\*!\*\*\* (\.\/src\/[^\s]+) \*\*\*!/,
81-
// );
82-
// const modulePath = modulePathMatch ? modulePathMatch[1] : update.filename; // Fallback to filename
83-
84-
return {
80+
// Get the list of updates this client has already received
81+
if (!clientUpdateTracking.has(clientId)) {
82+
clientUpdateTracking.set(clientId, new Set());
83+
}
84+
const clientReceivedUpdates = clientUpdateTracking.get(clientId);
85+
86+
// Find the latest triggered update that:
87+
// 1. Has a different hash than client's current hash
88+
// 2. Has not been sent to this client before
89+
const latestUpdate = updateHistory
90+
.filter((update) => {
91+
return (
92+
update.triggered &&
93+
update.webpackHash !== currentHash &&
94+
!clientReceivedUpdates.has(update.updateId)
95+
);
96+
})
97+
.slice(-1)[0]; // Get the most recent one
98+
99+
let formattedUpdate = null;
100+
if (latestUpdate) {
101+
// Mark this update as sent to this client
102+
clientReceivedUpdates.add(latestUpdate.updateId);
103+
104+
formattedUpdate = {
85105
manifest: {
86106
c: ['index'], // Assuming 'index' is the main chunk for all these updates
87107
r: [], // Removed chunks, empty for now
88108
m: [
89109
// modulePath
90110
], // Modules affected by this update
91111
},
92-
script: update.content,
112+
script: latestUpdate.content,
93113
// Keep original update info for reference if needed by client
94-
originalUpdateInfo: {
95-
id: update.id,
96-
updateId: update.updateId,
97-
filename: update.filename,
98-
description: update.description,
99-
triggered: update.triggered,
100-
timestamp: update.timestamp,
114+
originalInfo: {
115+
updateId: latestUpdate.updateId,
116+
filename: latestUpdate.filename,
117+
description: latestUpdate.description,
118+
triggered: latestUpdate.triggered,
119+
timestamp: latestUpdate.timestamp,
120+
webpackHash: latestUpdate.webpackHash,
101121
},
102122
};
103-
});
123+
124+
console.log(
125+
`Sending update ${latestUpdate.updateId} to client ${clientId} with hash ${latestUpdate.webpackHash}`,
126+
);
127+
}
104128

105129
res.json({
106-
updates: formattedUpdates,
107-
lastUpdateId: Math.max(...updateHistory.map((u) => u.id), 0),
130+
update: formattedUpdate, // Single update object instead of array
108131
serverTime: Date.now(),
109132
});
110133
});
@@ -126,14 +149,17 @@ app.post('/api/trigger-update', (req, res) => {
126149
return res.status(404).json({ error: 'Update not found' });
127150
}
128151

152+
// Generate a unique webpack hash for this update
153+
const webpackHash = generateWebpackHash();
154+
129155
const triggeredUpdate = {
130-
id: ++lastUpdateId,
131156
updateId: updateId,
132157
filename: update.filename,
133158
description: description || update.description,
134159
content: update.content,
135160
triggered: true,
136161
timestamp: Date.now(),
162+
webpackHash: webpackHash,
137163
};
138164

139165
updateHistory.push(triggeredUpdate);
@@ -159,19 +185,45 @@ app.post('/api/trigger-basic-debugger-test', (req, res) => {
159185
const { description = 'Basic Debugger Test Update' } = req.body;
160186

161187
// Create a test update specifically for basic debugger
188+
// Generate a unique hash for this update to prevent continuous reloads
189+
const uniqueUpdateHash = `basic-debugger-test-${Date.now().toString(36)}`;
190+
191+
// Generate a unique webpack hash for this update
192+
const webpackHash = generateWebpackHash();
193+
162194
const testUpdate = {
163-
id: ++lastUpdateId,
164-
updateId: 'basic-debugger-test',
165-
filename: 'basic-debugger-test.hot-update.js',
195+
updateId: uniqueUpdateHash, // Use unique hash instead of fixed string
196+
filename: `${uniqueUpdateHash}.hot-update.js`,
166197
description: description,
198+
webpackHash: webpackHash,
167199
content: `/** Basic Debugger Test Update */
168200
exports.id = 'main';
169-
exports.ids = ['something'];
170-
exports.modules = {};
201+
exports.ids = null;
202+
exports.modules = {
203+
'./src/index.js': function(module, exports, __webpack_require__) {
204+
console.log('🧪 Basic debugger test module loaded at:', new Date().toISOString());
205+
console.log('🔄 This is a test hot update for the basic debugger');
206+
207+
// Test module that demonstrates HMR functionality
208+
const testData = {
209+
timestamp: Date.now(),
210+
message: 'Hot update applied successfully!',
211+
counter: Math.floor(Math.random() * 1000)
212+
};
213+
214+
console.log('📊 Test data:', testData);
215+
if (module.hot) {
216+
console.log('🔥 Debug demo has module.hot support');
217+
// process.exit();
218+
module.hot.accept();
219+
}
220+
module.exports = testData;
221+
}
222+
};
171223
exports.runtime = /******/ function (__webpack_require__) {
172224
/******/ /* webpack/runtime/getFullHash */
173225
/******/ (() => {
174-
/******/ __webpack_require__.h = () => 'basic-debugger-test';
226+
/******/ __webpack_require__.h = () => '${webpackHash}';
175227
/******/
176228
})();
177229
/******/
@@ -204,7 +256,7 @@ app.get('/api/status', (req, res) => {
204256
connectedClients: connectedClients.size,
205257
availableUpdates: availableUpdates.length,
206258
updateHistory: updateHistory.length,
207-
lastUpdateId: lastUpdateId,
259+
totalUpdates: updateHistory.length,
208260
uptime: process.uptime(),
209261
});
210262
});

apps/vm-hotreload/full-demo/basic_debugging/custom-hmr-helpers.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ const { createHMRRuntime } = require('./hmr-runtime');
33

44
// Webpack runtime code for in-memory HMR - only executed when webpack is available
55
function injectInMemoryHMRRuntime(__webpack_require__) {
6-
if (typeof __webpack_require__ === 'undefined') {
6+
if (
7+
typeof __webpack_require__ === 'undefined' ||
8+
__webpack_require__.setInMemoryManifest
9+
) {
710
console.warn(
811
'[Custom HMR Helper] __webpack_require__ not available, skipping runtime injection',
912
);
@@ -76,9 +79,7 @@ function applyHotUpdateFromStringsByPatching(
7679
manifestJsonString,
7780
chunkJsStringsMap,
7881
) {
79-
console.log(
80-
'🔥 [Custom HMR Helper] Applying update using patched runtime modules',
81-
);
82+
// Applying update using patched runtime modules
8283

8384
return new Promise((resolve, reject) => {
8485
try {
@@ -95,29 +96,27 @@ function applyHotUpdateFromStringsByPatching(
9596

9697
// Inject the in-memory HMR runtime if not already injected
9798
if (!webpackRequire.setInMemoryManifest) {
98-
console.log('🔧 [Custom HMR Helper] Injecting in-memory HMR runtime');
99+
// Injecting in-memory HMR runtime
99100
injectInMemoryHMRRuntime(webpackRequire);
100101
}
101102

102-
console.log('🚀 [Custom HMR Helper] Setting in-memory content for HMR');
103+
// Setting in-memory content for HMR
103104

104105
// Set the in-memory manifest content
105106
if (webpackRequire.setInMemoryManifest) {
106107
webpackRequire.setInMemoryManifest(manifestJsonString);
107-
console.log('📄 [Custom HMR Helper] Set in-memory manifest');
108+
// Set in-memory manifest
108109
} else {
109-
console.warn(
110-
'⚠️ [Custom HMR Helper] setInMemoryManifest not available',
111-
);
110+
// setInMemoryManifest not available
112111
}
113112
// Set the in-memory chunk content for each chunk
114113
if (webpackRequire.setInMemoryChunk) {
115114
for (const chunkId in chunkJsStringsMap) {
116115
webpackRequire.setInMemoryChunk(chunkId, chunkJsStringsMap[chunkId]);
117-
console.log(`📦 [Custom HMR Helper] Set in-memory chunk: ${chunkId}`);
116+
// Set in-memory chunk
118117
}
119118
} else {
120-
console.warn('⚠️ [Custom HMR Helper] setInMemoryChunk not available');
119+
// setInMemoryChunk not available
121120
}
122121

123122
console.log(
@@ -136,10 +135,7 @@ function applyHotUpdateFromStringsByPatching(
136135
resolve([]);
137136
return;
138137
}
139-
console.log(
140-
'✅ [Custom HMR Helper] Update applied. Updated modules:',
141-
updatedModules,
142-
);
138+
// Update applied
143139
resolve(updatedModules || []);
144140
})
145141
.catch((error) => {

0 commit comments

Comments
 (0)