auklet 0.2.0 → 0.2.1

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.
@@ -6,6 +6,7 @@ import { aukletConfigFiles, isAukletConfigFile } from '#auklet/config';
6
6
  const asRecord = (value) => {
7
7
  return isPlainObject(value) ? value : null;
8
8
  };
9
+ let configImportVersion = 0;
9
10
  const assertSupportedConfigFile = (configFile) => {
10
11
  const basename = path.basename(configFile);
11
12
  if (isAukletConfigFile(basename))
@@ -53,7 +54,8 @@ export async function loadAukletConfig(packageRoot, options = {}) {
53
54
  }
54
55
  const url = pathToFileURL(configPath);
55
56
  if (options.cacheBust) {
56
- url.searchParams.set('t', Date.now().toString());
57
+ configImportVersion += 1;
58
+ url.searchParams.set('t', configImportVersion.toString());
57
59
  }
58
60
  const module = (await import(url.href));
59
61
  return resolveAukletConfigModule(module);
@@ -1,8 +1,10 @@
1
1
  import type { PackageStyleContext } from '#auklet/css/vite/moduleGraph/requestCache';
2
2
  export declare function toDevDependencyImportSpecifier(context: PackageStyleContext, specifier: string): {
3
3
  specifier: string;
4
+ cacheInputFiles?: undefined;
4
5
  watchFile?: undefined;
5
6
  } | {
7
+ cacheInputFiles: string[];
6
8
  specifier: string;
7
9
  watchFile: string;
8
10
  };
@@ -1,11 +1,62 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
1
3
  import { toFsSpecifier } from '#auklet/utils';
4
+ const getPackageName = (specifier) => {
5
+ if (!specifier.startsWith('@'))
6
+ return specifier.split('/')[0] ?? specifier;
7
+ const [scope, name] = specifier.split('/');
8
+ if (!scope || !name)
9
+ return null;
10
+ return `${scope}/${name}`;
11
+ };
12
+ const findNodeModulesPackageRoot = (file, packageName) => {
13
+ const resolved = path.resolve(file);
14
+ const root = path.parse(resolved).root;
15
+ const parts = path.relative(root, resolved).split(path.sep);
16
+ const packageParts = packageName.split('/');
17
+ const packageLength = packageParts.length;
18
+ for (let index = parts.length - packageLength - 1; index >= 0; index -= 1) {
19
+ if (parts[index] !== 'node_modules')
20
+ continue;
21
+ const candidateParts = parts.slice(index + 1, index + 1 + packageLength);
22
+ if (candidateParts.join('/') !== packageName)
23
+ continue;
24
+ return path.join(root, ...parts.slice(0, index + 1 + packageLength));
25
+ }
26
+ return null;
27
+ };
28
+ const findPackageJson = (file, context, specifier) => {
29
+ const packageName = getPackageName(specifier);
30
+ if (!packageName)
31
+ return null;
32
+ const packageRoot = findNodeModulesPackageRoot(file, packageName);
33
+ if (!packageRoot) {
34
+ return path.join(context.context.packageRoot, 'node_modules', packageName, 'package.json');
35
+ }
36
+ let current = path.dirname(file);
37
+ let next = current;
38
+ const packageRootKey = path.resolve(packageRoot);
39
+ do {
40
+ const packageJson = path.join(current, 'package.json');
41
+ if (fs.existsSync(packageJson))
42
+ return packageJson;
43
+ if (path.resolve(current) === packageRootKey)
44
+ break;
45
+ next = path.dirname(current);
46
+ if (next !== current)
47
+ current = next;
48
+ } while (next !== current);
49
+ return path.join(context.context.packageRoot, 'node_modules', packageName, 'package.json');
50
+ };
2
51
  // 在 Vite 虚拟 CSS 中,第三方 CSS 依赖要用声明它的包作为解析根。
3
52
  export function toDevDependencyImportSpecifier(context, specifier) {
4
53
  if (specifier.startsWith('.') || specifier.startsWith('/')) {
5
54
  return { specifier };
6
55
  }
7
56
  const resolved = context.resolver.resolveStyleDependency(specifier);
57
+ const packageJson = findPackageJson(resolved, context, specifier);
8
58
  return {
59
+ cacheInputFiles: packageJson ? [packageJson] : [],
9
60
  specifier: toFsSpecifier(resolved),
10
61
  watchFile: resolved,
11
62
  };
@@ -3,11 +3,13 @@ export type PersistentStyleGraphCacheOptions = {
3
3
  root: string;
4
4
  };
5
5
  export declare class PersistentStyleGraphCache {
6
+ private static readonly cleanedRoots;
6
7
  private readonly cacheRoot;
7
8
  constructor(options: PersistentStyleGraphCacheOptions);
8
9
  createKey(value: unknown): string;
9
10
  read(key: string): PackageStyleLoadResult | null;
10
11
  write(key: string, result: PackageStyleLoadResult, inputFiles: Array<string>): void;
12
+ private cleanupStaleEntries;
11
13
  private getCacheFile;
12
14
  private isFresh;
13
15
  }
@@ -3,6 +3,8 @@ import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
4
  import { toPosixPath } from '#auklet/utils';
5
5
  const cacheVersion = 'v1';
6
+ const maxCacheFiles = 5000;
7
+ const staleCacheAgeMs = 7 * 24 * 60 * 60 * 1000;
6
8
  const stableStringify = (value) => {
7
9
  if (Array.isArray(value)) {
8
10
  return `[${value.map((item) => stableStringify(item)).join(',')}]`;
@@ -66,6 +68,7 @@ const statCachedFile = (file) => {
66
68
  };
67
69
  };
68
70
  export class PersistentStyleGraphCache {
71
+ static cleanedRoots = new Set();
69
72
  cacheRoot;
70
73
  constructor(options) {
71
74
  this.cacheRoot = path.join(options.root, 'node_modules', '.auklet', 'cache', 'vite-style', cacheVersion);
@@ -106,6 +109,45 @@ export class PersistentStyleGraphCache {
106
109
  }
107
110
  catch {
108
111
  // Cache writes are an optimization; dev CSS generation must keep working.
112
+ return;
113
+ }
114
+ this.cleanupStaleEntries();
115
+ }
116
+ cleanupStaleEntries() {
117
+ if (PersistentStyleGraphCache.cleanedRoots.has(this.cacheRoot))
118
+ return;
119
+ PersistentStyleGraphCache.cleanedRoots.add(this.cacheRoot);
120
+ try {
121
+ const now = Date.now();
122
+ const entries = fs
123
+ .readdirSync(this.cacheRoot, { withFileTypes: true })
124
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
125
+ .map((entry) => {
126
+ const file = path.join(this.cacheRoot, entry.name);
127
+ return {
128
+ file,
129
+ mtimeMs: fs.statSync(file).mtimeMs,
130
+ };
131
+ });
132
+ const freshEntries = [];
133
+ for (const entry of entries) {
134
+ if (now - entry.mtimeMs > staleCacheAgeMs) {
135
+ fs.unlinkSync(entry.file);
136
+ continue;
137
+ }
138
+ freshEntries.push(entry);
139
+ }
140
+ const overflowCount = freshEntries.length - maxCacheFiles;
141
+ if (overflowCount <= 0)
142
+ return;
143
+ for (const entry of freshEntries
144
+ .sort((left, right) => left.mtimeMs - right.mtimeMs)
145
+ .slice(0, overflowCount)) {
146
+ fs.unlinkSync(entry.file);
147
+ }
148
+ }
149
+ catch {
150
+ // Cache cleanup is best-effort and must not affect dev CSS generation.
109
151
  }
110
152
  }
111
153
  getCacheFile(key) {
@@ -79,6 +79,8 @@ export declare class ModuleStyleGraphRequestCache {
79
79
  private createContext;
80
80
  private createPersistentKey;
81
81
  private getPersistentInputFiles;
82
+ private getPackageKeyEntries;
83
+ private getWorkspaceInputFiles;
82
84
  private getSourceInputFiles;
83
85
  private getResolutionInputFiles;
84
86
  private getLoadResultKey;
@@ -103,6 +103,7 @@ export class ModuleStyleGraphRequestCache {
103
103
  normalizedConfig: context.normalizedConfig,
104
104
  packageName: context.packageName,
105
105
  packageNames: this.getPackageNames(),
106
+ packages: this.getPackageKeyEntries(),
106
107
  packageRoot: normalizeFileKey(context.context.packageRoot),
107
108
  parsed,
108
109
  root: normalizeFileKey(this.options.root),
@@ -116,21 +117,42 @@ export class ModuleStyleGraphRequestCache {
116
117
  ...(result.cacheInputFiles ?? []),
117
118
  ...this.getSourceInputFiles(context.sourceRoot, context.packageContext.sourceFiles),
118
119
  ...this.getResolutionInputFiles(context.context.packageRoot),
120
+ ...this.getWorkspaceInputFiles(),
119
121
  ];
120
122
  }
123
+ getPackageKeyEntries() {
124
+ return this.options.packageSource
125
+ .getPackages()
126
+ .map((item) => ({
127
+ packageName: item.packageName,
128
+ packageRoot: normalizeFileKey(item.packageRoot),
129
+ }))
130
+ .sort((left, right) => {
131
+ const nameOrder = left.packageName.localeCompare(right.packageName);
132
+ if (nameOrder !== 0)
133
+ return nameOrder;
134
+ return left.packageRoot.localeCompare(right.packageRoot);
135
+ });
136
+ }
137
+ getWorkspaceInputFiles() {
138
+ if (this.options.mode !== 'monorepo')
139
+ return [];
140
+ return [path.join(this.options.root, 'pnpm-workspace.yaml')];
141
+ }
121
142
  getSourceInputFiles(sourceRoot, sourceFiles) {
122
143
  const sourceInputFiles = new Set([sourceRoot, ...sourceFiles]);
123
144
  const normalizedSourceRoot = normalizeFileKey(sourceRoot);
124
145
  for (const file of sourceFiles) {
125
146
  let current = path.dirname(file);
126
- while (true) {
147
+ let currentKey = normalizeFileKey(current);
148
+ while (currentKey === normalizedSourceRoot ||
149
+ currentKey.startsWith(`${normalizedSourceRoot}/`)) {
127
150
  sourceInputFiles.add(current);
128
- if (normalizeFileKey(current) === normalizedSourceRoot)
151
+ if (currentKey === normalizedSourceRoot)
129
152
  break;
130
153
  const parent = path.dirname(current);
131
- if (parent === current)
132
- break;
133
154
  current = parent;
155
+ currentKey = normalizeFileKey(current);
134
156
  }
135
157
  }
136
158
  return Array.from(sourceInputFiles);
@@ -139,16 +161,19 @@ export class ModuleStyleGraphRequestCache {
139
161
  const files = [path.join(packageRoot, 'package.json')];
140
162
  let current = packageRoot;
141
163
  const graphRoot = normalizeFileKey(this.options.root);
142
- while (true) {
143
- files.push(path.join(current, 'tsconfig.json'));
144
- if (normalizeFileKey(current) === graphRoot)
164
+ let reachedGraphRoot = false;
165
+ while (!reachedGraphRoot) {
166
+ const tsconfig = path.join(current, 'tsconfig.json');
167
+ files.push(tsconfig);
168
+ reachedGraphRoot = normalizeFileKey(current) === graphRoot;
169
+ if (reachedGraphRoot)
145
170
  break;
146
171
  const parent = path.dirname(current);
147
172
  if (parent === current)
148
173
  break;
149
174
  current = parent;
150
175
  }
151
- return files;
176
+ return Array.from(new Set(files));
152
177
  }
153
178
  getLoadResultKey(parsed) {
154
179
  return `${parsed.packageName}\0${parsed.stylePath}`;
@@ -62,6 +62,7 @@ export class StyleCodeFactory {
62
62
  }
63
63
  async createDependencyStyleCode(context, cache, specifiers, mapSpecifier = (specifier) => specifier) {
64
64
  const results = [];
65
+ const cacheInputFiles = [];
65
66
  const imports = [];
66
67
  const watchFiles = [...context.configPaths];
67
68
  for (const specifier of specifiers) {
@@ -74,12 +75,14 @@ export class StyleCodeFactory {
74
75
  }
75
76
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, outputSpecifier);
76
77
  imports.push(resolvedSpecifier.specifier);
78
+ cacheInputFiles.push(...(resolvedSpecifier.cacheInputFiles ?? []));
77
79
  if (resolvedSpecifier.watchFile) {
78
80
  watchFiles.push(resolvedSpecifier.watchFile);
79
81
  }
80
82
  }
81
83
  return mergeLoadResults({
82
84
  code: createImportCode(imports),
85
+ cacheInputFiles,
83
86
  watchFiles,
84
87
  }, ...results);
85
88
  }
@@ -144,6 +147,7 @@ export class StyleCodeFactory {
144
147
  const moduleStyleResults = [];
145
148
  const moduleStyleSpecifiers = [];
146
149
  const moduleStyleWatchFiles = [];
150
+ const moduleStyleCacheInputFiles = [];
147
151
  for (const specifier of entry.moduleStyleImports) {
148
152
  const result = this.toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir);
149
153
  const parsed = this.parsePackageStyleIdInRequest(result, cache);
@@ -154,6 +158,7 @@ export class StyleCodeFactory {
154
158
  }
155
159
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, result);
156
160
  moduleStyleSpecifiers.push(resolvedSpecifier.specifier);
161
+ moduleStyleCacheInputFiles.push(...(resolvedSpecifier.cacheInputFiles ?? []));
157
162
  if (resolvedSpecifier.watchFile) {
158
163
  moduleStyleWatchFiles.push(resolvedSpecifier.watchFile);
159
164
  }
@@ -179,6 +184,7 @@ export class StyleCodeFactory {
179
184
  ...moduleStyleWatchFiles,
180
185
  ...sourceFiles.filter((file) => /\.(ts|tsx)$/.test(file)),
181
186
  ],
187
+ cacheInputFiles: moduleStyleCacheInputFiles,
182
188
  });
183
189
  }
184
190
  toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auklet",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "author": "chentao.arthur",
6
6
  "description": "Build utilities for TypeScript packages and module CSS output.",