auklet 0.2.0 → 0.2.2

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;
@@ -57,6 +57,8 @@ export class ModuleStyleGraphRequestCache {
57
57
  ...result,
58
58
  cacheInputFiles,
59
59
  };
60
+ if (!result.code.trim())
61
+ return cacheResult;
60
62
  this.persistentCache.write(this.createPersistentKey(parsed, context), cacheResult, cacheInputFiles);
61
63
  return cacheResult;
62
64
  }
@@ -103,6 +105,7 @@ export class ModuleStyleGraphRequestCache {
103
105
  normalizedConfig: context.normalizedConfig,
104
106
  packageName: context.packageName,
105
107
  packageNames: this.getPackageNames(),
108
+ packages: this.getPackageKeyEntries(),
106
109
  packageRoot: normalizeFileKey(context.context.packageRoot),
107
110
  parsed,
108
111
  root: normalizeFileKey(this.options.root),
@@ -116,21 +119,42 @@ export class ModuleStyleGraphRequestCache {
116
119
  ...(result.cacheInputFiles ?? []),
117
120
  ...this.getSourceInputFiles(context.sourceRoot, context.packageContext.sourceFiles),
118
121
  ...this.getResolutionInputFiles(context.context.packageRoot),
122
+ ...this.getWorkspaceInputFiles(),
119
123
  ];
120
124
  }
125
+ getPackageKeyEntries() {
126
+ return this.options.packageSource
127
+ .getPackages()
128
+ .map((item) => ({
129
+ packageName: item.packageName,
130
+ packageRoot: normalizeFileKey(item.packageRoot),
131
+ }))
132
+ .sort((left, right) => {
133
+ const nameOrder = left.packageName.localeCompare(right.packageName);
134
+ if (nameOrder !== 0)
135
+ return nameOrder;
136
+ return left.packageRoot.localeCompare(right.packageRoot);
137
+ });
138
+ }
139
+ getWorkspaceInputFiles() {
140
+ if (this.options.mode !== 'monorepo')
141
+ return [];
142
+ return [path.join(this.options.root, 'pnpm-workspace.yaml')];
143
+ }
121
144
  getSourceInputFiles(sourceRoot, sourceFiles) {
122
145
  const sourceInputFiles = new Set([sourceRoot, ...sourceFiles]);
123
146
  const normalizedSourceRoot = normalizeFileKey(sourceRoot);
124
147
  for (const file of sourceFiles) {
125
148
  let current = path.dirname(file);
126
- while (true) {
149
+ let currentKey = normalizeFileKey(current);
150
+ while (currentKey === normalizedSourceRoot ||
151
+ currentKey.startsWith(`${normalizedSourceRoot}/`)) {
127
152
  sourceInputFiles.add(current);
128
- if (normalizeFileKey(current) === normalizedSourceRoot)
153
+ if (currentKey === normalizedSourceRoot)
129
154
  break;
130
155
  const parent = path.dirname(current);
131
- if (parent === current)
132
- break;
133
156
  current = parent;
157
+ currentKey = normalizeFileKey(current);
134
158
  }
135
159
  }
136
160
  return Array.from(sourceInputFiles);
@@ -139,16 +163,19 @@ export class ModuleStyleGraphRequestCache {
139
163
  const files = [path.join(packageRoot, 'package.json')];
140
164
  let current = packageRoot;
141
165
  const graphRoot = normalizeFileKey(this.options.root);
142
- while (true) {
143
- files.push(path.join(current, 'tsconfig.json'));
144
- if (normalizeFileKey(current) === graphRoot)
166
+ let reachedGraphRoot = false;
167
+ while (!reachedGraphRoot) {
168
+ const tsconfig = path.join(current, 'tsconfig.json');
169
+ files.push(tsconfig);
170
+ reachedGraphRoot = normalizeFileKey(current) === graphRoot;
171
+ if (reachedGraphRoot)
145
172
  break;
146
173
  const parent = path.dirname(current);
147
174
  if (parent === current)
148
175
  break;
149
176
  current = parent;
150
177
  }
151
- return files;
178
+ return Array.from(new Set(files));
152
179
  }
153
180
  getLoadResultKey(parsed) {
154
181
  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.2",
4
4
  "type": "module",
5
5
  "author": "chentao.arthur",
6
6
  "description": "Build utilities for TypeScript packages and module CSS output.",