Skip to content

Commit 8e1676a

Browse files
fix: enhance Vite plugin to track type usage and refresh project on hot updates
1 parent 416c203 commit 8e1676a

2 files changed

Lines changed: 66 additions & 4 deletions

File tree

src/ts-transformer.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import { Project, SyntaxKind } from "ts-morph";
22

3-
const project = new Project({
4-
tsConfigFilePath: "tsconfig.json",
5-
});
3+
export const fileToTypes = new Map<string, Set<string>>();
4+
5+
export const typeToFile = new Map<string, string>();
6+
7+
let project = null as unknown as Project;
8+
9+
export const refreshProject = () => {
10+
project = new Project({
11+
tsConfigFilePath: "tsconfig.json",
12+
});
13+
}
14+
15+
refreshProject();
616

717
export function transform(code: string, filePath: string): string {
818

19+
const usedTypes = new Set<string>();
20+
921
let sourceFile = project.getSourceFile(filePath);
1022

1123
if (!sourceFile) {
@@ -16,6 +28,20 @@ export function transform(code: string, filePath: string): string {
1628
sourceFile.replaceWithText(code);
1729
}
1830

31+
sourceFile.getDescendantsOfKind(SyntaxKind.TypeReference).forEach(ref => {
32+
const typeName = ref.getTypeName().getText();
33+
usedTypes.add(typeName);
34+
35+
// BONUS: resolve where this type is defined
36+
const decl = ref.getType().getSymbol()?.getDeclarations()?.[0];
37+
const source = decl?.getSourceFile()?.getFilePath();
38+
if (source) {
39+
typeToFile.set(typeName, source);
40+
}
41+
});
42+
43+
fileToTypes.set(filePath, usedTypes);
44+
1945
// Figure out what the “createPicker” import is called (alias or direct)
2046
let createPickerAlias: string | null = null;
2147
for (const importDecl of sourceFile.getImportDeclarations()) {

src/vite-plugin.ts

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

44
// noinspection JSUnusedGlobalSymbols
55
/**
@@ -27,6 +27,42 @@ export default function TsRuntimePickerVitePlugin(): PluginOption {
2727

2828
return null;
2929
},
30+
31+
handleHotUpdate: ({server, file}) => {
32+
const affectedFiles = [];
33+
34+
// Loop over all files that used types
35+
for (const [userFile, usedTypes] of fileToTypes.entries()) {
36+
for (const typeName of usedTypes) {
37+
if (typeToFile.get(typeName) === file) {
38+
affectedFiles.push(userFile);
39+
break;
40+
}
41+
}
42+
}
43+
44+
if (affectedFiles.length) {
45+
refreshProject();
46+
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+
}
63+
}
64+
}
65+
}
3066
} as PluginOption;
3167
}
3268

0 commit comments

Comments
 (0)