auklet 0.2.10 → 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.
@@ -1,17 +1,32 @@
1
1
  import type { HotUpdateOptions, ViteDevServer } from 'vite';
2
2
  import type { ModuleStyleGraph } from '#auklet/css/vite/moduleGraph/graph';
3
+ export type AukletStyleHmrOptions = {
4
+ pruneDelayMs?: number;
5
+ };
3
6
  export declare class AukletStyleHmr {
4
7
  private readonly graph;
5
8
  private readonly lastUpdateTimes;
6
9
  private suppressFullReloadUntil;
7
10
  private readonly virtualIdsByDependency;
8
- constructor(graph: () => ModuleStyleGraph);
11
+ private readonly filesByVirtualId;
12
+ private readonly schedulePruneStaleVirtualDependencies;
13
+ private readonly pruneDelayMs;
14
+ constructor(graph: () => ModuleStyleGraph, options?: AukletStyleHmrOptions);
9
15
  trackVirtualStyleDependency(file: string, virtualId: string): void;
10
- hasTrackedStyleDependency(file: string): boolean;
16
+ replaceVirtualStyleDependency(virtualId: string, files: Array<string>): void;
17
+ hasTrackedStyleDependency(file: string, moduleGraph?: Pick<ViteDevServer['moduleGraph'], 'getModuleById'>): boolean;
18
+ pruneStaleVirtualDependencies(moduleGraph: Pick<ViteDevServer['moduleGraph'], 'getModuleById'>): void;
19
+ scheduleStaleVirtualDependencyPrune(moduleGraph: Pick<ViteDevServer['moduleGraph'], 'getModuleById'>): void;
20
+ cancelStaleVirtualDependencyPrune(): void;
11
21
  installFullReloadGuard(server: Pick<ViteDevServer, 'ws'>): void;
12
- handleStyleHotUpdate(context: HotUpdateOptions): never[] | undefined;
13
- handleSourceModuleChange(server: Pick<ViteDevServer, 'moduleGraph' | 'ws'>, file: string): boolean;
22
+ handleStyleHotUpdate(context: HotUpdateOptions): Promise<never[] | undefined>;
23
+ handleSourceModuleChange(server: Pick<ViteDevServer, 'moduleGraph' | 'ws'>, file: string): Promise<boolean>;
14
24
  private sendVirtualStyleUpdates;
25
+ private parseTrackedVirtualIds;
26
+ private getDependencyVirtualIds;
27
+ private parseTrackedVirtualId;
28
+ private createTrackedVirtualStyleResults;
29
+ private refreshSourceModuleUpdates;
15
30
  private suppressFullReload;
16
31
  private shouldSuppressFullReload;
17
32
  private isDuplicateUpdate;
@@ -1,9 +1,10 @@
1
1
  import path from 'node:path';
2
- import { isString } from 'aidly';
2
+ import { isString, throttle } from 'aidly';
3
3
  import { normalizeFileKey } from '#auklet/utils';
4
4
  import { createAukletLogger } from '#auklet/logger';
5
5
  const FULL_RELOAD_SUPPRESS_MS = 100;
6
6
  const DUPLICATE_UPDATE_IGNORE_MS = 500;
7
+ const RESOLVED_VIRTUAL_ID_PREFIX = '\0auklet-css:';
7
8
  const logger = createAukletLogger({ scope: 'css:vite' });
8
9
  const toBrowserVirtualPath = (id) => {
9
10
  return `/@id/${id.replace('\0', '__x00__')}`;
@@ -11,20 +12,39 @@ const toBrowserVirtualPath = (id) => {
11
12
  const getRelativeFile = (file) => {
12
13
  return path.relative(process.cwd(), file);
13
14
  };
14
- const addVirtualStyleDependency = (virtualIdsByDependency, file, virtualId) => {
15
+ const addVirtualStyleDependency = (virtualIdsByDependency, filesByVirtualId, file, virtualId) => {
15
16
  const normalizedFile = normalizeFileKey(file);
16
17
  const values = virtualIdsByDependency.get(normalizedFile) ?? new Set();
17
18
  values.add(virtualId);
18
19
  virtualIdsByDependency.set(normalizedFile, values);
20
+ const files = filesByVirtualId.get(virtualId) ?? new Set();
21
+ files.add(normalizedFile);
22
+ filesByVirtualId.set(virtualId, files);
19
23
  };
20
- const getDependencyVirtualIds = (virtualIdsByDependency, file) => {
21
- return Array.from(virtualIdsByDependency.get(normalizeFileKey(file)) ?? []);
24
+ const removeVirtualStyleDependency = (virtualIdsByDependency, filesByVirtualId, file, virtualId) => {
25
+ const normalizedFile = normalizeFileKey(file);
26
+ const values = virtualIdsByDependency.get(normalizedFile);
27
+ if (values) {
28
+ values.delete(virtualId);
29
+ if (!values.size) {
30
+ virtualIdsByDependency.delete(normalizedFile);
31
+ }
32
+ }
33
+ const files = filesByVirtualId.get(virtualId);
34
+ if (files) {
35
+ files.delete(normalizedFile);
36
+ if (!files.size) {
37
+ filesByVirtualId.delete(virtualId);
38
+ }
39
+ }
22
40
  };
23
- const getDependencyVirtualModules = (virtualIdsByDependency, server, file) => {
24
- return getDependencyVirtualIds(virtualIdsByDependency, file).flatMap((id) => {
25
- const module = server.moduleGraph.getModuleById(id);
26
- return module ? [module] : [];
27
- });
41
+ const sameStringSets = (left, right) => {
42
+ if (left.length !== right.length)
43
+ return false;
44
+ const rightSet = new Set(right);
45
+ if (rightSet.size !== left.length)
46
+ return false;
47
+ return left.every((item) => rightSet.has(item));
28
48
  };
29
49
  const createJsUpdates = (virtualIds, timestamp) => {
30
50
  return virtualIds.map((id) => {
@@ -44,14 +64,46 @@ export class AukletStyleHmr {
44
64
  lastUpdateTimes = new Map();
45
65
  suppressFullReloadUntil = 0;
46
66
  virtualIdsByDependency = new Map();
47
- constructor(graph) {
67
+ filesByVirtualId = new Map();
68
+ schedulePruneStaleVirtualDependencies;
69
+ pruneDelayMs;
70
+ constructor(graph, options = {}) {
48
71
  this.graph = graph;
72
+ this.pruneDelayMs = options.pruneDelayMs ?? 2 * 60 * 1000;
73
+ this.schedulePruneStaleVirtualDependencies = throttle(this.pruneDelayMs, (moduleGraph) => this.pruneStaleVirtualDependencies(moduleGraph));
49
74
  }
50
75
  trackVirtualStyleDependency(file, virtualId) {
51
- addVirtualStyleDependency(this.virtualIdsByDependency, file, virtualId);
76
+ addVirtualStyleDependency(this.virtualIdsByDependency, this.filesByVirtualId, file, virtualId);
77
+ }
78
+ replaceVirtualStyleDependency(virtualId, files) {
79
+ const normalizedFiles = new Set(files.map((file) => normalizeFileKey(file)));
80
+ const previousFiles = this.filesByVirtualId.get(virtualId) ?? new Set();
81
+ for (const file of previousFiles) {
82
+ if (normalizedFiles.has(file))
83
+ continue;
84
+ removeVirtualStyleDependency(this.virtualIdsByDependency, this.filesByVirtualId, file, virtualId);
85
+ }
86
+ for (const file of normalizedFiles) {
87
+ addVirtualStyleDependency(this.virtualIdsByDependency, this.filesByVirtualId, file, virtualId);
88
+ }
89
+ }
90
+ hasTrackedStyleDependency(file, moduleGraph) {
91
+ return this.getDependencyVirtualIds(file, moduleGraph).length > 0;
92
+ }
93
+ pruneStaleVirtualDependencies(moduleGraph) {
94
+ for (const [normalizedFile, virtualIds] of this.virtualIdsByDependency) {
95
+ for (const virtualId of Array.from(virtualIds)) {
96
+ if (moduleGraph.getModuleById(virtualId))
97
+ continue;
98
+ removeVirtualStyleDependency(this.virtualIdsByDependency, this.filesByVirtualId, normalizedFile, virtualId);
99
+ }
100
+ }
101
+ }
102
+ scheduleStaleVirtualDependencyPrune(moduleGraph) {
103
+ this.schedulePruneStaleVirtualDependencies(moduleGraph);
52
104
  }
53
- hasTrackedStyleDependency(file) {
54
- return (getDependencyVirtualIds(this.virtualIdsByDependency, file).length > 0);
105
+ cancelStaleVirtualDependencyPrune() {
106
+ this.schedulePruneStaleVirtualDependencies.cancel();
55
107
  }
56
108
  installFullReloadGuard(server) {
57
109
  const send = server.ws.send.bind(server.ws);
@@ -69,7 +121,7 @@ export class AukletStyleHmr {
69
121
  send(payload);
70
122
  });
71
123
  }
72
- handleStyleHotUpdate(context) {
124
+ async handleStyleHotUpdate(context) {
73
125
  const graph = this.graph();
74
126
  if (!graph.isSourceGraphFile(context.file) ||
75
127
  !graph.isStyleFile(context.file)) {
@@ -81,24 +133,43 @@ export class AukletStyleHmr {
81
133
  return [];
82
134
  }
83
135
  const updates = this.sendVirtualStyleUpdates(context.file, context.server, context.timestamp);
136
+ if (!updates.sent) {
137
+ return [];
138
+ }
84
139
  logger.info(`package css hmr ${getRelativeFile(context.file)} tracked=${updates.tracked} updates=${updates.sent}`);
85
140
  return [];
86
141
  }
87
- handleSourceModuleChange(server, file) {
142
+ async handleSourceModuleChange(server, file) {
88
143
  const graph = this.graph();
89
144
  if (!graph.isSourceGraphFile(file) || !graph.isSourceModuleFile(file)) {
90
145
  return false;
91
146
  }
147
+ const virtualIds = this.getDependencyVirtualIds(file, server.moduleGraph);
148
+ if (!virtualIds.length) {
149
+ graph.invalidateFile(file);
150
+ return false;
151
+ }
152
+ const parsedVirtualIds = this.parseTrackedVirtualIds(graph, virtualIds);
153
+ if (!parsedVirtualIds.length) {
154
+ graph.invalidateFile(file);
155
+ return false;
156
+ }
157
+ const previousResults = this.createTrackedVirtualStyleResults(graph, parsedVirtualIds);
92
158
  graph.invalidateFile(file);
93
- const updates = this.sendVirtualStyleUpdates(file, server, Date.now());
159
+ const updates = await this.refreshSourceModuleUpdates(server, parsedVirtualIds, previousResults);
160
+ if (!updates.sent) {
161
+ return false;
162
+ }
94
163
  logger.info(`package css source hmr ${getRelativeFile(file)} tracked=${updates.tracked} updates=${updates.sent}`);
95
- return true;
164
+ return updates.sent > 0;
96
165
  }
97
166
  sendVirtualStyleUpdates(file, server, timestamp) {
98
- const virtualIds = getDependencyVirtualIds(this.virtualIdsByDependency, file);
99
- const modules = getDependencyVirtualModules(this.virtualIdsByDependency, server, file);
100
- for (const module of modules) {
101
- server.moduleGraph.invalidateModule(module);
167
+ const virtualIds = this.getDependencyVirtualIds(file, server.moduleGraph);
168
+ for (const id of virtualIds) {
169
+ const module = server.moduleGraph.getModuleById(id);
170
+ if (module) {
171
+ server.moduleGraph.invalidateModule(module);
172
+ }
102
173
  }
103
174
  const updates = createJsUpdates(virtualIds, timestamp);
104
175
  if (updates.length) {
@@ -112,6 +183,76 @@ export class AukletStyleHmr {
112
183
  tracked: virtualIds.length,
113
184
  };
114
185
  }
186
+ parseTrackedVirtualIds(graph, virtualIds) {
187
+ return virtualIds
188
+ .map((id) => this.parseTrackedVirtualId(graph, id))
189
+ .filter((item) => Boolean(item));
190
+ }
191
+ getDependencyVirtualIds(file, moduleGraph) {
192
+ const normalizedFile = normalizeFileKey(file);
193
+ const virtualIds = Array.from(this.virtualIdsByDependency.get(normalizedFile) ?? []);
194
+ if (!moduleGraph) {
195
+ return virtualIds;
196
+ }
197
+ const liveVirtualIds = virtualIds.filter((virtualId) => moduleGraph.getModuleById(virtualId));
198
+ if (liveVirtualIds.length !== virtualIds.length) {
199
+ const liveSet = new Set(liveVirtualIds);
200
+ for (const virtualId of virtualIds) {
201
+ if (liveSet.has(virtualId))
202
+ continue;
203
+ removeVirtualStyleDependency(this.virtualIdsByDependency, this.filesByVirtualId, normalizedFile, virtualId);
204
+ }
205
+ }
206
+ return liveVirtualIds;
207
+ }
208
+ parseTrackedVirtualId(graph, id) {
209
+ const parsedId = id.startsWith(RESOLVED_VIRTUAL_ID_PREFIX)
210
+ ? id.slice(RESOLVED_VIRTUAL_ID_PREFIX.length)
211
+ : id;
212
+ const parsed = graph.parsePackageStyleId(parsedId);
213
+ if (!parsed || !graph.getPackageNames().includes(parsed.packageName)) {
214
+ return null;
215
+ }
216
+ return {
217
+ id,
218
+ parsed,
219
+ };
220
+ }
221
+ createTrackedVirtualStyleResults(graph, parsedVirtualIds) {
222
+ const previousResults = new Map();
223
+ for (const item of parsedVirtualIds) {
224
+ previousResults.set(item.id, graph.peekPackageStyleCode(item.parsed));
225
+ }
226
+ return previousResults;
227
+ }
228
+ async refreshSourceModuleUpdates(server, parsedVirtualIds, previousResults) {
229
+ const graph = this.graph();
230
+ const changedVirtualIds = [];
231
+ for (const item of parsedVirtualIds) {
232
+ const nextResult = await graph.createPackageStyleCode(item.parsed);
233
+ const previousResult = previousResults.get(item.id);
234
+ if (!previousResult ||
235
+ previousResult.code !== nextResult.code ||
236
+ !sameStringSets(previousResult.watchFiles, nextResult.watchFiles)) {
237
+ changedVirtualIds.push(item.id);
238
+ const module = server.moduleGraph.getModuleById(item.id);
239
+ if (module) {
240
+ server.moduleGraph.invalidateModule(module);
241
+ }
242
+ }
243
+ }
244
+ const updates = createJsUpdates(changedVirtualIds, Date.now());
245
+ if (updates.length) {
246
+ server.ws.send({
247
+ type: 'update',
248
+ updates,
249
+ });
250
+ }
251
+ return {
252
+ sent: updates.length,
253
+ tracked: parsedVirtualIds.length,
254
+ };
255
+ }
115
256
  suppressFullReload() {
116
257
  this.suppressFullReloadUntil = Date.now() + FULL_RELOAD_SUPPRESS_MS;
117
258
  }
@@ -16,6 +16,7 @@ export declare class ModuleStyleGraph {
16
16
  getPackageNames(): string[];
17
17
  getWatchRoots(): Promise<string[]>;
18
18
  createPackageStyleCode(parsed: PackageStyleId): Promise<import("#auklet/css/vite/moduleGraph/types").PackageStyleLoadResult>;
19
+ peekPackageStyleCode(parsed: PackageStyleId): import("#auklet/css/vite/moduleGraph/types").PackageStyleLoadResult | null;
19
20
  invalidatePackage(packageName: string): void;
20
21
  invalidateFile(file: string): string | null;
21
22
  isSourceModuleFile(file: string): boolean;
@@ -60,6 +60,9 @@ export class ModuleStyleGraph {
60
60
  createPackageStyleCode(parsed) {
61
61
  return this.styleCodeFactory.createPackageStyleCode(parsed, this.requestCache);
62
62
  }
63
+ peekPackageStyleCode(parsed) {
64
+ return this.requestCache.peekLoadResult(parsed);
65
+ }
63
66
  invalidatePackage(packageName) {
64
67
  this.requestCache.invalidatePackage(packageName);
65
68
  }
@@ -1,10 +1,12 @@
1
1
  import type { PackageStyleLoadResult } from '#auklet/css/vite/moduleGraph/types';
2
2
  export type PersistentStyleGraphCacheOptions = {
3
3
  root: string;
4
+ maxCacheFiles?: number;
4
5
  };
5
6
  export declare class PersistentStyleGraphCache {
6
- private static readonly cleanedRoots;
7
+ private static readonly lastCleanupTimes;
7
8
  private readonly cacheRoot;
9
+ private readonly maxCacheFiles;
8
10
  constructor(options: PersistentStyleGraphCacheOptions);
9
11
  createKey(value: unknown): string;
10
12
  read(key: string): PackageStyleLoadResult | null;
@@ -4,6 +4,7 @@ import crypto from 'node:crypto';
4
4
  import { toPosixPath } from '#auklet/utils';
5
5
  const cacheVersion = 'v1';
6
6
  const maxCacheFiles = 5000;
7
+ const cleanupIntervalMs = 5 * 60 * 1000;
7
8
  const staleCacheAgeMs = 7 * 24 * 60 * 60 * 1000;
8
9
  const stableStringify = (value) => {
9
10
  if (Array.isArray(value)) {
@@ -68,10 +69,12 @@ const statCachedFile = (file) => {
68
69
  };
69
70
  };
70
71
  export class PersistentStyleGraphCache {
71
- static cleanedRoots = new Set();
72
+ static lastCleanupTimes = new Map();
72
73
  cacheRoot;
74
+ maxCacheFiles;
73
75
  constructor(options) {
74
76
  this.cacheRoot = path.join(options.root, 'node_modules', '.auklet', 'cache', 'vite-style', cacheVersion);
77
+ this.maxCacheFiles = options.maxCacheFiles ?? maxCacheFiles;
75
78
  }
76
79
  createKey(value) {
77
80
  return hashValue({
@@ -96,10 +99,10 @@ export class PersistentStyleGraphCache {
96
99
  }
97
100
  }
98
101
  write(key, result, inputFiles) {
99
- const files = Array.from(new Map(inputFiles
100
- .map((file) => statCachedFile(file))
101
- .map((file) => [file.file, file])).values());
102
102
  try {
103
+ const files = Array.from(new Map(inputFiles
104
+ .map((file) => statCachedFile(file))
105
+ .map((file) => [file.file, file])).values());
103
106
  fs.mkdirSync(this.cacheRoot, { recursive: true });
104
107
  fs.writeFileSync(this.getCacheFile(key), `${JSON.stringify({
105
108
  files,
@@ -114,11 +117,12 @@ export class PersistentStyleGraphCache {
114
117
  this.cleanupStaleEntries();
115
118
  }
116
119
  cleanupStaleEntries() {
117
- if (PersistentStyleGraphCache.cleanedRoots.has(this.cacheRoot))
120
+ const now = Date.now();
121
+ const lastCleanupTime = PersistentStyleGraphCache.lastCleanupTimes.get(this.cacheRoot) ?? 0;
122
+ if (now - lastCleanupTime < cleanupIntervalMs)
118
123
  return;
119
- PersistentStyleGraphCache.cleanedRoots.add(this.cacheRoot);
124
+ PersistentStyleGraphCache.lastCleanupTimes.set(this.cacheRoot, now);
120
125
  try {
121
- const now = Date.now();
122
126
  const entries = fs
123
127
  .readdirSync(this.cacheRoot, { withFileTypes: true })
124
128
  .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
@@ -137,7 +141,7 @@ export class PersistentStyleGraphCache {
137
141
  }
138
142
  freshEntries.push(entry);
139
143
  }
140
- const overflowCount = freshEntries.length - maxCacheFiles;
144
+ const overflowCount = freshEntries.length - this.maxCacheFiles;
141
145
  if (overflowCount <= 0)
142
146
  return;
143
147
  for (const entry of freshEntries
@@ -4,6 +4,10 @@ import type { StyleProcessor } from '#auklet/css/core/styleProcessor';
4
4
  import type { WorkspaceStyleResolver } from '#auklet/css/core/workspaceStyleResolver';
5
5
  import type { StylePackageSource } from '#auklet/css/vite/moduleGraph/packageSource/types';
6
6
  import type { LoadAukletConfig, PackageStyleId, PackageStyleLoadResult } from '#auklet/css/vite/moduleGraph/types';
7
+ type LoadResultEntry = {
8
+ commit?: () => PackageStyleLoadResult;
9
+ result: PackageStyleLoadResult;
10
+ };
7
11
  export type PackageStyleContext = {
8
12
  normalizedConfig: NormalizedAukletConfig;
9
13
  context: ResolvedModuleStyleBuildContext;
@@ -25,9 +29,12 @@ export declare class ModuleStyleGraphRequestCache {
25
29
  private readonly options;
26
30
  private readonly contexts;
27
31
  private readonly loadResults;
32
+ private readonly settledLoadResults;
28
33
  private readonly loadResultDependencies;
34
+ private readonly loadResultVersions;
29
35
  private readonly loadAukletConfig;
30
36
  private readonly persistentCache;
37
+ private graphVersion;
31
38
  constructor(options: ModuleStyleGraphRequestCacheOptions);
32
39
  getPackageNames(): string[];
33
40
  isKnownPackageName(packageName: string): boolean;
@@ -68,7 +75,7 @@ export declare class ModuleStyleGraphRequestCache {
68
75
  sourceRoot: string;
69
76
  styleProcessor: StyleProcessor;
70
77
  } | null>;
71
- getLoadResult(parsed: PackageStyleId, create: () => Promise<PackageStyleLoadResult>): Promise<PackageStyleLoadResult>;
78
+ getLoadResult(parsed: PackageStyleId, create: () => Promise<LoadResultEntry>): Promise<PackageStyleLoadResult>;
72
79
  invalidatePackage(packageName: string): void;
73
80
  readPersistentLoadResult(parsed: PackageStyleId, context: PackageStyleContext): PackageStyleLoadResult | null;
74
81
  writePersistentLoadResult(parsed: PackageStyleId, context: PackageStyleContext, result: PackageStyleLoadResult): {
@@ -87,4 +94,10 @@ export declare class ModuleStyleGraphRequestCache {
87
94
  private getLoadResultKey;
88
95
  private getLoadResultPackageName;
89
96
  private invalidateLoadResults;
97
+ private discardLoadResult;
98
+ peekLoadResult(parsed: PackageStyleId): PackageStyleLoadResult | null;
99
+ private getGraphVersion;
100
+ private bumpGraphVersion;
101
+ private bumpLoadResultVersion;
90
102
  }
103
+ export {};
@@ -8,9 +8,12 @@ export class ModuleStyleGraphRequestCache {
8
8
  options;
9
9
  contexts = new Map();
10
10
  loadResults = new Map();
11
+ settledLoadResults = new Map();
11
12
  loadResultDependencies = new Map();
13
+ loadResultVersions = new Map();
12
14
  loadAukletConfig;
13
15
  persistentCache;
16
+ graphVersion = 0;
14
17
  constructor(options) {
15
18
  this.options = options;
16
19
  this.loadAukletConfig = options.loadAukletConfig ?? loadAukletConfig;
@@ -37,14 +40,53 @@ export class ModuleStyleGraphRequestCache {
37
40
  const cachedResult = this.loadResults.get(key);
38
41
  if (cachedResult)
39
42
  return cachedResult;
40
- const result = create().then((value) => {
41
- this.loadResultDependencies.set(key, new Set(value.dependencyPackages ?? []));
42
- return value;
43
+ const version = this.bumpLoadResultVersion(key);
44
+ const graphVersion = this.getGraphVersion();
45
+ let result;
46
+ result = create()
47
+ .then(async (entry) => {
48
+ if (this.getGraphVersion() !== graphVersion) {
49
+ const currentResult = this.loadResults.get(key);
50
+ if (currentResult && currentResult !== result) {
51
+ return currentResult;
52
+ }
53
+ this.discardLoadResult(key);
54
+ return this.getLoadResult(parsed, create);
55
+ }
56
+ if (this.loadResultVersions.get(key) === version) {
57
+ this.loadResultDependencies.set(key, new Set(entry.result.dependencyPackages ?? []));
58
+ }
59
+ try {
60
+ const committedResult = entry.commit?.();
61
+ if (committedResult) {
62
+ entry.result = committedResult;
63
+ }
64
+ }
65
+ catch {
66
+ // Persistent cache writes are best-effort and must not fail dev CSS.
67
+ }
68
+ this.settledLoadResults.set(key, entry.result);
69
+ return entry.result;
70
+ })
71
+ .catch((error) => {
72
+ if (this.getGraphVersion() !== graphVersion) {
73
+ const currentResult = this.loadResults.get(key);
74
+ if (currentResult && currentResult !== result) {
75
+ return currentResult;
76
+ }
77
+ this.discardLoadResult(key);
78
+ return this.getLoadResult(parsed, create);
79
+ }
80
+ if (this.loadResultVersions.get(key) === version) {
81
+ this.discardLoadResult(key);
82
+ }
83
+ throw error;
43
84
  });
44
85
  this.loadResults.set(key, result);
45
86
  return result;
46
87
  }
47
88
  invalidatePackage(packageName) {
89
+ this.bumpGraphVersion();
48
90
  this.contexts.delete(packageName);
49
91
  this.invalidateLoadResults(packageName);
50
92
  }
@@ -208,8 +250,29 @@ export class ModuleStyleGraphRequestCache {
208
250
  continue;
209
251
  }
210
252
  }
211
- this.loadResults.delete(key);
212
- this.loadResultDependencies.delete(key);
253
+ this.discardLoadResult(key);
213
254
  }
214
255
  }
256
+ discardLoadResult(key) {
257
+ this.bumpGraphVersion();
258
+ this.bumpLoadResultVersion(key);
259
+ this.loadResults.delete(key);
260
+ this.loadResultDependencies.delete(key);
261
+ this.settledLoadResults.delete(key);
262
+ }
263
+ peekLoadResult(parsed) {
264
+ return this.settledLoadResults.get(this.getLoadResultKey(parsed)) ?? null;
265
+ }
266
+ getGraphVersion() {
267
+ return this.graphVersion;
268
+ }
269
+ bumpGraphVersion() {
270
+ this.graphVersion += 1;
271
+ return this.graphVersion;
272
+ }
273
+ bumpLoadResultVersion(key) {
274
+ const nextVersion = (this.loadResultVersions.get(key) ?? 0) + 1;
275
+ this.loadResultVersions.set(key, nextVersion);
276
+ return nextVersion;
277
+ }
215
278
  }
@@ -14,19 +14,30 @@ export class StyleCodeFactory {
14
14
  this.config = config;
15
15
  }
16
16
  async createPackageStyleCode(parsed, cache) {
17
- return cache.getLoadResult(parsed, async () => this.normalizeTopLevelImports(await this.createUncachedPackageStyleCode(parsed, cache)));
18
- }
19
- async createUncachedPackageStyleCode(parsed, cache) {
20
- const context = await cache.getContext(parsed);
21
- if (!context) {
17
+ return cache.getLoadResult(parsed, async () => {
18
+ const context = await cache.getContext(parsed);
19
+ if (!context) {
20
+ return {
21
+ result: {
22
+ code: '',
23
+ watchFiles: [],
24
+ },
25
+ };
26
+ }
27
+ const cachedResult = cache.readPersistentLoadResult(parsed, context);
28
+ if (cachedResult) {
29
+ return {
30
+ result: cachedResult,
31
+ };
32
+ }
33
+ const result = this.normalizeTopLevelImports(await this.createUncachedPackageStyleCode(parsed, cache, context));
22
34
  return {
23
- code: '',
24
- watchFiles: [],
35
+ result,
36
+ commit: () => cache.writePersistentLoadResult(parsed, context, result),
25
37
  };
26
- }
27
- const cachedResult = cache.readPersistentLoadResult(parsed, context);
28
- if (cachedResult)
29
- return cachedResult;
38
+ });
39
+ }
40
+ async createUncachedPackageStyleCode(parsed, cache, context) {
30
41
  let result;
31
42
  if (parsed.stylePath === STYLE_ENTRY) {
32
43
  result = await this.createStyleCode(context, cache);
@@ -45,7 +56,7 @@ export class StyleCodeFactory {
45
56
  else {
46
57
  result = await this.createSourceModuleStyleCode(context, cache, parsed.stylePath);
47
58
  }
48
- return cache.writePersistentLoadResult(parsed, context, this.normalizeTopLevelImports(result));
59
+ return result;
49
60
  }
50
61
  async createStyleCode(context, cache) {
51
62
  const results = [];
@@ -15,6 +15,6 @@ export declare function aukletStylePlugin(options?: AukletStylePluginOptions): {
15
15
  configureServer(server: ViteDevServer): Promise<void>;
16
16
  hotUpdate: {
17
17
  order: "pre";
18
- handler(context: HotUpdateOptions): never[] | undefined;
18
+ handler(context: HotUpdateOptions): Promise<never[] | undefined>;
19
19
  };
20
20
  };
@@ -2,10 +2,12 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { AukletStyleHmr } from '#auklet/css/vite/hmr';
4
4
  import { ModuleStyleGraph } from '#auklet/css/vite/moduleGraph/graph';
5
+ import { createAukletLogger } from '#auklet/logger';
5
6
  const WORKSPACE_FILE = 'pnpm-workspace.yaml';
6
7
  const VIRTUAL_ID_PREFIX = 'virtual:auklet-css:';
7
8
  const RESOLVED_VIRTUAL_ID_PREFIX = '\0auklet-css:';
8
9
  const BROWSER_VIRTUAL_ID_PREFIX = 'auklet-css:';
10
+ const logger = createAukletLogger({ scope: 'css:vite' });
9
11
  const stripQuery = (id) => id.split('?')[0];
10
12
  const toResolvedVirtualId = (id) => {
11
13
  if (id.startsWith(RESOLVED_VIRTUAL_ID_PREFIX)) {
@@ -42,6 +44,20 @@ const resolveGraphRoot = (mode, viteRoot) => {
42
44
  return findWorkspaceRoot(viteRoot) ?? process.cwd();
43
45
  return viteRoot;
44
46
  };
47
+ const createWatcherErrorPayload = (error, file) => {
48
+ const err = error instanceof Error
49
+ ? error
50
+ : new Error(typeof error === 'string' ? error : String(error));
51
+ return {
52
+ type: 'error',
53
+ err: {
54
+ message: err.message,
55
+ stack: err.stack ?? err.message,
56
+ plugin: 'auklet-css',
57
+ id: file,
58
+ },
59
+ };
60
+ };
45
61
  const invalidateVirtualModules = (server, graph) => {
46
62
  const modules = [];
47
63
  for (const packageName of graph.getPackageNames()) {
@@ -57,6 +73,7 @@ const invalidateVirtualModules = (server, graph) => {
57
73
  };
58
74
  export function aukletStylePlugin(options = {}) {
59
75
  let graph = null;
76
+ let moduleGraph = null;
60
77
  const getGraph = () => {
61
78
  if (!graph) {
62
79
  graph = createModuleStyleGraph(options, process.cwd());
@@ -93,15 +110,24 @@ export function aukletStylePlugin(options = {}) {
93
110
  if (!parsed)
94
111
  return null;
95
112
  const result = await graph.createPackageStyleCode(parsed);
113
+ if (moduleGraph) {
114
+ hmr.pruneStaleVirtualDependencies(moduleGraph);
115
+ }
116
+ hmr.replaceVirtualStyleDependency(id, result.watchFiles);
96
117
  for (const file of result.watchFiles) {
97
- hmr.trackVirtualStyleDependency(file, id);
98
118
  this.addWatchFile?.(file);
99
119
  }
100
120
  return result.code;
101
121
  },
102
122
  async configureServer(server) {
103
123
  const graph = getGraph();
124
+ moduleGraph = server.moduleGraph;
104
125
  hmr.installFullReloadGuard(server);
126
+ const close = server.close.bind(server);
127
+ server.close = (async () => {
128
+ hmr.cancelStaleVirtualDependencyPrune();
129
+ return close();
130
+ });
105
131
  server.watcher.add(await graph.getWatchRoots());
106
132
  const invalidateStyleGraph = (file) => {
107
133
  if (!graph.isSourceGraphFile(file))
@@ -117,23 +143,33 @@ export function aukletStylePlugin(options = {}) {
117
143
  };
118
144
  server.watcher.on('add', reloadStyleGraph);
119
145
  server.watcher.on('unlink', reloadStyleGraph);
120
- server.watcher.on('change', (file) => {
121
- if (graph.isStyleConfigFile(file)) {
122
- reloadStyleGraph(file);
123
- }
124
- else if (graph.isStyleFile(file) &&
125
- hmr.hasTrackedStyleDependency(file)) {
126
- hmr.handleStyleHotUpdate({
127
- file,
128
- modules: [],
129
- server,
130
- timestamp: Date.now(),
131
- type: 'update',
132
- read: async () => '',
133
- });
146
+ server.watcher.on('change', async (file) => {
147
+ try {
148
+ hmr.scheduleStaleVirtualDependencyPrune(server.moduleGraph);
149
+ if (graph.isStyleConfigFile(file)) {
150
+ reloadStyleGraph(file);
151
+ }
152
+ else if (graph.isStyleFile(file)) {
153
+ graph.invalidateFile(file);
154
+ if (hmr.hasTrackedStyleDependency(file, server.moduleGraph)) {
155
+ await hmr.handleStyleHotUpdate({
156
+ file,
157
+ modules: [],
158
+ server,
159
+ timestamp: Date.now(),
160
+ type: 'update',
161
+ read: async () => '',
162
+ });
163
+ }
164
+ }
165
+ else if (graph.isSourceModuleFile(file)) {
166
+ await hmr.handleSourceModuleChange(server, file);
167
+ }
134
168
  }
135
- else if (graph.isSourceModuleFile(file)) {
136
- hmr.handleSourceModuleChange(server, file);
169
+ catch (error) {
170
+ logger.error('package css change watcher failed');
171
+ logger.error(error);
172
+ server.ws.send(createWatcherErrorPayload(error, file));
137
173
  }
138
174
  });
139
175
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auklet",
3
- "version": "0.2.10",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "author": "chentao.arthur",
6
6
  "description": "Build utilities for TypeScript packages and module CSS output.",