auklet 0.1.9 → 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,5 +1,4 @@
1
1
  import { getGlobalStyleDependencies, getThemeNames, getThemeStyleDependencies, } from '#auklet/css/core/style/dependencies';
2
- import { StyleModuleEntryPlanner } from '#auklet/css/core/styleModuleEntryPlanner';
3
2
  const dependenciesPart = (specifiers) => ({ type: 'dependencies', specifiers });
4
3
  // 环境无关的 style entry graph。production writer 和 Vite/dev renderer 都从这里取入口语义。
5
4
  export function createStyleEntryParts(config) {
@@ -25,11 +24,15 @@ export function createExternalEntryParts(config) {
25
24
  ];
26
25
  }
27
26
  export function collectModuleStyleImports(packageContext) {
28
- return packageContext.importCollector.collect(packageContext.sourceFiles, packageContext.normalizedConfig);
27
+ return packageContext.getModuleStyleImports();
29
28
  }
30
29
  export function createModuleStyleEntryPlan(packageContext, sourceDir) {
31
- return new StyleModuleEntryPlanner(packageContext).createEntry(sourceDir, collectModuleStyleImports(packageContext));
30
+ return packageContext
31
+ .getModuleStyleEntryPlanner()
32
+ .createEntry(sourceDir, collectModuleStyleImports(packageContext));
32
33
  }
33
34
  export function createModuleStyleEntryPlans(packageContext) {
34
- return new StyleModuleEntryPlanner(packageContext).createEntries(collectModuleStyleImports(packageContext));
35
+ return packageContext
36
+ .getModuleStyleEntryPlanner()
37
+ .createEntries(collectModuleStyleImports(packageContext));
35
38
  }
@@ -1,4 +1,5 @@
1
1
  import { ModuleStyleImportCollector } from '#auklet/css/core/styleImports/collector';
2
+ import { StyleModuleEntryPlanner } from '#auklet/css/core/styleModuleEntryPlanner';
2
3
  import { StyleProcessor } from '#auklet/css/core/styleProcessor';
3
4
  import { WorkspaceStyleResolver } from '#auklet/css/core/workspaceStyleResolver';
4
5
  import type { ModuleStyleBuildConfig, NormalizedAukletConfig, ResolvedModuleStyleBuildContext } from '#auklet/types';
@@ -18,6 +19,10 @@ export declare class StylePackageContext {
18
19
  readonly themeFiles: Map<string, string>;
19
20
  readonly themeNames: Array<string>;
20
21
  readonly styleFiles: Array<string>;
22
+ private moduleStyleEntryPlanner?;
23
+ private moduleStyleImports?;
21
24
  constructor(options: StylePackageContextOptions);
22
25
  getStyleFiles(files: Array<string>): string[];
26
+ getModuleStyleImports(): Map<string, string[]>;
27
+ getModuleStyleEntryPlanner(): StyleModuleEntryPlanner;
23
28
  }
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { ModuleStyleImportCollector } from '#auklet/css/core/styleImports/collector';
4
+ import { StyleModuleEntryPlanner } from '#auklet/css/core/styleModuleEntryPlanner';
4
5
  import { StyleProcessor } from '#auklet/css/core/styleProcessor';
5
6
  import { WorkspaceStyleResolver } from '#auklet/css/core/workspaceStyleResolver';
6
7
  import { createStyleFileKeySet } from '#auklet/css/core/style/files';
@@ -17,6 +18,8 @@ export class StylePackageContext {
17
18
  themeFiles;
18
19
  themeNames;
19
20
  styleFiles;
21
+ moduleStyleEntryPlanner;
22
+ moduleStyleImports;
20
23
  constructor(options) {
21
24
  this.options = options;
22
25
  const { config, context, normalizedConfig } = this.options;
@@ -38,4 +41,12 @@ export class StylePackageContext {
38
41
  .filter((file) => this.options.config.styleExtensions.includes(path.extname(file)))
39
42
  .filter((styleFile) => !themeFileKeys.has(normalizeFileKey(styleFile)));
40
43
  }
44
+ getModuleStyleImports() {
45
+ this.moduleStyleImports ??= this.importCollector.collect(this.sourceFiles, this.normalizedConfig);
46
+ return this.moduleStyleImports;
47
+ }
48
+ getModuleStyleEntryPlanner() {
49
+ this.moduleStyleEntryPlanner ??= new StyleModuleEntryPlanner(this);
50
+ return this.moduleStyleEntryPlanner;
51
+ }
41
52
  }
@@ -9,6 +9,8 @@ export declare class AukletStyleHmr {
9
9
  trackVirtualStyleDependency(file: string, virtualId: string): void;
10
10
  installFullReloadGuard(server: Pick<ViteDevServer, 'ws'>): void;
11
11
  handleStyleHotUpdate(context: HotUpdateOptions): never[] | undefined;
12
+ handleSourceModuleChange(server: Pick<ViteDevServer, 'moduleGraph' | 'ws'>, file: string): boolean;
13
+ private sendVirtualStyleUpdates;
12
14
  private suppressFullReload;
13
15
  private shouldSuppressFullReload;
14
16
  private isDuplicateUpdate;
@@ -26,6 +26,19 @@ const getDependencyVirtualModules = (virtualIdsByDependency, server, file) => {
26
26
  return module ? [module] : [];
27
27
  });
28
28
  };
29
+ const createJsUpdates = (virtualIds, timestamp) => {
30
+ return virtualIds.map((id) => {
31
+ const browserPath = toBrowserVirtualPath(id);
32
+ return {
33
+ type: 'js-update',
34
+ path: browserPath,
35
+ acceptedPath: browserPath,
36
+ timestamp,
37
+ explicitImportRequired: false,
38
+ isWithinCircularImport: false,
39
+ };
40
+ });
41
+ };
29
42
  export class AukletStyleHmr {
30
43
  graph;
31
44
  lastUpdateTimes = new Map();
@@ -64,30 +77,37 @@ export class AukletStyleHmr {
64
77
  if (this.isDuplicateUpdate(context.file)) {
65
78
  return [];
66
79
  }
67
- const virtualIds = getDependencyVirtualIds(this.virtualIdsByDependency, context.file);
68
- const modules = getDependencyVirtualModules(this.virtualIdsByDependency, context.server, context.file);
80
+ const updates = this.sendVirtualStyleUpdates(context.file, context.server, context.timestamp);
81
+ logger.info(`package css hmr ${getRelativeFile(context.file)} tracked=${updates.tracked} updates=${updates.sent}`);
82
+ return [];
83
+ }
84
+ handleSourceModuleChange(server, file) {
85
+ const graph = this.graph();
86
+ if (!graph.isSourceGraphFile(file) || !graph.isSourceModuleFile(file)) {
87
+ return false;
88
+ }
89
+ graph.invalidateFile(file);
90
+ const updates = this.sendVirtualStyleUpdates(file, server, Date.now());
91
+ logger.info(`package css source hmr ${getRelativeFile(file)} tracked=${updates.tracked} updates=${updates.sent}`);
92
+ return true;
93
+ }
94
+ sendVirtualStyleUpdates(file, server, timestamp) {
95
+ const virtualIds = getDependencyVirtualIds(this.virtualIdsByDependency, file);
96
+ const modules = getDependencyVirtualModules(this.virtualIdsByDependency, server, file);
69
97
  for (const module of modules) {
70
- context.server.moduleGraph.invalidateModule(module);
98
+ server.moduleGraph.invalidateModule(module);
71
99
  }
72
- const updates = virtualIds.map((id) => {
73
- const browserPath = toBrowserVirtualPath(id);
74
- return {
75
- type: 'js-update',
76
- path: browserPath,
77
- acceptedPath: browserPath,
78
- timestamp: context.timestamp,
79
- explicitImportRequired: false,
80
- isWithinCircularImport: false,
81
- };
82
- });
83
- logger.info(`package css hmr ${getRelativeFile(context.file)} tracked=${virtualIds.length} updates=${updates.length}`);
100
+ const updates = createJsUpdates(virtualIds, timestamp);
84
101
  if (updates.length) {
85
- context.server.ws.send({
102
+ server.ws.send({
86
103
  type: 'update',
87
104
  updates,
88
105
  });
89
106
  }
90
- return [];
107
+ return {
108
+ sent: updates.length,
109
+ tracked: virtualIds.length,
110
+ };
91
111
  }
92
112
  suppressFullReload() {
93
113
  this.suppressFullReloadUntil = Date.now() + FULL_RELOAD_SUPPRESS_MS;
@@ -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
  };
@@ -15,10 +15,7 @@ export declare class ModuleStyleGraph {
15
15
  isStyleFile(file: string): boolean;
16
16
  getPackageNames(): string[];
17
17
  getWatchRoots(): Promise<string[]>;
18
- createPackageStyleCode(parsed: PackageStyleId): Promise<{
19
- code: string;
20
- watchFiles: string[];
21
- }>;
18
+ createPackageStyleCode(parsed: PackageStyleId): Promise<import("#auklet/css/vite/moduleGraph/types").PackageStyleLoadResult>;
22
19
  invalidatePackage(packageName: string): void;
23
20
  invalidateFile(file: string): string | null;
24
21
  isSourceModuleFile(file: string): boolean;
@@ -32,6 +32,8 @@ export class ModuleStyleGraph {
32
32
  loadAukletConfig: this.loadAukletConfig,
33
33
  });
34
34
  this.requestCache = new ModuleStyleGraphRequestCache({
35
+ root: normalizeFileKey(options.root),
36
+ mode: options.mode ?? 'package',
35
37
  packageSource: this.packageSource,
36
38
  config: this.config,
37
39
  loadAukletConfig: this.loadAukletConfig,
@@ -2,4 +2,6 @@ import type { PackageStyleLoadResult } from '#auklet/css/vite/moduleGraph/types'
2
2
  export declare function mergeLoadResults(...results: Array<PackageStyleLoadResult>): {
3
3
  code: string;
4
4
  watchFiles: string[];
5
+ cacheInputFiles: string[];
6
+ dependencyPackages: string[];
5
7
  };
@@ -6,5 +6,7 @@ export function mergeLoadResults(...results) {
6
6
  .filter((code) => code.trim())
7
7
  .join('\n'),
8
8
  watchFiles: Array.from(new Set(results.flatMap((result) => result.watchFiles))),
9
+ cacheInputFiles: Array.from(new Set(results.flatMap((result) => result.cacheInputFiles ?? []))),
10
+ dependencyPackages: Array.from(new Set(results.flatMap((result) => result.dependencyPackages ?? []))),
9
11
  };
10
12
  }
@@ -0,0 +1,15 @@
1
+ import type { PackageStyleLoadResult } from '#auklet/css/vite/moduleGraph/types';
2
+ export type PersistentStyleGraphCacheOptions = {
3
+ root: string;
4
+ };
5
+ export declare class PersistentStyleGraphCache {
6
+ private static readonly cleanedRoots;
7
+ private readonly cacheRoot;
8
+ constructor(options: PersistentStyleGraphCacheOptions);
9
+ createKey(value: unknown): string;
10
+ read(key: string): PackageStyleLoadResult | null;
11
+ write(key: string, result: PackageStyleLoadResult, inputFiles: Array<string>): void;
12
+ private cleanupStaleEntries;
13
+ private getCacheFile;
14
+ private isFresh;
15
+ }
@@ -0,0 +1,179 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { toPosixPath } from '#auklet/utils';
5
+ const cacheVersion = 'v1';
6
+ const maxCacheFiles = 5000;
7
+ const staleCacheAgeMs = 7 * 24 * 60 * 60 * 1000;
8
+ const stableStringify = (value) => {
9
+ if (Array.isArray(value)) {
10
+ return `[${value.map((item) => stableStringify(item)).join(',')}]`;
11
+ }
12
+ if (value && typeof value === 'object') {
13
+ return `{${Object.entries(value)
14
+ .sort(([left], [right]) => left.localeCompare(right))
15
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
16
+ .join(',')}}`;
17
+ }
18
+ return JSON.stringify(value);
19
+ };
20
+ const hashValue = (value) => {
21
+ return crypto
22
+ .createHash('sha256')
23
+ .update(stableStringify(value))
24
+ .digest('hex');
25
+ };
26
+ const hashFile = (file) => {
27
+ return crypto.createHash('md5').update(fs.readFileSync(file)).digest('hex');
28
+ };
29
+ const normalizeCachePath = (file) => {
30
+ return toPosixPath(path.resolve(file));
31
+ };
32
+ const normalizeRealpath = (file) => {
33
+ return toPosixPath(fs.realpathSync.native(file));
34
+ };
35
+ const statCachedFile = (file) => {
36
+ const resolved = path.resolve(file);
37
+ if (!fs.existsSync(resolved)) {
38
+ return {
39
+ file: normalizeCachePath(file),
40
+ exists: false,
41
+ };
42
+ }
43
+ const stat = fs.statSync(resolved);
44
+ if (stat.isDirectory()) {
45
+ return {
46
+ file: normalizeCachePath(file),
47
+ exists: true,
48
+ realpath: normalizeRealpath(resolved),
49
+ type: 'directory',
50
+ mtimeMs: stat.mtimeMs,
51
+ size: stat.size,
52
+ };
53
+ }
54
+ if (!stat.isFile()) {
55
+ return {
56
+ file: normalizeCachePath(file),
57
+ exists: false,
58
+ };
59
+ }
60
+ return {
61
+ hash: hashFile(resolved),
62
+ file: normalizeCachePath(file),
63
+ exists: true,
64
+ realpath: normalizeRealpath(resolved),
65
+ type: 'file',
66
+ mtimeMs: stat.mtimeMs,
67
+ size: stat.size,
68
+ };
69
+ };
70
+ export class PersistentStyleGraphCache {
71
+ static cleanedRoots = new Set();
72
+ cacheRoot;
73
+ constructor(options) {
74
+ this.cacheRoot = path.join(options.root, 'node_modules', '.auklet', 'cache', 'vite-style', cacheVersion);
75
+ }
76
+ createKey(value) {
77
+ return hashValue({
78
+ cacheVersion,
79
+ value,
80
+ });
81
+ }
82
+ read(key) {
83
+ const cacheFile = this.getCacheFile(key);
84
+ if (!fs.existsSync(cacheFile))
85
+ return null;
86
+ try {
87
+ const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
88
+ if (cached.key !== key)
89
+ return null;
90
+ if (!this.isFresh(cached.files))
91
+ return null;
92
+ return cached.result;
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ 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
+ try {
103
+ fs.mkdirSync(this.cacheRoot, { recursive: true });
104
+ fs.writeFileSync(this.getCacheFile(key), `${JSON.stringify({
105
+ files,
106
+ key,
107
+ result,
108
+ }, null, 2)}\n`);
109
+ }
110
+ catch {
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.
151
+ }
152
+ }
153
+ getCacheFile(key) {
154
+ return path.join(this.cacheRoot, `${key}.json`);
155
+ }
156
+ isFresh(files) {
157
+ for (const file of files) {
158
+ const current = statCachedFile(file.file);
159
+ if (current.exists !== file.exists)
160
+ return false;
161
+ if (!current.exists || !file.exists)
162
+ continue;
163
+ if (current.type !== file.type)
164
+ return false;
165
+ if (current.realpath !== file.realpath ||
166
+ current.size !== file.size ||
167
+ current.mtimeMs !== file.mtimeMs ||
168
+ toPosixPath(current.file) !== toPosixPath(file.file)) {
169
+ return false;
170
+ }
171
+ if (current.type === 'file' &&
172
+ file.type === 'file' &&
173
+ current.hash !== file.hash) {
174
+ return false;
175
+ }
176
+ }
177
+ return true;
178
+ }
179
+ }
@@ -3,7 +3,7 @@ import type { ModuleStyleBuildConfig, NormalizedAukletConfig, ResolvedModuleStyl
3
3
  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
- import type { LoadAukletConfig, PackageStyleId } from '#auklet/css/vite/moduleGraph/types';
6
+ import type { LoadAukletConfig, PackageStyleId, PackageStyleLoadResult } from '#auklet/css/vite/moduleGraph/types';
7
7
  export type PackageStyleContext = {
8
8
  normalizedConfig: NormalizedAukletConfig;
9
9
  context: ResolvedModuleStyleBuildContext;
@@ -15,6 +15,8 @@ export type PackageStyleContext = {
15
15
  styleProcessor: StyleProcessor;
16
16
  };
17
17
  export type ModuleStyleGraphRequestCacheOptions = {
18
+ root: string;
19
+ mode: 'monorepo' | 'package';
18
20
  packageSource: StylePackageSource;
19
21
  config: ModuleStyleBuildConfig;
20
22
  loadAukletConfig?: LoadAukletConfig;
@@ -22,7 +24,10 @@ export type ModuleStyleGraphRequestCacheOptions = {
22
24
  export declare class ModuleStyleGraphRequestCache {
23
25
  private readonly options;
24
26
  private readonly contexts;
27
+ private readonly loadResults;
28
+ private readonly loadResultDependencies;
25
29
  private readonly loadAukletConfig;
30
+ private readonly persistentCache;
26
31
  constructor(options: ModuleStyleGraphRequestCacheOptions);
27
32
  getPackageNames(): string[];
28
33
  isKnownPackageName(packageName: string): boolean;
@@ -62,6 +67,23 @@ export declare class ModuleStyleGraphRequestCache {
62
67
  sourceRoot: string;
63
68
  styleProcessor: StyleProcessor;
64
69
  } | null>;
70
+ getLoadResult(parsed: PackageStyleId, create: () => Promise<PackageStyleLoadResult>): Promise<PackageStyleLoadResult>;
65
71
  invalidatePackage(packageName: string): void;
72
+ readPersistentLoadResult(parsed: PackageStyleId, context: PackageStyleContext): PackageStyleLoadResult | null;
73
+ writePersistentLoadResult(parsed: PackageStyleId, context: PackageStyleContext, result: PackageStyleLoadResult): {
74
+ cacheInputFiles: string[];
75
+ code: string;
76
+ watchFiles: Array<string>;
77
+ dependencyPackages?: Array<string>;
78
+ };
66
79
  private createContext;
80
+ private createPersistentKey;
81
+ private getPersistentInputFiles;
82
+ private getPackageKeyEntries;
83
+ private getWorkspaceInputFiles;
84
+ private getSourceInputFiles;
85
+ private getResolutionInputFiles;
86
+ private getLoadResultKey;
87
+ private getLoadResultPackageName;
88
+ private invalidateLoadResults;
67
89
  }
@@ -2,13 +2,21 @@ import path from 'node:path';
2
2
  import { loadAukletConfig, resolveAukletConfigPath, } from '#auklet/configLoader';
3
3
  import { aukletConfigFiles, normalizeAukletConfig } from '#auklet/config';
4
4
  import { StylePackageContext } from '#auklet/css/core/stylePackageContext';
5
+ import { PersistentStyleGraphCache } from '#auklet/css/vite/moduleGraph/persistentCache';
6
+ import { normalizeFileKey, toPosixPath } from '#auklet/utils';
5
7
  export class ModuleStyleGraphRequestCache {
6
8
  options;
7
9
  contexts = new Map();
10
+ loadResults = new Map();
11
+ loadResultDependencies = new Map();
8
12
  loadAukletConfig;
13
+ persistentCache;
9
14
  constructor(options) {
10
15
  this.options = options;
11
16
  this.loadAukletConfig = options.loadAukletConfig ?? loadAukletConfig;
17
+ this.persistentCache = new PersistentStyleGraphCache({
18
+ root: options.root,
19
+ });
12
20
  }
13
21
  getPackageNames() {
14
22
  return this.options.packageSource.getPackageNames();
@@ -24,8 +32,33 @@ export class ModuleStyleGraphRequestCache {
24
32
  this.contexts.set(parsed.packageName, context);
25
33
  return context;
26
34
  }
35
+ getLoadResult(parsed, create) {
36
+ const key = this.getLoadResultKey(parsed);
37
+ const cachedResult = this.loadResults.get(key);
38
+ if (cachedResult)
39
+ return cachedResult;
40
+ const result = create().then((value) => {
41
+ this.loadResultDependencies.set(key, new Set(value.dependencyPackages ?? []));
42
+ return value;
43
+ });
44
+ this.loadResults.set(key, result);
45
+ return result;
46
+ }
27
47
  invalidatePackage(packageName) {
28
48
  this.contexts.delete(packageName);
49
+ this.invalidateLoadResults(packageName);
50
+ }
51
+ readPersistentLoadResult(parsed, context) {
52
+ return this.persistentCache.read(this.createPersistentKey(parsed, context));
53
+ }
54
+ writePersistentLoadResult(parsed, context, result) {
55
+ const cacheInputFiles = this.getPersistentInputFiles(context, result);
56
+ const cacheResult = {
57
+ ...result,
58
+ cacheInputFiles,
59
+ };
60
+ this.persistentCache.write(this.createPersistentKey(parsed, context), cacheResult, cacheInputFiles);
61
+ return cacheResult;
29
62
  }
30
63
  async createContext(parsed) {
31
64
  const stylePackage = this.options.packageSource
@@ -63,4 +96,118 @@ export class ModuleStyleGraphRequestCache {
63
96
  styleProcessor: packageContext.styleProcessor,
64
97
  };
65
98
  }
99
+ createPersistentKey(parsed, context) {
100
+ return this.persistentCache.createKey({
101
+ config: this.options.config,
102
+ mode: this.options.mode,
103
+ normalizedConfig: context.normalizedConfig,
104
+ packageName: context.packageName,
105
+ packageNames: this.getPackageNames(),
106
+ packages: this.getPackageKeyEntries(),
107
+ packageRoot: normalizeFileKey(context.context.packageRoot),
108
+ parsed,
109
+ root: normalizeFileKey(this.options.root),
110
+ sourceFiles: context.packageContext.sourceFiles.map((file) => toPosixPath(path.relative(context.sourceRoot, file))),
111
+ sourceRoot: normalizeFileKey(context.sourceRoot),
112
+ });
113
+ }
114
+ getPersistentInputFiles(context, result) {
115
+ return [
116
+ ...result.watchFiles,
117
+ ...(result.cacheInputFiles ?? []),
118
+ ...this.getSourceInputFiles(context.sourceRoot, context.packageContext.sourceFiles),
119
+ ...this.getResolutionInputFiles(context.context.packageRoot),
120
+ ...this.getWorkspaceInputFiles(),
121
+ ];
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
+ }
142
+ getSourceInputFiles(sourceRoot, sourceFiles) {
143
+ const sourceInputFiles = new Set([sourceRoot, ...sourceFiles]);
144
+ const normalizedSourceRoot = normalizeFileKey(sourceRoot);
145
+ for (const file of sourceFiles) {
146
+ let current = path.dirname(file);
147
+ let currentKey = normalizeFileKey(current);
148
+ while (currentKey === normalizedSourceRoot ||
149
+ currentKey.startsWith(`${normalizedSourceRoot}/`)) {
150
+ sourceInputFiles.add(current);
151
+ if (currentKey === normalizedSourceRoot)
152
+ break;
153
+ const parent = path.dirname(current);
154
+ current = parent;
155
+ currentKey = normalizeFileKey(current);
156
+ }
157
+ }
158
+ return Array.from(sourceInputFiles);
159
+ }
160
+ getResolutionInputFiles(packageRoot) {
161
+ const files = [path.join(packageRoot, 'package.json')];
162
+ let current = packageRoot;
163
+ const graphRoot = normalizeFileKey(this.options.root);
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)
170
+ break;
171
+ const parent = path.dirname(current);
172
+ if (parent === current)
173
+ break;
174
+ current = parent;
175
+ }
176
+ return Array.from(new Set(files));
177
+ }
178
+ getLoadResultKey(parsed) {
179
+ return `${parsed.packageName}\0${parsed.stylePath}`;
180
+ }
181
+ getLoadResultPackageName(key) {
182
+ return key.split('\0')[0];
183
+ }
184
+ invalidateLoadResults(packageName) {
185
+ const invalidPackageNames = new Set([packageName]);
186
+ let changed = true;
187
+ while (changed) {
188
+ changed = false;
189
+ for (const [key, dependencyPackages] of this.loadResultDependencies) {
190
+ const resultPackageName = this.getLoadResultPackageName(key);
191
+ const shouldInvalidate = invalidPackageNames.has(resultPackageName) ||
192
+ Array.from(dependencyPackages).some((dependencyPackage) => invalidPackageNames.has(dependencyPackage));
193
+ if (!shouldInvalidate)
194
+ continue;
195
+ if (!invalidPackageNames.has(resultPackageName)) {
196
+ invalidPackageNames.add(resultPackageName);
197
+ changed = true;
198
+ }
199
+ }
200
+ }
201
+ for (const key of this.loadResults.keys()) {
202
+ if (!invalidPackageNames.has(this.getLoadResultPackageName(key))) {
203
+ const dependencyPackages = this.loadResultDependencies.get(key);
204
+ if (!dependencyPackages ||
205
+ !Array.from(dependencyPackages).some((dependencyPackage) => invalidPackageNames.has(dependencyPackage))) {
206
+ continue;
207
+ }
208
+ }
209
+ this.loadResults.delete(key);
210
+ this.loadResultDependencies.delete(key);
211
+ }
212
+ }
66
213
  }
@@ -1,13 +1,11 @@
1
1
  import type { ModuleStyleBuildConfig } from '#auklet/types';
2
2
  import type { ModuleStyleGraphRequestCache } from '#auklet/css/vite/moduleGraph/requestCache';
3
- import type { PackageStyleId } from '#auklet/css/vite/moduleGraph/types';
3
+ import type { PackageStyleId, PackageStyleLoadResult } from '#auklet/css/vite/moduleGraph/types';
4
4
  export declare class StyleCodeFactory {
5
5
  private readonly config;
6
6
  constructor(config: ModuleStyleBuildConfig);
7
- createPackageStyleCode(parsed: PackageStyleId, cache: ModuleStyleGraphRequestCache): Promise<{
8
- code: string;
9
- watchFiles: string[];
10
- }>;
7
+ createPackageStyleCode(parsed: PackageStyleId, cache: ModuleStyleGraphRequestCache): Promise<PackageStyleLoadResult>;
8
+ private createUncachedPackageStyleCode;
11
9
  private createStyleCode;
12
10
  private createDependencyStyleCode;
13
11
  private createExternalStyleCode;
@@ -17,4 +15,5 @@ export declare class StyleCodeFactory {
17
15
  private toDevModuleImportSpecifier;
18
16
  private toDevExternalStyleSpecifier;
19
17
  private parsePackageStyleIdInRequest;
18
+ private withDependencyPackage;
20
19
  }
@@ -12,6 +12,9 @@ export class StyleCodeFactory {
12
12
  this.config = config;
13
13
  }
14
14
  async createPackageStyleCode(parsed, cache) {
15
+ return cache.getLoadResult(parsed, () => this.createUncachedPackageStyleCode(parsed, cache));
16
+ }
17
+ async createUncachedPackageStyleCode(parsed, cache) {
15
18
  const context = await cache.getContext(parsed);
16
19
  if (!context) {
17
20
  return {
@@ -19,21 +22,28 @@ export class StyleCodeFactory {
19
22
  watchFiles: [],
20
23
  };
21
24
  }
25
+ const cachedResult = cache.readPersistentLoadResult(parsed, context);
26
+ if (cachedResult)
27
+ return cachedResult;
28
+ let result;
22
29
  if (parsed.stylePath === STYLE_ENTRY) {
23
- return this.createStyleCode(context, cache);
30
+ result = await this.createStyleCode(context, cache);
24
31
  }
25
- if (parsed.stylePath === EXTERNAL_ENTRY) {
26
- return this.createExternalStyleCode(context, cache);
32
+ else if (parsed.stylePath === EXTERNAL_ENTRY) {
33
+ result = await this.createExternalStyleCode(context, cache);
27
34
  }
28
- if (parsed.stylePath === MODULE_ENTRY) {
29
- return this.createModuleStyleCode(context);
35
+ else if (parsed.stylePath === MODULE_ENTRY) {
36
+ result = this.createModuleStyleCode(context);
30
37
  }
31
- if (parsed.stylePath.startsWith(THEMES_ENTRY_PREFIX)) {
32
- return this.createThemeStyleCode(context, cache, [
38
+ else if (parsed.stylePath.startsWith(THEMES_ENTRY_PREFIX)) {
39
+ result = await this.createThemeStyleCode(context, cache, [
33
40
  removeStyleExtension(parsed.stylePath.slice(THEMES_ENTRY_PREFIX.length)),
34
41
  ]);
35
42
  }
36
- return this.createSourceModuleStyleCode(context, cache, parsed.stylePath);
43
+ else {
44
+ result = await this.createSourceModuleStyleCode(context, cache, parsed.stylePath);
45
+ }
46
+ return cache.writePersistentLoadResult(parsed, context, result);
37
47
  }
38
48
  async createStyleCode(context, cache) {
39
49
  const results = [];
@@ -52,23 +62,27 @@ export class StyleCodeFactory {
52
62
  }
53
63
  async createDependencyStyleCode(context, cache, specifiers, mapSpecifier = (specifier) => specifier) {
54
64
  const results = [];
65
+ const cacheInputFiles = [];
55
66
  const imports = [];
56
67
  const watchFiles = [...context.configPaths];
57
68
  for (const specifier of specifiers) {
58
69
  const outputSpecifier = mapSpecifier(specifier);
59
70
  const parsed = this.parsePackageStyleIdInRequest(outputSpecifier, cache);
60
71
  if (parsed) {
61
- results.push(await this.createPackageStyleCode(parsed, cache));
72
+ const result = await this.createPackageStyleCode(parsed, cache);
73
+ results.push(this.withDependencyPackage(result, parsed.packageName));
62
74
  continue;
63
75
  }
64
76
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, outputSpecifier);
65
77
  imports.push(resolvedSpecifier.specifier);
78
+ cacheInputFiles.push(...(resolvedSpecifier.cacheInputFiles ?? []));
66
79
  if (resolvedSpecifier.watchFile) {
67
80
  watchFiles.push(resolvedSpecifier.watchFile);
68
81
  }
69
82
  }
70
83
  return mergeLoadResults({
71
84
  code: createImportCode(imports),
85
+ cacheInputFiles,
72
86
  watchFiles,
73
87
  }, ...results);
74
88
  }
@@ -133,15 +147,18 @@ export class StyleCodeFactory {
133
147
  const moduleStyleResults = [];
134
148
  const moduleStyleSpecifiers = [];
135
149
  const moduleStyleWatchFiles = [];
150
+ const moduleStyleCacheInputFiles = [];
136
151
  for (const specifier of entry.moduleStyleImports) {
137
152
  const result = this.toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir);
138
153
  const parsed = this.parsePackageStyleIdInRequest(result, cache);
139
154
  if (parsed) {
140
- moduleStyleResults.push(await this.createPackageStyleCode(parsed, cache));
155
+ const loadResult = await this.createPackageStyleCode(parsed, cache);
156
+ moduleStyleResults.push(this.withDependencyPackage(loadResult, parsed.packageName));
141
157
  continue;
142
158
  }
143
159
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, result);
144
160
  moduleStyleSpecifiers.push(resolvedSpecifier.specifier);
161
+ moduleStyleCacheInputFiles.push(...(resolvedSpecifier.cacheInputFiles ?? []));
145
162
  if (resolvedSpecifier.watchFile) {
146
163
  moduleStyleWatchFiles.push(resolvedSpecifier.watchFile);
147
164
  }
@@ -167,6 +184,7 @@ export class StyleCodeFactory {
167
184
  ...moduleStyleWatchFiles,
168
185
  ...sourceFiles.filter((file) => /\.(ts|tsx)$/.test(file)),
169
186
  ],
187
+ cacheInputFiles: moduleStyleCacheInputFiles,
170
188
  });
171
189
  }
172
190
  toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir) {
@@ -190,4 +208,10 @@ export class StyleCodeFactory {
190
208
  parsePackageStyleIdInRequest(id, cache) {
191
209
  return parsePackageStyleId(id, cache.getPackageNames());
192
210
  }
211
+ withDependencyPackage(result, packageName) {
212
+ return {
213
+ ...result,
214
+ dependencyPackages: Array.from(new Set([packageName, ...(result.dependencyPackages ?? [])])),
215
+ };
216
+ }
193
217
  }
@@ -11,7 +11,9 @@ export type PackageStyleId = {
11
11
  };
12
12
  export type PackageStyleLoadResult = {
13
13
  code: string;
14
+ cacheInputFiles?: Array<string>;
14
15
  watchFiles: Array<string>;
16
+ dependencyPackages?: Array<string>;
15
17
  };
16
18
  export type LoadAukletConfig = (packageRoot: string, options?: {
17
19
  cacheBust?: boolean;
@@ -130,7 +130,7 @@ export function aukletStylePlugin(options = {}) {
130
130
  return;
131
131
  }
132
132
  if (graph.isSourceModuleFile(file)) {
133
- reloadStyleGraph(file);
133
+ hmr.handleSourceModuleChange(server, file);
134
134
  }
135
135
  });
136
136
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auklet",
3
- "version": "0.1.9",
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.",