-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathesbuild.ts
131 lines (109 loc) · 3.66 KB
/
esbuild.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import fs from 'node:fs/promises';
import path from 'node:path';
import { buildIgnore, takeSnapshot, buildTree, SnapshotProps, snapshotDefaults } from '@jsnix/utils/snapshot';
import { FileSystemTree } from '@webcontainer/api';
export type Esbuild = {
cacheDir?: string;
};
export type SnapshotCache = {
timestamp: number;
files: string[];
totalSize: number;
snapshot: FileSystemTree;
};
const cacheFolder = '.cache';
const cacheFile = '@jsnix-snapshot.json';
const esbuildDefaults: Required<SnapshotProps<Esbuild>> = {
...snapshotDefaults,
cacheDir: path.resolve(process.cwd(), cacheFolder),
};
export const snapshot = (props: SnapshotProps<Esbuild> = {}) => {
const { root, cacheDir, include, exclude, gitignore, transform } = { ...esbuildDefaults, ...props };
return {
name: '@jsnix/snapshot',
async setup(build: any) {
const cacheFilePath = path.resolve(cacheDir, cacheFile);
const excluded = await buildIgnore(root, include, exclude, gitignore);
const resolved = path.resolve(root);
// Check if the snapshot needs to be rebuilt
async function needsRebuild(resolved: string, cache: SnapshotCache): Promise<boolean> {
const seenFiles = new Set<string>(cache.files);
let seenSize = 0;
for await (const filePath of buildTree(resolved, undefined, excluded)) {
const stats = await fs.stat(filePath);
// Check for newer modifications
if (stats.mtimeMs > cache.timestamp) return true;
// Check whether we've seen this file
if (!seenFiles.has(filePath)) return true;
// Update seen files and size
seenFiles.delete(filePath);
seenSize += stats.size;
}
// Check for added or missing files and size mismatch
if (seenFiles.size > 0 || seenSize !== cache.totalSize) {
return true;
}
return false;
}
// Load or create the cache
async function loadOrCreateCache(): Promise<SnapshotCache> {
try {
const cacheContent = await fs.readFile(cacheFilePath, 'utf-8');
const cache = JSON.parse(cacheContent);
return {
...cache,
files: new Set(cache.files),
};
}
catch {
return { timestamp: 0, files: [], totalSize: 0, snapshot: {} };
}
}
// Save the cache
async function saveCache(cache: SnapshotCache) {
await fs.mkdir(path.dirname(cacheFilePath), { recursive: true });
const serializedCache = {
...cache,
files: Array.from(cache.files), // Convert Set to array for JSON serialization
};
await fs.writeFile(cacheFilePath, JSON.stringify(serializedCache, null, 2));
}
build.onResolve({ filter: /^virtual:@jsnix\/snapshot/ }, (args: any) => {
return {
path: args.path,
namespace: 'jsnix-snapshot',
};
});
build.onLoad({ filter: /.*/, namespace: 'jsnix-snapshot' }, async () => {
const cache = await loadOrCreateCache();
const shouldRebuild = await needsRebuild(resolved, cache);
let snapshot: FileSystemTree;
if (shouldRebuild) {
snapshot = await transform(await takeSnapshot({ root: resolved, include, exclude, gitignore }));
const files: string[] = [];
let totalSize = 0;
for await (const filePath of buildTree(resolved, undefined, excluded)) {
const stats = await fs.stat(filePath);
files.push(filePath);
totalSize += stats.size;
// Update latest timestamp
if (stats.mtimeMs > cache.timestamp) {
cache.timestamp = stats.mtimeMs;
}
}
cache.files = files;
cache.totalSize = totalSize;
cache.snapshot = snapshot;
await saveCache(cache);
}
else {
snapshot = cache.snapshot;
}
return {
contents: `export default ${JSON.stringify(snapshot)}`,
loader: 'js',
};
});
},
};
};