Skip to content

Commit 4d58b4a

Browse files
fix: improve Vite plugin hot update handling with file invalidation
1 parent 8e1676a commit 4d58b4a

2 files changed

Lines changed: 58 additions & 26 deletions

File tree

src/ts-transformer.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@ export const refreshProject = () => {
1212
});
1313
}
1414

15-
refreshProject();
15+
export const invalidateOneFile = (file: string) => {
16+
17+
const sourceFileToRefresh = project.getSourceFile(file);
18+
if (sourceFileToRefresh) {
19+
sourceFileToRefresh.refreshFromFileSystemSync();
20+
return true;
21+
} else {
22+
return false;
23+
}
24+
}
1625

1726
export function transform(code: string, filePath: string): string {
1827

@@ -89,4 +98,6 @@ export function transform(code: string, filePath: string): string {
8998
}
9099

91100
return sourceFile.getFullText();
92-
}
101+
}
102+
103+
refreshProject();

src/vite-plugin.ts

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PluginOption } from "vite";
2-
import {fileToTypes, refreshProject, transform, typeToFile} from "./ts-transformer.js";
2+
import {fileToTypes, invalidateOneFile, transform, typeToFile} from "./ts-transformer.js";
33

44
// noinspection JSUnusedGlobalSymbols
55
/**
@@ -28,40 +28,61 @@ export default function TsRuntimePickerVitePlugin(): PluginOption {
2828
return null;
2929
},
3030

31-
handleHotUpdate: ({server, file}) => {
32-
const affectedFiles = [];
31+
handleHotUpdate: ({server, file, timestamp}) => {
32+
const isKnownTypeDefinitionFile = [...typeToFile.values()].includes(file);
3333

34-
// Loop over all files that used types
34+
if (!isKnownTypeDefinitionFile) {
35+
// This file change doesn't affect any of our transformations, so we can ignore it.
36+
return;
37+
}
38+
39+
console.log(`[ts-runtime-picker] Change detected in a tracked type definition: ${file}`);
40+
41+
42+
// Surgically update the ts-morph project
43+
if (!invalidateOneFile(file)) return;
44+
45+
const dependentModulesToUpdate = [];
46+
47+
// Find all files (`userFile`) that import a type from the file that just changed (`file`).
3548
for (const [userFile, usedTypes] of fileToTypes.entries()) {
3649
for (const typeName of usedTypes) {
3750
if (typeToFile.get(typeName) === file) {
38-
affectedFiles.push(userFile);
39-
break;
51+
// This `userFile` depends on the changed type. We need to update it.
52+
const moduleNode = server.moduleGraph.getModuleById(userFile);
53+
if (moduleNode) {
54+
dependentModulesToUpdate.push(moduleNode);
55+
// Once we know the file is affected, we don't need to check its other types.
56+
break;
57+
}
4058
}
4159
}
4260
}
4361

44-
if (affectedFiles.length) {
45-
refreshProject();
62+
if (dependentModulesToUpdate.length > 0) {
63+
console.log(`[ts-runtime-picker] Found ${dependentModulesToUpdate.length} module(s) to update.`);
4664

47-
for (const file of affectedFiles) {
48-
const mod = server.moduleGraph.getModuleById(file);
49-
if (mod) {
50-
server.moduleGraph.invalidateModule(mod);
51-
server.ws.send({
52-
type: 'update',
53-
updates: [
54-
{
55-
type: 'js-update',
56-
path: mod.url, // relative to root
57-
acceptedPath: mod.url,
58-
timestamp: Date.now()
59-
}
60-
]
61-
});
62-
}
65+
//Invalidate modules and notify the client
66+
for (const mod of dependentModulesToUpdate) {
67+
server.moduleGraph.invalidateModule(mod);
6368
}
69+
70+
// This sends the HMR update to the client (browser), telling it which
71+
// modules have changed so it can re-request them.
72+
server.ws.send({
73+
type: 'update',
74+
updates: dependentModulesToUpdate.map((mod) => ({
75+
type: 'js-update',
76+
path: mod.url,
77+
acceptedPath: mod.url,
78+
timestamp: timestamp
79+
}))
80+
});
6481
}
82+
83+
// We return the list of modules we've handled so Vite's core HMR logic
84+
// doesn't try to process them again.
85+
return dependentModulesToUpdate;
6586
}
6687
} as PluginOption;
6788
}

0 commit comments

Comments
 (0)